@jbrowse/plugin-linear-genome-view 2.2.0 → 2.2.1

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 (71) hide show
  1. package/dist/BaseLinearDisplay/components/Tooltip.d.ts +1 -1
  2. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +3 -3
  3. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -2
  4. package/dist/BasicTrack/index.d.ts +1 -1
  5. package/dist/BasicTrack/index.js +4 -4
  6. package/dist/BasicTrack/index.js.map +1 -1
  7. package/dist/LinearBasicDisplay/model.d.ts +2 -2
  8. package/dist/LinearGenomeView/components/CenterLine.d.ts +1 -1
  9. package/dist/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -1
  10. package/dist/LinearGenomeView/components/Gridlines.d.ts +1 -1
  11. package/dist/LinearGenomeView/components/Header.d.ts +1 -1
  12. package/dist/LinearGenomeView/components/ImportForm.d.ts +1 -1
  13. package/dist/LinearGenomeView/components/ImportForm.js +8 -7
  14. package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
  15. package/dist/LinearGenomeView/components/LinearGenomeView.d.ts +1 -1
  16. package/dist/LinearGenomeView/components/LinearGenomeView.js +1 -3
  17. package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
  18. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +1 -1
  19. package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +1 -1
  20. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +1 -1
  21. package/dist/LinearGenomeView/components/RubberBand.d.ts +1 -1
  22. package/dist/LinearGenomeView/components/ScaleBar.d.ts +1 -1
  23. package/dist/LinearGenomeView/components/TrackContainer.d.ts +1 -1
  24. package/dist/LinearGenomeView/components/TrackContainer.js +12 -9
  25. package/dist/LinearGenomeView/components/TrackContainer.js.map +1 -1
  26. package/dist/LinearGenomeView/components/TrackLabel.js +9 -1
  27. package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -1
  28. package/dist/LinearGenomeView/components/TracksContainer.d.ts +1 -1
  29. package/dist/LinearGenomeView/index.d.ts +84 -3
  30. package/dist/LinearGenomeView/index.js +143 -63
  31. package/dist/LinearGenomeView/index.js.map +1 -1
  32. package/dist/index.d.ts +18 -6
  33. package/esm/BaseLinearDisplay/components/Tooltip.d.ts +1 -1
  34. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +3 -3
  35. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -2
  36. package/esm/BasicTrack/index.d.ts +1 -1
  37. package/esm/BasicTrack/index.js +4 -4
  38. package/esm/BasicTrack/index.js.map +1 -1
  39. package/esm/LinearBasicDisplay/model.d.ts +2 -2
  40. package/esm/LinearGenomeView/components/CenterLine.d.ts +1 -1
  41. package/esm/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -1
  42. package/esm/LinearGenomeView/components/Gridlines.d.ts +1 -1
  43. package/esm/LinearGenomeView/components/Header.d.ts +1 -1
  44. package/esm/LinearGenomeView/components/ImportForm.d.ts +1 -1
  45. package/esm/LinearGenomeView/components/ImportForm.js +8 -7
  46. package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
  47. package/esm/LinearGenomeView/components/LinearGenomeView.d.ts +1 -1
  48. package/esm/LinearGenomeView/components/LinearGenomeView.js +2 -4
  49. package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
  50. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +1 -1
  51. package/esm/LinearGenomeView/components/OverviewRubberBand.d.ts +1 -1
  52. package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +1 -1
  53. package/esm/LinearGenomeView/components/RubberBand.d.ts +1 -1
  54. package/esm/LinearGenomeView/components/ScaleBar.d.ts +1 -1
  55. package/esm/LinearGenomeView/components/TrackContainer.d.ts +1 -1
  56. package/esm/LinearGenomeView/components/TrackContainer.js +13 -10
  57. package/esm/LinearGenomeView/components/TrackContainer.js.map +1 -1
  58. package/esm/LinearGenomeView/components/TrackLabel.js +9 -1
  59. package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -1
  60. package/esm/LinearGenomeView/components/TracksContainer.d.ts +1 -1
  61. package/esm/LinearGenomeView/index.d.ts +84 -3
  62. package/esm/LinearGenomeView/index.js +143 -63
  63. package/esm/LinearGenomeView/index.js.map +1 -1
  64. package/esm/index.d.ts +18 -6
  65. package/package.json +2 -2
  66. package/src/BasicTrack/index.ts +4 -8
  67. package/src/LinearGenomeView/components/ImportForm.tsx +9 -7
  68. package/src/LinearGenomeView/components/LinearGenomeView.tsx +2 -8
  69. package/src/LinearGenomeView/components/TrackContainer.tsx +38 -27
  70. package/src/LinearGenomeView/components/TrackLabel.tsx +10 -1
  71. package/src/LinearGenomeView/index.tsx +154 -73
@@ -5,18 +5,14 @@ import {
5
5
  import PluginManager from '@jbrowse/core/PluginManager'
6
6
  import configSchemaF from './configSchema'
7
7
 
8
- export default (pluginManager: PluginManager) => {
9
- pluginManager.addTrackType(() => {
10
- const configSchema = configSchemaF(pluginManager)
8
+ export default (pm: PluginManager) => {
9
+ pm.addTrackType(() => {
10
+ const configSchema = configSchemaF(pm)
11
11
 
12
12
  return new TrackType({
13
13
  name: 'BasicTrack',
14
14
  configSchema,
15
- stateModel: createBaseTrackModel(
16
- pluginManager,
17
- 'BasicTrack',
18
- configSchema,
19
- ),
15
+ stateModel: createBaseTrackModel(pm, 'BasicTrack', configSchema),
20
16
  })
21
17
  })
22
18
  }
@@ -11,6 +11,8 @@ import {
11
11
  } from '@mui/material'
12
12
  import { ErrorMessage, AssemblySelector } from '@jbrowse/core/ui'
13
13
  import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
14
+
15
+ // icons
14
16
  import CloseIcon from '@mui/icons-material/Close'
15
17
 
16
18
  // locals
@@ -39,7 +41,6 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
39
41
  const { assemblyNames, assemblyManager, textSearchManager } = session
40
42
  const { rankSearchResults, isSearchDialogDisplayed, error } = model
41
43
  const [selectedAsm, setSelectedAsm] = useState(assemblyNames[0])
42
- const [importError, setImportError] = useState(error)
43
44
  const [option, setOption] = useState<BaseResult>()
44
45
 
45
46
  const searchScope = model.searchScope(selectedAsm)
@@ -49,15 +50,17 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
49
50
  ? assembly?.error
50
51
  : 'No configured assemblies'
51
52
  const regions = assembly?.regions || []
52
- const err = assemblyError || importError
53
+ const displayError = assemblyError || error
53
54
  const [value, setValue] = useState('')
54
55
  const r0 = regions[0]?.refName
55
56
 
56
57
  // useEffect resets to an "initial state" of displaying first region from assembly
57
- // after assembly change
58
+ // after assembly change. needs to react to selectedAsm as well as r0 because changing
59
+ // assembly will run setValue('') and then r0 may not change if assembly names are the
60
+ // same across assemblies, but it still needs to be reset
58
61
  useEffect(() => {
59
62
  setValue(r0)
60
- }, [r0])
63
+ }, [r0, selectedAsm])
61
64
 
62
65
  function navToOption(option: BaseResult) {
63
66
  const location = option.getLocation()
@@ -116,7 +119,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
116
119
  // having this wrapped in a form allows intuitive use of enter key to submit
117
120
  return (
118
121
  <div className={classes.container}>
119
- {err ? <ErrorMessage error={err} /> : null}
122
+ {displayError ? <ErrorMessage error={displayError} /> : null}
120
123
  <Container className={classes.importFormContainer}>
121
124
  <form
122
125
  onSubmit={event => {
@@ -137,7 +140,6 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
137
140
  <FormControl>
138
141
  <AssemblySelector
139
142
  onChange={val => {
140
- setImportError('')
141
143
  setSelectedAsm(val)
142
144
  setValue('')
143
145
  }}
@@ -148,7 +150,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
148
150
  </Grid>
149
151
  <Grid item>
150
152
  {selectedAsm ? (
151
- err ? (
153
+ assemblyError ? (
152
154
  <CloseIcon style={{ color: 'red' }} />
153
155
  ) : value ? (
154
156
  <FormControl>
@@ -1,9 +1,8 @@
1
1
  import React from 'react'
2
2
  import { Button, Paper, Typography } from '@mui/material'
3
3
  import { makeStyles } from 'tss-react/mui'
4
- import { ErrorBoundary } from 'react-error-boundary'
5
4
  import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
6
- import { LoadingEllipses, ErrorMessage } from '@jbrowse/core/ui'
5
+ import { LoadingEllipses } from '@jbrowse/core/ui'
7
6
  import { observer } from 'mobx-react'
8
7
 
9
8
  // locals
@@ -76,12 +75,7 @@ const LinearGenomeView = observer(({ model }: { model: LGV }) => {
76
75
  </Paper>
77
76
  ) : (
78
77
  tracks.map(track => (
79
- <ErrorBoundary
80
- key={track.id}
81
- FallbackComponent={({ error }) => <ErrorMessage error={error} />}
82
- >
83
- <TrackContainer model={model} track={track} />
84
- </ErrorBoundary>
78
+ <TrackContainer key={track.id} model={model} track={track} />
85
79
  ))
86
80
  )}
87
81
  </TracksContainer>
@@ -5,7 +5,8 @@ import { observer } from 'mobx-react'
5
5
  import { isAlive } from 'mobx-state-tree'
6
6
  import { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes/models'
7
7
  import { getConf } from '@jbrowse/core/configuration'
8
- import { ResizeHandle } from '@jbrowse/core/ui'
8
+ import { ResizeHandle, ErrorMessage } from '@jbrowse/core/ui'
9
+ import { ErrorBoundary } from 'react-error-boundary'
9
10
  import { useDebouncedCallback } from '@jbrowse/core/util'
10
11
 
11
12
  // locals
@@ -92,6 +93,7 @@ function TrackContainer({
92
93
  const trackId = getConf(track, 'trackId')
93
94
  const ref = useRef(null)
94
95
  const dimmed = draggingTrackId !== undefined && draggingTrackId !== display.id
96
+ const minimized = track.minimized
95
97
  const debouncedOnDragEnter = useDebouncedCallback(() => {
96
98
  if (isAlive(display) && dimmed) {
97
99
  moveTrack(draggingTrackId, track.id)
@@ -109,36 +111,45 @@ function TrackContainer({
109
111
  return (
110
112
  <Paper className={classes.root} variant="outlined">
111
113
  <TrackContainerLabel model={track} view={model} />
112
- <div
113
- className={classes.trackRenderingContainer}
114
- style={{ height }}
115
- onScroll={event => display.setScrollTop(event.currentTarget.scrollTop)}
116
- onDragEnter={debouncedOnDragEnter}
117
- data-testid={`trackRenderingContainer-${model.id}-${trackId}`}
114
+ <ErrorBoundary
115
+ key={track.id}
116
+ FallbackComponent={({ error }) => <ErrorMessage error={error} />}
118
117
  >
119
118
  <div
120
- ref={ref}
121
- className={classes.renderingComponentContainer}
122
- style={{ transform: `scaleX(${model.scaleFactor})` }}
119
+ className={classes.trackRenderingContainer}
120
+ style={{ height: minimized ? 20 : height }}
121
+ onScroll={evt => display.setScrollTop(evt.currentTarget.scrollTop)}
122
+ onDragEnter={debouncedOnDragEnter}
123
+ data-testid={`trackRenderingContainer-${model.id}-${trackId}`}
123
124
  >
124
- <RenderingComponent
125
- model={display}
126
- onHorizontalScroll={horizontalScroll}
127
- />
128
- </div>
125
+ {!minimized ? (
126
+ <>
127
+ <div
128
+ ref={ref}
129
+ className={classes.renderingComponentContainer}
130
+ style={{ transform: `scaleX(${model.scaleFactor})` }}
131
+ >
132
+ <RenderingComponent
133
+ model={display}
134
+ onHorizontalScroll={horizontalScroll}
135
+ />
136
+ </div>
129
137
 
130
- {DisplayBlurb ? (
131
- <div
132
- style={{
133
- position: 'absolute',
134
- left: 0,
135
- top: display.height - 20,
136
- }}
137
- >
138
- <DisplayBlurb model={display} />
139
- </div>
140
- ) : null}
141
- </div>
138
+ {DisplayBlurb ? (
139
+ <div
140
+ style={{
141
+ position: 'absolute',
142
+ left: 0,
143
+ top: display.height - 20,
144
+ }}
145
+ >
146
+ <DisplayBlurb model={display} />
147
+ </div>
148
+ ) : null}
149
+ </>
150
+ ) : null}
151
+ </div>
152
+ </ErrorBoundary>
142
153
  <div
143
154
  className={classes.overlay}
144
155
  style={{
@@ -18,6 +18,8 @@ import {
18
18
  import MoreVertIcon from '@mui/icons-material/MoreVert'
19
19
  import DragIcon from '@mui/icons-material/DragIndicator'
20
20
  import CloseIcon from '@mui/icons-material/Close'
21
+ import MinimizeIcon from '@mui/icons-material/Minimize'
22
+ import AddIcon from '@mui/icons-material/Add'
21
23
 
22
24
  import { LinearGenomeViewModel } from '..'
23
25
 
@@ -64,6 +66,7 @@ const TrackLabel = React.forwardRef<HTMLDivElement, Props>(
64
66
  const view = getContainingView(track) as LGV
65
67
  const session = getSession(track)
66
68
  const trackConf = track.configuration
69
+ const minimized = track.minimized
67
70
  const trackId = getConf(track, 'trackId')
68
71
  const trackName = getTrackName(trackConf, session)
69
72
 
@@ -73,6 +76,11 @@ const TrackLabel = React.forwardRef<HTMLDivElement, Props>(
73
76
  })
74
77
 
75
78
  const items = [
79
+ {
80
+ label: minimized ? 'Restore track' : 'Minimize track',
81
+ icon: minimized ? AddIcon : MinimizeIcon,
82
+ onClick: () => track.setMinimized(!minimized),
83
+ },
76
84
  ...(session.getTrackActionMenuItems?.(trackConf) || []),
77
85
  ...track.trackMenuItems(),
78
86
  ].sort((a, b) => (b.priority || 0) - (a.priority || 0))
@@ -103,12 +111,13 @@ const TrackLabel = React.forwardRef<HTMLDivElement, Props>(
103
111
  >
104
112
  <CloseIcon fontSize="small" />
105
113
  </IconButton>
114
+
106
115
  <Typography
107
116
  variant="body1"
108
117
  component="span"
109
118
  className={classes.trackName}
110
119
  >
111
- {trackName}
120
+ {trackName + (minimized ? ' (minimized)' : '')}
112
121
  </Typography>
113
122
  <IconButton
114
123
  {...bindTrigger(popupState)}
@@ -59,6 +59,7 @@ import Header from './components/Header'
59
59
  import ZoomControls from './components/ZoomControls'
60
60
  import LinearGenomeView from './components/LinearGenomeView'
61
61
 
62
+ // lazies
62
63
  const SequenceSearchDialog = lazy(
63
64
  () => import('./components/SequenceSearchDialog'),
64
65
  )
@@ -136,8 +137,11 @@ export function stateModelFactory(pluginManager: PluginManager) {
136
137
 
137
138
  /**
138
139
  * #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
139
143
  */
140
- type: types.literal('LinearGenomeView'),
144
+ type: types.literal('LinearGenomeView') as unknown as string,
141
145
 
142
146
  /**
143
147
  * #property
@@ -274,16 +278,25 @@ export function stateModelFactory(pluginManager: PluginManager) {
274
278
  },
275
279
  }))
276
280
  .views(self => ({
281
+ /**
282
+ * #method
283
+ */
277
284
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
278
285
  MiniControlsComponent(): React.FC<any> {
279
286
  return MiniControls
280
287
  },
281
288
 
289
+ /**
290
+ * #method
291
+ */
282
292
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
283
293
  HeaderComponent(): React.FC<any> {
284
294
  return Header
285
295
  },
286
296
 
297
+ /**
298
+ * #getter
299
+ */
287
300
  get assemblyErrors() {
288
301
  const { assemblyManager } = getSession(self)
289
302
  const { assemblyNames } = self
@@ -293,23 +306,46 @@ export function stateModelFactory(pluginManager: PluginManager) {
293
306
  .join(', ')
294
307
  },
295
308
 
309
+ /**
310
+ * #getter
311
+ */
296
312
  get assembliesInitialized() {
297
313
  const { assemblyManager } = getSession(self)
298
314
  const { assemblyNames } = self
299
315
  return assemblyNames.every(a => assemblyManager.get(a)?.initialized)
300
316
  },
317
+
318
+ /**
319
+ * #getter
320
+ */
301
321
  get initialized() {
302
322
  return self.volatileWidth !== undefined && this.assembliesInitialized
303
323
  },
324
+
325
+ /**
326
+ * #getter
327
+ */
304
328
  get hasDisplayedRegions() {
305
329
  return self.displayedRegions.length > 0
306
330
  },
331
+
332
+ /**
333
+ * #getter
334
+ */
307
335
  get isSearchDialogDisplayed() {
308
336
  return self.searchResults !== undefined
309
337
  },
338
+
339
+ /**
340
+ * #getter
341
+ */
310
342
  get scaleBarHeight() {
311
343
  return SCALE_BAR_HEIGHT + RESIZE_HANDLE_HEIGHT
312
344
  },
345
+
346
+ /**
347
+ * #getter
348
+ */
313
349
  get headerHeight() {
314
350
  if (self.hideHeader) {
315
351
  return 0
@@ -319,15 +355,26 @@ export function stateModelFactory(pluginManager: PluginManager) {
319
355
  }
320
356
  return HEADER_BAR_HEIGHT + HEADER_OVERVIEW_HEIGHT
321
357
  },
358
+
359
+ /**
360
+ * #getter
361
+ */
322
362
  get trackHeights() {
323
363
  return self.tracks
324
364
  .map(t => t.displays[0].height)
325
365
  .reduce((a, b) => a + b, 0)
326
366
  },
327
367
 
368
+ /**
369
+ * #getter
370
+ */
328
371
  get trackHeightsWithResizeHandles() {
329
372
  return this.trackHeights + self.tracks.length * RESIZE_HANDLE_HEIGHT
330
373
  },
374
+
375
+ /**
376
+ * #getter
377
+ */
331
378
  get height() {
332
379
  return (
333
380
  this.trackHeightsWithResizeHandles +
@@ -335,38 +382,63 @@ export function stateModelFactory(pluginManager: PluginManager) {
335
382
  this.scaleBarHeight
336
383
  )
337
384
  },
385
+
386
+ /**
387
+ * #getter
388
+ */
338
389
  get totalBp() {
339
390
  return self.displayedRegions.reduce((a, b) => a + b.end - b.start, 0)
340
391
  },
341
392
 
393
+ /**
394
+ * #getter
395
+ */
342
396
  get maxBpPerPx() {
343
397
  return this.totalBp / (self.width * 0.9)
344
398
  },
345
399
 
400
+ /**
401
+ * #getter
402
+ */
346
403
  get minBpPerPx() {
347
404
  return 1 / 50
348
405
  },
349
406
 
407
+ /**
408
+ * #getter
409
+ */
350
410
  get error() {
351
411
  return self.volatileError || this.assemblyErrors
352
412
  },
353
413
 
414
+ /**
415
+ * #getter
416
+ */
354
417
  get maxOffset() {
355
418
  // objectively determined to keep the linear genome on the main screen
356
419
  const leftPadding = 10
357
420
  return this.displayedRegionsTotalPx - leftPadding
358
421
  },
359
422
 
423
+ /**
424
+ * #getter
425
+ */
360
426
  get minOffset() {
361
427
  // objectively determined to keep the linear genome on the main screen
362
428
  const rightPadding = 30
363
429
  return -self.width + rightPadding
364
430
  },
365
431
 
432
+ /**
433
+ * #getter
434
+ */
366
435
  get displayedRegionsTotalPx() {
367
436
  return this.totalBp / self.bpPerPx
368
437
  },
369
438
 
439
+ /**
440
+ * #method
441
+ */
370
442
  renderProps() {
371
443
  return {
372
444
  ...getParentRenderProps(self),
@@ -378,6 +450,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
378
450
  }
379
451
  },
380
452
 
453
+ /**
454
+ * #method
455
+ */
381
456
  searchScope(assemblyName: string) {
382
457
  return {
383
458
  assemblyName,
@@ -386,10 +461,16 @@ export function stateModelFactory(pluginManager: PluginManager) {
386
461
  }
387
462
  },
388
463
 
464
+ /**
465
+ * #method
466
+ */
389
467
  getTrack(id: string) {
390
468
  return self.tracks.find(t => t.configuration.trackId === id)
391
469
  },
392
470
 
471
+ /**
472
+ * #method
473
+ */
393
474
  rankSearchResults(results: BaseResult[]) {
394
475
  // order of rank
395
476
  const openTrackIds = self.tracks.map(
@@ -403,7 +484,10 @@ export function stateModelFactory(pluginManager: PluginManager) {
403
484
  return results
404
485
  },
405
486
 
406
- // modifies view menu action onClick to apply to all tracks of same type
487
+ /**
488
+ * #method
489
+ * modifies view menu action onClick to apply to all tracks of same type
490
+ */
407
491
  rewriteOnClicks(trackType: string, viewMenuActions: MenuItem[]) {
408
492
  viewMenuActions.forEach(action => {
409
493
  // go to lowest level menu
@@ -422,7 +506,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
422
506
  }
423
507
  })
424
508
  },
425
-
509
+ /**
510
+ * #getter
511
+ */
426
512
  get trackTypeActions() {
427
513
  const allActions: Map<string, MenuItem[]> = new Map()
428
514
  self.tracks.forEach(track => {
@@ -609,7 +695,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
609
695
  }
610
696
  return t[0]
611
697
  },
612
-
698
+ /**
699
+ * #action
700
+ */
613
701
  hideTrack(trackId: string) {
614
702
  const schema = pluginManager.pluggableConfigSchemaType('track')
615
703
  const conf = resolveIdentifier(schema, getRoot(self), trackId)
@@ -814,7 +902,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
814
902
  * this "clears the view" and makes the view return to the import form
815
903
  */
816
904
  clearView() {
817
- this.setDisplayedRegions([])
905
+ self.displayedRegions.clear()
818
906
  self.tracks.clear()
819
907
  // it is necessary to run these after setting displayed regions empty
820
908
  // or else model.offsetPx gets set to Infinity and breaks
@@ -973,59 +1061,57 @@ export function stateModelFactory(pluginManager: PluginManager) {
973
1061
  icon: SyncAltIcon,
974
1062
  onClick: self.horizontallyFlip,
975
1063
  },
976
- { type: 'divider' },
977
1064
  {
978
- label: 'Show all regions in assembly',
1065
+ label: 'Show...',
979
1066
  icon: VisibilityIcon,
980
- onClick: self.showAllRegionsInAssembly,
981
- },
982
- {
983
- label: 'Show center line',
984
- icon: VisibilityIcon,
985
- type: 'checkbox',
986
- checked: self.showCenterLine,
987
- onClick: self.toggleCenterLine,
988
- },
989
- {
990
- label: 'Show header',
991
- icon: VisibilityIcon,
992
- type: 'checkbox',
993
- checked: !self.hideHeader,
994
- onClick: self.toggleHeader,
995
- },
996
- {
997
- label: 'Show header overview',
998
- icon: VisibilityIcon,
999
- type: 'checkbox',
1000
- checked: !self.hideHeaderOverview,
1001
- onClick: self.toggleHeaderOverview,
1002
- disabled: self.hideHeader,
1003
- },
1004
- {
1005
- label: 'Show no tracks active button',
1006
- icon: VisibilityIcon,
1007
- type: 'checkbox',
1008
- checked: !self.hideNoTracksActive,
1009
- onClick: self.toggleNoTracksActive,
1010
- },
1011
- {
1012
- label: 'Show guidelines',
1013
- icon: VisibilityIcon,
1014
- type: 'checkbox',
1015
- checked: self.showGridlines,
1016
- onClick: self.toggleShowGridlines,
1067
+ subMenu: [
1068
+ {
1069
+ label: 'Show all regions in assembly',
1070
+ onClick: self.showAllRegionsInAssembly,
1071
+ },
1072
+ {
1073
+ label: 'Show center line',
1074
+ type: 'checkbox',
1075
+ checked: self.showCenterLine,
1076
+ onClick: self.toggleCenterLine,
1077
+ },
1078
+ {
1079
+ label: 'Show header',
1080
+ type: 'checkbox',
1081
+ checked: !self.hideHeader,
1082
+ onClick: self.toggleHeader,
1083
+ },
1084
+ {
1085
+ label: 'Show header overview',
1086
+ type: 'checkbox',
1087
+ checked: !self.hideHeaderOverview,
1088
+ onClick: self.toggleHeaderOverview,
1089
+ disabled: self.hideHeader,
1090
+ },
1091
+ {
1092
+ label: 'Show no tracks active button',
1093
+ type: 'checkbox',
1094
+ checked: !self.hideNoTracksActive,
1095
+ onClick: self.toggleNoTracksActive,
1096
+ },
1097
+ {
1098
+ label: 'Show guidelines',
1099
+ type: 'checkbox',
1100
+ checked: self.showGridlines,
1101
+ onClick: self.toggleShowGridlines,
1102
+ },
1103
+ ...(canShowCytobands
1104
+ ? [
1105
+ {
1106
+ label: 'Show ideogram',
1107
+ type: 'checkbox' as const,
1108
+ checked: self.showCytobands,
1109
+ onClick: () => self.setShowCytobands(!showCytobands),
1110
+ },
1111
+ ]
1112
+ : []),
1113
+ ],
1017
1114
  },
1018
- ...(canShowCytobands
1019
- ? [
1020
- {
1021
- label: 'Show ideogram',
1022
- icon: VisibilityIcon,
1023
- type: 'checkbox' as const,
1024
- checked: self.showCytobands,
1025
- onClick: () => self.setShowCytobands(!showCytobands),
1026
- },
1027
- ]
1028
- : []),
1029
1115
  {
1030
1116
  label: 'Track labels',
1031
1117
  icon: LabelIcon,
@@ -1375,18 +1461,14 @@ export function stateModelFactory(pluginManager: PluginManager) {
1375
1461
  )
1376
1462
  return
1377
1463
  }
1378
- let locationIndex = 0
1379
- let locationStart = 0
1380
- let locationEnd = 0
1381
- for (
1382
- locationIndex;
1383
- locationIndex < locations.length;
1384
- locationIndex++
1385
- ) {
1386
- const location = locations[locationIndex]
1387
- const region = self.displayedRegions[index + locationIndex]
1388
- locationStart = location.start || region.start
1389
- locationEnd = location.end || region.end
1464
+ let idx = 0
1465
+ let start = 0
1466
+ let end = 0
1467
+ for (idx; idx < locations.length; idx++) {
1468
+ const location = locations[idx]
1469
+ const region = self.displayedRegions[index + idx]
1470
+ start = location.start || region.start
1471
+ end = location.end || region.end
1390
1472
  if (location.refName !== region.refName) {
1391
1473
  throw new Error(
1392
1474
  `Entered location ${assembleLocString(
@@ -1395,10 +1477,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
1395
1477
  )
1396
1478
  }
1397
1479
  }
1398
- locationIndex -= 1
1480
+ idx -= 1
1399
1481
  const startDisplayedRegion = self.displayedRegions[index]
1400
- const endDisplayedRegion =
1401
- self.displayedRegions[index + locationIndex]
1482
+ const endDisplayedRegion = self.displayedRegions[index + idx]
1402
1483
  this.moveTo(
1403
1484
  {
1404
1485
  index,
@@ -1407,10 +1488,10 @@ export function stateModelFactory(pluginManager: PluginManager) {
1407
1488
  : s - startDisplayedRegion.start,
1408
1489
  },
1409
1490
  {
1410
- index: index + locationIndex,
1491
+ index: index + idx,
1411
1492
  offset: endDisplayedRegion.reversed
1412
- ? endDisplayedRegion.end - locationStart
1413
- : locationEnd - endDisplayedRegion.start,
1493
+ ? endDisplayedRegion.end - start
1494
+ : end - endDisplayedRegion.start,
1414
1495
  },
1415
1496
  )
1416
1497
  return