@jbrowse/plugin-linear-genome-view 1.6.3 → 1.6.6

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 (30) hide show
  1. package/dist/BaseLinearDisplay/components/LinearBlocks.d.ts +0 -5
  2. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.d.ts +1 -1
  3. package/dist/LinearBareDisplay/configSchema.d.ts +1 -1
  4. package/dist/LinearBasicDisplay/configSchema.d.ts +1 -1
  5. package/dist/LinearGenomeView/components/ScaleBar.d.ts +2 -0
  6. package/dist/LinearGenomeView/components/TrackContainer.d.ts +3 -2
  7. package/dist/LinearGenomeView/index.d.ts +1 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/plugin-linear-genome-view.cjs.development.js +143 -250
  10. package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
  11. package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
  12. package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
  13. package/dist/plugin-linear-genome-view.esm.js +145 -252
  14. package/dist/plugin-linear-genome-view.esm.js.map +1 -1
  15. package/package.json +2 -2
  16. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +2 -4
  17. package/src/BaseLinearDisplay/components/LinearBlocks.tsx +4 -8
  18. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +11 -3
  19. package/src/LinearBasicDisplay/model.ts +1 -1
  20. package/src/LinearGenomeView/components/HelpDialog.tsx +14 -1
  21. package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +1 -13
  22. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +7 -3
  23. package/src/LinearGenomeView/components/TrackContainer.tsx +24 -39
  24. package/src/LinearGenomeView/components/TrackLabel.tsx +5 -5
  25. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +194 -191
  26. package/src/LinearGenomeView/index.test.ts +44 -42
  27. package/src/LinearGenomeView/index.tsx +176 -245
  28. package/src/LinearGenomeView/volvoxDisplayedRegions.json +16 -0
  29. package/dist/LinearGenomeView/components/ReturnToImportFormDialog.d.ts +0 -9
  30. package/src/LinearGenomeView/components/ReturnToImportFormDialog.tsx +0 -83
@@ -1,4 +1,4 @@
1
- import { getConf } from '@jbrowse/core/configuration'
1
+ import { getConf, AnyConfigurationModel } from '@jbrowse/core/configuration'
2
2
  import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
3
3
  import { Region } from '@jbrowse/core/util/types'
4
4
  import { ElementId, Region as MUIRegion } from '@jbrowse/core/util/types/mst'
@@ -35,7 +35,6 @@ import {
35
35
  import Base1DView from '@jbrowse/core/util/Base1DViewModel'
36
36
  import PluginManager from '@jbrowse/core/PluginManager'
37
37
  import clone from 'clone'
38
- import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema'
39
38
  import { saveAs } from 'file-saver'
40
39
 
41
40
  // icons
@@ -53,7 +52,7 @@ import { renderToSvg } from './components/LinearGenomeViewSvg'
53
52
  import RefNameAutocomplete from './components/RefNameAutocomplete'
54
53
  import SearchBox from './components/SearchBox'
55
54
  import ExportSvgDlg from './components/ExportSvgDialog'
56
- import ReturnToImportFormDlg from './components/ReturnToImportFormDialog'
55
+ import { ReturnToImportFormDialog } from '@jbrowse/core/ui'
57
56
 
58
57
  export interface BpOffset {
59
58
  refName?: string
@@ -86,7 +85,7 @@ function calculateVisibleLocStrings(contentBlocks: BaseBlock[]) {
86
85
  assemblyName: isSingleAssemblyName ? undefined : block.assemblyName,
87
86
  }),
88
87
  )
89
- return locs.join(';')
88
+ return locs.join(' ')
90
89
  }
91
90
 
92
91
  export interface NavLocation {
@@ -104,6 +103,12 @@ export const INTER_REGION_PADDING_WIDTH = 2
104
103
  export const WIDGET_HEIGHT = 32
105
104
  export const SPACING = 7
106
105
 
106
+ function localStorageGetItem(item: string) {
107
+ return typeof localStorage !== 'undefined'
108
+ ? localStorage.getItem(item)
109
+ : undefined
110
+ }
111
+
107
112
  export function stateModelFactory(pluginManager: PluginManager) {
108
113
  return types
109
114
  .compose(
@@ -128,15 +133,15 @@ export function stateModelFactory(pluginManager: PluginManager) {
128
133
  ),
129
134
  trackLabels: types.optional(
130
135
  types.string,
131
- () => localStorage.getItem('lgv-trackLabels') || 'overlapping',
136
+ () => localStorageGetItem('lgv-trackLabels') || 'overlapping',
132
137
  ),
133
138
  showCenterLine: types.optional(types.boolean, () => {
134
- const setting = localStorage.getItem('lgv-showCenterLine')
135
- return setting !== undefined ? !!setting : false
139
+ const setting = localStorageGetItem('lgv-showCenterLine')
140
+ return setting !== undefined && setting !== null ? !!+setting : false
136
141
  }),
137
142
  showCytobandsSetting: types.optional(types.boolean, () => {
138
- const setting = localStorage.getItem('lgv-showCytobands')
139
- return setting !== undefined ? !!setting : true
143
+ const setting = localStorageGetItem('lgv-showCytobands')
144
+ return setting !== undefined && setting !== null ? !!+setting : true
140
145
  }),
141
146
  }),
142
147
  )
@@ -666,101 +671,63 @@ export function stateModelFactory(pluginManager: PluginManager) {
666
671
  },
667
672
 
668
673
  navToLocString(locString: string, optAssemblyName?: string) {
674
+ const { assemblyNames } = self
669
675
  const { assemblyManager } = getSession(self)
670
676
  const { isValidRefName } = assemblyManager
671
- const locStrings = locString.split(';')
672
- if (self.displayedRegions.length > 1) {
673
- const locations = locStrings.map(ls =>
674
- parseLocString(ls, isValidRefName),
675
- )
676
- this.navToMultiple(locations)
677
- return
678
- }
679
- let assemblyName = optAssemblyName
680
- let defaultRefName = ''
681
- if (self.displayedRegions.length !== 0) {
682
- // defaults
683
- assemblyName = self.displayedRegions[0].assemblyName
684
- defaultRefName = self.displayedRegions[0].refName
685
- }
686
- let assembly = assemblyName && assemblyManager.get(assemblyName)
687
- if (!assembly) {
688
- throw new Error(`Could not find assembly ${assemblyName}`)
689
- }
690
- let { regions } = assembly
691
- if (!regions) {
692
- throw new Error(`Regions for assembly ${assemblyName} not yet loaded`)
693
- }
694
- if (locStrings.length > 1) {
695
- throw new Error(
696
- 'Navigating to multiple locations is not allowed when viewing a whole chromosome',
697
- )
698
- }
699
- const parsedLocString = parseLocString(locStrings[0], refName =>
700
- isValidRefName(refName, assemblyName),
701
- )
702
- let changedAssembly = false
703
- if (
704
- parsedLocString.assemblyName &&
705
- parsedLocString.assemblyName !== assemblyName
706
- ) {
707
- const newAssembly = assemblyManager.get(parsedLocString.assemblyName)
708
- if (!newAssembly) {
709
- throw new Error(
710
- `Could not find assembly ${parsedLocString.assemblyName}`,
711
- )
677
+ const assemblyName = optAssemblyName || assemblyNames[0]
678
+
679
+ const parsedLocStrings = locString
680
+ .split(' ')
681
+ .filter(f => !!f.trim())
682
+ .map(l => parseLocString(l, ref => isValidRefName(ref, assemblyName)))
683
+
684
+ const locations = parsedLocStrings.map(region => {
685
+ const asmName = region.assemblyName || assemblyName
686
+ const asm = assemblyManager.get(asmName)
687
+ const { refName } = region
688
+ if (!asm) {
689
+ throw new Error(`assembly ${asmName} not found`)
712
690
  }
713
- assembly = newAssembly
714
- changedAssembly = true
715
- const newRegions = newAssembly.regions
716
- if (!newRegions) {
717
- throw new Error(
718
- `Regions for assembly ${parsedLocString.assemblyName} not yet loaded`,
719
- )
691
+ const { regions } = asm
692
+ if (!regions) {
693
+ throw new Error(`regions not loaded yet for ${asmName}`)
720
694
  }
721
- regions = newRegions
722
- }
723
- const canonicalRefName = assembly.getCanonicalRefName(
724
- parsedLocString.refName,
725
- )
726
-
727
- if (!canonicalRefName) {
728
- throw new Error(
729
- `Could not find refName ${parsedLocString.refName} in ${assembly.name}`,
730
- )
731
- }
732
- if (changedAssembly || canonicalRefName !== defaultRefName) {
733
- const newDisplayedRegion = regions.find(
695
+ const canonicalRefName = asm.getCanonicalRefName(region.refName)
696
+ if (!canonicalRefName) {
697
+ throw new Error(`Could not find refName ${refName} in ${asm.name}`)
698
+ }
699
+ const parentRegion = regions.find(
734
700
  region => region.refName === canonicalRefName,
735
701
  )
736
- if (newDisplayedRegion) {
737
- this.setDisplayedRegions([newDisplayedRegion])
738
- } else {
739
- throw new Error(
740
- `Could not find refName ${parsedLocString.refName} in ${assembly.name}`,
741
- )
702
+ if (!parentRegion) {
703
+ throw new Error(`Could not find refName ${refName} in ${asmName}`)
742
704
  }
743
- }
744
- const displayedRegion = regions.find(
745
- region => region.refName === canonicalRefName,
746
- )
747
- if (displayedRegion) {
748
- const start = clamp(
749
- parsedLocString?.start ?? 0,
750
- 0,
751
- displayedRegion.end,
752
- )
753
- const end = clamp(
754
- parsedLocString?.end ?? displayedRegion.end,
755
- 0,
756
- displayedRegion.end,
757
- )
705
+
706
+ return {
707
+ ...region,
708
+ assemblyName: asmName,
709
+ parentRegion,
710
+ }
711
+ })
712
+
713
+ if (locations.length === 1) {
714
+ const loc = locations[0]
715
+ this.setDisplayedRegions([
716
+ { reversed: loc.reversed, ...loc.parentRegion },
717
+ ])
718
+ const { start, end, parentRegion } = loc
758
719
 
759
720
  this.navTo({
760
- ...parsedLocString,
761
- start,
762
- end,
721
+ ...loc,
722
+ start: clamp(start ?? 0, 0, parentRegion.end),
723
+ end: clamp(end ?? parentRegion.end, 0, parentRegion.end),
763
724
  })
725
+ } else {
726
+ this.setDisplayedRegions(
727
+ // @ts-ignore
728
+ locations.map(r => (r.start === undefined ? r.parentRegion : r)),
729
+ )
730
+ this.showAllRegions()
764
731
  }
765
732
  },
766
733
 
@@ -883,43 +850,6 @@ export function stateModelFactory(pluginManager: PluginManager) {
883
850
  )} does not match with displayed regions`,
884
851
  )
885
852
  }
886
- if (locationIndex > 0) {
887
- // does it reach the left side?
888
- const matchesLeft = region.reversed
889
- ? locationEnd === region.end
890
- : locationStart === region.start
891
- if (!matchesLeft) {
892
- throw new Error(
893
- `${
894
- region.reversed ? 'End' : 'Start'
895
- } of region ${assembleLocString(
896
- location,
897
- )} should be ${(region.reversed
898
- ? region.end
899
- : region.start + 1
900
- ).toLocaleString('en-US')}, but it is not`,
901
- )
902
- }
903
- }
904
- const isLast = locationIndex === locations.length - 1
905
- if (!isLast) {
906
- // does it reach the right side?
907
- const matchesRight = region.reversed
908
- ? locationStart === region.start
909
- : locationEnd === region.end
910
- if (!matchesRight) {
911
- throw new Error(
912
- `${
913
- region.reversed ? 'Start' : 'End'
914
- } of region ${assembleLocString(
915
- location,
916
- )} should be ${(region.reversed
917
- ? region.start + 1
918
- : region.end
919
- ).toLocaleString('en-US')}, but it is not`,
920
- )
921
- }
922
- }
923
853
  }
924
854
  locationIndex -= 1
925
855
  const startDisplayedRegion = self.displayedRegions[index]
@@ -1214,127 +1144,128 @@ export function stateModelFactory(pluginManager: PluginManager) {
1214
1144
  : 0
1215
1145
  },
1216
1146
  }))
1217
- .views(self => {
1218
- let currentlyCalculatedStaticBlocks: BlockSet | undefined
1219
- let stringifiedCurrentlyCalculatedStaticBlocks = ''
1220
- return {
1221
- menuItems(): MenuItem[] {
1222
- const { canShowCytobands, showCytobands } = self
1223
-
1224
- const menuItems: MenuItem[] = [
1225
- {
1226
- label: 'Return to import form',
1227
- onClick: () => {
1228
- getSession(self).queueDialog((doneCallback: Function) => [
1229
- ReturnToImportFormDlg,
1230
- { model: self, handleClose: doneCallback },
1231
- ])
1232
- },
1233
- icon: FolderOpenIcon,
1234
- },
1235
- {
1236
- label: 'Export SVG',
1237
- icon: PhotoCameraIcon,
1238
- onClick: () => {
1239
- getSession(self).queueDialog((doneCallback: Function) => [
1240
- ExportSvgDlg,
1241
- { model: self, handleClose: doneCallback },
1242
- ])
1243
- },
1244
- },
1245
- {
1246
- label: 'Open track selector',
1247
- onClick: self.activateTrackSelector,
1248
- icon: TrackSelectorIcon,
1249
- },
1250
- {
1251
- label: 'Horizontally flip',
1252
- icon: SyncAltIcon,
1253
- onClick: self.horizontallyFlip,
1254
- },
1255
- { type: 'divider' },
1256
- {
1257
- label: 'Show all regions in assembly',
1258
- icon: VisibilityIcon,
1259
- onClick: self.showAllRegionsInAssembly,
1260
- },
1261
- {
1262
- label: 'Show center line',
1263
- icon: VisibilityIcon,
1264
- type: 'checkbox',
1265
- checked: self.showCenterLine,
1266
- onClick: self.toggleCenterLine,
1267
- },
1268
- {
1269
- label: 'Show header',
1270
- icon: VisibilityIcon,
1271
- type: 'checkbox',
1272
- checked: !self.hideHeader,
1273
- onClick: self.toggleHeader,
1147
+ .views(self => ({
1148
+ menuItems(): MenuItem[] {
1149
+ const { canShowCytobands, showCytobands } = self
1150
+
1151
+ const menuItems: MenuItem[] = [
1152
+ {
1153
+ label: 'Return to import form',
1154
+ onClick: () => {
1155
+ getSession(self).queueDialog(handleClose => [
1156
+ ReturnToImportFormDialog,
1157
+ { model: self, handleClose },
1158
+ ])
1274
1159
  },
1275
- {
1276
- label: 'Show header overview',
1277
- icon: VisibilityIcon,
1278
- type: 'checkbox',
1279
- checked: !self.hideHeaderOverview,
1280
- onClick: self.toggleHeaderOverview,
1281
- disabled: self.hideHeader,
1160
+ icon: FolderOpenIcon,
1161
+ },
1162
+ {
1163
+ label: 'Export SVG',
1164
+ icon: PhotoCameraIcon,
1165
+ onClick: () => {
1166
+ getSession(self).queueDialog(handleClose => [
1167
+ ExportSvgDlg,
1168
+ { model: self, handleClose },
1169
+ ])
1282
1170
  },
1283
- {
1284
- label: 'Track labels',
1285
- icon: LabelIcon,
1286
- subMenu: [
1287
- {
1288
- label: 'Overlapping',
1289
- icon: VisibilityIcon,
1290
- type: 'radio',
1291
- checked: self.trackLabels === 'overlapping',
1292
- onClick: () => self.setTrackLabels('overlapping'),
1293
- },
1294
- {
1295
- label: 'Offset',
1296
- icon: VisibilityIcon,
1297
- type: 'radio',
1298
- checked: self.trackLabels === 'offset',
1299
- onClick: () => self.setTrackLabels('offset'),
1300
- },
1171
+ },
1172
+ {
1173
+ label: 'Open track selector',
1174
+ onClick: self.activateTrackSelector,
1175
+ icon: TrackSelectorIcon,
1176
+ },
1177
+ {
1178
+ label: 'Horizontally flip',
1179
+ icon: SyncAltIcon,
1180
+ onClick: self.horizontallyFlip,
1181
+ },
1182
+ { type: 'divider' },
1183
+ {
1184
+ label: 'Show all regions in assembly',
1185
+ icon: VisibilityIcon,
1186
+ onClick: self.showAllRegionsInAssembly,
1187
+ },
1188
+ {
1189
+ label: 'Show center line',
1190
+ icon: VisibilityIcon,
1191
+ type: 'checkbox',
1192
+ checked: self.showCenterLine,
1193
+ onClick: self.toggleCenterLine,
1194
+ },
1195
+ {
1196
+ label: 'Show header',
1197
+ icon: VisibilityIcon,
1198
+ type: 'checkbox',
1199
+ checked: !self.hideHeader,
1200
+ onClick: self.toggleHeader,
1201
+ },
1202
+ {
1203
+ label: 'Show header overview',
1204
+ icon: VisibilityIcon,
1205
+ type: 'checkbox',
1206
+ checked: !self.hideHeaderOverview,
1207
+ onClick: self.toggleHeaderOverview,
1208
+ disabled: self.hideHeader,
1209
+ },
1210
+ {
1211
+ label: 'Track labels',
1212
+ icon: LabelIcon,
1213
+ subMenu: [
1214
+ {
1215
+ label: 'Overlapping',
1216
+ icon: VisibilityIcon,
1217
+ type: 'radio',
1218
+ checked: self.trackLabels === 'overlapping',
1219
+ onClick: () => self.setTrackLabels('overlapping'),
1220
+ },
1221
+ {
1222
+ label: 'Offset',
1223
+ icon: VisibilityIcon,
1224
+ type: 'radio',
1225
+ checked: self.trackLabels === 'offset',
1226
+ onClick: () => self.setTrackLabels('offset'),
1227
+ },
1228
+ {
1229
+ label: 'Hidden',
1230
+ icon: VisibilityIcon,
1231
+ type: 'radio',
1232
+ checked: self.trackLabels === 'hidden',
1233
+ onClick: () => self.setTrackLabels('hidden'),
1234
+ },
1235
+ ],
1236
+ },
1237
+ ...(canShowCytobands
1238
+ ? [
1301
1239
  {
1302
- label: 'Hidden',
1303
- icon: VisibilityIcon,
1304
- type: 'radio',
1305
- checked: self.trackLabels === 'hidden',
1306
- onClick: () => self.setTrackLabels('hidden'),
1307
- },
1308
- ],
1309
- },
1310
- ...(canShowCytobands
1311
- ? [
1312
- {
1313
- label: showCytobands ? 'Hide ideogram' : 'Show ideograms',
1314
- onClick: () => {
1315
- self.setShowCytobands(!showCytobands)
1316
- },
1240
+ label: showCytobands ? 'Hide ideogram' : 'Show ideograms',
1241
+ onClick: () => {
1242
+ self.setShowCytobands(!showCytobands)
1317
1243
  },
1318
- ]
1319
- : []),
1320
- ]
1244
+ },
1245
+ ]
1246
+ : []),
1247
+ ]
1321
1248
 
1322
- // add track's view level menu options
1323
- for (const [key, value] of self.trackTypeActions.entries()) {
1324
- if (value.length) {
1325
- menuItems.push(
1326
- { type: 'divider' },
1327
- { type: 'subHeader', label: key },
1328
- )
1329
- value.forEach(action => {
1330
- menuItems.push(action)
1331
- })
1332
- }
1249
+ // add track's view level menu options
1250
+ for (const [key, value] of self.trackTypeActions.entries()) {
1251
+ if (value.length) {
1252
+ menuItems.push(
1253
+ { type: 'divider' },
1254
+ { type: 'subHeader', label: key },
1255
+ )
1256
+ value.forEach(action => {
1257
+ menuItems.push(action)
1258
+ })
1333
1259
  }
1260
+ }
1334
1261
 
1335
- return menuItems
1336
- },
1337
-
1262
+ return menuItems
1263
+ },
1264
+ }))
1265
+ .views(self => {
1266
+ let currentlyCalculatedStaticBlocks: BlockSet | undefined
1267
+ let stringifiedCurrentlyCalculatedStaticBlocks = ''
1268
+ return {
1338
1269
  get staticBlocks() {
1339
1270
  const ret = calculateStaticBlocks(self)
1340
1271
  const sret = JSON.stringify(ret)
@@ -0,0 +1,16 @@
1
+ [
2
+ {
3
+ "assemblyName": "volvox",
4
+ "start": 0,
5
+ "end": 50001,
6
+ "refName": "ctgA",
7
+ "reversed": false
8
+ },
9
+ {
10
+ "assemblyName": "volvox",
11
+ "start": 0,
12
+ "end": 6079,
13
+ "refName": "ctgB",
14
+ "reversed": false
15
+ }
16
+ ]
@@ -1,9 +0,0 @@
1
- /// <reference types="react" />
2
- declare function ReturnToImportFormDialog({ model, handleClose, }: {
3
- model: {
4
- clearView: Function;
5
- };
6
- handleClose: () => void;
7
- }): JSX.Element;
8
- declare const _default: typeof ReturnToImportFormDialog;
9
- export default _default;
@@ -1,83 +0,0 @@
1
- import React from 'react'
2
- import { observer } from 'mobx-react'
3
- import { makeStyles } from '@material-ui/core/styles'
4
- import {
5
- Button,
6
- Dialog,
7
- DialogActions,
8
- DialogContent,
9
- DialogTitle,
10
- Divider,
11
- IconButton,
12
- Typography,
13
- } from '@material-ui/core'
14
- import CloseIcon from '@material-ui/icons/Close'
15
-
16
- const useStyles = makeStyles(theme => ({
17
- closeButton: {
18
- position: 'absolute',
19
- right: theme.spacing(1),
20
- top: theme.spacing(1),
21
- color: theme.palette.grey[500],
22
- },
23
- }))
24
-
25
- function ReturnToImportFormDialog({
26
- model,
27
- handleClose,
28
- }: {
29
- model: { clearView: Function }
30
- handleClose: () => void
31
- }) {
32
- const classes = useStyles()
33
- return (
34
- <Dialog maxWidth="xl" open onClose={handleClose}>
35
- <DialogTitle>
36
- Reference sequence
37
- {handleClose ? (
38
- <IconButton
39
- className={classes.closeButton}
40
- onClick={() => {
41
- handleClose()
42
- }}
43
- >
44
- <CloseIcon />
45
- </IconButton>
46
- ) : null}
47
- </DialogTitle>
48
- <Divider />
49
-
50
- <DialogContent>
51
- <Typography>
52
- Are you sure you want to return to the import form? This will lose
53
- your current view
54
- </Typography>
55
- </DialogContent>
56
- <DialogActions>
57
- <Button
58
- onClick={() => {
59
- model.clearView()
60
- handleClose()
61
- }}
62
- variant="contained"
63
- color="primary"
64
- autoFocus
65
- >
66
- OK
67
- </Button>
68
- <Button
69
- onClick={() => {
70
- handleClose()
71
- }}
72
- color="secondary"
73
- variant="contained"
74
- autoFocus
75
- >
76
- Cancel
77
- </Button>
78
- </DialogActions>
79
- </Dialog>
80
- )
81
- }
82
-
83
- export default observer(ReturnToImportFormDialog)