From e76db1460b9ab3a5bf5cab8952f849baa980b91d Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 12:46:31 -0400 Subject: [PATCH 01/41] Try some lazy react components --- .../core/pluggableElementTypes/ViewType.ts | 30 ++++++++++++++----- packages/core/ui/App.js | 30 +++++++++++-------- .../circular-view/src/CircularView/index.ts | 14 --------- plugins/circular-view/src/index.ts | 14 +++++++-- plugins/linear-genome-view/src/index.ts | 6 ++-- 5 files changed, 55 insertions(+), 39 deletions(-) delete mode 100644 plugins/circular-view/src/CircularView/index.ts diff --git a/packages/core/pluggableElementTypes/ViewType.ts b/packages/core/pluggableElementTypes/ViewType.ts index f259ee7e99..f2eb64c89c 100644 --- a/packages/core/pluggableElementTypes/ViewType.ts +++ b/packages/core/pluggableElementTypes/ViewType.ts @@ -10,21 +10,35 @@ type ViewReactComponent = React.ComponentType<{ }> export default class ViewType extends PluggableElementBase { - ReactComponent: ViewReactComponent + ReactComponent?: ViewReactComponent + + LazyReactComponent?: ViewReactComponent stateModel: IAnyModelType displayTypes: DisplayType[] = [] - constructor(stuff: { - name: string - ReactComponent: ViewReactComponent - stateModel: IAnyModelType - }) { + constructor( + stuff: + | { + name: string + ReactComponent: ViewReactComponent + stateModel: IAnyModelType + } + | { + name: string + LazyReactComponent: ViewReactComponent + stateModel: IAnyModelType + }, + ) { super(stuff) - this.ReactComponent = stuff.ReactComponent + if ('ReactComponent' in stuff) { + this.ReactComponent = stuff.ReactComponent + } else { + this.LazyReactComponent = stuff.LazyReactComponent + } this.stateModel = stuff.stateModel - if (!this.ReactComponent) { + if (!this.ReactComponent && !this.LazyReactComponent) { throw new Error(`no ReactComponent defined for view ${this.name}`) } if (!this.stateModel) { diff --git a/packages/core/ui/App.js b/packages/core/ui/App.js index e13a0c33cf..3386df6a12 100644 --- a/packages/core/ui/App.js +++ b/packages/core/ui/App.js @@ -1,11 +1,7 @@ /* eslint-disable react/prop-types */ -import React from 'react' -import AppBar from '@material-ui/core/AppBar' -import { makeStyles } from '@material-ui/core/styles' -import Fab from '@material-ui/core/Fab' +import React, { Suspense } from 'react' +import { AppBar, Fab, Toolbar, Tooltip, makeStyles } from '@material-ui/core' import LaunchIcon from '@material-ui/icons/Launch' -import Toolbar from '@material-ui/core/Toolbar' -import Tooltip from '@material-ui/core/Tooltip' import { observer } from 'mobx-react' import { getEnv } from 'mobx-state-tree' import DrawerWidget from './DrawerWidget' @@ -148,18 +144,28 @@ function App({ session, HeaderButtons }) { if (!viewType) { throw new Error(`unknown view type ${view.type}`) } - const { ReactComponent } = viewType + const { LazyReactComponent, ReactComponent } = viewType return ( session.removeView(view)} > - + {LazyReactComponent ? ( + Loading...}> + + + ) : ( + + )} ) })} diff --git a/plugins/circular-view/src/CircularView/index.ts b/plugins/circular-view/src/CircularView/index.ts deleted file mode 100644 index e54c87fd5f..0000000000 --- a/plugins/circular-view/src/CircularView/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import PluginManager from '@jbrowse/core/PluginManager' -import ReactComponentF from './components/CircularView' -import ModelF from './models/CircularView' - -export default ({ lib, load }: PluginManager) => { - const ViewType = lib['@jbrowse/core/pluggableElementTypes/ViewType'] - - const ReactComponent = load(ReactComponentF) - const { stateModel } = load(ModelF) - - return new ViewType({ name: 'CircularView', stateModel, ReactComponent }) -} - -export type { CircularViewModel } from './models/CircularView' diff --git a/plugins/circular-view/src/index.ts b/plugins/circular-view/src/index.ts index 50af45949c..151ecfbf4e 100644 --- a/plugins/circular-view/src/index.ts +++ b/plugins/circular-view/src/index.ts @@ -1,15 +1,23 @@ +import { lazy } from 'react' import { AbstractSessionModel, isAbstractMenuManager } from '@jbrowse/core/util' import PluginManager from '@jbrowse/core/PluginManager' import Plugin from '@jbrowse/core/Plugin' +import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType' import DataUsageIcon from '@material-ui/icons/DataUsage' -import CircularViewFactory from './CircularView' +import stateModelFactory from './CircularView/models/CircularView' export default class CircularViewPlugin extends Plugin { name = 'CircularViewPlugin' install(pluginManager: PluginManager) { - pluginManager.addViewType(() => - pluginManager.jbrequire(CircularViewFactory), + pluginManager.addViewType( + () => + new ViewType({ + LazyReactComponent: lazy( + () => import('./CircularView/components/CircularView'), + ), + stateModel: load(stateModelFactory), + }), ) } diff --git a/plugins/linear-genome-view/src/index.ts b/plugins/linear-genome-view/src/index.ts index 16627b7cc4..14807cd571 100644 --- a/plugins/linear-genome-view/src/index.ts +++ b/plugins/linear-genome-view/src/index.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import { configSchema as baseFeatureWidgetConfigSchema, ReactComponent as BaseFeatureWidgetReactComponent, @@ -29,7 +30,6 @@ import { import { LinearGenomeViewModel, LinearGenomeViewStateModel, - ReactComponent as LinearGenomeViewReactComponent, stateModelFactory as linearGenomeViewStateModelFactory, } from './LinearGenomeView' @@ -117,7 +117,9 @@ export default class LinearGenomeViewPlugin extends Plugin { new ViewType({ name: 'LinearGenomeView', stateModel: linearGenomeViewStateModelFactory(pluginManager), - ReactComponent: LinearGenomeViewReactComponent, + LazyReactComponent: lazy( + () => import('./LinearGenomeView/components/LinearGenomeView'), + ), }), ) pluginManager.addWidgetType( From da6c2d2b39132ae78701fadc8a634dd692160397 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 13:03:49 -0400 Subject: [PATCH 02/41] Circular view lazy --- plugins/circular-view/package.json | 2 + .../CircularView/components/CircularView.js | 550 +++++++------- .../src/CircularView/components/Ruler.js | 379 +++++----- .../src/CircularView/models/CircularView.ts | 684 +++++++++--------- plugins/circular-view/src/index.ts | 3 +- 5 files changed, 808 insertions(+), 810 deletions(-) diff --git a/plugins/circular-view/package.json b/plugins/circular-view/package.json index 99eb7a141d..e52a9ac12d 100644 --- a/plugins/circular-view/package.json +++ b/plugins/circular-view/package.json @@ -40,7 +40,9 @@ "peerDependencies": { "@jbrowse/core": "^1.0.0", "@material-ui/core": "^4.9.13", + "mobx": "^5.0.0", "mobx-state-tree": "3.14.1", + "mobx-react": "^6.0.0", "react": "^16.8.0", "react-dom": "^16.8.0" }, diff --git a/plugins/circular-view/src/CircularView/components/CircularView.js b/plugins/circular-view/src/CircularView/components/CircularView.js index e27c5a53a4..7cb2b34311 100644 --- a/plugins/circular-view/src/CircularView/components/CircularView.js +++ b/plugins/circular-view/src/CircularView/components/CircularView.js @@ -5,317 +5,309 @@ import RotateRight from '@material-ui/icons/RotateRight' import LockOutline from '@material-ui/icons/LockOutlined' import LockOpen from '@material-ui/icons/LockOpen' import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons' -import RulerFactory from './Ruler' -const dragHandleHeight = 3 +import { observer, PropTypes } from 'mobx-react' +import { getSnapshot } from 'mobx-state-tree' +import React, { useState } from 'react' + +// material-ui stuff +import { + Button, + Container, + Grid, + IconButton, + MenuItem, + TextField, + makeStyles, +} from '@material-ui/core' -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer, PropTypes } = jbrequire('mobx-react') - const { getSnapshot } = jbrequire('mobx-state-tree') - const React = jbrequire('react') - const { useState } = jbrequire('react') +import { grey } from '@material-ui/core/colors' - // material-ui stuff - const Button = jbrequire('@material-ui/core/Button') - const Container = jbrequire('@material-ui/core/Container') - const Grid = jbrequire('@material-ui/core/Grid') - const IconButton = jbrequire('@material-ui/core/IconButton') - const MenuItem = jbrequire('@material-ui/core/MenuItem') - const TextField = jbrequire('@material-ui/core/TextField') - const { makeStyles } = jbrequire('@material-ui/core/styles') - const { grey } = jbrequire('@material-ui/core/colors') +import { ResizeHandle } from '@jbrowse/core/ui' +import { assembleLocString, getSession } from '@jbrowse/core/util' +import Ruler from './Ruler' - const { ResizeHandle } = jbrequire('@jbrowse/core/ui') - const { assembleLocString, getSession } = jbrequire('@jbrowse/core/util') - const Ruler = jbrequire(RulerFactory) +const dragHandleHeight = 3 - const useStyles = makeStyles(theme => { - return { - root: { - position: 'relative', - marginBottom: theme.spacing(1), - overflow: 'hidden', - background: 'white', - }, - scroller: { - overflow: 'auto', - }, - sliceRoot: { - background: 'none', - // background: theme.palette.background.paper, - boxSizing: 'content-box', - display: 'block', - }, - iconButton: { - padding: '4px', - margin: '0 2px 0 2px', - }, - controls: { - overflow: 'hidden', - whiteSpace: 'nowrap', - position: 'absolute', - background: grey[200], - boxSizing: 'border-box', - borderRight: '1px solid #a2a2a2', - borderBottom: '1px solid #a2a2a2', - left: 0, - top: 0, - }, - importFormContainer: { - marginBottom: theme.spacing(4), - }, - } - }) +const useStyles = makeStyles(theme => { + return { + root: { + position: 'relative', + marginBottom: theme.spacing(1), + overflow: 'hidden', + background: 'white', + }, + scroller: { + overflow: 'auto', + }, + sliceRoot: { + background: 'none', + // background: theme.palette.background.paper, + boxSizing: 'content-box', + display: 'block', + }, + iconButton: { + padding: '4px', + margin: '0 2px 0 2px', + }, + controls: { + overflow: 'hidden', + whiteSpace: 'nowrap', + position: 'absolute', + background: grey[200], + boxSizing: 'border-box', + borderRight: '1px solid #a2a2a2', + borderBottom: '1px solid #a2a2a2', + left: 0, + top: 0, + }, + importFormContainer: { + marginBottom: theme.spacing(4), + }, + } +}) - const Slices = observer(({ model }) => { - return ( +const Slices = observer(({ model }) => { + return ( + <> <> - <> - {model.staticSlices.map(slice => { - return ( - - ) - })} - - <> - {model.tracks.map(track => { - const display = track.displays[0] - return ( - - ) - })} - + {model.staticSlices.map(slice => { + return ( + + ) + })} - ) - }) + <> + {model.tracks.map(track => { + const display = track.displays[0] + return ( + + ) + })} + + + ) +}) - const Controls = observer(({ model, showingFigure }) => { - const classes = useStyles() - return ( -
- - - +const Controls = observer(({ model, showingFigure }) => { + const classes = useStyles() + return ( +
+ + + - - - + + + - - - + + + - - - + + + + + + {model.lockedFitToWindow ? : } + + {model.hideTrackSelectorButton ? null : ( - {model.lockedFitToWindow ? : } + + )} +
+ ) +}) - {model.hideTrackSelectorButton ? null : ( - - - - )} -
- ) - }) - - const ImportForm = observer(({ model }) => { - const classes = useStyles() - const [selectedAssemblyIdx, setSelectedAssemblyIdx] = useState(0) - const { assemblyNames, assemblyManager } = getSession(model) - const assemblyError = assemblyNames.length ? '' : 'No configured assemblies' - const assembly = assemblyManager.get(assemblyNames[selectedAssemblyIdx]) - const regions = - assembly && assembly.regions ? getSnapshot(assembly.regions) : [] +const ImportForm = observer(({ model }) => { + const classes = useStyles() + const [selectedAssemblyIdx, setSelectedAssemblyIdx] = useState(0) + const { assemblyNames, assemblyManager } = getSession(model) + const assemblyError = assemblyNames.length ? '' : 'No configured assemblies' + const assembly = assemblyManager.get(assemblyNames[selectedAssemblyIdx]) + const regions = + assembly && assembly.regions ? getSnapshot(assembly.regions) : [] - function onAssemblyChange(event) { - setSelectedAssemblyIdx(Number(event.target.value)) - } + function onAssemblyChange(event) { + setSelectedAssemblyIdx(Number(event.target.value)) + } - function onOpenClick() { - model.setDisplayedRegions(regions) - } + function onOpenClick() { + model.setDisplayedRegions(regions) + } - return ( - <> - - - - - {assemblyNames.map((name, idx) => ( - - {name} - - ))} - - - - - + return ( + <> + + + + + {assemblyNames.map((name, idx) => ( + + {name} + + ))} + - - - ) - }) + + + + + + + ) +}) - function CircularView({ model }) { - const classes = useStyles() - const initialized = - !!model.displayedRegions.length && model.figureWidth && model.figureHeight +export default observer(({ model }) => { + const classes = useStyles() + const initialized = + !!model.displayedRegions.length && model.figureWidth && model.figureHeight - const showImportForm = !initialized && !model.disableImportForm - const showFigure = initialized && !showImportForm + const showImportForm = !initialized && !model.disableImportForm + const showFigure = initialized && !showImportForm - return ( -
- {model.error ? ( -

{model.error.message}

- ) : ( + return ( +
+ {model.error ? ( +

{model.error.message}

+ ) : ( + <> + {showImportForm ? : null} <> - {showImportForm ? : null} - <> - {!showFigure ? null : ( + {!showFigure ? null : ( +
`${x}px`) + .join(' '), }} > -
`${x}px`) - .join(' '), + position: 'absolute', + left: 0, + top: 0, }} + className={classes.sliceRoot} + width={`${model.figureWidth}px`} + height={`${model.figureHeight}px`} + version="1.1" > - - - - - -
+ + + +
- )} - - {model.hideVerticalResizeHandle ? null : ( - - )} - +
+ )} + + {model.hideVerticalResizeHandle ? null : ( + + )} - )} -
- ) - } - CircularView.propTypes = { - model: PropTypes.objectOrObservableObject.isRequired, - } - return observer(CircularView) -} + + )} +
+ ) +}) diff --git a/plugins/circular-view/src/CircularView/components/Ruler.js b/plugins/circular-view/src/CircularView/components/Ruler.js index e0500b1953..a9d410b917 100644 --- a/plugins/circular-view/src/CircularView/components/Ruler.js +++ b/plugins/circular-view/src/CircularView/components/Ruler.js @@ -1,146 +1,127 @@ -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer } = jbrequire('mobx-react') - const React = jbrequire('react') - const { getSession } = jbrequire('@jbrowse/core/util') - const { makeContrasting } = jbrequire('@jbrowse/core/util/color') - const { makeStyles, useTheme } = jbrequire('@material-ui/core/styles') - const { polarToCartesian, radToDeg, assembleLocString } = jbrequire( - '@jbrowse/core/util', - ) +import React from 'react' +import { observer } from 'mobx-react' +import { + getSession, + polarToCartesian, + radToDeg, + assembleLocString, +} from '@jbrowse/core/util' +import { makeContrasting } from '@jbrowse/core/util/color' +import { makeStyles, useTheme } from '@material-ui/core/styles' - const useStyles = makeStyles({ - rulerLabel: { - fontSize: '0.8rem', - fontWeight: 500, - lineHeight: 1.6, - letterSpacing: '0.0075em', - }, - }) +const useStyles = makeStyles({ + rulerLabel: { + fontSize: '0.8rem', + fontWeight: 500, + lineHeight: 1.6, + letterSpacing: '0.0075em', + }, +}) - function sliceArcPath(slice, radiusPx, startBase, endBase) { - // A rx ry x-axis-rotation large-arc-flag sweep-flag x y - if (slice.flipped) [startBase, endBase] = [endBase, startBase] - const startXY = slice.bpToXY(startBase, radiusPx) - const endXY = slice.bpToXY(endBase, radiusPx) - const largeArc = - Math.abs(endBase - startBase) / slice.bpPerRadian > Math.PI ? '1' : '0' - const sweepFlag = '1' - return [ - 'M', - ...startXY, - 'A', - radiusPx, - radiusPx, - '0', - largeArc, - sweepFlag, - ...endXY, - ].join(' ') - } +function sliceArcPath(slice, radiusPx, startBase, endBase) { + // A rx ry x-axis-rotation large-arc-flag sweep-flag x y + if (slice.flipped) [startBase, endBase] = [endBase, startBase] + const startXY = slice.bpToXY(startBase, radiusPx) + const endXY = slice.bpToXY(endBase, radiusPx) + const largeArc = + Math.abs(endBase - startBase) / slice.bpPerRadian > Math.PI ? '1' : '0' + const sweepFlag = '1' + return [ + 'M', + ...startXY, + 'A', + radiusPx, + radiusPx, + '0', + largeArc, + sweepFlag, + ...endXY, + ].join(' ') +} - const ElisionRulerArc = observer(({ model, slice }) => { - const theme = useTheme() - const { radiusPx: modelRadiusPx } = model - const radiusPx = modelRadiusPx + 1 - const { endRadians, startRadians, region } = slice - const startXY = polarToCartesian(radiusPx, startRadians) - const endXY = polarToCartesian(radiusPx, endRadians) - const widthPx = (endRadians - startRadians) * radiusPx - const largeArc = endRadians - startRadians > Math.PI ? '1' : '0' - // TODO: draw the elision - const centerRadians = (endRadians + startRadians) / 2 - const regionCountString = `[${Number( - region.regions.length, - ).toLocaleString()}]` - return ( - <> - - - - ) - }) +const ElisionRulerArc = observer(({ model, slice }) => { + const theme = useTheme() + const { radiusPx: modelRadiusPx } = model + const radiusPx = modelRadiusPx + 1 + const { endRadians, startRadians, region } = slice + const startXY = polarToCartesian(radiusPx, startRadians) + const endXY = polarToCartesian(radiusPx, endRadians) + const widthPx = (endRadians - startRadians) * radiusPx + const largeArc = endRadians - startRadians > Math.PI ? '1' : '0' + // TODO: draw the elision + const centerRadians = (endRadians + startRadians) / 2 + const regionCountString = `[${Number( + region.regions.length, + ).toLocaleString()}]` + return ( + <> + + + + ) +}) - const RulerLabel = observer( - ({ view, text, maxWidthPx, radians, radiusPx, title, color }) => { - const classes = useStyles() - const textXY = polarToCartesian(radiusPx + 5, radians) - if (!text) return null +const RulerLabel = observer( + ({ view, text, maxWidthPx, radians, radiusPx, title, color }) => { + const classes = useStyles() + const textXY = polarToCartesian(radiusPx + 5, radians) + if (!text) return null - if (text.length * 6.5 < maxWidthPx) { - // text is rotated parallel to the ruler arc - return ( - - {text} - {title || text} - - ) - } - if (maxWidthPx > 4) { - // text is rotated perpendicular to the ruler arc - const overallRotation = radToDeg( - radians + view.offsetRadians - Math.PI / 2, - ) - if (overallRotation >= 180) { - return ( - - {text} - {title || text} - - ) - } + if (text.length * 6.5 < maxWidthPx) { + // text is rotated parallel to the ruler arc + return ( + + {text} + {title || text} + + ) + } + if (maxWidthPx > 4) { + // text is rotated perpendicular to the ruler arc + const overallRotation = radToDeg( + radians + view.offsetRadians - Math.PI / 2, + ) + if (overallRotation >= 180) { return ( {text} @@ -148,75 +129,87 @@ export default pluginManager => { ) } + return ( + + {text} + {title || text} + + ) + } - // if you get here there is no room for the text at all - return null - }, - ) + // if you get here there is no room for the text at all + return null + }, +) - const RegionRulerArc = observer(({ model, slice }) => { - const theme = useTheme() - const { radiusPx } = model - const { region, endRadians, startRadians } = slice - const centerRadians = (endRadians + startRadians) / 2 - const widthPx = (endRadians - startRadians) * radiusPx - const session = getSession(model) - let color - const assembly = session.assemblyManager.get(slice.region.assemblyName) - if (assembly) { - color = assembly.getRefNameColor(region.refName) - } - if (color) { - try { - color = makeContrasting(color, theme.palette.background.paper) - } catch (error) { - color = theme.palette.text.primary - } - } else { +const RegionRulerArc = observer(({ model, slice }) => { + const theme = useTheme() + const { radiusPx } = model + const { region, endRadians, startRadians } = slice + const centerRadians = (endRadians + startRadians) / 2 + const widthPx = (endRadians - startRadians) * radiusPx + const session = getSession(model) + let color + const assembly = session.assemblyManager.get(slice.region.assemblyName) + if (assembly) { + color = assembly.getRefNameColor(region.refName) + } + if (color) { + try { + color = makeContrasting(color, theme.palette.background.paper) + } catch (error) { color = theme.palette.text.primary } + } else { + color = theme.palette.text.primary + } - // TODO: slice flipping - return ( - <> - - - {region.refName} - - - ) - }) + // TODO: slice flipping + return ( + <> + + + {region.refName} + + + ) +}) - const Ruler = observer(function Ruler({ model, slice }) { - if (slice.region.elided) { - return ( - - ) - } +export default observer(function Ruler({ model, slice }) { + if (slice.region.elided) { return ( - ) - }) - - return Ruler -} + } + return ( + + ) +}) diff --git a/plugins/circular-view/src/CircularView/models/CircularView.ts b/plugins/circular-view/src/CircularView/models/CircularView.ts index 0f4fc6bdf1..2755f10bb2 100644 --- a/plugins/circular-view/src/CircularView/models/CircularView.ts +++ b/plugins/circular-view/src/CircularView/models/CircularView.ts @@ -1,377 +1,387 @@ import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema' import PluginManager from '@jbrowse/core/PluginManager' -import { SnapshotOrInstance, Instance } from 'mobx-state-tree' +import { + SnapshotOrInstance, + Instance, + types, + getParent, + resolveIdentifier, + getRoot, + cast, +} from 'mobx-state-tree' +import { Region } from '@jbrowse/core/util/types/mst' +import { transaction } from 'mobx' +import { readConfObject } from '@jbrowse/core/configuration' +import { + getSession, + clamp, + isSessionModelWithWidgets, +} from '@jbrowse/core/util' +import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes/models' import { calculateStaticSlices, sliceIsVisible } from './slices' import { viewportVisibleSection } from './viewportVisibleRegion' export default function CircularView(pluginManager: PluginManager) { - const { lib } = pluginManager - const { transaction } = lib.mobx - const { types, getParent, resolveIdentifier, getRoot, cast } = lib[ - 'mobx-state-tree' - ] - const { Region } = lib['@jbrowse/core/util/types/mst'] - const { readConfObject } = lib['@jbrowse/core/configuration'] - const { clamp, getSession, isSessionModelWithWidgets } = lib[ - '@jbrowse/core/util' - ] - const { BaseViewModel } = lib['@jbrowse/core/pluggableElementTypes/models'] - const minHeight = 40 const minWidth = 100 const defaultHeight = 400 - const model = types - .model('CircularView', { - type: types.literal('CircularView'), - offsetRadians: -Math.PI / 2, - bpPerPx: 2000000, - tracks: types.array( - pluginManager.pluggableMstType('track', 'stateModel'), - ), + return types.compose( + BaseViewModel, + types + .model('CircularView', { + type: types.literal('CircularView'), + offsetRadians: -Math.PI / 2, + bpPerPx: 2000000, + tracks: types.array( + pluginManager.pluggableMstType('track', 'stateModel'), + ), - hideVerticalResizeHandle: false, - hideTrackSelectorButton: false, - lockedFitToWindow: true, - disableImportForm: false, + hideVerticalResizeHandle: false, + hideTrackSelectorButton: false, + lockedFitToWindow: true, + disableImportForm: false, - height: types.optional( - types.refinement('trackHeight', types.number, n => n >= minHeight), - defaultHeight, - ), - minimumRadiusPx: 25, - spacingPx: 10, - paddingPx: 80, - lockedPaddingPx: 100, - minVisibleWidth: 6, - minimumBlockWidth: 20, - displayedRegions: types.array(Region), - scrollX: 0, - scrollY: 0, - trackSelectorType: 'hierarchical', - }) - .volatile(() => ({ - width: 800, - })) - .views(self => ({ - get staticSlices() { - return calculateStaticSlices(self) - }, - get visibleStaticSlices() { - return this.staticSlices.filter(sliceIsVisible.bind(this, self)) - }, - get visibleSection() { - return viewportVisibleSection( - [ - self.scrollX, - self.scrollX + self.width, - self.scrollY, - self.scrollY + self.height, - ], - this.centerXY, - this.radiusPx, - ) - }, - get circumferencePx() { - let elidedBp = 0 - for (const r of this.elidedRegions) { - elidedBp += r.widthBp - } - return ( - elidedBp / self.bpPerPx + self.spacingPx * this.elidedRegions.length - ) - }, - get radiusPx() { - return this.circumferencePx / (2 * Math.PI) - }, - get bpPerRadian() { - return self.bpPerPx * this.radiusPx - }, - get pxPerRadian() { - return this.radiusPx - }, - get centerXY(): [number, number] { - return [this.radiusPx + self.paddingPx, this.radiusPx + self.paddingPx] - }, - get totalBp() { - let total = 0 - for (const region of self.displayedRegions) { - total += region.end - region.start - } - return total - }, - get maximumRadiusPx() { - return self.lockedFitToWindow - ? Math.min(self.width, self.height) / 2 - self.lockedPaddingPx - : 1000000 - }, - get maxBpPerPx() { - const minCircumferencePx = 2 * Math.PI * self.minimumRadiusPx - return this.totalBp / minCircumferencePx - }, - get minBpPerPx() { - // min depends on window dimensions, clamp between old min(0.01) and max - const maxCircumferencePx = 2 * Math.PI * this.maximumRadiusPx - return clamp( - this.totalBp / maxCircumferencePx, - 0.0000000001, - this.maxBpPerPx, - ) - }, - get atMaxBpPerPx() { - return self.bpPerPx >= this.maxBpPerPx - }, - get atMinBpPerPx() { - return self.bpPerPx <= this.minBpPerPx - }, - get tooSmallToLock() { - return this.minBpPerPx <= 0.0000000001 - }, - get figureDimensions() { - return [ - this.radiusPx * 2 + 2 * self.paddingPx, - this.radiusPx * 2 + 2 * self.paddingPx, - ] - }, - get figureWidth() { - return this.figureDimensions[0] - }, - get figureHeight() { - return this.figureDimensions[1] - }, - // this is displayedRegions, post-processed to - // elide regions that are too small to see reasonably - get elidedRegions() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const visible: any[] = [] - self.displayedRegions.forEach(region => { - const widthBp = region.end - region.start - const widthPx = widthBp / self.bpPerPx - if (widthPx < self.minVisibleWidth) { - // too small to see, collapse into a single elision region - const lastVisible = visible[visible.length - 1] - if (lastVisible && lastVisible.elided) { - lastVisible.regions.push({ ...region }) - lastVisible.widthBp += widthBp + height: types.optional( + types.refinement('trackHeight', types.number, n => n >= minHeight), + defaultHeight, + ), + minimumRadiusPx: 25, + spacingPx: 10, + paddingPx: 80, + lockedPaddingPx: 100, + minVisibleWidth: 6, + minimumBlockWidth: 20, + displayedRegions: types.array(Region), + scrollX: 0, + scrollY: 0, + trackSelectorType: 'hierarchical', + }) + .volatile(() => ({ + width: 800, + })) + .views(self => ({ + get staticSlices() { + return calculateStaticSlices(self) + }, + get visibleStaticSlices() { + return this.staticSlices.filter(sliceIsVisible.bind(this, self)) + }, + get visibleSection() { + return viewportVisibleSection( + [ + self.scrollX, + self.scrollX + self.width, + self.scrollY, + self.scrollY + self.height, + ], + this.centerXY, + this.radiusPx, + ) + }, + get circumferencePx() { + let elidedBp = 0 + for (const r of this.elidedRegions) { + elidedBp += r.widthBp + } + return ( + elidedBp / self.bpPerPx + self.spacingPx * this.elidedRegions.length + ) + }, + get radiusPx() { + return this.circumferencePx / (2 * Math.PI) + }, + get bpPerRadian() { + return self.bpPerPx * this.radiusPx + }, + get pxPerRadian() { + return this.radiusPx + }, + get centerXY(): [number, number] { + return [ + this.radiusPx + self.paddingPx, + this.radiusPx + self.paddingPx, + ] + }, + get totalBp() { + let total = 0 + for (const region of self.displayedRegions) { + total += region.end - region.start + } + return total + }, + get maximumRadiusPx() { + return self.lockedFitToWindow + ? Math.min(self.width, self.height) / 2 - self.lockedPaddingPx + : 1000000 + }, + get maxBpPerPx() { + const minCircumferencePx = 2 * Math.PI * self.minimumRadiusPx + return this.totalBp / minCircumferencePx + }, + get minBpPerPx() { + // min depends on window dimensions, clamp between old min(0.01) and max + const maxCircumferencePx = 2 * Math.PI * this.maximumRadiusPx + return clamp( + this.totalBp / maxCircumferencePx, + 0.0000000001, + this.maxBpPerPx, + ) + }, + get atMaxBpPerPx() { + return self.bpPerPx >= this.maxBpPerPx + }, + get atMinBpPerPx() { + return self.bpPerPx <= this.minBpPerPx + }, + get tooSmallToLock() { + return this.minBpPerPx <= 0.0000000001 + }, + get figureDimensions() { + return [ + this.radiusPx * 2 + 2 * self.paddingPx, + this.radiusPx * 2 + 2 * self.paddingPx, + ] + }, + get figureWidth() { + return this.figureDimensions[0] + }, + get figureHeight() { + return this.figureDimensions[1] + }, + // this is displayedRegions, post-processed to + // elide regions that are too small to see reasonably + get elidedRegions() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const visible: any[] = [] + self.displayedRegions.forEach(region => { + const widthBp = region.end - region.start + const widthPx = widthBp / self.bpPerPx + if (widthPx < self.minVisibleWidth) { + // too small to see, collapse into a single elision region + const lastVisible = visible[visible.length - 1] + if (lastVisible && lastVisible.elided) { + lastVisible.regions.push({ ...region }) + lastVisible.widthBp += widthBp + } else { + visible.push({ + elided: true, + widthBp, + regions: [{ ...region }], + }) + } } else { - visible.push({ - elided: true, - widthBp, - regions: [{ ...region }], - }) + // big enough to see, display it + visible.push({ ...region, widthBp }) } - } else { - // big enough to see, display it - visible.push({ ...region, widthBp }) - } - }) + }) - // remove any single-region elisions - for (let i = 0; i < visible.length; i += 1) { - const v = visible[i] - if (v.elided && v.regions.length === 1) { - delete v.elided - visible[i] = { ...v, ...v.regions[0] } + // remove any single-region elisions + for (let i = 0; i < visible.length; i += 1) { + const v = visible[i] + if (v.elided && v.regions.length === 1) { + delete v.elided + visible[i] = { ...v, ...v.regions[0] } + } } - } - return visible - }, + return visible + }, - get assemblyNames() { - const assemblyNames: string[] = [] - self.displayedRegions.forEach(displayedRegion => { - if (!assemblyNames.includes(displayedRegion.assemblyName)) - assemblyNames.push(displayedRegion.assemblyName) - }) - return assemblyNames - }, - })) - .volatile(() => ({ - error: undefined as Error | undefined, - })) - .actions(self => ({ - // toggle action with a flag stating which mode it's in - setWidth(newWidth: number) { - self.width = Math.max(newWidth, minWidth) - return self.width - }, - setHeight(newHeight: number) { - self.height = Math.max(newHeight, minHeight) - return self.height - }, - resizeHeight(distance: number) { - const oldHeight = self.height - const newHeight = this.setHeight(self.height + distance) - this.setModelViewWhenAdjust(!self.tooSmallToLock) - return newHeight - oldHeight - }, - resizeWidth(distance: number) { - const oldWidth = self.width - const newWidth = this.setWidth(self.width + distance) - this.setModelViewWhenAdjust(!self.tooSmallToLock) - return newWidth - oldWidth - }, - rotateClockwiseButton() { - this.rotateClockwise(Math.PI / 6) - }, + get assemblyNames() { + const assemblyNames: string[] = [] + self.displayedRegions.forEach(displayedRegion => { + if (!assemblyNames.includes(displayedRegion.assemblyName)) + assemblyNames.push(displayedRegion.assemblyName) + }) + return assemblyNames + }, + })) + .volatile(() => ({ + error: undefined as Error | undefined, + })) + .actions(self => ({ + // toggle action with a flag stating which mode it's in + setWidth(newWidth: number) { + self.width = Math.max(newWidth, minWidth) + return self.width + }, + setHeight(newHeight: number) { + self.height = Math.max(newHeight, minHeight) + return self.height + }, + resizeHeight(distance: number) { + const oldHeight = self.height + const newHeight = this.setHeight(self.height + distance) + this.setModelViewWhenAdjust(!self.tooSmallToLock) + return newHeight - oldHeight + }, + resizeWidth(distance: number) { + const oldWidth = self.width + const newWidth = this.setWidth(self.width + distance) + this.setModelViewWhenAdjust(!self.tooSmallToLock) + return newWidth - oldWidth + }, + rotateClockwiseButton() { + this.rotateClockwise(Math.PI / 6) + }, - rotateCounterClockwiseButton() { - this.rotateCounterClockwise(Math.PI / 6) - }, + rotateCounterClockwiseButton() { + this.rotateCounterClockwise(Math.PI / 6) + }, - rotateClockwise(distance = 0.17) { - self.offsetRadians += distance - }, + rotateClockwise(distance = 0.17) { + self.offsetRadians += distance + }, - rotateCounterClockwise(distance = 0.17) { - self.offsetRadians -= distance - }, + rotateCounterClockwise(distance = 0.17) { + self.offsetRadians -= distance + }, - zoomInButton() { - this.setBpPerPx(self.bpPerPx / 1.4) - }, + zoomInButton() { + this.setBpPerPx(self.bpPerPx / 1.4) + }, - zoomOutButton() { - this.setBpPerPx(self.bpPerPx * 1.4) - }, + zoomOutButton() { + this.setBpPerPx(self.bpPerPx * 1.4) + }, - setBpPerPx(newVal: number) { - self.bpPerPx = clamp(newVal, self.minBpPerPx, self.maxBpPerPx) - }, + setBpPerPx(newVal: number) { + self.bpPerPx = clamp(newVal, self.minBpPerPx, self.maxBpPerPx) + }, - setModelViewWhenAdjust(secondCondition: boolean) { - if (self.lockedFitToWindow && secondCondition) { - this.setBpPerPx(self.minBpPerPx) - } - }, + setModelViewWhenAdjust(secondCondition: boolean) { + if (self.lockedFitToWindow && secondCondition) { + this.setBpPerPx(self.minBpPerPx) + } + }, - closeView() { - getParent(self, 2).removeView(self) - }, + closeView() { + getParent(self, 2).removeView(self) + }, - setDisplayedRegions(regions: SnapshotOrInstance[]) { - const previouslyEmpty = self.displayedRegions.length === 0 - self.displayedRegions = cast(regions) + setDisplayedRegions(regions: SnapshotOrInstance[]) { + const previouslyEmpty = self.displayedRegions.length === 0 + self.displayedRegions = cast(regions) - if (previouslyEmpty) this.setBpPerPx(self.minBpPerPx) - else this.setBpPerPx(self.bpPerPx) - }, + if (previouslyEmpty) this.setBpPerPx(self.minBpPerPx) + else this.setBpPerPx(self.bpPerPx) + }, - activateTrackSelector() { - if (self.trackSelectorType === 'hierarchical') { - const session = getSession(self) - if (isSessionModelWithWidgets(session)) { - const selector = session.addWidget( - 'HierarchicalTrackSelectorWidget', - 'hierarchicalTrackSelector', - { view: self }, - ) - session.showWidget(selector) - return selector + activateTrackSelector() { + if (self.trackSelectorType === 'hierarchical') { + const session = getSession(self) + if (isSessionModelWithWidgets(session)) { + const selector = session.addWidget( + 'HierarchicalTrackSelectorWidget', + 'hierarchicalTrackSelector', + { view: self }, + ) + session.showWidget(selector) + return selector + } } - } - throw new Error(`invalid track selector type ${self.trackSelectorType}`) - }, - - toggleTrack(trackId: string) { - // if we have any tracks with that configuration, turn them off - const hiddenCount = this.hideTrack(trackId) - // if none had that configuration, turn one on - if (!hiddenCount) { - this.showTrack(trackId) - } - }, - - setError(error: Error) { - self.error = error - }, + throw new Error( + `invalid track selector type ${self.trackSelectorType}`, + ) + }, - showTrack(trackId: string, initialSnapshot = {}) { - const trackConfigSchema = pluginManager.pluggableConfigSchemaType( - 'track', - ) - const configuration = resolveIdentifier( - trackConfigSchema, - getRoot(self), - trackId, - ) - const trackType = pluginManager.getTrackType(configuration.type) - if (!trackType) { - throw new Error(`unknown track type ${configuration.type}`) - } - const viewType = pluginManager.getViewType(self.type) - const supportedDisplays = viewType.displayTypes.map( - displayType => displayType.name, - ) - const displayConf = configuration.displays.find( - (d: AnyConfigurationModel) => supportedDisplays.includes(d.type), - ) - const track = trackType.stateModel.create({ - ...initialSnapshot, - type: configuration.type, - configuration, - displays: [{ type: displayConf.type, configuration: displayConf }], - }) - self.tracks.push(track) - }, + toggleTrack(trackId: string) { + // if we have any tracks with that configuration, turn them off + const hiddenCount = this.hideTrack(trackId) + // if none had that configuration, turn one on + if (!hiddenCount) { + this.showTrack(trackId) + } + }, - addTrackConf(configuration: AnyConfigurationModel, initialSnapshot = {}) { - const { type } = configuration - const name = readConfObject(configuration, 'name') - const trackType = pluginManager.getTrackType(type) - if (!trackType) { - throw new Error(`unknown track type ${configuration.type}`) - } - const viewType = pluginManager.getViewType(self.type) - const supportedDisplays = viewType.displayTypes.map( - displayType => displayType.name, - ) - const displayConf = configuration.displays.find( - (d: AnyConfigurationModel) => supportedDisplays.includes(d.type), - ) - const track = trackType.stateModel.create({ - ...initialSnapshot, - name, - type, - configuration, - displays: [{ type: displayConf.type, configuration: displayConf }], - }) - self.tracks.push(track) - }, + setError(error: Error) { + self.error = error + }, - hideTrack(trackId: string) { - const trackConfigSchema = pluginManager.pluggableConfigSchemaType( - 'track', - ) - const configuration = resolveIdentifier( - trackConfigSchema, - getRoot(self), - trackId, - ) - // if we have any tracks with that configuration, turn them off - const shownTracks = self.tracks.filter( - t => t.configuration === configuration, - ) - transaction(() => shownTracks.forEach(t => self.tracks.remove(t))) - return shownTracks.length - }, + showTrack(trackId: string, initialSnapshot = {}) { + const trackConfigSchema = pluginManager.pluggableConfigSchemaType( + 'track', + ) + const configuration = resolveIdentifier( + trackConfigSchema, + getRoot(self), + trackId, + ) + const trackType = pluginManager.getTrackType(configuration.type) + if (!trackType) { + throw new Error(`unknown track type ${configuration.type}`) + } + const viewType = pluginManager.getViewType(self.type) + const supportedDisplays = viewType.displayTypes.map( + displayType => displayType.name, + ) + const displayConf = configuration.displays.find( + (d: AnyConfigurationModel) => supportedDisplays.includes(d.type), + ) + const track = trackType.stateModel.create({ + ...initialSnapshot, + type: configuration.type, + configuration, + displays: [{ type: displayConf.type, configuration: displayConf }], + }) + self.tracks.push(track) + }, - toggleFitToWindowLock() { - self.lockedFitToWindow = !self.lockedFitToWindow - // when going unlocked -> locked and circle is cut off, set to the locked minBpPerPx - this.setModelViewWhenAdjust(self.atMinBpPerPx) - return self.lockedFitToWindow - }, - })) + addTrackConf( + configuration: AnyConfigurationModel, + initialSnapshot = {}, + ) { + const { type } = configuration + const name = readConfObject(configuration, 'name') + const trackType = pluginManager.getTrackType(type) + if (!trackType) { + throw new Error(`unknown track type ${configuration.type}`) + } + const viewType = pluginManager.getViewType(self.type) + const supportedDisplays = viewType.displayTypes.map( + displayType => displayType.name, + ) + const displayConf = configuration.displays.find( + (d: AnyConfigurationModel) => supportedDisplays.includes(d.type), + ) + const track = trackType.stateModel.create({ + ...initialSnapshot, + name, + type, + configuration, + displays: [{ type: displayConf.type, configuration: displayConf }], + }) + self.tracks.push(track) + }, - const stateModel = types.compose(BaseViewModel, model) + hideTrack(trackId: string) { + const trackConfigSchema = pluginManager.pluggableConfigSchemaType( + 'track', + ) + const configuration = resolveIdentifier( + trackConfigSchema, + getRoot(self), + trackId, + ) + // if we have any tracks with that configuration, turn them off + const shownTracks = self.tracks.filter( + t => t.configuration === configuration, + ) + transaction(() => shownTracks.forEach(t => self.tracks.remove(t))) + return shownTracks.length + }, - return { stateModel } + toggleFitToWindowLock() { + self.lockedFitToWindow = !self.lockedFitToWindow + // when going unlocked -> locked and circle is cut off, set to the locked minBpPerPx + this.setModelViewWhenAdjust(self.atMinBpPerPx) + return self.lockedFitToWindow + }, + })), + ) } -export type CircularViewStateModel = ReturnType< - typeof CircularView ->['stateModel'] +export type CircularViewStateModel = ReturnType export type CircularViewModel = Instance /* diff --git a/plugins/circular-view/src/index.ts b/plugins/circular-view/src/index.ts index 151ecfbf4e..21a916962a 100644 --- a/plugins/circular-view/src/index.ts +++ b/plugins/circular-view/src/index.ts @@ -16,7 +16,8 @@ export default class CircularViewPlugin extends Plugin { LazyReactComponent: lazy( () => import('./CircularView/components/CircularView'), ), - stateModel: load(stateModelFactory), + stateModel: stateModelFactory(pluginManager), + name: 'CircularView', }), ) } From f11f9e9bd9a48630d278f5c7eb6511d21c16e2ff Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 13:15:26 -0400 Subject: [PATCH 03/41] Make dotplot react component lazy --- .../src/DotplotView/components/Controls.tsx | 179 +++--- .../DotplotView/components/DotplotView.tsx | 540 +++++++++--------- .../src/DotplotView/components/ImportForm.tsx | 311 +++++----- plugins/dotplot-view/src/DotplotView/index.ts | 11 - plugins/dotplot-view/src/index.ts | 16 +- 5 files changed, 521 insertions(+), 536 deletions(-) delete mode 100644 plugins/dotplot-view/src/DotplotView/index.ts diff --git a/plugins/dotplot-view/src/DotplotView/components/Controls.tsx b/plugins/dotplot-view/src/DotplotView/components/Controls.tsx index 37f4b31f84..d3d03df026 100644 --- a/plugins/dotplot-view/src/DotplotView/components/Controls.tsx +++ b/plugins/dotplot-view/src/DotplotView/components/Controls.tsx @@ -13,98 +13,95 @@ import IconButton from '@material-ui/core/IconButton' import { observer } from 'mobx-react' import { DotplotViewModel } from '../model' -export default () => { - const useStyles = makeStyles({ - iconButton: { - padding: '4px', - margin: '0 2px 0 2px', - }, - controls: { - overflow: 'hidden', - background: 'white', - whiteSpace: 'nowrap', - position: 'absolute', - boxSizing: 'border-box', - border: '1px solid #a2a2a2', - right: 0, - top: 0, - zIndex: 1001, // needs to be above overlay but below things that might naturally go on top of it like a context menu from jbrowse-core/ui - }, - }) +const useStyles = makeStyles({ + iconButton: { + padding: '4px', + margin: '0 2px 0 2px', + }, + controls: { + overflow: 'hidden', + background: 'white', + whiteSpace: 'nowrap', + position: 'absolute', + boxSizing: 'border-box', + border: '1px solid #a2a2a2', + right: 0, + top: 0, + zIndex: 1001, // needs to be above overlay but below things that might naturally go on top of it like a context menu from jbrowse-core/ui + }, +}) - const Controls = observer(({ model }: { model: DotplotViewModel }) => { - const classes = useStyles() - return ( -
- { - model.hview.scroll(-100) - }} - className={classes.iconButton} - title="left" - color="secondary" - > - - +export default observer(({ model }: { model: DotplotViewModel }) => { + const classes = useStyles() + return ( +
+ { + model.hview.scroll(-100) + }} + className={classes.iconButton} + title="left" + color="secondary" + > + + - { - model.hview.scroll(100) - }} - className={classes.iconButton} - title="left" - color="secondary" - > - - - { - model.vview.scroll(100) - }} - className={classes.iconButton} - title="left" - color="secondary" - > - - - { - model.vview.scroll(-100) - }} - className={classes.iconButton} - title="left" - color="secondary" - > - - - - - + { + model.hview.scroll(100) + }} + className={classes.iconButton} + title="left" + color="secondary" + > + + + { + model.vview.scroll(100) + }} + className={classes.iconButton} + title="left" + color="secondary" + > + + + { + model.vview.scroll(-100) + }} + className={classes.iconButton} + title="left" + color="secondary" + > + + + + + - - - + + + - - - -
- ) - }) - return Controls -} + + + +
+ ) +}) diff --git a/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx b/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx index 05ca339f06..2372d58532 100644 --- a/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx +++ b/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx @@ -6,11 +6,10 @@ import { LinearProgress } from '@material-ui/core' import { getConf } from '@jbrowse/core/configuration' import { Menu } from '@jbrowse/core/ui' import { BaseBlock } from '@jbrowse/core/util/blockTypes' -import PluginManager from '@jbrowse/core/PluginManager' import normalizeWheel from 'normalize-wheel' import { DotplotViewModel, Dotplot1DViewModel } from '../model' -import ImportFormFactory from './ImportForm' -import ControlsFactory from './Controls' +import ImportForm from './ImportForm' +import Controls from './Controls' const useStyles = makeStyles(theme => { return { @@ -423,306 +422,297 @@ const Grid = observer( ) }, ) +// produces offsetX/offsetY coordinates from a clientX and an element's getBoundingClientRect +function getOffset(coord: Coord, rect: Rect) { + return coord && ([coord[0] - rect.left, coord[1] - rect.top] as Coord) +} +const DotplotViewInternal = observer( + ({ model }: { model: DotplotViewModel }) => { + const { hview, vview, viewHeight } = model + const classes = useStyles() + const [mousecurrClient, setMouseCurrClient] = useState() + const [mousedownClient, setMouseDownClient] = useState() + const [mouseupClient, setMouseUpClient] = useState() + const ref = useRef(null) + const root = useRef(null) + const distanceX = useRef(0) + const distanceY = useRef(0) + const lref = useRef(null) + const rref = useRef(null) + const timeout = useRef() + const delta = useRef(0) + const scheduled = useRef(false) + const blank = { left: 0, top: 0, width: 0, height: 0 } + const svg = ref.current?.getBoundingClientRect() || blank + const rrect = rref.current?.getBoundingClientRect() || blank + const lrect = lref.current?.getBoundingClientRect() || blank + const mousedown = getOffset(mousedownClient, svg) + const mousecurr = getOffset(mousecurrClient, svg) + const mouseup = getOffset(mouseupClient, svg) + const mouserect = mouseup || mousecurr -export default (pluginManager: PluginManager) => { - const { jbrequire } = pluginManager - const ImportForm = jbrequire(ImportFormFactory) - const Controls = jbrequire(ControlsFactory) - - // produces offsetX/offsetY coordinates from a clientX and an element's getBoundingClientRect - function getOffset(coord: Coord, rect: Rect) { - return coord && ([coord[0] - rect.left, coord[1] - rect.top] as Coord) - } - const DotplotViewInternal = observer( - ({ model }: { model: DotplotViewModel }) => { - const { hview, vview, viewHeight } = model - const classes = useStyles() - const [mousecurrClient, setMouseCurrClient] = useState() - const [mousedownClient, setMouseDownClient] = useState() - const [mouseupClient, setMouseUpClient] = useState() - const ref = useRef(null) - const root = useRef(null) - const distanceX = useRef(0) - const distanceY = useRef(0) - const lref = useRef(null) - const rref = useRef(null) - const timeout = useRef() - const delta = useRef(0) - const scheduled = useRef(false) - const blank = { left: 0, top: 0, width: 0, height: 0 } - const svg = ref.current?.getBoundingClientRect() || blank - const rrect = rref.current?.getBoundingClientRect() || blank - const lrect = lref.current?.getBoundingClientRect() || blank - const mousedown = getOffset(mousedownClient, svg) - const mousecurr = getOffset(mousecurrClient, svg) - const mouseup = getOffset(mouseupClient, svg) - const mouserect = mouseup || mousecurr + // use non-React wheel handler to properly prevent body scrolling + useEffect(() => { + function onWheel(origEvent: WheelEvent) { + const event = normalizeWheel(origEvent) + origEvent.preventDefault() + if (origEvent.ctrlKey === true) { + delta.current += event.pixelY / 500 + model.vview.setScaleFactor( + delta.current < 0 ? 1 - delta.current : 1 / (1 + delta.current), + ) + model.hview.setScaleFactor( + delta.current < 0 ? 1 - delta.current : 1 / (1 + delta.current), + ) + if (timeout.current) { + clearTimeout(timeout.current) + } + timeout.current = setTimeout(() => { + transaction(() => { + model.hview.setScaleFactor(1) + model.vview.setScaleFactor(1) + model.hview.zoomTo( + delta.current > 0 + ? model.hview.bpPerPx * (1 + delta.current) + : model.hview.bpPerPx / (1 - delta.current), + ) + model.vview.zoomTo( + delta.current > 0 + ? model.vview.bpPerPx * (1 + delta.current) + : model.vview.bpPerPx / (1 - delta.current), + ) + }) + delta.current = 0 + }, 300) + } else { + distanceX.current += event.pixelX + distanceY.current -= event.pixelY + if (!scheduled.current) { + scheduled.current = true - // use non-React wheel handler to properly prevent body scrolling - useEffect(() => { - function onWheel(origEvent: WheelEvent) { - const event = normalizeWheel(origEvent) - origEvent.preventDefault() - if (origEvent.ctrlKey === true) { - delta.current += event.pixelY / 500 - model.vview.setScaleFactor( - delta.current < 0 ? 1 - delta.current : 1 / (1 + delta.current), - ) - model.hview.setScaleFactor( - delta.current < 0 ? 1 - delta.current : 1 / (1 + delta.current), - ) - if (timeout.current) { - clearTimeout(timeout.current) - } - timeout.current = setTimeout(() => { + window.requestAnimationFrame(() => { transaction(() => { - model.hview.setScaleFactor(1) - model.vview.setScaleFactor(1) - model.hview.zoomTo( - delta.current > 0 - ? model.hview.bpPerPx * (1 + delta.current) - : model.hview.bpPerPx / (1 - delta.current), - ) - model.vview.zoomTo( - delta.current > 0 - ? model.vview.bpPerPx * (1 + delta.current) - : model.vview.bpPerPx / (1 - delta.current), - ) + model.hview.scroll(distanceX.current) + model.vview.scroll(distanceY.current) }) - delta.current = 0 - }, 300) - } else { - distanceX.current += event.pixelX - distanceY.current -= event.pixelY - if (!scheduled.current) { - scheduled.current = true - - window.requestAnimationFrame(() => { - transaction(() => { - model.hview.scroll(distanceX.current) - model.vview.scroll(distanceY.current) - }) - scheduled.current = false - distanceX.current = 0 - distanceY.current = 0 - }) - } + scheduled.current = false + distanceX.current = 0 + distanceY.current = 0 + }) } } - if (ref.current) { - const curr = ref.current - curr.addEventListener('wheel', onWheel) - return () => { - curr.removeEventListener('wheel', onWheel) - } - } - return () => {} - }, [model.hview, model.vview]) - - useEffect(() => { - function globalMouseMove(event: MouseEvent) { - setMouseCurrClient([event.clientX, event.clientY]) - } - window.addEventListener('mousemove', globalMouseMove) + } + if (ref.current) { + const curr = ref.current + curr.addEventListener('wheel', onWheel) return () => { - window.removeEventListener('mousemove', globalMouseMove) + curr.removeEventListener('wheel', onWheel) } - }, []) + } + return () => {} + }, [model.hview, model.vview]) + + useEffect(() => { + function globalMouseMove(event: MouseEvent) { + setMouseCurrClient([event.clientX, event.clientY]) + } + window.addEventListener('mousemove', globalMouseMove) + return () => { + window.removeEventListener('mousemove', globalMouseMove) + } + }, []) - // detect a mouseup after a mousedown was submitted, autoremoves mouseup - // once that single mouseup is set - useEffect(() => { - let cleanup = () => {} + // detect a mouseup after a mousedown was submitted, autoremoves mouseup + // once that single mouseup is set + useEffect(() => { + let cleanup = () => {} - function globalMouseUp(event: MouseEvent) { - if ( - mousedown && - mousecurr && - Math.abs(mousedown[0] - mousecurr[0]) > 3 && - Math.abs(mousedown[1] - mousecurr[1]) > 3 - ) { - setMouseUpClient([event.clientX, event.clientY]) - } else { - setMouseDownClient(undefined) - } + function globalMouseUp(event: MouseEvent) { + if ( + mousedown && + mousecurr && + Math.abs(mousedown[0] - mousecurr[0]) > 3 && + Math.abs(mousedown[1] - mousecurr[1]) > 3 + ) { + setMouseUpClient([event.clientX, event.clientY]) + } else { + setMouseDownClient(undefined) } + } - if (mousedown && !mouseup) { - window.addEventListener('mouseup', globalMouseUp, true) - cleanup = () => { - window.removeEventListener('mouseup', globalMouseUp, true) - } + if (mousedown && !mouseup) { + window.addEventListener('mouseup', globalMouseUp, true) + cleanup = () => { + window.removeEventListener('mouseup', globalMouseUp, true) } - return cleanup - }, [mousedown, mousecurr, mouseup]) + } + return cleanup + }, [mousedown, mousecurr, mouseup]) - return ( -
- + return ( +
+ -
+
+
+ +
- - -
- {mouserect ? ( -
- {`x-${locstr(mouserect[0], hview)}`} -
- {`y-${locstr(viewHeight - mouserect[1], vview)}`} -
- ) : null} - {mousedown && - mouserect && - Math.abs(mousedown[0] - mouserect[0]) > 3 && - Math.abs(mousedown[1] - mouserect[1]) > 3 ? ( -
- {`x-${locstr(mousedown[0], hview)}`} -
- {`y-${locstr(viewHeight - mousedown[1], vview)}`} -
- ) : null} - + {mouserect ? (
{ - if (event.button === 0) { - setMouseDownClient([event.clientX, event.clientY]) - setMouseCurrClient([event.clientX, event.clientY]) - } + ref={lref} + className={classes.popover} + style={{ + left: + mouserect[0] - + (mousedown && mouserect[0] - mousedown[0] < 0 + ? lrect.width + : 0), + top: + mouserect[1] - + (mousedown && mouserect[1] - mousedown[1] < 0 + ? lrect.height + : 0), }} > - - {mousedown && mouserect ? ( - - ) : null} - + {`x-${locstr(mouserect[0], hview)}`} +
+ {`y-${locstr(viewHeight - mouserect[1], vview)}`}
-
-
-
- {model.tracks.map(track => { - const [display] = track.displays - const { RenderingComponent } = display + ) : null} + {mousedown && + mouserect && + Math.abs(mousedown[0] - mouserect[0]) > 3 && + Math.abs(mousedown[1] - mouserect[1]) > 3 ? ( +
+ {`x-${locstr(mousedown[0], hview)}`} +
+ {`y-${locstr(viewHeight - mousedown[1], vview)}`} +
+ ) : null} - return RenderingComponent ? ( - { + if (event.button === 0) { + setMouseDownClient([event.clientX, event.clientY]) + setMouseCurrClient([event.clientX, event.clientY]) + } + }} + > + + {mousedown && mouserect ? ( + - ) : null - })} + ) : null} +
- { - callback() - setMouseUpClient(undefined) - setMouseDownClient(undefined) - }} - onClose={() => { - setMouseUpClient(undefined) - setMouseDownClient(undefined) - }} - anchorReference="anchorPosition" - anchorPosition={ - mouseupClient - ? { - top: mouseupClient[1] + 30, - left: mouseupClient[0] + 30, - } - : undefined - } - style={{ zIndex: 1000 }} - menuItems={[ - { - label: 'Zoom in', - onClick: () => { - if (mousedown && mouseup) { - model.zoomIn(mousedown, mouseup) - } - }, +
+
+
+ {model.tracks.map(track => { + const [display] = track.displays + const { RenderingComponent } = display + + return RenderingComponent ? ( + + ) : null + })} +
+ { + callback() + setMouseUpClient(undefined) + setMouseDownClient(undefined) + }} + onClose={() => { + setMouseUpClient(undefined) + setMouseDownClient(undefined) + }} + anchorReference="anchorPosition" + anchorPosition={ + mouseupClient + ? { + top: mouseupClient[1] + 30, + left: mouseupClient[0] + 30, + } + : undefined + } + style={{ zIndex: 1000 }} + menuItems={[ + { + label: 'Zoom in', + onClick: () => { + if (mousedown && mouseup) { + model.zoomIn(mousedown, mouseup) + } }, - { - label: 'Open linear synteny view', - onClick: () => { - if (mousedown && mouseup) { - model.onDotplotView(mousedown, mouseup) - } - }, + }, + { + label: 'Open linear synteny view', + onClick: () => { + if (mousedown && mouseup) { + model.onDotplotView(mousedown, mouseup) + } }, - ]} - /> -
+ }, + ]} + />
- ) - }, - ) - const DotplotView = observer(({ model }: { model: DotplotViewModel }) => { - const { initialized, loading, error } = model - const classes = useStyles() - - if (!initialized && !loading) { - return - } +
+ ) + }, +) +export default observer(({ model }: { model: DotplotViewModel }) => { + const { initialized, loading, error } = model + const classes = useStyles() - if (error) { - return

{String(error)}

- } - if (loading) { - return ( -
-

Loading...

- -
- ) - } + if (!initialized && !loading) { + return + } - return - }) + if (error) { + return

{String(error)}

+ } + if (loading) { + return ( +
+

Loading...

+ +
+ ) + } - return DotplotView -} + return +}) diff --git a/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx b/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx index 5b33e572d2..b240f8a4ad 100644 --- a/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx +++ b/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx @@ -13,169 +13,166 @@ import TextField from '@material-ui/core/TextField' import Typography from '@material-ui/core/Typography' import { DotplotViewModel } from '../model' -export default () => { - const useStyles = makeStyles(theme => ({ - importFormContainer: { - padding: theme.spacing(4), - margin: '0 auto', - }, - importFormEntry: { - minWidth: 180, - }, - errorMessage: { - textAlign: 'center', - paddingTop: theme.spacing(1), - paddingBottom: theme.spacing(1), - }, - })) - const FormRow = observer( - ({ - model, - selected, - onChange, - error, - }: { - model: DotplotViewModel - selected: number - onChange: (arg0: number) => void - error?: string - }) => { - const classes = useStyles() - const { assemblyNames } = getSession(model) as { assemblyNames: string[] } - return ( - - { - onChange(Number(event.target.value)) - }} - error={Boolean(error)} - disabled={Boolean(error)} - className={classes.importFormEntry} - > - {assemblyNames.map((name, idx) => ( - - {name} - - ))} - - - ) - }, - ) - const ImportForm = observer(({ model }: { model: DotplotViewModel }) => { +const useStyles = makeStyles(theme => ({ + importFormContainer: { + padding: theme.spacing(4), + margin: '0 auto', + }, + importFormEntry: { + minWidth: 180, + }, + errorMessage: { + textAlign: 'center', + paddingTop: theme.spacing(1), + paddingBottom: theme.spacing(1), + }, +})) +const FormRow = observer( + ({ + model, + selected, + onChange, + error, + }: { + model: DotplotViewModel + selected: number + onChange: (arg0: number) => void + error?: string + }) => { const classes = useStyles() - const [numRows] = useState(2) - const [selected, setSelected] = useState([0, 0]) - const [trackData, setTrackData] = useState({ uri: '' }) - const session = getSession(model) - const { assemblyNames } = session - const error = assemblyNames.length ? '' : 'No configured assemblies' + const { assemblyNames } = getSession(model) as { assemblyNames: string[] } + return ( + + { + onChange(Number(event.target.value)) + }} + error={Boolean(error)} + disabled={Boolean(error)} + className={classes.importFormEntry} + > + {assemblyNames.map((name, idx) => ( + + {name} + + ))} + + + ) + }, +) +export default observer(({ model }: { model: DotplotViewModel }) => { + const classes = useStyles() + const [numRows] = useState(2) + const [selected, setSelected] = useState([0, 0]) + const [trackData, setTrackData] = useState({ uri: '' }) + const session = getSession(model) + const { assemblyNames } = session + const error = assemblyNames.length ? '' : 'No configured assemblies' - function onOpenClick() { - model.setViews([ - { bpPerPx: 0.1, offsetPx: 0 }, - { bpPerPx: 0.1, offsetPx: 0 }, - ]) - model.setAssemblyNames([ - assemblyNames[selected[0]], - assemblyNames[selected[1]], - ]) + function onOpenClick() { + model.setViews([ + { bpPerPx: 0.1, offsetPx: 0 }, + { bpPerPx: 0.1, offsetPx: 0 }, + ]) + model.setAssemblyNames([ + assemblyNames[selected[0]], + assemblyNames[selected[1]], + ]) - if ('uri' in trackData && trackData.uri) { - const fileName = trackData.uri - ? trackData.uri.slice(trackData.uri.lastIndexOf('/') + 1) - : null + if ('uri' in trackData && trackData.uri) { + const fileName = trackData.uri + ? trackData.uri.slice(trackData.uri.lastIndexOf('/') + 1) + : null - // @ts-ignore - const configuration = session.addTrackConf({ - trackId: `fileName-${Date.now()}`, - name: fileName, + // @ts-ignore + const configuration = session.addTrackConf({ + trackId: `fileName-${Date.now()}`, + name: fileName, + assemblyNames: selected.map(selection => assemblyNames[selection]), + type: 'SyntenyTrack', + adapter: { + type: 'PAFAdapter', + pafLocation: trackData, assemblyNames: selected.map(selection => assemblyNames[selection]), - type: 'SyntenyTrack', - adapter: { - type: 'PAFAdapter', - pafLocation: trackData, - assemblyNames: selected.map(selection => assemblyNames[selection]), - }, - }) - model.toggleTrack(configuration.trackId) - } + }, + }) + model.toggleTrack(configuration.trackId) } + } - return ( - - - {error ? ( - {error} - ) : ( - <> - - -

- Select assemblies for dotplot view -

- {[...new Array(numRows)].map((_, index) => ( - { - const copy = selected.slice(0) - copy[index] = val - setSelected(copy) - }} - model={model} - /> - ))} -
+ return ( + + + {error ? ( + {error} + ) : ( + <> + + +

+ Select assemblies for dotplot view +

+ {[...new Array(numRows)].map((_, index) => ( + { + const copy = selected.slice(0) + copy[index] = val + setSelected(copy) + }} + model={model} + /> + ))} +
- -

- Optional: Add a PAF{' '} - - (pairwise mapping format) - {' '} - file for the dotplot view. Note that the first assembly - should be the left column of the PAF and the second assembly - should be the right column -

- - - setTrackData(loc)} - /> - + +

+ Optional: Add a PAF{' '} + + (pairwise mapping format) + {' '} + file for the dotplot view. Note that the first assembly should + be the left column of the PAF and the second assembly should + be the right column +

+ + + setTrackData(loc)} + /> -
-
- - - - - )} -
-
- ) - }) - return ImportForm -} +
+ +
+ + + + + )} + +
+ ) +}) diff --git a/plugins/dotplot-view/src/DotplotView/index.ts b/plugins/dotplot-view/src/DotplotView/index.ts deleted file mode 100644 index 29f982693b..0000000000 --- a/plugins/dotplot-view/src/DotplotView/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import modelFactory from './model' -import ReactComponentFactory from './components/DotplotView' - -export default ({ jbrequire }: { jbrequire: Function }) => { - const ViewType = jbrequire('@jbrowse/core/pluggableElementTypes/ViewType') - return new ViewType({ - name: 'DotplotView', - stateModel: jbrequire(modelFactory), - ReactComponent: jbrequire(ReactComponentFactory), - }) -} diff --git a/plugins/dotplot-view/src/index.ts b/plugins/dotplot-view/src/index.ts index 762ab9b658..7c08e25cfc 100644 --- a/plugins/dotplot-view/src/index.ts +++ b/plugins/dotplot-view/src/index.ts @@ -1,6 +1,9 @@ +import { lazy } from 'react' import Plugin from '@jbrowse/core/Plugin' import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType' import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType' + +import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType' import AddIcon from '@material-ui/icons/Add' import { autorun } from 'mobx' import PluginManager from '@jbrowse/core/PluginManager' @@ -9,6 +12,7 @@ import { isAbstractMenuManager, getSession, } from '@jbrowse/core/util' + import { getConf } from '@jbrowse/core/configuration' import { Feature } from '@jbrowse/core/util/simpleFeature' import { AbstractDisplayModel } from '@jbrowse/core/util/types' @@ -24,13 +28,13 @@ import DotplotRenderer, { configSchema as dotplotRendererConfigSchema, ReactComponent as DotplotRendererReactComponent, } from './DotplotRenderer' +import stateModelFactory from './DotplotView/model' import { configSchema as PAFAdapterConfigSchema, AdapterClass as PAFAdapter, } from './PAFAdapter' import ComparativeRender from './DotplotRenderer/ComparativeRenderRpc' -import DotplotViewFactory from './DotplotView' const { parseCigar } = MismatchParser @@ -111,7 +115,15 @@ export default class DotplotPlugin extends Plugin { name = 'DotplotPlugin' install(pluginManager: PluginManager) { - pluginManager.addViewType(() => pluginManager.jbrequire(DotplotViewFactory)) + pluginManager.addViewType(() => { + return new ViewType({ + name: 'DotplotView', + stateModel: stateModelFactory(pluginManager), + LazyReactComponent: lazy( + () => import('./DotplotView/components/DotplotView'), + ), + }) + }) pluginManager.addDisplayType(() => { const configSchema = dotplotDisplayConfigSchemaFactory(pluginManager) From 83022ab936532e670f0f13e02ad81013713131dd Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 13:22:50 -0400 Subject: [PATCH 04/41] Smaller icons --- packages/core/ui/RecentSessionCard.js | 2 +- packages/core/ui/linearGenomeViewIcon.png | Bin 17597 -> 5765 bytes packages/core/ui/svInspectorIcon.png | Bin 235462 -> 19278 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/ui/RecentSessionCard.js b/packages/core/ui/RecentSessionCard.js index e259a047ad..b134e7006e 100644 --- a/packages/core/ui/RecentSessionCard.js +++ b/packages/core/ui/RecentSessionCard.js @@ -126,7 +126,7 @@ RecentSessionCard.propTypes = { RecentSessionCard.defaultProps = { sessionScreenshot: - 'data:image/png;base64,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', + 'data:image/png;base64,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', } export default RecentSessionCard diff --git a/packages/core/ui/linearGenomeViewIcon.png b/packages/core/ui/linearGenomeViewIcon.png index 22a7309f39725d74121907a2ca2ccab760420064..a551cdcf522c823c638480a849bb68a1333616b8 100644 GIT binary patch literal 5765 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 F{{u-P>SzD} literal 17597 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 J-+TQ0e*vfl#Qy*Q diff --git a/packages/core/ui/svInspectorIcon.png b/packages/core/ui/svInspectorIcon.png index 3202fb874c52446d369f856f473bd517dd8cdd6c..9900ccf75ec489288a1642a3ca521fecd250aa47 100644 GIT binary patch literal 19278 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 hq=Ra%w9-vup`&+g$tWVIpkdX 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 B!88B> literal 235462 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

* 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

oy=_}J8iQqIuh>E5I zZ;3)p!3MC%kSLb{O=JZe`4T{uG7;bME6|LFWnVeWuWRo7nw`x=#i{_dt=?->pK>K^D7w>{~OrJ&z z-=JATF8_My?kSF3!;o&hxc~fH?PT+1I+=GL6>x1M{&*!~Pdu6z6A&rtf1fe^fk~Y=v+?X2d0UB!;{r6rvG2_sW{&dizh6uq2#Dx?b_!VlNWyjL^C9 zdNlysZRHwW)wzHTQ`IDfeLL#`K6d48^JAvfl5GqInA8?MHhKROGC3xHbE~tNDJF{k<2LmX}%^XpIlstWkzjO3eUBeR^J&7FBiGETjz~u zI5>E6%~vMK1hQ_tGBI+P7F*h^&_wOrmiCFvsi6puSq~|9E_HiC=or3#zEXB@^;3`B zOxl^3zl!-*0vi*TjE$a&3ERZP1QqfK;6Z5JJ=fs^oLzzF zYLCE-W*}`>*^kCKPJSB#q-gZFW)j%#&cWI!&wG1@A7`rLh!ft>!^4_DF6@FcWcnv; zp?cVi>XQKTJ1X)%G7`0ft`4B%SUH6B5>#FqtnE!gQ;eNZy*|X$p*F+6AR`Fnd4cE4 zW_)jS1g+#DxmUx7NvDQb7*qV&q0ohc1NP7b{KqI-bGkiLB;2s@N6l#XR2H%$Y5zhZ zLeF+{mn3)p!|34PV1Baa+Fy-T&~|t zY^Uq_;qRJ^H3v$9TpXsDzu^E3BXU|hLQ$e%^ZgYk$GEWz!Zp>JPX6MezE4!?wvWjB z;`)8W&huWtxPt%!fv^*cZWdbxVx$FBEY+I#$$8#J$HgtfqhC<{5=0Atj&FJwuW;Ce zI~~2=@n3z)e^9ufM&i(u#g5oLfK-!PcOMdwko1Bm#7q0p$6IvMG20>l2QC*P!wFaEdXrkQJK~jt4Ka$* zb^3J^HBIO%9(I>z%m{!IG%z!CXpQNLHblKUkh$=`318yTb< z3MC6K0lJ@!ukGeU@f*Pk%wJ`H z%T358<>OaP6Pp23od!{tSk4N5%^fY=MYnsaj0u7EqCH zuq}=1OxHq=eFQxrlos){k~gS3suAm;+4|uvN2?#5TKjC;{j1c$2^vL%6++dy!*G{S zOG4!)TCh!HrYuHBaIT#Er$$*)ZbI$A8cSc=vHiqwMQ&AR?U#O$Ead)Znk4ip*jmv} z9ly@2c_aMOB4(DaNwU8_qpPLfDJzt}y&@-%i%|(pI_&_<{qaK%)(|K{@yoM=x}1$iqp~k=48RT(Mr)%Jqu+hqkG7^at}bCv zXOp(<7120$WfvA6Zdy5>BI;c_+1ki)DM}qjMbHET4wc__a%mjHSI_5OGkt8as^s?F zTQsR|2Bt2=0lq_J?gYXyGyQ}l2zb6F3Zjo5K zXyEaueu-sBCliIr0N{_G`pr>I(Ekfy9v+hb7W97FAEST97gP|+%4_xmN5ElB&D1kbNmlZ~H#) zQ2KNj*&CmZ1P@+sIv#h?98+t4j+WlBi2a7o+%@09PA$8Afio7uO&AFA&<}IkKdd}h zt3VL$A3U^J9>B@Osd_m2vGU;E`N=QkrJyahd9oSeIp**px3G?g)Ni!rqd!ypml2!c zEb+pxxZV|ilya6hz99{TU65e9NVEFUoU{1D+*%+~^CziTSy3{Y~12`UFGP@)6vzrn^7H zc7$~ug_5B@Ec&3Bf2_lZI5(zP@*#`mvi z?-!b`Hqh*dSg{RD&Au2B!`(Z`#-?8TuEcp}vs|DCy&oTF?JvxfcTu}{TeVDTa z0b3xAgWGLxe_#ZC7rIlEW%ZE-Gt}q|)a-_Vmxt9y_%tF(poouyUEQw&bw*SeP-)Yd zT}|NtOc8lRpFDnS3aY7pGmQ<}rIX=j+YsRlb9zP~`om2paWaP<5)xtqa`+Du*PnOc z#zigsAZ|!QR&Q=;DIB7>0I%8d?aRD33=-TXHBKZ&yN;r~frtT?V5p}E1R z5CbWT67-Nf0E2>=m&ZnDN1Of~q6;Rsm|0l(zTa z0?CB9wIq21`3N&JbHRwSni>%r8rl?eRH*Q5DB<1U3`0$K_7>l&{(@un*RNkbsCEki zSYy2l^sio>?3hBmvnFZv>q?OumMHLhCI}_cx2{$Nc@C_cVSZsq-2GVo-F5+-`@qOwA}n~!bPKD(ya zN;2RQG8D56nU^Zy&@B7_)v^W`x*2ioIX#tEfx1MW@@&E4)+?6qC-1BfJ(Y==k;y|w zFYd~@Xn?yTzS8%0%kUX6;}9Oi9nTx`Xg{tmjv_*fMGMAp3@#&Q#EnOF zPBL#=)IA>h&3?MOT<^W(vr4a(Nf@4MFydvT*svvU@trFM84x>W?H zH5ntzsZebx91bs_=ZAQ|!D@RQxWPt5wy{A#QPB{w4ZMB5V%n4Nvp|^cp&-d9kg)=E z%g|_}9&vu0t#Ekgxq-$C@+e9nmlv^{DA5#vOv$GMxltK9s8mpB#uE|~rQy&xMeX@( z#h}@jyay_b1sL{Q!{>SLNlo77%>Sl10&02`AQ>eCLv09P$H4#nX3-Ed@Nm1MdV5L_` zuI7J*o0t^X%Z%c67X-*2r}s%LwwgE8G6fuqty zZ(B4}I>%-8hr@*g5n!IZL$Z7|pxN)|f_S%)Z-$brM-{8<&`9Chhr0&we{z0K?3js# zGmC{p9x2T-R}J#^k0NS?^_2xvT z))O6?n2wmyejF|Tg1Um4lz8ydpHs1j-p;x+;>D&1U5t`4T9|(4*X^f=CAwGxEkay` z)RwwffuCZ=JUC-UR9^_J$L&OWJ-QiZs((2s^LEJT6l>*pAjqmi_gDnMg_T+fye|BC zo{_hp!Vhq^SSRd?`#h*0(`G1+ruQ)#Zesf>a$a-Rw%>Fm(*72`a$1cD(t!rOG!3&`oi*E>8qy$Y^E5_&f_K;uzZW(Qp3iu%m`~ zU(bHR>7$~VfT}&W9f~VqEoXu3^+VnF($qww1dTmD$qTf@6x|ivn;XfYO@o@6jjKWx zb4njFg>OJL(wX>R)fJt-0!Bjaz5hNSTk=yP-@n+ppdcQ>WPe4Z`_k1Z0Q2dJBBv1c z`+KN2+t;&n4ph}B?6%mo|D@Lh9=};51Unbk;O5^~d0Dl!Cs8cb?scxPsy{nDp()60 zc3&9a;S7A|YK(J@Kf3euLeThk$4;|PUo$oYgrlBy*j$B-jg3hzW&p;#L|ud==|=-? zARDx-eJCzCbmNw5Chx4w@-IIjV~M)HlUVL8naV;nk-XnMk++_t^DfJ~mYkSEb%b1& zc<4i!$IIp3y4&$Et0}HJH<&+Fk zXz&!CQiSZpXWPwh+OR7~`AS%=KdFLI_Ak5xZF3S^Obbh59{Yx(x36f0n*Hz8?X&LS zD!>kB`--nW+&kB^o{)HuLJwt>vy!&@CpCdPgqSF<3ZA+|g83z-cxVBe1ykCT#V>k@ z!r#i2*7){PIG}+Jt~3(H8;B(4g_@~jZTv;w#Sy9GqePM&+bG3smv`lx~QeF_k}0|LA7%a;q450EwE_FF}e zn1sY*H_A2aer`)$UVOM0_|EsVGG<@q#JM)YUsrHnc^2A9D?pKj7M89r0M4VO7CevV%$M zv}}xa>tWm9aJI+j#hE-;BG^?AFB8Mh8F(@Og{LqD%FrEfEFTdPf1?K zk<`Z-JhXD%i5PhdKAB~jpf`NIve7q!K|$V>xavBZ!!(`KJ!rYva&anav4wWvwUFTJ zpYXF;(V3fychjHwnQZjtynyT7Z)&6BVo|-4Io+413I%AQtcedk4~E>4I(Au-Rm{N@ z#QgdqOug`9ZttdA;tJ1-`%NkmYUvZfHf5t*dj5lEBEgY_7F%lPo2T@A7M2VZ2Yevf zC!v+b5bRJsC*ChUH>z&K%vrDrQw@viPtWJMz_o{O@imSONb|ipE7%O=M{o)1G)alc zUttYIIAIbxTg;$^nIs_=I+zkL6$cR$x%~fv{x~50uqwSnO)&PlYV2Hd5#)M$IQcYT zn5K0XSHfWRy5^5^XqbNGy5C##oAMyWd^>%aH6YkOflS^wYI}uHCDpZZO#;oyd zJc1^*9Dk^c(eD>KPO)Ug4n;rRLn$p`3;~(#OP;o;>={}w{qXf0nsb=q<(BDw^ok;J zq7Dq_6o7PYk;*4XTIq}RyAnxAa`IUZ|_^Rd&+??R!AdJW6uJ`hOrz* z2jk8D0v|9XF1K>hbCNn-!30(p>m`z{uZ2y5;3_eNsaX+2Q1OICHN(!0$zg>_$!}nV z+XHuRj>SdWG~oLCo962swhAGYWCB)+*RD2b`jy!!*vsZ;3OW4$!q_D8x?OMKd)!kJ z?K~c!zgsGg!H?dI0|FWpRzRYN#d%@Yee3?)nY+Kx(PU(pH07gkZs(LSuNUc#|1J8u zQC!*};80L(&)HDFUVl_ktVCS+gji-vSWG+IxT zp&R%A{Bk0Zm0mQ6FR!K(RLaS>I3{^JSVm`s;ckT4p(vHVopS)1Q3VaLB*{?W*Z+o` zLN{XPyVQ??PFP;kj4AzrsP*xnex%6v+naVl`7D*?L_s{lqcZm>J?0fW4o%C_8JN#( zoIFmAJ%Ap=LPty#SIQbXA@eV%Wb6rL8}0BKGeZ&?pH^-$nJKxm%ny78j1J1TT+K=v zr}*0h{B;Gt95>H?$4i}Hr75oua_FK5Mt(RHirZD3B($*MIf_%I|IYh|vIb(e*$TT& zB9L3r%9M^|5-dkm#0?&DN)>B|@oV1Hw+=D{nQ1Q&iHuH)B`EHKT*LphC<0Lga=m)QwzcoWftFCzdobffDc6{M#lz6`6j3m~e&63OtTMLJ7C@dh4 zn#qbcFs4kdZlzJFn@Gg03Q&!2259yNSe)f4vaY0v`TWc>C?gDrn>gj*Id!mMk=I$u zPP(`0#1qX#5a@Cesv4wDaDo-8dZR)k-`5DI%xFwFlOZGfVWx3i>j++}D-ls-?h4-& zD_zh;J5r#q&Z$s-J2P@d4Exg$*WU_qYJA$6_AZxsmE4>M@t8fq|Ay&?Y;UkQm%B<3 zZrq-T#fg*sX_Z50o!wJ8q;=lPdFP)(Rq?pkdADWJUwz8I+c+53S?V$;;fxn;eK`qO zlruDxf;bJ?82-nKx|E742+JvY;(F#< zPcZ7F=gmj7{Z5)3(6bK&}jvzmGa>r&Mryu*tgq8Cyk zv2{9@0(|cnbNuPgRb+oyQoXUptRil@p;5>&9kO#v&M^@C4i=&z16^0acJ@)(zhoTuKVI$QD&V+G`9C_w?hMY`5`yCtRP z^`d%hCGAhwPP$g7j>EO&7sBNV{X$QEJ*}ZMqxikdw}mJFgOGovWWu>~ym!#hR7+Cw|?w?k=vK zxltx~N&0inps$>VqqXrLEp;wks{+0GS*{y-Zjbk0j>$MA9Uiz2s-3g;D+gnj)C|J9 zQ$v;0?e3p++-seVY7G3SC$<|ew|PmC>c@e|VMTZm{bFiK*!ms3WNtV$gxt~e&|p`> zuB966NLM3}l#|o(c$vuUy5WU(^jET_m4==@;L0WIvQFe!*7=l(d*1e-lu*4pR;Y?P z;O3BgdR7w=ak8oAonP3gVs{?UtjZRb^{1f`e5QPRAk9iXRuWOxKsCgEI9DX;M`&QD zY+=|Zon#XBpQdwkhWl4v68o0EO_oIjGwsh5CgmSSGtZNX(WdpI|0TVSk-TemM)g2O zqwW;%kArmGI`LIv{$`8h-lyAmoOs{mrvLXm+G&f6?1L+Iy%`;b93`de%yV;rZ%gJ%G=CmGAdqTU zCt{b#R~5bw{QFQGq!aI_w*|j8S3|xIMrupFV1u7rg3$y*-I2UL4@=*EC{}*82z@mz zboFl^G6AE?Eq5a-_Ob4+l}2o}Bhl@tlELSZPNi}i-&ih7Rf+oPJzT8iZ0;cHqZt-$ z7;C(1vhH;ch1(DP;*m41Q+c3O*CL2R`G>T4#j;jMJjQ^4I}@Fp!xq4jW`Iu zPmgF|Y*3Xu!AkGI)QMJS;1f(U-i|UHCXdhEcoO5AHf@))9#?o!^f$KPpRjIYDVN*1 zGtNAnZABN2L=b+kIx^^)0t?+VwvoY$E!V5(h8reMjB9r?Cyy!LIMt-Bey;v3qtot)zNk0j$#}ZI``(Ri@T}L5TJux9rR~x*@NVP06ES(A?+vFlh8HR?|dD zfvidDiV{xM^zG_MS2&Rq1F&Gbbu))Zx*VCMn=5uJN`CX=#`sG_{kOrC+3}S9 zwjB7GT11*w43f8HXmpVhwQUCi@WD5V!Abcmm&K9Ug*T^lv0{@W4r=(&sHU7;YI2sa zY#h<@hVq1e8Zy)HvgEZaDV*zOHOm+{YUwhxRN&43rn(57^vilnEX}RpG^>f*XUSZm z&R$JK^qONFZRtE)L*!4{B);PQEjg_fDI>JDJ<9(4|1ZjY6X}sdWQJx#_pLYcu*4dk*=;A zdhA+N70*zN$sb8=o^{Dl5Hyo()nmT2d9d|wyz5yi@`RV@6+`FA4ze}-kK$|pCEDS6 zH`xjFoH*^7j$7|Zu2|Rj} z#__-K6ZI7hYCOK{ab)OWpUXh@4!zo$0AWfGfm*c=*gsSEzXVcd8XS3v#Vy+n&nK_8 z+YgcYw`_*|@!Q$u{gKN;_o4Z=A$$HErv^M*EDg&;*Kd#C(9F3=t6A^F2Nea#X$s;* zVY1^%Csidn-MICR?}t3@LF)*zjKt%Rfzg>DO5pj%J1WMX&ILisej4&$Cu7KdQ7f8} z;ZW;YiIrL*m4aH7x+a5e8{oVP$5D}|GmKECGPOAS%%DM5-Z^imlQ!dTWD+#;@=anO z-T1@70wT>dZ3|_|fKo%`#w@w)F2_IN=C-sQF^Tt_Er)BQU5ms8mzfOb%CeRN?K4*o zD|&SAKK*lQ_?wZAI=cS~&)_V}eveqak%-}g%FOq6LQ^0Be)0bM;QV4F5X@nY*#x(gjkwO%X^@Wvsfh z3t|@`dU_(N9%Z_kz$)KPs;8|^(qkL}51S)5>Sk8kH(sQ0@+Hyd_%lB5m#*OpYJbf! z9YGM&y6)Fhh}5AAdPn8)pSHzc$}#Ns4DAiH$EiY;7E|<^^CD98z`V&ea>?+N-0}=; zT^FSpuVa_#AF(0pw; zn*m?XT!yWBH@YI%YTqqLuD)~p+fnu;wdHN@|1E`fXX7$5sGZ}^;xX#8e=G!WWX!`g z2G(cYrh+}A)sX7d5VqgV)4q7xqqh{jogAf-ofI}Nh>WVn6XdU5*sw67%qZg1%GDx4H&asdT#r-WDR) zJiPI50Sx#59Gqjxg&asLVJk7=U=M~M?}c#a$8*ev@-GJBOG_SMn|3{(+|Jqb_W#*{ zT$OnE$9|h_2j9W{?y)@3w<5JuefTF8bhc$j-&2~+s99yS7T;D@ZD=>e32=8e;5U|0 zV?M!1k;el2e*ELE(iKEo{fCQJBcwSSxCujz<=!uGu86x*B#?s~n>EE#k=+I%$-cQO z{KprwS;w5%*F<03uS1k6WBqrxrS0WDE4z$9M}^a@R8>rtW>2&mI)rI29fVwq2QPOg zp{+NEr;G5>yU}b5!8lg(tPMi*KsSE3)z~hlyX884a&#xRMGk*(U&+h8h~e(v7^X9N z8(lgUtt@nxI4xjeprmLlv~iA1u4X^W<6cgWuRKbs!Tg%8Y8? zU)~yjdBgif2^4>x0)R+)7Gv&tPtaKI?%;s|HXU#bV!oy5UoN?nYq|Q8)tKS?eel!>(akg)EE0C7(tLu@Duf8#gAo_8n z@Z$gif-oZ`>wQ?M6XzoG=|k?KxePRr8NH+so)hsfV&F@n3{gwOJ+#c{DdE09^=1w) zB#Rl!Xz|*2oV5nZ9U^6S5uc(Nya@u$=~r-U&aZE1to0d%DU0Dkx!-u1*;Qqd!m?xA zHgheURaURUXXVi(4Mmq@G=WjsabU#ArOd!lkqZq%hO7=L&xSiXbO){1szYPQQJ{Il34z>wH&dlT-RLHL{!@C68SJIZZ4?j%a;04(0Dz z%_LUBEZor-_;sYjiGQ4wSiBh~repNo=-kHek)MVN;O^9+ZJ|27!SjlXIoy9fb~PiJ zXcTvR$>B@WXzIDg32PAR(YLe^KCIv7+9^VIDgm zJ@cL@BPeDwvXotFl$2iMXAQfEMQAY^vEZONiI0d{qz+?SgYVfi+P}W9h@4-sfB#~X z2{xN8v-^JH{!%OwR4R(m$Nvy5yL#voJ>(ZD9Lr+-%=B|gFV3!`p0?>~*twM@BTqz< z9?{%<{pk4mQTDfQe^_7s|LdyAj@ah3&fa_djBFD4#1z&_#Z`lz;bYkFI>xNM+rd|W z3=^+)!hSA~R=a=G+cK5{7@gl958X>>cajeC434-{mhsSgLJ%WSh??>Zo{i=y?QphM z!BV`G8b*!=-S9iX>~A<}lV@EY(1?f#ny&J_MTBvm&Ji(uk}IYoK2?5kJQMOFyFFL>dX{?(XK<=l8B>t>?dY)|xdlF5;Z; zoO|CpuKl@QlK)>Vz+0idQ+>XH2D+ON-8QO-j0c9t85mf(-?gh_$@_JhNU2 z`6j7v_^L(QSDObc-U4dVxgz6&vOT}>Kgi{~P#m6GJ{BhZcl!SG=amf^4aRM`O%SY% zV-jhqSCjsS@$Zys1C09oS&JLM-YS=TmdNKRW$@SmiJA*V$+dOSm+2_>e}3lqQV)^? zjhg?URgq5kbh^2bcQXA-qEyi5=fU)= zz7m8a{u{jgY&%p5xd(rWMubD_cNCWN(|=C5iNE^ks|*&Ogk~dGFwu60^CT5Lmx%1bvDJ zQQ{7r!s*3dcW^KoA4ka5&^*1t9)9J?Kk7%VYQ;!4uZ500HBG$v@5vrPIzUS%6v*wf0n7Go`} z4+`Gc#?cFJ@h6ZhpmEjkJs;1W2!vP`VehPqew4^WmzT6ka1@0(uJfgSk;mgf-J9?p z4}bW81%qQ0Jew?i%m&{TKfIM1DOs-RS2zW{q-UF@HrA(PH>3!Q>j{6G(ATC!SutCw zdlEy>W6#v+sgnUZmlGYA72(!I;L4JDfPVas=wVXvr?=#t6d#ZSl3(6S;n$&`*R%d?M`xybEh?_Q$xVYp?A|woo%ZdvZV|eG(}Nlh zKCao73?KP|mdGoz`YY;%^8J{Y)dyo7|Ka?lmOQE_(N#CNK>tt)ov46&k}m3{%>9-b z%5j*|^lrM51sqLNhcs||ux^rmf0Fbmv_&~s&2MFKm$itC9X^iDQy1!a=zLCf`d@5A zvY$*?q=;v|lyssnxPvW-=c9m;)_&K)6IlZQXjAH=BWHv0@sPfI#-0B{s(vyZB3yQd zDy!a{Jz}ChS0o>X-5}Z(5UweFFKxlQI_>p-rK@|Huh=&+ArHK#mY`%etTnOFxHyvL z=7t1;U%li!JUst@cefO9UA(+D8_U|-Q)|{1TAf>(eo7^HwFXYO>EW*R+1Ug!2;n^m zC~QDbJeTKi?st1ltUwhhpL(M+uwWuMysK%AlUO&|pM46=hke}aX&C^ai`rC5!uT@6 zc$*uDmA>L#DeT|`#ao52XU6<6qRZnxIb)Q(6Z4FbusFdoNWHnr$(h0@oYq{v!sA9m zzhll`az~m*z~Zl|BLXSo1~n{EYDB8IO3w}By{C-V1wCe;X3oVesDZ0)dj(ne*dB#l z-%E{24loOjAch{Ab35n3(Np$B!gwHT+hD@35L-B7DbA(HZWM>sHey@Z)Zpi<9QdU^3B3zE*O5G5)*Q zareQXDDrMp=OvYH1x{|S>I_1hTIbdkQbV^_PJK&?FV1J zZw_ydFuXZS?{p?3dLq~U%0jn>Shos)B4P$t&XltJ$hvO;SHW9)O;f`&EB!WK+ImxaT602{VlwUZY>nsq%qoZo=4M1l(mG{67;9D;Y+H@`@1`O{ti9k(bbxEGr2j-0J<_EVT2VMw{cE64YaFR(kgJ; zu?pRtAXJRrrxl+x%zpHX!Y+7OWK!uy=i9v=p~L2>tDBLA-qM)u?kq!Gv$)31Rk*Vrz?g&WMgo8s%c&%RIo7+q^=nx8`+7g7+Oz&@Hvd&Q-na6He{(^+b@x`qt5s zI@sow?_(e$f3i=9Zn*ul{f|hTIFEPhBK_fm0?Y01O6nM#p-QhKH@vBH94Kqt#MG7| z+UWAXA$3de+WW-N=p;Ydh9{bZkAIY-H^TPi#uS)E{?!@GviWaWU-nz(jsD7SbaNP~FB>=}kIptgOH~v$V)&^i3RDV37QnqEDoVv=OhAM2`A^;6om zI#K?c0Nsr>R=NsGyI;v|=%V~I{{`+4NJ$?`$~pvQaoO-B>753ohon_m>j-Qka*T1f z*od{(m1;$QQS6LcQ(X>ixwyWN|MqW>hyC#7+bp}fW1|f`4#UI7o~Qj5dyyX*%o6qs zz0uoO>y_W4++J8T(>%5uE zb+*SqN+;Qn?p}?H2t(Q@6Dw&A7PYqfKC;VfH)@H=g~;l+jZ}VnBI{(U1{a{|HUlkAfa_CV_XL z*7fb-zNGGx3r6K&b$Z6Z?$Qm?NW?Q<9Hp8ewuNkxBJCzVvgA^-wcBaMRLChTav=mv zpFA*>1@@J(zhrJc$doRMTn=Vn#@DTGkLNj~j@bNU>mUB&(l_v(VY)xA12WMMvHhWb zU)KTN`QAPAJFAa5!$Vt&YhHqNhd5tcn*A4WdiweGAFch+rs5rtY0Md21oRIK_z54r zyA}X@mpxd=vHAD*OEJiW=BGHAnP=j(t!BHpdyPTTI^njSf0u;h3XSpb|7kX#O9P$q zhuyCdb%}5VT}zVW?R~yllnCA1i8m!uMf8kl5jM zEPVxi=%~^(f|WtXdbgIeMG){Oj&EtpxdSYt)v=*dYRqNj?R{*Wa$PPZV)$R$X# zBO%Xh7#37n<%O2thG!`uzL?7HyLp>!8;08O)~THv4E#U&XK8=?V-rPvpPB+ zp8ELv2cqtF*dl%Q^5y2vGBmQ$#4Wl}yROmnQXL%~GoW$@Dd<93q_^O9Wm(xy2V>M@ zSPQ?2Pt^ge&*2`$pNn>!_Dk|Wj97(zdRQmfxs@90xIP>M+alW5^@sV(IgE9%YKG!Z zz)rb6Xr*Ag(spe5`oa9QAgq^n$dKwvZ%@^e!}==@Df@+9!SZ<%NX-BEpkrj`<-I>X z1Nr9DmoLA8$2B&^zg0u{S|*R9*RNlr_F&P-gq-r0%VDn$MVUR{Zk(Rwoc7p%2_o;v zBqEN;cKAu-Rt?&^_h`T z=JMs)nH!DQiD8f6I-A?k#_#)Ibeh+OaFXK(s=&e?9V$LRCzkpB`uq@piF)B z`n4wP2BU!--IC7)NIP>GVeID4BKp(s0HK2-5Zt|cm&$pn@EYLu%8ay?W zW$-%zY7Mvb1My~?^Tz1R-{ZN_-hpDV?S^x!;E`+4^>PDY0~kN>UfH(m_VEp_9^e6^ z04%01syucke-nN|NB&lFM8qDlZ4t2ls=v^4Ff!DRy?D&x zjI5%F6#CC-L4HM)YOiuG_zwTrjim{spE58AQs?X|VSkZ& z9UJ3ZnJ}(7$>h(CaK9`yIHh~lmZrU?0dV@c(7^?TN+Z{fZ?;FdGbTVaJ*cT zT6TGFf@y8rZE4fnTd~JU2yuG4A|mtl;CJ7nnF;C6b*$X>Cm(H|OW` z^(v_0=3*UO=*lEMN{K4wYYb=6vL<;S8U8Sfpt+q^wW?zBN95C4)hn)z4Ogb>to@?1 zN`aPbkL#*dAEM^H#nln9#jZbFqV=2J%_qtZQ(xlZUa6@30`G>GKy<4WJCiH&x^%;* z;%@>j=CkRBcZci`ABN@@)O?7b1gOHBVXs(Gxlp`x6baH8wc&3n*Y_h2>V&JU(oRSI-7fj#0O7I-z#srLpfM zZ~Fo6VFwt?yMJFR#=tH5abiSaxxHg*^C51Y;M5iZ!og}>0qgk5%QQ^#Rj zr}z4LmcZKppa3Wf_+RbqrSJc70dlnm0%>oWgnuOE$m#?R|Cy#B3Gimyskeb%KU2M& zG7o9aJrR*)K>h6XNfAH>)C{|@|BK)xY%@{b7e=+0By1Yuu!{qm2ZCGHNu<+a03Ad5 zG66Z3;mMP-$z}V2|DH|Qw|9T`ykcr@$wV8E_MfwCJLt9w%3?V<(hOP&_!Tc~pHCQL z7NT$oPNvAjaBz9Mn!|T^@o2cJyJa6-s?xoGm(E}8jwJZ*< zT?rn@ zAT}l^cQZXNV@`dxJ>zz)bI^lT)k53CdE$*qZGq8YeTqgs)!NbcN!GxmSK>cK2}^Iv zNv){Ze^W6!N2x(t$1LIRo7V)WiOt!4@bBB!gH6W0xQQ&~l^Q_+k{)a}ubiDR=@&0=>F>?)oi2=5@ru#iFsmriFpgi`PzV|u(}F9y1y)Zy2R6O%Pm~D{ttM+= zulfSxaX=j-@ccdj<&)qUHmZ{bD_`&j`|k8>6_(tPk0{=sX{2UKBGA{Lo_CW(!PFrm z5FYOVT=^gN+Hm_`5mFzZ+$Dvv*8tn{Htd!v3QT`@^M>U-6x$<(!<%o)g|TwdDQWrA z`*K|$1swkBx|D#1S}YUZ{YBJ{oP@9N2gduyj~^RW?`afDEa+%{Jcb0Qy_+7G<1E8&a2?o_Y=*c=NT6$1yq`_9C@|cbsMgEyqCg-J zX#g5cnT93U-+;|Y5c3uVGCP)hpbG##0QBWkZZTrlMsDI#k;-j?4;zDv-dD@sXprU6 z@$<(+c#a4B4gse(tjh_k=amXCkJhHt=n+`{x?jqB|E!n}f-Y+94_2&Dt3^9&xrKI6 zl9i7yWlig}_weisSgfFhJjdANwL1#BE#>vqmoNoWlFa_ciWewYc5t{CSor(n)w+`q z%4Qwtjk1-ziCDWn3J>Ha`F1cFFRs#Ao#+_`i$}zws1C$t_=`({Iirbv<#f+DWvj_55G7dW!HD}_(UEIfoPA=v| z92{w2Oo0-cF;n)>OTMNjjh{LQ(vqGtn2$w!S3F17!aGapD;n{kBxDs^;+AZB|Mjbb zvC888I0MJjfbgd+qE)!-n9|hY#*+o37z5aS4>;*}lf>1` zZBw0FCJaW{hT9&rwZ=GVz4|oH(~&c9XlJ2b5?R!_q}s`hZb-!ko?Ybm9D0`9q33TI zj_QRwP-0w{mYrPlo@DLs!q?gY@YCI^x2MPIScaLA5hI`H{CHa|e?E2X5?0>r0MO~~ z<;+h7Ro9zw-tO*h*h#Ns%!1k@JufdW`J%3>D*lU}TWBzi+lqT(HXk2fCF8=?)zxUl z%NH;HJnZ$Zncqgh7d!+jJG0}&mRVD#`#PJl$4%yso=4CUf`8NRaOmI#lIp$jMmoHl;fP795_EUm3Yf{5xBVj0nBeS*k;|&qO4foZ zzp}DOJbwj84h3vjaX>8;V>FfI&2No=`=YmI_PevI>mD$Vv2aLSrZ|AM56h05V>opu z*AjYqG~m0-TVc5m9hApV6+v<4I^on&TfB=CkcI#~jB#&R zQP5;0Xp;ZA-c{=)n^)W1Y3Ers-{`fHJ*Xo~@Io>a5pnSj9`wq-y^woW!!r4fM92(VYN3uFVhadSsFii4(LBzT8`1>>_N2|0eDEVYJDvIw?n@yJ4%iB1sD znR1>4AEV&6BpK{Kw`N0?)cv%Msi5(&67*){a$o-I1m{k*7v9N9I>{794i;2dL(44E zE-W=R+(&I+2pc4_I=6AL^u0GCdTIHV89W)x!<1b*tv-qW%f<)`PjbahnB&T#1?h*7 ziD*sr%}J#X1tO;Zje}oOfMXlyRiVcG)s6CREBV{$>X(y*hr=W`XX#-T86{%yxG27O zIEMe7EFnz)oqh4iyO(fQq;IHN1H;P4hZ%88=26Y~(_?BcXmwmrW^#AfwQ>7ZIncyjxO5n;*ZzmIV-yXspKU*1c~ z9yRr7hgwb#1ljt3r{TaJpE`0VR8r2weOmpVM`MT(JU zY&q2m-FqMRcGj-i%LnOd%Lez1l%XL#=+M)b%c1Uxt$y z_#6L!wE)qCz1(mHeu5E$Gs)#)PQYg` z^`lKqmz?Ut^2-F6Kof^13lp6 zWd74d<|RfZzycu~M^QpP#l{{cC#T}vz8$G;gDON|i=774JqJJuOnxr_7LqeF1}HZb zI7Mg4q_@2a`m_KPx)vh|>>sL!_aX4?K246g6hg(V*I>H?Y_J3rdg#$3pd&tpG6qmP zw-AV#oZNypZQfrp0N6qDZg;Z1psPGGFR;WJg5r}IDpPWdV-YMCR)yUkEJE%g7v3bDYRm6#jvu%aDT+!fr zzG-xtGGMYXNQ_iW^U|H76 z$2oNVYa=UYAX6a<;(QP=;1?$tpm-jLh&{%t zX~50hRt*WUTFH4I5S{JEW<18x7wfM4Ob4c=%t|qah|jTjc>tN6pKz+r*1%j zPmcf$WP+aBI1x@UgMopq+K9>TL^3!@0y&SnsL7hMp> zVMu-=^u6_HjERS2xj_S_W1*NK2CfWMt~%Hj4>*)flOgG>9VRK5=w~rD-VL zp45pL+ICwgN=i@1k8Q+NExAQLHTiU0(FxZCQL-M_GO=9uv~edh z6=_{0&l0E2kP*iv(UJPGl!XX`V2jGaw-)AyXvksWNiCB%=SSUKHy!l-p9zFC)vwJG zQFQyRdOG7a5=?6N*3X_g9bfOZ(=LO-$7P|i*rjpE+3y`sb;)`c zr8{MBan;FO7&%ac0oIv`qV53X5-Rqt{j1+S-h%UD#5Az4j_Mo2KKw7h{w>BtUui@G zo*$}YRM)bhvNdTNXJ}{$nztff?Rx}ZEU*WyR#ZXnY4I`ZGXvzXkmyH+u?w2heg5ku& zL4URdLYbU~hKA6MbNGb-TJGOk?x=%^S;v$2N4*Yn3me8tM+E?< zheD1(=2ZnfV{!qz|GtlTVFFhH1wbxED+MMq7|@I>pf9!_`$K`q4p~{$^Evqc^$1cg zlb?QA#>U1w&?ZW@nGUVI^lwdif!xI?Zo9<#cWx&=VIVCns5Y`6(_fDqz*cP_avvA7 z^|oa)A9~&4J2KCGXk*@>NwA&-)&zkrQ59@o3C70P{iSuOnM(F(Jia&Ns zVmRUC7!%dF5NVBvWT$9MvWvIDxcr{4HDS#W-4<4(6evp0ug6rvqXLWMvChbT7r{IY z3i$R8Z1m{aN#)poE1mVSZwWt!cWQswcrtT>Q{+($5=I!m$&&|;aZ^(KA>3_=-j$Ny z7zv%RVK8#@wN9&!-1>yH^@qhC5PTIm*F~^QT5T;iC578+YH-!O=Nn}=a#BwY8@uE@ z+B+{g?v@xK&d9ZwAHGbe^s&rbn!MSg1Zuesy+%xv$L8y`qwA?)%qYcAwX#NKy!UT3 z={m^D?7U%iC-^_#QOzat&mvhFNo#2bCx3r9^^Hk)oyr+(yAY_>-9jm3f45AXsBdft zD5>MdT1pSK6O`eV%eD?TSvF>&)>FWoB$~gES z0r&9oT?%B@^l&eW@=s_w*4NlmS7t~IQB?gQ&s{S@-y zh?aL@J{CBnbphW3#m36cX11z7RO@wz(^&XDhZm4i1A`G0Y+#2sJgkO7QlPvCpzqmG zxl0uWMo_^3<3zE6xM9wMLV;gjZC_`Ui{|gAzQ6eHKYh974BU%gNclq`U~I@eOl?@r zO4>O*T!ct-o*q1akBv_?g97_OD`BSB`>M2^X(nk7Q4Iv{^PvS6I^AI!5&h_5dsrhw@W`mIqOd?sesiM#<=RMOx9b6-E?12O1Wp z@825(4}|8%nu8#@h}(hfCS7P~sPOTGeV$%@BBVCQU5PwlfZuSRB9+{M&Mt(OxE&ZN z5y9>|9OlbRP6`W4%lnTWMWl$hyA@bM5_K_0+9-~&rBb#wGc%KO@V-8uwufE<*b@AQ zUx^BqlzdhT;3m~ReQip+2(1jrJ@jf*8NK~IJv|#$!@7TLVKqV9WN_;%6Qc_%eSrQb ziVpM@9xJK|D}26DnsN;1ToKyLfJ98o$nXQJP4~Ck*NO;y{0Blpl;7?`n1P(-e{(8m z1EFFtkp4WHoeF@z<|Oy>-BAUJ^wUn|0gAg}R?ITg8UgkDmMUAX{x0!}`}9T#72TZj zXr3Crz6WD}Y4ImVZKTNGnSJ+@2t~bQGib!_&LYG|7ASt$c+xj>++baN4}OI%x#@-$2$>=-iU}==4~+_@^_)0{L(U_uj86SZBJ8WYpFp1g@!$6D zZ4%uQawJ(q=k>sWKNCL2lP9TlSiRg|oA|T&a6+3ox}>Wh4@eM%6GW=pKp9($a5O+N zln9md#%5X9mfzAX`uX+GY!v%s#Mzqig#HCm*RxN@#OB7E(ZjwmKdmc7*>IxpL6z@} zO$I^U<5x#MrZymvZNwVDcE23Vmip5_gblUP%~rc6{HNsOABw zYILCcY|J^MWn6beEbeWwi;+5jI2Eeg~dd#K$xh+zR{wezNtp# z?;W*+C-JtIvwEXEIEto=4`I=S7cz4Xc3;I~EyZ@Z?+%c+7m3laG3g77o?ed%ZSSz; zY82>K10aLEm*N_Fc>w!C~=xgMl%C zsq!BqUHvCCdJ+2iuiT~H-WxU_rEZ`X`!0K?2oofL@#40KJ-?=V{-GuqO8$KIWzlZ= z6Qc27*~x^90|=Ykg%Re(t;xR~jG`y~fk-tX7Y4F~7;3w54w zo|lZky9DMIWXhc|`$W-tp?(I3jvX*%17eSVR1^Uv?|VAP^NWfeqda{gX+%0vLqzx+ zLmWs#sD2x&3kilv^()1f%VO6kTM`||*jGP24u{murW-_{X-Ln=nECTO)#74V3i|UB z5)!~6Edlq+(j4~d7#BCUJ>UxM0|V^phwv4(po0Ne40U}K_yp&i3(%U-15pHzBKGW5 zKwto?hYXS=av<}fe4t+ODLi{d|L9SEj|tcjwbB;=II|taNSdg&?|%}3vUdZ|yWKe^ z*IR?dMsmS>F5==ogU(IhdG`WdJ}Sh{*ET=KotT{bk)7St(&7(1QzS5jfv*X#6EzzF zf1@Dy88HA3Bc!5|cXHwde3C3eVx&NK6*`WU6LvT##3F>W6R9QLq`{=LmSBQqkOdj7 z^reGa#P4Eu6LYbu>grDnWZIJ4-a9~&T=r@$BUTQrp3K1Db*{HAy&Df7|aY0E*ln@pj29kbsFZ?L%<7fGRJkE9N z=kjj6%~`;9W12a(rGXD?^F!#0rPVpLiIY`Vo@K|Uq{mTtvM*?YNchkU4_`G~F!7-Y zx&$-$eeCO5j4f!pYI!{~8})XlmIwQV0)u4li2|1;OMuR6@2T;T#sE`6aq_64j+{5K z-wRghUK%f{Njns-b^Dpi`8?tDx+>%f!4q<8E`R=%-7KJ!mcT+?F1B8eji%GIfKB@E z-W86sH_x!_W{j(Q%s0&r#y_!kuEyHLYWdnR@qZk!EBs&XIcS${mojrHFFhrivBgV zU}!2MNT_cZ^=H%=S-Id0@=?6_avf||C?0iQoAa;X_%(1z>HgdX4_Y*>X}=v@S(iY; zlDpCV0kFlzjrnlOj{HHnqT`OQLDRK=LSD2fSp)Ev zn3(tihI#l#Mu46HA-nDlZ7;i*2yV*^obg2%A>`Q?fTh>QSa}$dQ5KCw?&aWOr8qb}>$y42VgE?=V$W zRRtFN;B2IuQNX{GI$WQ%d9$N>qM)LmXMT-sZh4hwn2gd+z|28`rcew64&M??Hs6AT z2^AyO3ma450Q3U9Q}iz{JBl?7w{I6{NkIK+2xk<Zvz*2iV!!NY`Y%g%0^QChVe6uFCSlDEQ_sRNeo-9Q%m7KV&#AL%I5|7zw4IZFS8u~nc@XiM!7xIQ z@kWxNz5^-3_i!uK%U@Zazl4oc(}POW1y|Dr^LA9Ga}@@1iU#bpOP>(U(t_~fKb47I z3~o>Awl?w=>gz&CcUZ+SHbI!YR)iZyIJW34XzdWg1TBTMbrrcooZyW1ge3 zH#j2ol&fTgtgRrb?@kZ{OW5Za{mbfpNo_;7n>lCe$mAwF8rSA2WJkvQjB?KBjX(A= zN6%viw}``s9ZVg~xP;()-;F)*lKRs(Ht}I60^DB3Y4iyIDipTPIq3Ck;*#9uL?SH4 zG~epyXSzzSpo6R1EozZdyW4;>1;aJKt1;l zm@@y;w4rrjuq$~_AG?*Q=n+|1Nd28hR&>qWnDs5wF>kBMYDLAjPVda!q_g}Muc?hB zu@2BT3eYE#+jQ)&V%<9tL|8_UOx)Q26-78sV!xTYBBj?NTyb+yFKEi`LVz~P=2$9> zHlW(M;e)KeWuBB49V?^itvVl#*c4^=G3Cj=j2c5Kt?+7-1{T^C>Vgb`x@N+Lh}apu zx5#F`+nP3HkLv2jKK~vMMwk~cNILKxR5%;Fv-){8N%Z`!XogX;Z3A@wCS3kZ_h{EH z*v)=C4J!n4C)~YUu$F=&$AvR@yy1G!xFcfolW3;$M zS#ES>q_QV?5&)uL&+&a@g5#U_^;I3fqh@kZ*v|T3!A7aa~GJ);$RLhIy444#N zoKIh8fwBqZG63~WF{lKlfc2~YR<98Y#p{H-)%)P_+I<0m1eAX*_!&TF^nPo#rGX9z%8DEHV+Cc+pi}r1P^kbf zMbQ?)Wi}KBtq>ztJO0NO1-S~O^8osnE$z^KqWOGQve0WU6e~@h!!&kN8zW$S-AaOT=?M)p`HOM5W%bw^$Z{+v_n0e1w>A()pnQ%{wgn(zK>2EeP2^W_Y(oiFP@a|t! zUOO`qG~O4x`X>E4I(NZe7iD{(aE6g~pmug|Wx{RR)}iq!IH|s2t0C>TqKfOe-e#-6 z6z%AIWBmITvv5{js8>Hrp0E+o$-}~QHvW(uiva`674BkdFY4zCZ|bj%_~&coNU|O( zu97QrmOLRs{>9ad4ic{G_=lnB$nRwnMxp3@r6=w8;axOwAlW%7UD!a8gBM|*UVMVK zYW=nr_@Klx@o=`!lIJ^HKIe*AtnPR%7gO#2dF87!0`ph;5YfA+cQ6{v#Qw#|8Wv5S zq(bAEi67F0X*{gfRgcq`NPv~gu@sa5*m9|WE!pkECr8aj9rdg+v8}LiEb*e@mQl+e zgTi0JWn_MOPqCLELk%`V7L)NN=CrKo>?UWacbk_D z`T~vglhp`i+vhSg2<6SyVU>_t>(4n%UK{Sr%0JLER?!xQHD{J?)rMT$oB&Gl?9D4(u%bKy4`=&7_)Qxg1RPPpDrz!v4cOl#Gzyt*k(NayPg9!{J_ zy#-}84sMg6B3)i z`s<$yOG_xEFnFG{z&;l>gLgT($MKVt_%p!@tV-~>xcjGy*gHFhJuH?_>)m@vQ6Y`Adz@j*SETM!#z^5P z{(|a5wKZ}nhS+qHHmKx=@s)D;cB=TchZv?zbNb8p+U4}XN;%9n)^E^UX0wi(ZR52cS5!I7l|fqjBPA~4yS%8-TLuh1E9hs zmo_Apwp&Iyag2_!<2A=;ReV|gJg_ui#dfw1P`_6qOz>z|aN)OQ=cmbe+L_g0%PG1p zMxJlH<-_w5DBxGf9m(P&IySn$EiG@xa z1-??C}ATn<25|HPnbd<&%{e7 z>?c0>__g|*cbnpBu`DttU7HXr!A4rm`<tknMUvI(Vtg)8EAi30JjY_001MG z)K~4n0$t&ojD*Z#^@-y%MMV>E8?6J63>5SpoQF|XKwpD(# zr^2%Lm)>Sy3X@051(SudAj7DmS!LmhkDZJ6&$3+lE)TXzO9h=mY`sN2khOmLpPCnjjiQYs^y}1eH)Uz}`h!BzM^3fyO zvIm}ozTKSrM+}PZ#Yw*TsT1z494k58i4n%_#EJeMnS;Ri@zB4v!;HhFE zZq`lt&U^31eE-G#XL`3a=C<<@<|ua$?5CIH7Z`-pR&toXe(c%*6H@i^Fr#s+6^d?U zD)zqqZ!eobi8AxZnpWDvGk0(8yLFSiE&!@^=XZH?{$_Oqed!mP$j3eDe2w6<-q@G( zX~kLJw1{b4B@>gEy@xAdEn3$jI3lY?Vz`y!ALruFf}u|Eu1BF9Ig}m7h})BYZ<;!8f0(DD!@VTK}yIuC9wqrNn%@(aRy{v|@L572^4+(`~5n zhC|JTlkZU9xwH5EVe**;2z~z+$$7#^n6%snY$Uc{LR7K-wq*?zt%Qrw1UsKndHRoGfxbwu0LwtdwFEO~OBAA*OeI zMDXNg%pFY|!uh^8#)}Wj+9p5yvfW?$jTg_v$Bdre{G@NfyEw+``syLEljuU@@{<8{ zV>4OmO!YWp@mb{^gbnY2a`l#$isM4a6V4;=n+RzoKdykmk#weMgd}ru^`OSYT0jHF z3bgpYM;c`nymFj*F@QwhHQiOu#NsO?qIp!u`1$cT79d;oRVIYDC7lif`5BS&69Ep>_%-VLo5$1nsKfgv9~)X? zE&X3DfMQ;2*S{aleOQEPnA1CZn1cZyJi4f94QaF^cA{Rz+)yLvI=}-vl)RB%2~6Jh zBW1Dh$^E{$!k@xLXlVJn#2RkGfkkHq*IT!`{#r{oOW{~#@~XXG>?nC}{L`>YYTBUQ z2xH-Ele~WLj%E1p7QXQkNx0>;$Z=xrIT+U8-0x$^X8BPWK{7^x{2UNdQ8J~qiq_0c z_@c7CUuZH|UOTXBGGgk;IwU%-qwca}j%ZC}LYN51u|;mESPoTI9&?j$2`>0vPua0? zc-6&KUWrsk#x&S1J_jN8>c7(OjCgK9!X-V3)V=*=MY&PO*7a|)t*b3N{>a%-@3#{6 zFP;2=@_!u6T}`$8m(aUa;QF79>tAmnwE~^rkUvBetfNdpP&x9i*5qI}7amXyz=+}O z_{W_$g})%JW#flES81H5O3xP-UEx)-Pzf>Lnx)QK^RrF~b^K*Q>Or~xOECNqcmMcg zD9L!^EKbhzB5OCS7mCYT18q~ik1HFZ4wt_5TrO`&Y6z>O9RFa1m_$$EsCL?TbQ}J^ zg9R1#=NwTV#H*=injUvFKSmB`KX8;Gp;phZ9>2={ymEe}{ji(Q$JW62_0|5V!8AX$ zh$~xjrP^4k@;CD$THU}RHwK(YUwtwy6>H-y_9Auk2X@DHeVAj1*4caJ?g`%x4E7yM6tUD{qt7K#hJgRw*Vb z4{jJ)hB`*Vso9Q!_u<{#(d#Bs4mi^DP8}lCKGw_(hG-0BkGRg6`h=s~htqf(NsylR zKfgPh& zr%X0}o?YWhsy=*oCTRSV-dGdgt^J%Z#bhLm%l?=y$~$W6YRX=emiyc!n?)vUBPR#( zHGeYZEnNEa8MT>A)8Prl&qAkFRn&v_qGYXk8nV`W>Ths+i!l*cy=~fjJ?h>hFwu~c z^8rL+Z4s31C_^UrXM(UR7i=t{Dl725yexT2&c?zbxjo%@%?*CjcCWr)u_|e5o_^_a z49hP}v(l0GkJqvbUUrSvOFJG9{0~V@8**uWFDV~lY-GVQTT+#F!32u^Os6+oSAiPP}99EE8^|(eB_RnKUi-{ z&tte&@&u=|1nKoMnu*UR|L!bxB*_qwX5tUK4aTt%GQY&yJB|BM`CDaoo|mFj-miln zD_Y&iX>F%){55e%?v|TG^?Y{uzq!wM!fT@`j$bj@J7o;9OMPEe(hNZ;1Z2!38fBH@ zqJ-;iJaT=~%!HfZjksW>XTozm(T3~um%KJ!pjiiF?oTGPKBV4H4Z+DMl70!-;7yt6 zVrUxOo+SzuE>;WezU52OuNNz`kE_Nra+#EN+REHgzfzSW!;aU5@i5w%isF8bz?UUI zXTs(d+FA0D=sPb)f@AKt)0xn6ITM&#bxcflYFj$0yKYq6S=i!u%v}k{U^^|6M}V+* zkt6PpDaN|32sR4z6PJYOFJtbnuH?7BvAQJWz7!&EupaC4OZ4sIJoZMfsg)|6kq=PM z=8A*p9#|o|K98J2rq!~(b$OXxcW^d_^&pLhHmGYNsQSH`t85ya-4mG z+bbkxO_+U?(3B0YJx-PPI(wGL`@(L`8WJE>n-N6GK~+`!f=(MDL*{QmZHO`$hC%`M zQyjgvM^ocFJ3C*<$@hSo2rfFC+2+=7|J!K;y%TCX4!GO3X%_9_E@942O*u zOg>4paSa^FJR_A;7WNg43}XV+rQXMw3^_Oqk1N!qLS5}LU*YzS<(P+z=Mv85y!ze!qEAJ+L9k$x+-{iiElyQdqNG@u*q~6t$dHn*0aNR6LUxf1xeTHp19! z$+eeACD}5eQQx)1qEJFL^p2l}AQ_>O9zu^kemx5=O3ARzz?~iRMzrj_rXwfq`*F@L zcp0sSrul8@<&AjYI&QT6-QZC6M4EwN%pa56gl{SRuJjxpeD;JJDyHtDv27u)`QbY6 zBMM^hU`XHjFR@S#t+}-3X3ZA*TUVu&U{^ct_?ucAXge0+Q&-mJjQ^eR9}yoLEp5Jj zk?KJ-d_8`%8oS7o^Lb#{NLx2HL{PUqjlL)0w5iSk85YRo=*82O%&Fi^ZO%w`?0fIF z^bWH8`{6-%P5Kwx9|;UMkZROD#^(^RphTOc4w`Zx1gjoy$8Uq@VER#8+jrjhLAd(X zdE$2@4Z4P)Scp@rR7glgyjXABRkuj|4_1_$MOG1)&Ca3YS!j5;^xzZNVu}2G#|Iqm z3ToC^QeX)QlyS0e-!2NYd;XZ=`m2ljT>S3?a$$vPPEm<@U66irg|^YUdj2=I7hJK4 zj#Hv<9e7rAald~IH;-Ls4CzyY`c36(*Gyihf6uXsjT?S3**%`S!6?pVM> z7q85T-YgMrcWj>2kCkKnt#sO=8o2WEjHS@YvbTDO2>CG};poy8Pxpkk#z2Z%T-xa; z{{`N=BZcsmFitNw4Et@5h5lG2*W3B0;DK~+=fK~GxnnH&bdU`pkJk9u#yL3Sb%&)s zgWxoJY%5}0$>vz**U`_3L(ZlC#UYlnI~3)TR&J!=zb&~M!IIsfzV6!NYd*AgHFUFn zDUwb2=2K~LA}~snKK3aQ-Vp0nDQ^zrjy}3w_CVdRdMG=v`?b*Z;izEhsRB_A;Jv@2GTk!=fNo$U-OynB#}A{xmy zEK(#$sPdejyUiUc#i=$nUz5Yw1Q}67w|{K-?nF*TTG{n~ z=sL@=EZ4WogD9Z1N=Sza(kTcC(g;dQN_QjOA&rzMEiK*Mjf8ZAq=~9(yY+n4K6ml|c@T;&ho>dW%H*O|8F&NG&3y^x6dx6K ztjC$==SNg4iTB5!HDrC<&M#flq>!r|9ei+s^EEOX8=eCJ>b*5k_?NAxe!=F}gs$E2p9`u493Ste-M-S}s8Xto&MqGd@711p{B4B`UBEw2Pt<_$ z14hf#wP0u=M`Zkrl4PSUnx@YBF^*_?D^wzV9@;q#8v%4I%6hhS0eg2*0KQ*`pZp5L z)PG_{xqCX;W2u>nmB6K}6P@5=e+i~51}Asu(J1S&mp{1|RvU-p^3T{6y$ihz1uek? zwF;uJy)a?F%8sdj0*Jh#e-5g<8aQGuFrpuxna>iPbsW4is53M-JadC}&}|Ek^rn|Z zVHeHlBt-vk@;EH+yeG4{UnF&yWQcSDyCA67tt(=?Liik zH;L*GVg0iN8>h!FUOa;Pz-TZTF|YwGIvOQd&RYLz-BTsD`tx7M{y+DEp|`w}rd}R8 z-n7*6(yg*nzx%;J$HT- z;Y%m(GDeKWImze+_HD}k@^FdM4odz@wuqadJ@t3dl+t#ep?Za@Fgu(o{~jKJEF?{< z^21^C8xoT2oAr>hxyR&5seZv18zC`)`2x;WisUJ0m6BR~ms)N^UrUtU(?+Y~59zGTV z#4k-BvwJk&3zl8T`7x?@p6GDklSlvAyYZe{;TCh-3?)a^i?(jDcg7{Bl%tdS%3H5w z$+48JB?#da4V40Qxl>-5B~?G0Q(q_hZ>7GaP1Yv2dmQO6W3;}KO`3!iwhtUs#LpFt79gFJC z=e6%9mK7eY*4B`&e0UnWlt}!in8%oq*%Jp5)!oKfF{B)Xp81!BL59lC(R-Y5I0vKc ziWC;{dfN81kO+S0w>dChM9?|G9hp5+qQ$rAV>`S#TSu)Ojh9c5R9@_QyKzQvtTs`z zJ6+#Ie(rPKH$|7{YG063MSuQ58%Ljyf9_`_M9s!g4Rd6?G$uTu{>a+;{9*#RBoqyb zHin0n`cZ3BYM2gcaRDxuXa@51Z7*>l5o8@dbT3%=lvDZB7A-gRTmzm~WE{SWR0&JX z9qNj({g(QR+bzT?4M(n$j*R6~6dR!}kAaCc; z3MClgZx(Y|aliZIEq|wq9+$W64@MoIx;=}*fJ~q7`{4wvJA;&eaTM7`nnX^;((cgm zt)z?Dw^TSoSwe*|`q{0it?A5xF^S?;h?ciU|G|_%EkWaqNOnhqbT41F?YAyMQmPyKs0Xs-b-nAwh4iVpsn=VOU(D$%VZjN zp96(84+=D*z*xkFbMGFiB6Wd))bC>X*aVq3e4T^kl)IF!yR;wRqk0(Y;R(EBmRrP>zrBzJzyg3v`Axl#@ z(0nQNpy;gIQ*=fe28okF26TAo71^QVOvQyT**6VF}u?@*P9or~+QpK-#PoYnRO zgc!frm=^K8llciQCaPhFw6?YTBVM&}997zeSA64MMXHhU+ly1%ab^L&$nMqe;^S`D z#X$@bbN|z57zQFjz3g9<|sXqIkG&dXq5v}a)%sjuM^Bp{?aU?>-P(xg0Z?UJ>V{QmEedkzI&MZ z7+uq43~N~)4cW@G`^#7QA9H^)rJ6c4g3%#chc76;7K^EjL3mUt=}<8+f0CO34>y8Q zMnbv{JMBH|Nj9BO%V&dvc0sY*&s(26QFdP6G5LqW7QvkVC}Jmo01V@nz7r?9eVK~T ziwm32VsUshnk}vP&hJ=#M(sRy8U_-y4@fdD_{-K*3ysPA31%pkR&SCHl5$R7{<937 zw=bEdAXiBG5JBV39f!2oe>#TcrC#IqEThj+SgH5M06;;p|FCGQ*O9&-lB87ioMs#!D+oqgEkS`p@`x*22s;Mtbw~`h1_M*70u{Wb`Tq zMxF)YdcGZMQ>T$;-;5zY*i+Fxwwg~I{VwBh>h_9p!S(PPkK^;i`9(}r&T&Ot6ATy{wzfZijQgt{GH~kAqJW9X@@xPEAVS$ z5I&b1Y56~|3jYvogcn-L(&2sWy*q<*{^+K;q15?=@1pgxid6!H-pN)B#b7z9hj|Pq znI!Ef<}DisyqIizvk+w2+HVq8uV4G8R9j!2%fr}OMYn(9<^i4{5)~(G`BL9UFKR|c z%x6T5a&=ZY4b9AI>1U8A#dUd!PWLFuluLN?YB4=s_4`IqRyMuKRAIN8TYmw{B&yK~ z$ddEP9K@is>@Fx=D@EC`vZz6OC5GHNf7adLt6KL`S)u{+6)Y?pJLt9qWgMf<@52nJ z<55uRW87P68HfCRU%!1mLu=jepLaJnO8@oF-WwL*CL|S8*FiBfpX-~2ol(7uQxu$J z=lmetlfMSHfE*?eK|A4n)8X@O_a72M)A&h2a(5q>{f70Ft8?nOida#O!%rnniYW+Y1vZ5yMTeMw}=%Ln}QTaRo)gKMUv3~O9>qmo#){&Qn<2lX2 z3C*m#lBlqb;?`zXS_hV>wF5(EvP+yq3>CjfiPi~ny1a4UwjFDApS{*#POYS| z;(vNnYJo5MB!VhZJ56C^t9^-@a$nN+hZP8V|& zaz^zn#7ujJqP1q#&B4FX){mq1#q*pSUzz3b9L@MhII9qK5UHX>85TNu4S&mndhg~F zLIp6a9+Cf3L=Xd{PSU%prNyofrFV0KK8yZM9&Di{Wy+$hLDStzK*`b+dsxw2EzUSt z^5UAwod#MGQ{Jp^-21C8zjx3wGj*z|aJ)j$EwN#4g%+EN6Bk~(Bi2XLi}%y_^X7af zrfD#N7nk}UIfH34{5Fp*H~glZh1(fqOWoBQ^E=6A-&<%O+S)0-wd2+{FfG!@@*Brd zt0BRQ^WHP{gXH_n&F^^?>c@_uU%vLRT8|LMH&bv};=?Z(R3Yuh9dv!)7hopVHL|Q5 z>KjsG?TH0=sx?b%32QgLDjZjRZFb~dF6&&j+B3?_1f7-bE?ygoDzC0|&9jI5V{D-g zilvxwi>gle&q^rPSA2&GB-3cxruA-C!yYuX zyZ0C_*n3EtezOEIH2u%v?|2dd)Rr5Ulb2S`YnFkL%(9t!q?g3H|6YZVd{`7eA8{LY z6D;0{=sJY;4&z{J)8VepQQOGw^1=^E?$JpqI~-x97cl~ZlSJ!uvV7>Gsw7=(OWpTB z94Ia>WRbzb#6XP7b<{j6GW`A*JM`vtE6LFnd)dVu9PEg?-x`a%$8TBkT@QjM^(|5$ z&P@ozQpx(gnwp=-lD+6ww8Wns-Xhr$Y{wj$(CDRG`ZaSaVhO1^9HFVjOM6WOXU^${mff6|x{hy+ zx3lO45ued_+OS)>x%R$2igC0Pxz{KO)R)A$nPtCPD`@ydq9w?3(^2X3$YPbP@iSBF zpAElA>FoYvU!sy;O^RlPtWykw3vE^l*iFmKJ4iQs=~}o*N9Um&8i@>l!|Iwk#x|NZ zLt^|+0LMlDy|Z*b@og^OeSQPe=jnsKu#=InwqsClbC@EW`xt+wSXkAMN8psvi21UL z0-cOW7k&S)QD`H5gobPl!JBX2K^~_&TVun2F>S#}pwh8-eyaFr10m(w|4AE4{xShn z=$C(<|IbOj=EWov2#AX#nonjUOVysyA^BIO9Cw3QS92p! zZ~jm{!>N#Y@Gj`g+MBVrW9krfs;5L_o3tlX-aozx8A_eKca?=2=MJAdu(FL9!7hrBZYS&?e!sE;u1NsNjo6tY5#kq$SR#EyL1Xtgw+AlPUdZ$%F{rH(K1nuk zd{tTe+W$7Yq>xrt_5~3-+<~t$1pWntxlDro$5~==Kg^bcA|h77uyh+?!~&Aiz4nDV>i49?u%4VC-UE19uNgA@+=IUG9RA@@Yt{6BEVT-`Z{%Z>YmIG9Nr4+h2+ai z_v|HvK?FAV>FMd^8fU9Iz>G5XaDo(Pm-Qz~0ofpP4A+1C`ZXpoXAr2j#%ERm*!v1} zlu2AR^0Blj?jxp+eD-TC3)jco;beR(V*{+1Kum9odZxIsZ4AQZP|_D)plGp!p8zXJ z%V5t!x+*OtbIp zNHG5Fa#4brw2ZrTUdd+Xs86ni32G3HE2Fss+6fQ~kv4Zs`uuM#fbQH%q+!ojrYB{8 zAD2<%fH<&?uON9~n@haFG~YZ3152X2UfC=7`9)SN2mmEcMJb^=AwH42W&46}vm)B? z7tw`4>&VjU@kWW3JC8QJ?`5(GW`^3k-B}5yNPA2MVKM}xBzRFl@miZVCZj8V<(+=7 zz=RrwlUp?VeLgqi?Y|k2I0fSDSDZTmh`oLZ64MzfzLH!NMgUgf8J)o}+j40!U1#4^ zz#eu--*v6g+?JBgJD-kQ3E_-`;;OSk37|byQXf#xOV<4PdJkRnRE%$95@YaHW}uv! z&%7RV$vEcke&;uCQdi)!yKzSuy>Mwe%D^)zy()!;{NjTY=5EPZ8HkW#~q* z%O8n?YIihz!zo282`HNhrcv?Yg0AXmNj>h}@I?E1+0wLegNQrzQ-aADzWxbmU&Xjo z+&@C1O(yk<-{9hIOf|nYyL^Uuw6$dcA}D9@PCLW1;AQhIWEtkX84x3m81aKm=<4Z7 zO_cL`X6z}5Sb(<3eF_f=P%Rxs=uOrxO}8BRz<0nYjNVlW2COp2ZT0LF!`_(1ogJAJ z0T0q`TRXdWUI%*6I6GWc!0w}AKo+QwAQDVQC@aB0{Us%35RfasIjaB^@ex=n@of5? zCE!PHXlU>(Twbc2)YAV|X4n8&1H_6FPBMZJ^J0LTvnizU+5(QXEy4^5kOc(=g(#%K z-8aCT7|oWwncKZE$0a1}`h?FwDrjY8#ea1;8omISM;VD35MzJEXHZ6b48nl`Hl|(` z?Qwi<1ZgtMv<%FV3wHFejDU&K1D_-U%m7wNv}aF1Snj~SGf}K9>*?8~Z)sj`w<=@R zeB}ttIw`jpMO$6 zHvn4bO;RU2JG+8k@Qx4$9|r>U1_E?PGrRMb$!z+lE-pY}nX#O*;Ltovm^JGjQczGJ;CrrJ>wiXck9{a=H#Rm|PI*C{ z+nHcof_*{6VGU!_H;)u5XACSl;no#3P+^=)Ijs!X3T@O_rg=ZhdgAFnas1pb!fvW5 zz8=jUXAcvp_(R|KUKj}EPcLVx!F2|s%t22a)8cxE&_-Z!y z@FwmpJ}MBNK}pavO1^IHRK96IqGIepIx_b$5hN%eV14_y=;2{iwa|`LrZdYEuwYPg z-koh^t-iuhvwtWZ8;G0sx*G&WjR#7m-1)Jt%))e=Jx2KhUnf)3Z<`Y9lv0O8SVeGhu@9H#4wo-72FE?btk=7)uh z9$-lQ7&$P-H-wi;sj&dG&LS>N^c-tIrory&n+Z9(ArswE^JnZAf+@<@vg&z*ci(() zseaYQAPey#;S&16B(Z}+?NJ(&QpS&}6VlE)7lTLORJwk67a1arFL%B2o-Tlp7jbes zSTO;d@@Mb%<@gy4OK)BBVGvW^NklQYUy_S(2D(oOM~zHx=Zl76>%P3g85l%34HN`0drQ_aP2mL^bKLy zOyb+z@w}Nhy|~~2fTC61dY1G~`3kuHVA(|0j6sEtgX6^Om;mgRu7BX(2q%5f27bp! z0`4{6-@3Z;&Qx0?hzJ=O88yN!11O+{PT($<*Orly$%2~+VG0N(&3)*lsGtBgtjEAm zFa+BKFf03quR*p9g~fBw&)~_`pEor%At1W2(FRP5TB+V0VC^GA1!_O54Zl90m*r>&m^4|j8{bL#v#`wiuU`FyTQ z=H=w8WGOKMQp7D+>ZtAzTriV36J;dQ@l2_APAj-?CXn2#a^E|~Ag#Jq=~-xB|iz?ueUBG5sdi*+7qnh)l|Y?*djbu$09tgLR5?F!!7QU ze~cqslkch!(Irm*MftasA~hWeZ<4T3*|TX68@6kdvY9>=A9H96C_axMh17qFLpS{x z+4N|nO2p+wCm1>|WZI`Lvl8l^=%#w``q8MX`&9G=O9?=kSL(rq>etd_h#JF;Lv?VR z_Y$j~tbu==$4}01nm(k3|5_Y~Z}1^7}5XFC}%4jL-4%ZRjB|Sm0uQEt&H(Y!iyZ7cX7_uZ)WaaieO$c|2JA z9>HswcsXBTITHf}=RE+OQVz-=FAoYUD-$4?%0PD}9G6v4xDO@=C*GJZUxYyyT?1|> zxT-Ngnig(%=gytgL-9H}@ajGwB$O~Sqfb?cgA$<=fR7RgzQXI*HfGkPs@?tlnR$8Q z>owq->`M_$f#YtkDS<8|E*>!IfGEe|^)YBIka7Uz;Nz|QbyJJK2jzY#@I{SNQSKoW*`(WXQ4 z42+%EBy%^0(og_xAft6uP{3GaHMbF^_505Vpe@)=&(C?q(vq-1k=y61tzUbN^*2|< zTlDsGyTsVc7gMpaxBJPYZ&W-vXy*FnKUUy>@4)&od-5VGQFN2P)T;jQC@*R+q=x9EN7DE0KmD{qur znNz7lit5rY@Ch*-$CQxQ6{3i7GfwWKf1Y=XuRXcE=K1-!{ms1&{Mif7#+4(zo!XOR zTjMtVRhQ&J-~DuzzjK>P`{Vfvi}HlaRD0f@Yv!UbE!-<2%AQOUJ60q?C6LXuARoKG z|8?vo^2JIl>Ee!z34!0Z#Is+McxuYtj03{Xcc^YrM?Rv(T`Xlj7$@%3F(4C)!S~5b zSU4C#%BAs1@F_$uydnzPC|Yqnu1R=*XxTk``-w0e7FV|G^_uj}C-k5n-Ta?2Pu|h> zN+hdC^znuhj5P5}%wY$lVOqX_G_^mTctuQQ7lo9#S5&;RG%2~HN0}RSj|#j{Z0YW4{PNR+_&P%Sq9$*uu@DHtvUb^C&NyE_bs#9u%&> zsyHtkP7h!>@@fws>wPnLZrH{-_BQVcC1p#gX=Hr-Pef$}o-~4xOqp$@;)eZu-z6fl zL;@4+Y7p361%)q)boy`^Dd$HAKcF2+6G5}8SwyX}UeL;R1hA-8!#{CAn31jCz#VzG zyRcxh1$+)pi>c3Gg&8V0mPPoAfI!A#_>Eb`E zwLvnc*;cUEIsPQMuutChCsalTFq>57>7yp z>Fy8SQ?6(yjfCAOyCRDBp)iwB??X}5+qs(SG)tTd+S^yD9Eo5OHzE%#F{${ly5~T{ zhF$pD%RnBL{fxUJXOoQ`heq)vU2H*X_1Zw~gJB`fx1NrG9f7bImGwW~ovc(R? zpI#|@ynbPd6tG|8cJ8Y!0w0!^sgNFPL*Z@IF=9;I*=jwME}qjLrYu59OG0y%npaIe zb_;Ks0B`oz#d&rwp)g8)TGSlIXiG0iyd42276_ee$2+t4aw8ajil5$QXrJalYPj_| z^F{alRTGp#AqfLhX7=3z`g+1&Bp1irWZN+VPI{zg*mx6nI5EzDMtA=ypr3SVvF*28 z6BG=LJY-1Mq9dKyM)G0c~$P z^X;CjsJuKLB1Hhu<{EB=3O@jY{;WL!tKF;;F&KkV@f)|w$}X4XbQCnm;LyiFp!>j^ z4yzd1Cq0kdUFS{Dlrdnx+52jq`C(Fe} z=B%pAt>$1ylU!@RZpoSg1;YiG@z-f|>o zK*}ecC$j4LeBU16+qJhqjez188pJP9RUp6uKnhygt5QJ}A>kB)J5%0j@R9IyaIgqS z(C|ns7Lji~KiVYoA{>xd;Gj_Z9O}@5X&`j3jPlpB;+dwiCkE$21>BxK1S?ImQ<>D# z2hqr+)AG5Vv{qtIJE_|Ahlo$?cqb)i^7msrgjvIx!M`E4Li;g|M)*5k)6RFCIR)h$ z%-3>M%j1tWmggO5j=1T53YmKrvL6b6PwM%BoX1?tgyWNtW}O_C*`7h%_1N79w0jQZ zWqrI^!j+aT=r=||F|gvdXQ6x6;0w{$YFp=Sa&vE7#!8)qO~XH7?(K<*9)Ao7OX~|S zwWUYLD(ro^*4!8vM2sDPjr@^_A&Y85EGO*i+PV`v*^>OtgD`P17%2QBkK*yZ1v8^d1j3aWi#_t>eNy?b#+Ss;Hbi zQAXrQ*p0bg99fg`V8oXZUj61)RbTM)+@L*D6VBvNRSy>(v-U*~6JL2ksd$o*d?fwj zKc|nC^)ozO|3I#fOHABzbKwPWgKdDSAZ91o$*!Ngy}gMqfO%yMT$x$V`U9&E*)mD0 zH6N(pL_P&h4#J`Y^zT60jH=^%0JL>?~u|)C1T*!>lSZhXqB+NQz=((X7flEGPS(8_TaPv4(2DcZg6|?;89Q@01=2Rf;bN2 z0kLd}cSMB1kkALmH=@^Z+buloK^RuS(LP#Jns>nmbK>LenN{?~BFe{){Sd4h1fj2@ z0;WktH!3-1jE);)!GI5pf$uC99C~sP$x!F8Ns4OX|7sZ;);0SGa8E$p*9!wy7oc>& zkSFphZYr+N-3L!gU|VM6K`A!}&41}`%N>pwd-F!0{DBZyC7E7=sb z9K7`#ici}OXi66fR;+q_Z{QygpB;chF@R{2VK%gF5)mAn29(4C^~z6h7U6}d8_=ko z32NIL1e*wcE6}vA=#_B81+Sv*5e*Y_Ub5<&a{|e<{ZAp~pVhjCsNd1YN0wpCL^x4W z>y;k6B`;vaI}yZR=F?NHDpJIIkS?_ls$TTJG9p#()bFT$L5uwM(Sh-EI$9_Bx_ci5 zt$ovErtaa%1h{gg?T!R6{_Nms#FozHQ!?`oEPnsQWf4DB_}9VL)?RIw2=g|MoE>rp z5tXxrwp+EUkb9@peGusL@c6~7UD!K7f_exTLN4d+~n(^0L_*^{)R&gzn8Gi zYS-%L(VAIBP5Ax>;|D#x!ecu3?GLzGHIC}>R6};YlPejRIGRWLjT0AZTI6*RarwjU zFBX}qyX(Gv(jUOa296H1f=m|^U9!k{G|etdlducwQ5;I?1KjJd(qk9Yi(@ZJ426>8 zl#flDC_{|&f&4X3I^(grx0a$Ot4VNNAGI;ydQ7?@sq((*zAnD<+Jp5RtxKD zrCT1zkwm5!iJdy@w%qALoX1!!dB;Br&8+Y}Ah{1?+s|Z5%#uDyBiN>=Aj_sSE}-sp zm`Q>-^yi34J-p261#d3P6(416Osf>CcffBc?uFe`XuL9DdYJgaE&$k89DuLQ7C=0B z&^9IddCV>@E($^YmSpQ}ZVpqJm13_e2AIv+vVTS*<}~?zh%z=f7y&A4CAS-lC!Cy7 zP;-5R;cO|)AM2cUWq>!Os~dgOI?mZ7-FR)*($y7+I3&=lLQzd+yt4P*Zm$SWcCE1bD_jplZHz8$b+?sb8T*>)9%4C%(Q0_Rr$# zssunXG-yb}vstG@{=z2V?5@}iI)3tF(;67Li;kSA8J#r?)O(9!hoNkRL9h^%VFL{0UQ z253zgCB-l{Gx!sgDd6g4Oxh7QB4a9^)2x3gwv6>;MC%s$a^_`P(!C_9{4-4mWB81vI`20Q?f{jahmv=42k)F;okN2kl>1j zn04w5GVzh;+}!UvIjgAsR6Z}oW%iqdj%k{34=9;!2R^5>Y4#=vBABFl;3>}e=}xoj z=Ndwf)%xG~IiZK}e}QYFS4v7$M}Kc0t^ZcXub|Jz8)pEoG$2)eu}SXPvv!On7PCy% zu<1K4ySzkbSd$bpVNpUak{#_3Dbz(o zM9ir7zyoVMS=6(aJ~E;Jm956u8cXyitdEGw31BG$xrJH(G>bNi?ctP)0k$bgfKcTY zVu1kd3c^kYDOIe1l!e`am(WQLD>L{&T>u{A0-yYhPA*dg)}t-(u7ImJZS>^tM>KLB z7^NV=SZ27{%PWRivj>XjpHO&#_iz8Ef!#q#|DStnDFTPzj>V0bN@hFY3-cr|Al3np zYO`jq*>R#Z=>R5CZUks^e}7+NAeTPI<;e#~3bXn1<1QR#kvDHbMV?%_(?6U8ejS3? z_I~(h-YUXX8WkP=;!#Z{B7+Apcm;e!Vz?KC{*2$Fo;U_}6QHG_cip{@k55h>u1h5I zjS%L-f1o-?tnf9Q<{XTsE|mQGjh3L_Mda?li24BBi~B7oI6w(=hwFHAzwyqIn=bsQ zwk}V|g7(N&d1tX*AD#aSGwBbc4k<5zkkX6*hpCz7Z#txO{w}&l!Wg?dJVx4Axa2fZ zdr5D8spZC5vIu4ED8FcbJwwJdB6dah%HImZYw~SAaVdV@i2kyqh?98^MnB5ZUnz7G zpFQgU8!HZVgPV=r{8*~5PP*&Y=w$`n&m^Xqgee$|^gb=*p7m1CN! zs8?k1WhleCoDT_U6eDyJwemyXFW?UGN9%@*5*W}`o}zB26%g$YQoFams-`OMb$H;u zA>yynsO48z#6yNReK)?TditnDRCD)T`Ruegac3|ERmme&{-KC`ItILl{u4%=>})b& z4@yMCUS;i~4I&kIBnvH6x5#tMxh~QE#zz+lqvAroD7OCY?;yTHTW3XTLJUnH%| z4FmxYZm7zti;IiV1{b^R*m1KbJUmlBakYRn4h~bRb_8A(G0;ZL&mrb%1*U6|7t$L! z`2PI`kQ{oUgmVEp`p^FUCof+nL0u#vaEOSTg69I!&%FRBBb0-Wd3a9C@>(DnZx6PR zh)wwM&Rnm4#LE?vd`tJ(rSp7$n8wsx**HAGB_r!^a5+$g&wKlA;(m_`iqotO8Vm|x zwSbd@>9z|d>#x}#U_$^7v_xPE=~Sw_zzz)Y4eED>vN0%(Auhvj&n@*}*e7E?o%NO^ z0Q1qGpm#Ml*B5FfI>-mBgR4VnB8WU3#AT{ns6Jez$p`gjb z;g7SJZ-@u_3WxPPKWy&@K5pXUk`K?zx_}cVc2_hGiMWv`=oU_j#z+&7E9e%`5wP>eb$#zn53A zrl);|G+}Q1F&pQ6Sj^-tJr~uXp937zs7VP~p;K-J)GRMW14n*!)mRMjfC{CbvS^&hd(-_jKw_EMJ+{M?QySbY_E=k8gr^2$Jd7;zbV^)~WENvTwtg3+C zq#Kk~^*A(;L+Jva9>zr`x~!4 zq#oABb4g+Fy68KObl+9?2>+o-o<>^cTp^J})17?o2?5#sgmu~&ng-tzdoOe*qkCf! zqD5X9NK9pd;`_x`9Tycrss&fZ-ntTkdulGrw>x^G8O~0s1v#u+tECJE|N5}0^k=9e zHbDuTrftAC?>POp^zZQSF9LSFF{?!g)Cdev*cfHw9^P|3+3AN}6QZ9d;xc;(FYraE z%^Y(cPEK5}XQzCLjBG`OAVOT((h(s3k?G&q0hQbxrXYyf0+5gdu1}YGH`EX-L3p5P zpv8b-#DUjb4ORa%Vdq2uHDWuYQQxfTj!D8IEU9N%la8F69I;1thc+sa&nbMlH#^!)C9pa6iW|vRbfnB3jdz=IS2H~C*h75KDNEDGY0;p3xgaQoa zeTb?MD4(}+aByJfkLX1KlBNqhX4pCgAn2h8Q2-?Bo#Di(^~1kIm6aXrZ3<>}+){-f z)1Qaih(S?N&9i3X9cvj6wzf0iiLP4S2i1L4d^kPg)yzO?--3CN_rl9T)(?gjU+2#F0`#gKLx z{OOY|df{`kv7jc83%CW9wvSSZruyNElaZRuhW7kN)A|#G4FRyhD|kqr7`|Ge)lHlU z&FpZHxn+LrAe-(`ONcil{3s(na43P{AK%iJctwx^k(xy0dLWEQ#7?<|R+3TQr-e*u zB$X1rAlV4mQMaBgp^?4vo3Oq8(9k=OB3a^oNiL!2?=X1BQkkS;o7V{Fw8or`^Cw{J0 zca0z4KvK9)dDzdwmoo3jNS*rZyvU2{@|%yBerP$#yZ5X0c%Yf})uy_YKuKKD<$BO1 zH%Ku<(m(#9zer3r=V_i?x!$@h3jh;`svG@1E!dztV1T>B0Jc8M@~BS~M@#DvFn>KefOgB#^o3F=T zFW31X;tIsxt)BdqdLO${qu#0Zz$@TI;$Vu6Oy$33o>gfopz)a(H^u;ZR$JkRv=TxN zqN9^KS_-|tie1%Dudh&l#X*-ZO;pwKXiJkd=m0srPd-GH&OOZx9C?u%snN z;QU&6E4;~yC!N2bB01E}e+znAywZX8sl)r}@;DMV=S_pSSn0#WM=g?bh5ONA?4jlF zho(ctoze6S$UAxV?!*+~j4@9r&pD6aQ1UmBX?ZMdJ%v$JTQz%_$llp>26&9yRp&ZG>2M} z!k(+lS`Ui!=bCT{*L#DsVFUgM` zS=jRV_hR9@g+||~Y~CA2@NcHuFvc(|4#_fijp*^1D2IlMbZ>n%!Q%VaczdZ2m5~i+ ztRX<`kyL)_r_)r=JqCk6&yUVF?6VKfesvAwc!XqL(5&&T>V$B1)xVK zj9$l}qN7{;boeSR?lK-x!DvRs$HE1lo?!vZ}<5rhidAau$5l+#H!)gGI$jNTYePt!el;=7h!W z!RJghlQ7quA>9yK$zU7Tm+CX6=+;~kE@ol(PMRcX$mGZ!%%e$<%5M3^dM!`MFb4jP z333gKd~~7Yv1LYUqC6xj?AfMvJkvxVtE!!nnqWf zxx|KB({Hci#PXjjxrM);%jH@u1+AD$BKPc%9~nk@^zGl`;8&;yw^Y+ck3xb5zsLFE zB8bbDV2j>rRm}m6&sZr;;D7?i!U`DUEqDZ80(jf!d&8>>5DnJ6Q2009#*8do2rX95 z(&J`Tvb|-xlzpx@NIN)lSrXiiWppTu3lEppPE>gpmi8QbE*~3o;xYPOdzzx;tBYt> zBP&>9rZ#l5ZxT&RHK(PhW<9@CFdh=S;kK&3ZZENn9Ks#7lFR0Hhstg#TG$N>Jw?ah z_4BsuCh2dH4G$LABgT4L1n)1d@2+5{0-{KNm$rLW5j)d1gG@9tp2l4y@`W;d6Kxzw zJ)crHt_C>9kSj`C*h zX~`$hm_gL}H0GurNxm|IWusGNDlju$eHEFFUw`GU8nhLUlqj5@OaS$ChliRP|Y@(gNvgV%S=t-88u zZKTdf3Z;dQ{b`I81x?*s&e`g_)O7c@I=X!fDxRW5<~ramqloG}G$HRU&=1#-iMznl z9iCE|-909jj_7Ln6UN^-kvm_9ZV~WgrFe?FDTrVg18?dfwK~;Uciii4$vUj7TQ8>oevk%-d-7h)iPF*%Ce&6=AV_?7qIFT z*XlPIURhE;eC(_BX)k9ZE!HafXC*3G3&X3sr&u0B{w=Z5dY6Zxb;fcWCS&(&SKpW5 zr#W1NbZb_>6PW-_6;42dA1a}^=!>B)tZH^3oDGW{`=3@{4dZv12wY;2B}?$nGon( z6Za>~^`TtnKN?dExAh_nVf?Awvr}ko|4+DgRuTBkAMjK-?pM>xx=ye`3vx(iSK29~ zB(B*}dG@-y@4HmkOtb6q5pUSPOX7`V;br;iSlvYRh|H@(qDN@M8xRA4W!O+hywO?u z#6*6wo|KSWK(cZ6yA}(n+PnI+a24FBni!L>Klxf&TR2`lTXw4@Y;-)F%i%N8A~Nn( ze1tiNH=>PSD2%LC{i3@5AV)=GloeIJxl$p=dX&T-<=%cNOFahCX7^cMukV7l7td+5 zHBtsS>U+FXzh^@H@9#RF>nG0KOCVHrPi0kl)&Ezer&f{$!FUj$A ze+MCgtla(}+k*C0s=>}QinE9rbH?E%aK_snHJB4mT#vHJ6n`AiV0=K>TFUhMK~Pr) zSNAN(6oG6OsQ2CBQI>ha^_CeYE6oZn_W%5`9Z%cjg|U_%2nXN9v-`tt3?*c_OYj^RdwU{+hxqE>zD1qB*QKYf;Tjo2UMySJ$=F7_oj{_?&6&TC zNmAGIjkzbsL9(rv!jwfhz~RoZo#z5HcAr~P`!NBC?lDuRW^Fh)ay&f9jOZDby&p5%+ZfWdJ>O_ap$Gv28Jc}nTH%8 z+wD|>j%K>I@pIy+M*_BY!>F0eh>SzRuQnEq{y{BemlD(nUOGfyQ$qMX%=rN$@s$0c zVFWeC=pIocuI7v?>bl)ymxzfhkym(8~L2U^s3N4Wqd>r z_wkmqIH&jA5IoUf z+O(|HHI)mc|v()nQRuUMQI7!s+9ae3o(% zkXOWkFT-gHn}E z3cFH^y~yKJLeVWg!9}Ns4Mwvi4hZuWQ!|q5x05M=?2@ zLgjkf7o^9Aim6wshm92Q8ZYkUnn30#xw&LKUYPF~arvZpmk-vY!x7BF6(p^>Tqz&S z^BKiw?PQa>^lcY;nmmw^xmGXP4U~XR;g8{ugEc9nbZ@|BvHQQG`m0 zvWg0clB|?1JF^fPwnSECHO zIp_3x9iGp}<9@%*>!331FYI%7JDBlFI)86>ss6@K_m354tBX%-mxzt9GmKU9~}Qf)#J2Tz{JA z)_JwK_1{M*4BYDXo)-L=E(HJ}WyoIe=dKMd#&b(Q^vdYwcCtOoGBeW1605-H~Wy29GepPk?J25L6w@EQ`1;!`0HBFpt-N{ zSJJ<{R!`gb%v$K%nvaU?k%>{3IWhbE;HQpz20$Y@#vZTke8oJ{Sfm0;5Tv#jWF@(g2ST*yd*=q{IG1rq;iQ zjhYzz5^q%U>M?v)zs=5dOSm;VeShcn{MtJ~brqeCOpLpz6n*39lik# zX0=WCwT#|T!XSWv1Ok$-*Rgow|Ne5{fnhjSuEK*yCIr%ZdpG?DVzOpe1giuY6|XNl z_B-{9edJ|ST(j9#aVFwEn+>II;{Ndv@4mhM&NrUx0FIiu=MK5wry;g@S+3bKgXg=v zL}y8vS}I4-k?baV=G%8>Ev1_}&Z={&3@yd;Ug(xY9(ItRD6bXr`>HH{N0@Y@g(k@9 zu#aMkU{jUIZ?PV+60&tE)>Atp-&0MkNV)Os=paX#S5Bi=aU6rL;X_YsZtr}tI`c6* zu7KXh#&RED3d1q}!)z~%^sN#-aK`6Yw3;`w@P}uan%KSov=)g?+TQaS#cElha|&; z(%I_Y-1Yw|tl|2;v}{M|_R2hFz6pLg@9c-F(Nk+HW>g}nq7IEux+bZ|E(-n%3iT*= z5Qtx(=QyqE@N=|CO4V@Jp2zE!K_y(p5q7$q>x$l8E@xKD1952T|Fp)-zG2zgH$0py zaH?KrEdRvD*%0@P1zn*G-z#V0-L88U4T^j}{&)KgR>@w-F~rwoNxu+@;K;Wxr*Yjn zWO-_`DIxT--Bbr>k*vr^KmZ!?Z&x+LbEz@97pH`UxekoM_qQa4n(#Z6e_sCIe$=~; zMFRd=AeJ02)Y{A9`XMByabV*5k>u`U?MsGC|9sY#ehi%n$f|04a9H={9fJ?tO&(ZY zye|!=_w33}=Ud|0o-1@@Uq`&lJF%mO?Ny&g-jKR1R7xw!nj=wtX>5fmE#bbbOn?aM z`fJu^%d|d+!Ag404-JZ?;jB|dPjg-9LVi2m64BLNxb<_*eOp;aaligfr__4i<_o@F zUb5pcn!;1h2R2qFh&gI*Kw8-2`+|m122>~SOPVwM`P{56TmH=L=jAYqa=okvvG<}<7?sQisQs`Rog|*u%VuO z#lrcj|1vY2hOjX(gu*CrWMfZiOh%Z`UH*UK@nK^3P@X3vy81bf@JP9DF4#uHvAAU6 zXCE2sQND3HUx2NDJVVNnv+z&Q1526OsE5*@8WNVMSrv2l8A#CYKC+Lb@#80&NZQ$Q zW)X!BD$cV_mQ`(5deLi8MG4=o;`q~9UoqctCtell!8vM8<0i{ztbQV#<=12=XQ^L3 z4=!)s*uAuWZa0bZQC-yod>5GcPpS7#_j~S|*|s0I8C$aN2^Q%ZS3SxdsgIe6%8?ig z%w>8i348stA^%AhMwcG;s7kXSGT3cz#)|40g&^|}5{Jzi$)wB$)On?PDT1G+rVSy> znHK4_I0?ZXk)bMqS+@EpZrnahSY}6WD%N*Yd+Jt;Y~6+s^;*SeZMU;^Kc0bPzLXW$v25Xg*wrnlp^$oI55L8On88eHi;%Vj+rs^qSo?<$bJ7*w1bM}0?krf0f{ z%$yb6f=QQ%)jhETUP)uSl1gITpL-Yi9RtKL@X~6E%JSc(rEw5t&r>Zne5JdUGBGwb z52Fve0f%ldQLXR&>Ny)>2i$!z+yR2s34S|aFNnpEBRWaW)2GWU4LMJpf?=9Wziq>5 z?2s372bL|2fsiE9U|FH1+66~}*Zw+rCapkbq1}8q7JjY8VZbIMEQHuJ!LJbD-Dd2L z6)h~%63Pmv?@Vv0*IZLqufo#rXLlA0JPiBYw>BbNN0Z++Yuw%FHg-ARSW&o`l~Hzr zFfe>?GinB7LhKiU35DS-tApsjjRRKMHX z-`^PcY^&Kkhv};XsvodY!U7t5mUy6#TMblIKY~{%3F16Pu*8!!2GY4=xs(e9fIv6!+yLPc^G*f9aC{-6BiX@*aGV`VyL)jPYf*_*t-Ni6<=%1*$r zknli*xdf@I`vQP9HQ4El+bV<7(ePeLsec}fgm!GtR~Iz~t=Fyu-YUB#Q7|#>EGBL8 z#gJ*~Mc@5MJJM3py>3*UYZz%)-^%xDU^_<6^@}Ns^)KzIz5B|7jfGWTd8Q0iZ>$s^ zJAN#}kx|iin)ltgxT7=1Q$sQ3-^XU>$hw89pByNVP>u38dn1`_eCx2@qmd7z2G6PX zF+NlJHeEho@MMn4?nj&N8x9VIGFFJxdPqMt`BHo7dD8OGM=|kdQxSA*S2CY{e?C=~ zu}ie}{op-lS zpysT8ccif0+m*HPW5th7=MDwt=kvoy(-ByI*u~yoy;4Eo$@haHy1CNqJ~B48Go>^R zf(Su)=B;_A&5F!5@T2<(Uqnm%v^-=cq~R~#ZAdSc_j30u1Mgv&Gr zjxx_)jw8;%Kh2pil5~%NKL_3&@S&psu}QUaEhjC5dJAIhC$>P*D&ks! zf<*WN%gf2ZVD!?qo7kQcP*bo)XMsw|h5;B}4l@B@ynVaBTY1wu&#d)W%|s?CSb~4X z8uXh8sPe84FT%rfZhxU5tdIb`{FQQXW^!`!y#Nq9#N{F^!w5xi8k+DT0EDhY%-$_? z3YWZs2X46hdw_tLdwK10TNyh98gdoDKm(hupd37^pHraFH-m7-f&;w$lDoMSjY^i($WOYiarB*)^(c0O?4 z$LBy3FX0?+=z4Tl4^SQq}(<9mf+8g$kkGU(>+kPziePlba!)nx$M5#~gijUqm5*B@D=RAmWD*Y^=t#ZZt%caFxnFtBZADpJwDW+%5)RxK z_6)<%^$gad1fg;p!9s<1yrPN82~bZ93~pTCir~BS=?uYFa|Ex4k%GV>a~BOdodV+= zwTr>jpU!`dX&HzQTe>yZ`u_Lg5l0AooE-RE0dir6eno8KFRj?-Kf`FMMYzs{RdQE zUq{>^(l`O&y7kf>$&ldkwbmdw5VOjxT~cLm4kQdkVMin^aB>^of>)!7-HHUe$35LV zBqW>fA4v{!Yk!2r?yyku$U<+fvErcXvOXwvp8$Xr<2Rzz~n|-X>4y=Xe?4_oD$9=r20)Z$mC8oNozRbIU@xjL8JfAta#{-!=A}uo) zaoV!^S;imoD%yfj(E93w*j7AeorDRanqzIDq|v4hxBlPALT#Z zbb=oe`OZvL`FU4TT5Qsj(@thaJT9wEP1K7%Yx$kYWA5&o`UI`>9Z8v@?XPxaztV2e0H`WjoVkCAp(WVu$o?@59~~C$IU1o@zZ@{mAs9hx<9d zAJJz1Q%fJxMe=LQ>356encN`DyP>0~Gj78zp;mC?r`2hXn;@PLaHjQ*S$9)x4vCuB z|A=5)$>Ot&bng+LU9MS_1mPZ#)`<2T$O3#H9=7@B#y{XRok1AJYiC^hy!nMi1rc~# zTwEqaGP-OdBcqlyEn~-ERB>(X?R8(j?)VNQmx`DNYP!I%5Mc5mNo4NlV z%rPJBrAwD~9gPyTj*wK2P3~RIYWQbyJ@!>s1U4N!f!k5QsE03l~BmZTA}P~s`s zSyW0!jvU?LN}yMx(i#CYusm@M9Gn28SrMk|V4y)H!m-e&)6sO zxdx@1n_IqF>l38)?e&JG!0SUT-~<7r0>){|z8n}&Hnyi=!_?#;a{K|bY5)HHPrlri z*d<|Wo7?WdTlP0Z<8w0$p>MEj*RGr3gsapXI;{)WIkvqHsy@Gaa@ZhI(OKyE?AbnC z&_9tG0q5^@wcwgYG!_vFPT+38XAj`SQ&q}>13%;{a>wse}@{Os}h@NqUp%Amqy zbW1$a#s+IUxyrZKD`x8K4K520DbX+u@!_+q8ln2aHKO)U@M_X9&)#7s4?q=_2K=*~ z5*D3yl!}IXMeX1|%o;pD-MQj_`^_1qb(hY!5^jh2wxYO{uknnWm^v>@=_?`j$A9Yc z$m6bg6{FtG`n@aFu0isBybnu%{}bQ|=e^_>OZ7oTKR?)@66iZP{k?@455dUbZ{rP4 zuT~X{MZZVH4g}rn6I)-EdHj&O-(5Y0Q#+EdQPnpMd|$V@&v>OQZ?L9oS0a^Q3W=-5+~T> z>jlsGK4-HGYv~=bM@jW6X!({8Xp2Ty^%>h7{+y?Vae{koif2;T{4H7#5A+oKkU%Ef0@}N%wBW9)0kZ) z6v72`INwa{dxYx}z4VU!l_yu|gU4oZt&cv|iar6HX7AuZZgB&$hc7Nq8of@!#%5+D^pRc8R5rV1{nm| zO3r}h@Xw`H#Pj8FQVTLC5U2iOAqb8l@$pJL0qJe3MSU}(YrpcK@vs0g4tPc@?IY9E zipb=}3r!aY5H+Z>M$l$UOipI=xY?6^ix7SRxea(^Lfedlk=f|e8NJ_}9v{#6iRj@p=E<+eX;c z{@7Uj^JfHM%>#wy=a;n3L1Ez$HDOCF;?d|!YHj{vI-RX7&h|a?(sCvoI zVEhlskgIEFuCdnoqt`Jz_aNp6da==`TU3?F6Vr@}cf3!mbITSuPYEhrkq+3n=Ka=G zDxjP~F6GyqpUa#(UU?}h6ld8k1}9`adS)0i`Qz1vn##1O5&k0>)Ov-Tj^~NgK9%~{ zF-VJ<%I?S+_3E&8<|p2fxe@D}!RO9Zex?sk=wowOaA%2aH>>qYF}y`%PD6LWUnxv; z#p8ppUhB5ET?!QIPsNU&qf6+DefsQp%2-WP)(+`G^#c>peUHDfogC<=;A=n1!?-eD zdHL?~MD=jB@C;9UX4|ye32GTJLBSrYZ~wee)5_WX>cUCeGe0M5<&V5OLr~ywWS{xa z#YQ((r7L*bcT7MJ4ESiB68qEuFaPh~V9#0!U51~7WfyRTOfVS=X~1gej)`C=icVWpvH0%K z6B5|KYUC1-G(=z7k)}lnGm)8o)ag=~Sq8DoH>+JyRBX4guL)pkfGwHh+Dwt1rAC&) zMYpYu2f*0|f@@N!j32R!f}+jQcz0vut5;LPJu8(h&Ln4m1&cn5JxJ8_A^}vw#}c;v zu?ySNo1i-3k}5e5wzr3(>fg`EsDuNIiip;HmK4t5iTCD5WYAQ6!vhPrTT;3~T~2cA z*U;wqOVf6l7c*97+0%SZ+XvLu1=q^&Q~36dRHUXjuGnTeL7CXYt2k$!)9d9@ih#_O{p_#7JTyY&~<%>%>!T2A;Y) zI_3M%?04Ul*5@m7W>mYz@HmOhR&uj&Rn&XG7P<0YEzPkbM>{RZx#T(9$rLvJcJTlB zcSci%S#{Gv#&|F-f4|NZFNpvOi2ybAz9e<6pi%3Dv;oRSt)w53nk6TCu1W;#CjOy*?zds7R9n~ZS zIz~oD4*e2-Vkl@eP(s+f>J{7W#7#iSJvTQe9>R{}BpG@_nH~~*_Xd&ElQW>AEjHRFG#=-RCA zCRnIhA37#$LXr68scgot=xfQOtS4oTeWRPZbR%@0V}m)7Lb+9MVDq0|;l-(!eNvjW z3ABNX&UWNKKCj7jjm20g8QLZ0#wchGlpmBehz)j}zUJk-Q-{<@Rle5!xA^_#>RLnA zsqDPH4`_a+i<=x4b$KLWbf~)V?J*sZV`BfdYkhM{X;g^YE%r1`Qp3+;S<=;Nr){LO zg2SWS#g_HuQ)w1=DGRSYxvlK&lR`A?1aTgk1P3TP2O^?Z7AB@USYH@NLMKk6+{H2a zK9u3KwY9U28xCeX5#m_WMBksdv4(msMUxBn;=c#^!10Hal)id*PK#OZ@sI6xlv)tX z-&}EZk4XfiyZ^(3E2mZ?I*Z=iK8my#rKNyrs%n+{%s(puDWVDoLq zs~yMGv-jG4Q>nRMW&w-@BePo>x>E_zex=}{5%$4cxyNfRECCzgtiO>oPUB%nId=V5vo>jLRCQ8 zb?C|mBeE2dZ@n8^Y_fhI(b^IIk*BU@-rHPvBq|91!{_fgIE-Z!4aVcsL8@|V6jKml z9*r%+jMSqMkEc(!qd{Qj;E+rHb>mh}b~fD2=W&e{CgSvYof(;!pd}zkjHV$rhu?V* zCCJrx+7ih64qiuCjV-zQXynAY zb?Nw^V-oi(Z>pCZ#tjsVYRv|U1GN-wc7Z7kG+Q$2kxx?TLc&L0Hki-X=8}D-`1f5h zlq6&%Me#*aWj%@VKQ{m1U;7;L)LBh0HSx6=9Si4rpAb+_pL(12M@mEO`0 zRGIbUI98qb%v2gZzw&EZ4OD(*jL(!x&AoY-W}c>1uQ#Q~AdSK{N6BwnZ4-`t z4v)Er0PzK4O_==Nn;pDp(eeYZ83xnUJ2+JVp!xE3 z>%KyNTxC9+5orv(Qm$qkCYG3ZD7;R(WQ4}Y$ty7~<23sS;w>?5$N&H3MQ4FGyRMsf zFBucUPG}AHd_*9|^7JT5oy50q)j!<7OYp>S%WN6h#AbJXgiJ7mqcT*6=0|JJK^Wlo zU|G6D6`rO}h1*5s^pm2Zj3-Z?gp$qM0fmrg$l(xT!@)~Dwc(h!;E$`JyDSYNv03|D zD*O`EJdY4NCPmkSjU!REFz(D>Ef@5<-7mWyZzY6%6GitLrRqGU{zA_0;TNljRV7iY_wl&PNz^jT>4{qBCtNloX<|F zIAYZ5RIsxlM97|@_9sB-#^|#I#iPB1S%HaU%g?4T^xhH-~X9OSr)9yI>IAv;&^uokJ zIkyxek{+!m9`yXCSL2-Sz6rT?>-IB*-!vDgj$rqA?-DvVPHWUu_si>;^IQ#;BTtz z;!@f(8siV~$QzYl| zvtdBtN>|2TGh$b+%E0>#{PmXxyX&!|6b-#Ysc;a#ir85qxV1pO;pyFtbt1%z{ppu1 z#9PE7@LP$)saXMs9ki#|r)}j5o0`8`36G1s%1)5G&%?@aG+c{0u|e&cs+Lu1w*jrq zKH(8wKZ7GFyTXmxrJQaD++BD&J}AsSO`&l2PtSCU`}KhN*OZG({uekV0vY)s$j zME-RqXg2z?){OFxMDki1>-e;ucjBWBIo+-Om%P*N)3DoJ;Jg3#(UFPploEfs#KgxA zoJyD0eZPJW46UJLsFm?9UTt-Fx6Tv~rbRo*9l5o+OpHk5XHN)!5nFoRhqAMLyFeB zbV66&GhcX<_G4#iPHo-UlGNEpM!FQj=gByor3cd9#gk=q6$y*UEuF8dr59FL+Z43m zqyDNKev7s}%**Yz=u|^rke&!<+HCdPTAQuMj+|%m#LIJUe492%y09&HfKTsJ#9_Q9ZuZ%p-Jj|IIUt1K++C_sD$ONm{ue z6V|zR*N)>Pm>eAAOy_)fer{>W1lTKr2To{pAwZK+Gs*#2n17e~l8YR)Z&LJ%a|f6F zO0~BJXnyr=Toi0SrE!L{D=L^}lX6OlUGe&lm+Ywg!)fJd-bA^XcttTjjymY6xf*;z zTEXv_kf~){x4@8Sro4dA!O9+F|Y4_ls2!enkr7#)>irXK*;{O()T2(mP}t9i&D4E5Br=hG(P%AV9IyM z;!c`rT-v~qMX|LYORhY(PbQm!w)`yaLQ&pB1_xAD6`$Yh*hzeOJcB2UKm&1LzD=MB z(B1sOQuj_Xv7aXtsL*V+V3&L>UtJ=g;^{jTX|3w*jZei`lJicAZEJ{Py0Nm!l9I;3 zKx#WBCG(M+ZQoZ4jvr)!^?4Up-3{%IQ*C;Cl|=1N;P{Yy{7K~EXq$cY!Jz&6b8VK} zY@f&6i`Gn~v;C@^>sv>1^A=62O<;h0U_-r54_opVPM^7*(potTw(ECg_B2hN=NL6my2pt?)FNZ$&*o06z9v~@i-b;;{IA!^0}4w z8DU}4x}H74^E@H08J4be&;F5~Du24a%>7t=Cf6uQZLQ6NF!J4jL0_4I+L=4Gn+vZn ziRbcMd!JHrm+nl_Ww$xP&4}Z^_KYEw7cZ=VQud-HiHd{08nC(Av z)#Qtp%q%j#3SLyZtV%7O%bjk-c7RlCrMh;0xYm1FP3v8jbd7|3<&}V)wC8?QxQlb$ zxW>sD@xo8DQp4s4Th`ICP3nG;=FW5EI-;f`1cns51~d#5Y-|aodc#bA~_8GyK4Phpzggmq@W|Pp}_OX zIXRe%zf1T;@%BJ8hjnJ|Ni-@u04rsnmz)u0)EtKOPx~7Tv!+}e)%c1)X$FZbGp#P zNOi^5=Jk#fyAy-8UMNr0V^(p(kvtViwUHLL^?q_UX09^g;^L2&2YdSZ+F>Xk z6&+34apS)kc(15z2s^6vIjAW?SK}sXIn5%kH||+h`Als(=3(pGZ1)E{&s~sZmcoSI z+(ynMDTrmzDc}0TUbBwCXlwuHzgq3TWXhe%5fAwAOf%=(=;+j*S|v#?EoR}1D|H4} z&bpnTpT01)P3ZlxA>jf{my4`0r>bg6^yUUW-~DlrKI2s_9)avz&4W-C*qeaATl>HSh> zp=_v1%7A(mBku$6EB=#SiF{KV33!Pou{>(E6UZCwa2L`_sX|Zr-e#V?jdrnp?B!l| z+Upx4W9BwDO_IIR=(^P0`Ql@=5=5u%NA%g#ZG#RcNH^yNAS!?m#Fp z^D@-n&)E{aWMV=-6;`v@5c_$RL8h~zODcLjNQRv4J#%n<>q{T@MKSS4;amLEqlVe; z5L?6$=LF`0>p(jbs651rS1|3M>lKig|9n$=V^}Jw@H_OkDfvGFZL--PcILme0x3y{ zbxV--dJ!M5$g5ca>)Rb2&pc$Cldxm|JJsonaY1~s-QiSqjk0po1MJ~8!wHjbyb{d~ zA0Pfg;uf~blZ@g|PyVi8t~j5wXiU`KZFT(;@}13cJfHtDD5MrJqFP~Pc+GZMkF-Xl zUP9xJxO#n|Irrik#PtJ8{KCro^hkofS_(yc_j`7i{rsg0{}1LoYFm0jj(^yRJB~RU zq=r~9X~l-O0qQN3#u0D+9a~DF{7W9SEOE}ne-CnNGXwjBV8t)K!UMs(OOI8AI}Ye!@=EU?YH zGnokFDWPAt{HPI$FU^FB|~m&{!cHvjj{CoCf+Ub05N(1TAae1rm~-! zFGiyGf@tt+rsLnC%lj6?E=$@n)ZH|S&zrrj;;pIvCog_@r-R=@PXbSPT3S@Il7vct z8uj7GNA;w_Z1Ty~B`RX+wzcw($rYwX2L^+ieMKCm2+c!aWtGvErCiHmwkUXoDf(IW z&GiY9!Z&6d2LqfS=zz~nbZXqX`CfnwU(Ra85E+P18jNL3fDT84_ZVYEK;)k4X1JLY z0!#ql2QwiViYb5WHwQ&7G}`#0G7}D%dy20}{8?N~Lb`wGYl~sl4@*>E@KFYKLtfXeqtS)Fl9VUXl@yyAAn8Hg!B5Z^`$;*XmMv_ z*Dsl+<2DiT>Xi>9@nNO)5er4!^S#W>n_pLtj39RqAXpFxetuIDiY1`N_>lW(X%p<$ zab@r`4mi`bL*JroR(h%X$xg~`W_^VjOE1&YPeC8n2-9L=13P?3Y(^EWm3OS-2k4uY z!A7bXimT0-;W+fBc3K=3wDQZ!vJU7Lc9=RqAU`404<$MRcwzfzRI>rdM?7)=h=@a& z`9f9PQzKdBVt4VFtlM~0nKFj5MACyWQfs8fQnS%gH}vZA@A6=bjYS*Ez>g3)$Lbyg zVxiSSY;@sM7jH;d3=t;`6$#-90?`K*XC4@bbzY>&^g1m=DFD)E&60o{pRzIni1 zpi_y1S@~pVK1}uijpHMrum)xIxT!JTOaLHlZ+tCi3b8f4_xFY)_3{#F>FB~(WJDXVKuVTno$tSX zeFcoPfLB9913xcr!x{b(y@gopPXO5k@1aB34Z-24F6N^1-%N%B2RtxkK`YH`%XRz( z;=L^-Bmp<6#ef0{5!HhnfN1B|eb}mR{W2*^l5dG3DZpxmzu^ zS&@R)%y=MGIFHDve+X=2wxh{OF*~b{lzpBwEGLA7USb9sM$D-v8;o2}|{; zwwogeNdM+;5o>8IfD!v#oSZ4)`$z#!5MCWA_BN}_vw_?VSUi;n&F|!;etO%M_sQY& z7TLExk!`~)Il}NYUhFyhpQ?WTEUzuCtf)DHznDJO&8WtVA727sm3gI1oe&TTSg{Jg zs!=0Oa%b5BGy;skEg)dXkD%G-v6J#3fj8cU

5#ZxO_|xd0wr3iKC(=P1m4vfbt6 zc42UjWwd)^Z@ z$}z01t4mDnXNC|(379N4XDuHdSZ~8_f!HkJWJFzbeqqwc9I*|kUKY%V2)N<4_r=Ax z3=O4VPr8eW>PuziV;luS2o4B`d_x3;$H;9XlPf5s4vD*sTi&`Aw^7=eqV^P57U`n~ zq?m%Wqj~NpVvUBYo)KOyxCwUxGqiJ_VAN!bts!CSRd&gF=@+rUv!5MgdW2O1ErJvd zzdf_A);sO9-8OlIzQFt6SW({st|6tSQwo0780!h=t$Be|J?| zXd1_XDETxqbhWJ4xsQCRs``_!!OB4mFlX-njI04XXuA-bv}X{jIL)-uQQN}8Lc(?f zzhUxX5BJ{O6P_uB9#My%8X1vsY5e$1E*C!Z&_#qz=UboE5(nMjXLYsP?o)B?k4u>x z>Pkv;SvH0nEo=t_#)~F`tz9~v10Az$QYzZG~o@%_~uiKiWcnA=tm=#w;H=FzS zt1Bx>;8I919T8JfP?tVt?%Cxd_~lr942z|uWp+Vx%aqh0PIjJJ{mNK&(r zA5IV9EgS7glEsHT>NX(}k*R1!W#!{sTt2uMW`XCe`d`8*;O^=Q&>o()Tg9q_Yfsfo15c>rIh#(!j1i1N}f69?pH*hu~9@{PQ zO=n3l#6uy8%YLgAAI%t0bD1TYJs-hzQNaKLK6t#geh=vc!H0c>f&zR8jkwHF;Iy!) zvJcovMy6ij=8B!dX9Tfg_pN#6_W+c`GJvOWnEqpZJ%y>M>0SP#VCMk#JC7n|0agY3 z>FHD22YY&6rlfEHdtOjOz{`Q&dW>DGtgNhD$YE3py|NP$cm`Ub>DIW?%>gf56Q3=3AW}NdbRnqHtv0tEJk8Pd};@rScyk+Wa?zoM#zM6}m*5(Uic17pAhlD4Y zL%xjq(br*En(!!fR+jAd=;?Y+iiP`Ht!WVxDYa2cCq-@nYT_49#nRcLVV$A1=@(jicO zC?VRhal~d&8?@`u#>-SX{khv&NY&$@!2~E8Sz{IRf@}FIniL=)Jq=o@?{F` zxAW)Ef2pc^f^D?RVuk(WemHd)6OK(-IU|uuN-`&NI{fMhMx>d;JRj-53Rl}4I069y z0kWFr+W^Yp!f!-g{8d-gvW~Dh*_H9S>#Ay@0kGbPIvPJWk3S|v|4}Vx12rBmaQs@; zr%x-m*Qi~okb0ZW=KU~#@4TF;V!^{U%LD=x&DvuES* zUV$tio;Q}VgsBaxm(JfwO-_M6+pjbUpG3sy_Kl%*5Rk9h+S)hjNeWIA1ZV)| zuiY;}n0_Fo5Y3m6^AdM1w+1S&!$2)&IleC`8JU^!&&jbV?<&K>vm;gG^2v=21aBfK zIj_|SZkU^!6JF3rKGE+!0AbMrrQ`{oRrbB2)-b&iN2O&)Wnu{v0UTxG%3NB=0S)mV zs@7E90xn=%Ob~hr!&xW>s@4odJ1(rh{9AXDu#{vJ+oY`XmiMuXIZP>AUCWSq^Y+_> ze>^8IKanZ?7?dMZX_0z|?|{vFOE8&iCfkl9SrCHLl;tZiPd^lmY|KlF6j*qmuwbik zZ<>l_u|1c-M_4n9h`hox$}y_j@AUU)T&TAWBLN?|mZ^92#EIvqCN;q0i5HNcW8|=j z8%01)5zSp5uug=-2&yEH&`?^!W(5Eb z*pKa%4|xewmq!9@N0a|@%7?U=@ymr6WxH!?9x!c=F$Vjdpv1hpUKD^=X#=h^ZUK~p z55cV>9_Jf}0Ie~B!3+rR{`bZ!o6rJ=#I|VPGiUyQxE13GxV_-A$V}%SEOeJAju^2n zL9qbhfM>kGWlxj=x%Y-8XFdfY*iJZ|3uECwOhx=^kb)r~qU*dq~LwChtNvBsOkgWY}8)@|^<#H<>Y5j#4*Uh)1hM1PP!dS`Z|-uD+{9 z#F0aMap=Vty)qHEN_29_Z%Afcb?UXvB@aO5fdbPD=naRwdQ}fTWPH&fF~oFKVYjWU zG6y_a4j!z=XGOT-B!=^YdbyVCNH;MSR6Cv=B3fl3n~@z@I19&AI4|?y$(o=-Tk#1x z*+5$lY zx0&ubjc9cb->FSAT_$yl27fV{kOU0u!MlLo90sl$XW57gD|2A~08v{XPUfZ-hh za|0$mX(`Iilju8J)KW7sAwV}k6!!mX8cz&25E0G_3Yr38#dzVq(vh;d-v>;OHsp4- z@^=u=i7*o(*-p0YJDfZ4j}lEWk(dZ(Fz(MSz|@iIbHQrG5Rqt;LfFTI-N4o&Y{+6= z{_IXt2zTO{HZ^n8@$mFK2iGg0jS@oxbb1g=0q@Hp7D>$!MRMW$igR-EZr-z;(9n3l%^wrQPcH0msvK~5tScu%0eMR9IC#N?mGL@v` zw4pDh?W-WcH^ z+8WP&uk7_9Upd~u2_BRos{W35IvOWShy#a($Dpz@)Tgih?K?DpR&WuE+D+oSRpz9wQ$$h8Flng5d@84?$P`gqB)|z6 zXq3W1Sq@pq!&zlZhtXs~E@OCXEa~K%$Rt;Y2V6mZl)xDzAt52|lz0=vHAy8Ek;A-A z|HT5R?pICkB%sgoA(*b6Mw7yL$wm9*k^cZzQBjgr*zxWMo=;R%)WGY}uWV4{ivy4D zE#0N?m3a}AdvC(lEOJHL|E%JHcfc*m76H)g1k|{#QZ- zqon*S3OX(Vot}%tL7H*R>W6@sYFPhiZf@I*#dYr0Ll019N&+_)vr=;CAea~giU0oW zpfYx~s8-!A)MB`kYX=6RVG)=O?lguwBr;EjaRM#-3Y919{#BB*m=)doTvIbTHYS6a z9N`9z>CYZUp}Fuwes$`C_}nEipAO?RVM6|`!o8xYL?I;6b&DYJ6Fuds@kS<^9fE>^ z<%<*$QwB#0bT=4HUtDl(6iybvF zr^T^`@Kc_$362(VwaGgFX&D$9zXHJb6U9PixBY_$$(T}qeRbX!mFw8ItE?Qm(R=b4 zR_`G&sJL467w*3c2qhdYh-`*R!ndV9L>UH(I14g&pNp&79-D%w^HEsffd)MX(reJm zm$krm!&XP7T;|S>E0j6`-wst zz+1Hf3oR7=m$5ChKPKw9$O1_nqLMv$6Z%6`xFoBS#Fat+SR{0i)y$J4jF<{{8+i3pDt7q8@o$CTP(immH1t{hh9N!zft5%Xo$#`UkoyyIa4y zU`|p4T%k^xQ-J~dBmlU_@KDgCokLxXQSozpGAO^WpFEj|ylByp@)#3nOz8g$(=|jH zMDWHyzJChnIllch={#oGVW>^Mf= zett&(Ga%)w39%0d2p}2+^dM&;vu)9x*@#C^fWGh_T`OM)E8K5b_am;_0XR%>N#Tg= z2wRIkW&asq;OFQ%ESDlJlB3Fjg3bBnq3S5h1G{{V;Bv#d{aE(`226-z?XX}6iK3w7 z$dh$~&VS-cjGsc%2v#D!GZLf{YO1#x9j;xwM)-)rK<^9cj~vv?0DcnHuB6;=;Z`8u zT>!y^nL2O<33&U+T@sg#doUHl$^lX)U+{kHHFKO1g|xNlL7hGcFe(1I$=$I(IxEn{ z0Azjm#*G`O-ehq?zGdHfdY^X;+Bt-;1#a;%Vd4L;y)zHTx?kJ<-8|2VG!G)pnoAm0 zs6-?cnk1x22`LRvlhPp4ga$HIo|1}$<}##EO7nn_22!c8KbM}>TI*fwJ@&ize*f6V zKKA;fXC*iH{ri1?*Kl6vd0zW@s9t^e5OJwVQtG$VgRwdPK#ZLfz)`gC^k!qBm2rq^ zcniYpwdc>{Lnhw#xe~S-3-jJwKyh23*?bu9KAeBguxHI&|0hqM0$$AnVTkx_`8k;F zJSvWk#jU5L(`qM!e@+14hAeu81STwPYLM9u4ih0zd@3%!dToqRJy4xic$rzhy0(>_ z2$T7dLOSPmTYf%UH~HGuW-hcp0M%(awP&#O2Mq7N?LdI|1BEj1;>6T!H-Rwc zN9D^d*;MVv){nl;JGYV9!L6{9{!CyfFKLUP5U<7`DggjFP0dd7BPQkwg!giQb`R2r zNG-`evgP$s(IflyN5>Tv7w`G;rSM?*;k^L?0bknPTR+FfX7kRShDT0)I;S-Sj~rkDt%IL@%a0&vV2scWSUY4I1bkyLb5B0yrEAj~YKoIby%ybt1D`cOTy1JKYWs%F)9XpxlefqQ4{MM433L=<98{P4*JDn2 z^$1O2N_NU8z6zdA?cV|`?x7c(Hec7cwq;H3pwBVa;ynWc7&K4z<}uCLXWp@KZU43a z`g}~&jQM{O0DxvjAX=hRA|e#uH?ztcls|CMllB7!oU45@Lmf*jBGD91#QW#*JENlq z6GgivQ$zVTVnB>1)yXF zMIZV`de`U!hYp=EYx;=?<@z0?m4Jfx?+Z38*e^kg9x#XgO4s4Qi{}5p=vsI9E(1c= z1BXVQA8N2bYbXvny_|l;b5acesF|JA@b>au`)qX0buV-~v|jOx{w9}g!5Pgy*1hao zsH%oFo6Y_~buP<8-Hab41)CwL1DmLm{75)2IQQfTkF0)|yLtSbXjOIOk<+ltN$#R5 zTf1+c3UPR|udna96kc29><-uY^K-Th>{kmw_Tj~qy^M^ESnj*$&6{}?W4yAbU3&Eh z4YlJtV*WPMZLbjribvNkO1goIpYga>=YFD&IOb6N;&L^GG^DaPnepp2j?cEld2OhD zZ_qw$%c{RlaVsa}=FOYRL2Y#5%SSe$E%?{u&eAMia|9858 zoQ~}pQ!7WeA7=sf`rJ7Nw5EXjM_^4H0uztkZsf)6jDhP8&duwb9N9U4r&LBXuU;K39zr^Z z`))ID-V3I`bV4$?1|_6iD=f{(Fd_Qd-aA@@K?!McJx#XT*VLV>&Hs6z?Nsz#yXAp^ zBgLC?)RVL*cJ#*0_4M!C8DbUc5g zH+bgTlP+J(9xR^1;}mrh9djPuAN>EMPX}5JTogzQaJBDv*M{$7cY`<+W~)-%*@@}3W|R) z8H--_fw-#-K34&rzGUgrm!!Mu?R)p0Y?DI8auo^xvAzN7k10#{$!HE1Yrf}?HwDn~ zADqLQWgZP2gWRT+)+~v-4c{efu9ef{gpaFsmtFKOiz3vv&W)F4+EMnKLGGyYvcbWt zc|X48D z97^uvVePjTk@Fi6`*OKY8Ra)HRXRXaq!p48yHRM{;l51sDksC>w(~j7;Okl_Ue5NhVm(T@U4$_ga%S_)oYYg}FUt2#zi&BJ%+cLbrDZ{Qd6$ zDoLcGzmTC&)&TR>z!D7gyN=#eUu?C&dlh#>j5=NV;PA!RfJ*xnS4SFolaR^l7!mGg zNEQhK1QH!$W47j5J?Ebmi97b}+4HRAXIYs!^CljQYosHNW?%aU-GReZXgA^&moIWgVbB%l(E+0?`(m-U^2;zje};%a=#4dcrjk^bCBW&A2iK zO+N{GGX15yco~vgjq^@daI)+%T^SG661Z2GjWp0`@(Mty8EqnyC5&YDpDTm&5JG*( z2FEJin`iJ2g<1yr| zS^XiT0MM>;x4*|qokAa#cEY^x9sgnB7PXK9w;o=gz*e9$83tUY@M~5|is)pe!6=L` ze$?l)CCFZbyJbDIufu~-={-w94O;6kJSQv^{#^8Q2oSs|4aGKwYcr&g13PGM%~*VC zBS6~aKEHGZO_a7}d=0+LmS@>BNxxvTrtq(k<#x|)*NeAxRHualLVNRe-iD@2t1MZ* z+%3Nx@CJMYl(IqLX{N``TDH`~*nl5PEpAm1xPI9N4ZWtCK8dp`em<A~;j~M>6Pb#X{ z>il7C=Z^})vbcecvbf1TrMY<9x?cVI;*cZ2BOsU~zojtxU*?pfMvtzdHph*P&(GHU zaFA`vK>91PTD58gIi|$@%llzHbAP!1{C4b(eiOA^waw=`>+5Tc(%2lhynX%sI+4}p z-|4$~U+064kpqHr9;y%U^q7#WlG7X1so?5>$alF)9!I?!#t)AvDR(LiJCzh#E%isC z)6+$+@s=OH7JQ8BS>hJoP#OEV#M6>>*%z3ck`hYE?ds}E<3F)5mf1u|%EQnJG#-L{ z5uU=^?8MU)v~9?0e^?)e`uh6&^I%V1!2$C1uo-)4y=Kjwd+gIk2Mn)}3;4S3IJfFZ zJqCFQ?i-HaJNvzVOk@I4Rd{?qiPI}^hGrhu_XR_DyQF%5G#<4jaq7vcC9ozwJ+CPD zrHC|fGPSH7|FftbFexy&3KL$gQ}mLT>%G>%S*TyXz62EDhpVt+ge|i??#Pi2%ysST zPnl{qpZs~;9{r#>cMp#wlxgG%d*=TA=48;5xdq0<*V0R2kauiC*$IuTtl%DP&Ys5% zWR)OuCnpdIwfgq;nd@_Z&02OSdL25HOpk2((z+m@LraQ(k&|-+EH%+@|B-Egf(y1U zzj5=Xj6$)%6gqvz@gCgGxpU@Rfo~75=WVm$8qSu@TeM)T5jJ6CIrzoh%*kMRv4` zvo5*P+AMeD5+)yPZXN-KHjOryn?8p*_M10v6uKj2wV(+FZ-@GH8kjQNh4oa?51E$!)vYFpU?-31m9XQi5M zq!Pes@7}!+ljpVFzU|`DS`NOMmgWTwpJ~T+LKlEuUjnM};{=x5y6_&P4&YTEi;G_a zBs6~(cjiq_4t&KV&Xc#f3bxcT0=KcrT~_!rHdT+(e+WW$YzE{|3g6AZI%k#Af}fR4H~YE`SgN&v#yPLRR z)oIdy5tT)^MzetZmabfB*7i%?4u3Q@9T`<8L%0{XpVY2jzjLo%J9!g_7jzE#_WV^| zVoFLj?r5{#+}h1r({)I`1lNA2w9v+89I=*yO?)B&&5aq!S_h7LTne?)XzZn!l^=F^ zdNx>lqsuxEr}c+Z7rq)F2YzsfC$U`9}X7eel(bE7W{grg)N@0EWT?Z(hGX zd-38_JXWhK)o97kFH&pc#eKBxLhPo@>)5}4RPxhPR!V9cbP>Wz?grck`c&tN(vNFa z2_d9F^dw4+D^!K)BEA~0Z|)R(QP=^@{imj37ppR0p$caNnpgVak=1~KEUX8pCZ(iY z&&pEeV+J_@50c_lh7B9$IbxW)`nVo<{m^6sKPNnXd@)0ts^|?B;5_O=hDcpq=zkHp zn5}BQxL>ewXuqlpEqGoPBS((3v+gsNjj2(Z+f!mn?aK zKYvH)Bph(Z@1Je=f9d&0^PO{YZ?#bmO?_B?ft{ugvp{Z{Lu4DmO6ztjq|h( zsF--EHh6GLW+ui{rksw99BpXW2x<^~Us*iN6*|rg*eC71mq)uRrBGLeE*2X*Xhptv zTCa*HKSl-_H)+?dk<#k-5EhSqvHWS{&y?GXtY@3tvFqZ#gPDJctkUx}Mbc|V6Y#x{ zWSlK}ulwn$p2dJYNr{P|rY&itfcJzf3Y&G`{{6Dqm*!&Fh}GX$aUepOl=)l;9#y-; z7`^**#yq`sYYS&us`V(}(9o{*21M7BPgXd+61u=%qORuf+&6D_9XO!R0HeyZGxN4) ztyEHO(PCL?k+qt|E;P_N&N0R(@zZ`RwMaL>72@Q!h5+@jc$OyEUzTe!f1s0EQablowcF zGdHM2@3hsMk>H@`<6?Ag97z(xEfmpM9?(qS(@wT&(q zce7lMyWLFysF^vNwq(7(e(V-EinjapkM{>*rj8uBF6-(xiMe2wMHG=T9{BxicFM0= zvu4nwI;SYYa&mHlj&D^Th-o`nLh>jeV0+RgkFT}r5=ffFoE!JF@vY{@uG1dwWr$%M6FWKG)V(kFZKE4!vtp8XgYnAm)1=J9X+m z*6IgiH143lGL#|oHgo6C-xMAme&$YDwyza9Y)u?Uy7%ZI?&sIZn1r1sq+7L~J$LX1 z&5R55Z5Az>#MuE~GmD#1Zrht6LSGI)MFtDnjc?z%^I+P6+4JU&fe}GuxJ+L~uH{gy zClOG0VS#ZZIa@XBz3yoUxSq>Wl9NTAL0fgiy`o$YKGF#)0%_`nI))|YK&pTn>FHAn z`meVq;nmCVv!&*PcD|r;63=)Ckm|L%)8Q)A>3w zys>o2ZjNn6Nb;h)PC6qNEC?xTa|!iRP}Zp|s>qJi8L&A*?%PvUCywWNvk$kjPJf`| zke8J;5Q{hF*Wz7Fk0bYtG-x*MN8622QT=7OqNy2nCfxYA`qRRTJei`!fIQsC?b{MQ zIkC!nY@D!a?ClM>`anqx0n4@~uHH>yZVYs59NcG>t7{R3s@MMgTj>fJ?FyfWJmUqH zdKaTF*mOQ65ANN&hY$LCE)0OO9$k7V6ZMwb&P#L$8g!#U0{s7y(awpqbJ{k&3**aB z?dpS8e?NQ1){3YVa~fKff|M?<+{p}~1QVB)=VU_FMu8rziBy0O+$39M-0{=0t}7E; z09XCSZILjLw5GxSpr#1o1vFjb&Gn7FsMqMlylp=a|H3+70_nTLzxqTUF>!Vt^ytwe zLz688K^lfNNA0y8TDEM-VLy5%+{hY3jPdT;Rr2D~Onujc_AKe1T6}ktyKCFWfhpS_ z=BO{_hRLFNz;;GuZkn0e%ir$GGMh2#UR-SnwYoIacjLwmYQf9{b66`!@w=^a!)ogzPPa&Yy0;FFWdt|k|NvS??F z9^DVo;-Cc%_h0+jDlg0+-o)2+Z?4ONtHX!wbV$q5UFc})h#DjD)OvXjQ=V`9>#b}F zyEbCRCP2y4tG+H`;=@)jw+aHCL<|&CNji2hRcpWzBs7$P*>l~cui>9)NlJRmOf*0blU9M9jFMd zBaRZzI_x*&B%x(V?q=e+bNL$cJW{=2RzYzPB$nGfPoMnkM;>!pB3K&2Wi0tr1}pIQu_4;`QPz}k_XB?kyGSF*CEA2a;pI6o$&di(YYN@O8m5Yx=f&Go-C<&a4(;u5B5keC`{hgCk|@zlLAOJrX`cJ3>kL1^AW zOUaOioGMIW7AtT@o>Bm*FzaP*Ztw$t_nbUhqz|#h4QW#EX?zWcfN5iPo4xd6N-v{6 zCnqOFK*HCF`tEb0)q;3swc!S8RzT|AqRM8RTX>lkl4~n00Yb&PqtBI@&&<8;rKXxY z^OCFSF-y+M80)l{v6njHKD3OdSZqC+)MTv5^IZ;-aA4r#9T5>9`i9S0YHA& zWFrU^02C~TuR*PIB3;F~X{%!r!3cYkWmEc%nGQqj1|L z;3ni7VupC&DX1u4^I^JM#xjL>1R$uS{7;yG46b4G=WmViA<+DAagK|N3&XnwZ%ji? z5^tOWo{}LhG-@%yqPGox(^cmy-MHi$VfVnX!*{FB+Q`Vr8@cB|uoL5j(hcCTZ@9Y- z;K^%LS=DRRYSyOBMAq;0%MVJrw6@|$cb|+uXOH$Q(ACuy%n~BsS{l>%^6%a7@W9}2 zFfU2RrHH0j*1BZ5P%8mDYzEA<`}tlXo91Yr+8N|%Yw~vO1hfH3$t$@n5W(vwfVfb4 zf0=S}9G=@4wk3{F*!CVQoxOHq8zE;QtJ;YEoO$si(H{Q+)7Rc`;|`uq8m9*v)x_S3jZJooY0OM0?S$!>ey@g z2t1Q*L!ZhAI^lGJUO9T%6S``S!5%uP@EZ9%rZ0m7)^udjivwPrj1{xs`x8pM0~{P2 zAmO(nM5z%hG*mk@tX;c3y(p=+2$HU5>DPCUgb2jxtM1Wu(f)n=UVQkF%A`N2dXB`R z`}a+|zUGoZSPLaK(|ZZce4e#+=&%HBEFc=S(-Cmhu9S{D<9qU*J3F`!8@Wat|FAb* z*ThdZH>c>mEW2tLF!-#Y6x1xa)bL|K#m~(=24t2+*^W<N;1pB>|@qx~%MnD6MX*Q0=$iwfo7;_&u5+HeztDYMx5hq?i>;uYyIh9k}yZk)E_4RQ+k+bH!(*(yx^KM zSwsdQsK7j*H=~z$60>3(XmIv4-kzA2)5xG2uEt3ss2+EEMr?G3$K>y_YPqmtgbxH= zCF>LDc+L7Mlk@8bx!bX#t83S;*2iIV)h1SWeqCrzyxVL#nIiwe$?)pyz!9YKB?lQZTu{hPACY~q z?+}*>l%mUcYNNDT^qfgl+^JP^rn5tC!LqVBqJ0jlh^`48sCp(awGo;<;mEg~ZV z%Y)?_bu$|L3yeLpmc!Q%<_DRH;x6rmg?e-2XRpQRkrs?m^io(!#x^~*J^r5so*l>k zr@%840vZu+@dn@^fxft_kc8ut8(3f4BgQsVdd|+yvutc`GWf%2_VjdrnmRZ&JK;z% zA`+skeik*+pd~L9L`9iJ1GsJ>2x@kE063Zr_Kd4O+UM;QE?^sCB&ar;gnB&jp!{U^>SjHFYj+)nnF`%NNau;?m7G}3-O(PCT|4ojk z;h2M(MIc(D3{ul(;{EvRmex>Xw_Y8Wlk9SR8o>nx1HF1J9r+Nz(3$NMXz(U zv}_9zYaTRvw*JItXp7Do$N)NTSdBl0_d#7^x~jRa59TPh4ATA;j$ZX&gQMLw4g6^k zwgd-FuaUntYk3^ehl_3UK4aLYZ=zO(NZV?_LTFigxh`5TKcYet(erfD(Lr9OLhwsq z)*c*~os&}pzgTp+fPW)s09U?$@)Cvwpt}}y1U&blnRBdEp4M$ zHQfr`SA00B7>TPgW?4=E zrcH(^)0B5E#+Xb6%=UZU;2y)My7Az_RNEQkJ;3grLdV>* zzaav*QY7Q>H$^q6-xjU~kHme;>#b5kO*B0fGnBL5*MK#*Yx;ir|8B*uSRH`(?gCmR zflXuc1)<6$txfCJ&Urz;<0niI$^uk}>Ble8(1t7vOPTWydN@U#F8J2{@&$?vr{d;@ zsWt(tZ*T^EBI0Mjj}?p@1P1;^HQm+ESrGtR@HVTT=)lPK4O3 z$FG8pE@Vuwv1+^g{3iZJqrF$@9PWQ<8WRL!`;^Cn#%F#RW)C_bF1|$B&>Bl(j{BNK z&YyVV#EE`cR{llrt#k!i`rpN;E3DWtQHf7KqbHa$ATx%63>E&u=W%TSMxm8m1HlZ4 z-F<%_~#Sy;SXIf;g8IrH8h8HMGxj<4H-M)RqFhECki|M<5%F20_w?PO<%x3gmCrxU| z1rztS-+;8&Ux0MCPVKV&A`FHom91+F#l6pgQzut9w`SPHsKuw$A~idx7X?(IGo3n} z?}LYhas6VR#Cyo%w?lq@_s7PDRi*J2pB$;MX?n$eiF>tkxhE5+2M_jwCL&nSg33`# zsqpj?q>fNR?(5Y0KS8PEZe;du;l2oht~`3Ox)!KH)kSko+F{IsJp(%VV=XF$VCB?r`+ns!tlMongY4 zvCs5Acbc(iMq5jC5W6VHeOLs#9!Mnd>`DQvCPlS0qI|qb))B{JI1O-qZjsKSIl+u% zd@L^IdQt777wO8+zsZ3F_-zf?!-4G6uirIfzBI&S8nj2kE?wv6QNRa6F1=J?e zcT0a(F?dy{e+OURvsM0}{FF{Y*q^*!^;iU;b_b>mgwDSl3;Oi!dzD7UJhvQj8s-xl zfMe-oeim3&%ptu>t--vdsj{+}?T=~RvU-6J(2Oyh+xegAlVg|KgRVe8O02eT zXh&tg@W}tpIFEg)>q?4~hiKbSv0YuikGsi2r7W=!Y9viR@l6jyY8CP zm&(zkwmS_SbJ@zJJ@p=!g^y<&2NRz#msWOKYBw=a90M z+em33`t|EPWQZ!Aw0JvCjakOdt%xHtaNI?W2q+*e9S29 z?~v6`Z8b!5Wb1ykr(!rF(|xjY;><~b+(piy-`Hq=WEBsmgT2bP0|074{19}UY%jR_ zr=lX`z0-W&WoJekP2~3_IX3f!23~2XjyA(CEcx{^m z&x?Z3$HG&ylbV|D_t$mCSNNQY`E0Nv)W*es^MIe}#SgnUSY8`2d@F(Z9vvN2!HSG8 z4+brN+nAba%FvuQZ$6ux>On#FmK!?y#}-$#Q^$`V-)RAoZtK%}od6?aO7=o41T5*c zu-XLT#?hk-ERH>6sgveF)k+Y1w!Qs~{LJY>Gq`UNHT~FcWLx_~=gGeT-LS@WGJa#U znq#W2_#5PXwC?T^|A6#*TRiVedmq`>r=yx$(3$YbL16bS|2=j;Uw%f&l&U4bx>Eth zXO10P6UVBMcXlQ%IhJYd3`cyASbjgqD$HP5QnB{gmjV7a2iO*OJQp1n4U%PTHj?LK zSEvjWDpBh(rgIMWkZGha2@!toG+m2&5c&fmsQlA+et#Cr&eO+b04^0kJgEW%{)pr; zH{hR<@8{8uwbsJ8(wAFJcqMH3`&|1p9Mdd-v?QO35W_FW4Z0ZJMsRrqKyj z$w`-#m!}6Ni#JD{*O!kkN5gVkx_tRH0CJE-uYiCE$I`4*5eBZOsWt8)PNMtxIjziO zFBk?K!1fHq7ze%Nmq96_+YP&uC)~kcY5}_lp8PzMHF~LGWkI~VhLnO5*9*VNS{`s^ z1~1MzE1a7u_ndk6ZLwDb)aKd>EMK$t0c`-vsSg~vr>_VpV_p0Qh+=#sIGa;80P}1W4gf9EQzTx#$Dg~ zI?OPa2NaagCt%x;7j%O`IRQYBToKUjxGMO5O*)xM`?48@&Gc2}`-Sx-ooiAPf+{sZJ)QeZYv>~x5AxC_)Lf(uqIwpP@b zRVqCw0OCX{PF`Y%fG;OkWHZ^SIDAN@2zR3;;d{HMr_Wi)qvCd=Q~IVEbR7Xu*g{t$X(qR}$T(3+aj}yLSKx z_${)Ns2{2znJ`VG7h=M zI$?c+U8Y$N1|9WP($+)8v6jT&AW3b)X5TI zEwg|lR8@A3h7y*yBpRz#Ey1d7KjZAGOCahQ7xQqOhGx#NV*I!HOLM*}E1-IM(_EnGeXuU}tPNWBo3 zJsG9ZxQ;Qf}64&$9FaBpcWff${_JMP}48i-`*eF#VAvOVwnj8xw=@r#2IV zjAz4p`gtNaSUeQ&?`Yn4;6Sq-j~_n#hzE`+w4z3oF@lyhq}<5*pd*u2#W^{u7lqwcv<*S9@?0pv#v+3-L~H1KKCL zXj+Sf2zCdiTSq*FOvusObmBz!s9L*ND)y2d3Bb#$M?8#)=>asXpAeNd##E2+?xTJE zgBbum)v+_U;b51dz_dm@s6la=RoGcxfe+3ibc83}PKh3cra(3}`#djJv%RPcO{99p zaD!~-oYruvz&I3lS!f5#$s7_2=K=%Cru>?U@~^|Qk90|(KX{uS+-TFLP1BPq=OUwg z_I*FKO%QnyBAHZCXD-Dn02((De^`_jYh*@&8u#fP(loV~=2H*{`hmn$-XD7@=y|Ip z0F6_7nxuh=-bzU^Jstt2>m{g<-L9E#y=3N+J&loMv`g`EtxFa&O0zAje3!3<&clsX z)%p4HYOv1Hna_T7g|tYcqA_xiQWSN$(2h>#P44%q21x9NaXsT7MlN)B0S#I!Xj*P+ zFcQQoiK}NqMwTQ56YR^@Qxxu3sgF-O3596fxN%VWQph|P)|AC~QRYlqvgFV1SMMF= z$t2}_kmBoUsP;j-JN((RXCm(fNss#ZUqi*cYi;#Oxp!|jwTVQ_D3KD^SLr<>IF+e# zA*{l#eJ{akr{=ehwMklr8~}5#er@ACRr*x5w=XbGz30?xxwM3UDphFmH{Dp>3KW1r z$Oxl&X5Iqesg>REJlEukwnEXOhu)ZxCan$)TQ$|eMd~)AM`05WTmIa9g}UDPwdX+5 zRa4r6btGx z`SUZU6)R9fj$w*XLwHeQmLtRB?Ck3l&FwG4tS_};uNC`bp`vkoh!7x4MXAL_$~`B4cF z@CAIJSz-l39iCYp_m=Sez~VY}@4lVjURC33h~}-NBw?_D@OQllfm}~t|4L@2S&xrB zk^rL&{B$heQLr)K8Yi52nv7YvVl91w7bu-se1_xMr}Zod8bAxR6w*yne96*yX>`Fz zw;*#Bp3|F5vN4_;QIBQZH=*DRzOrh^o%pD1t-P?dv(`zC9hX?Jurz$)jZuXca}>WP10yd8udMTlfHj_eCGM(7|EF zr1O!Ho#>abDI9|Io(vT__neIL+mqkbDD^t#3d774(2z}N$QDH zTF*qp`Z3EsqDkY%2~c5uJ`+`elw1Ij*B)*EnruAv_!4EZb8gDO=kow&K`Zt~KQ7sJ zbl#r}m;h1aZrQTsFw!tel%??Q)fBM%uOV52)e&NWt>b!e)x$(XM7@CEWI6p9fB&|S z&#*w=ino>w5;)x9$z$j8X;HSw9N=sZ#t!5v(H=AjfjApdBL;!o9H3A}qP_=Y2qjXh zzr23bb|QY@O4~)e^2{;O^clPMWIczkRok=a?PM~jgpbQGDrV;N7|>oGxyg@e5ky$|F)hn%?;frKGvyUk ztneOSMN~mg%>XEH3hkSfr7IO7sLY)5pQYUUc54Bt&>}KQZQrF!PRo+87{2h#wV(&R zWk%IO7U>3I=8;{n(Xz8Ylw@q0wRqmVYp@UYX}g>`V{2lHPW%?93RzGB+8k;`5Tpgq z#rC-fkLB=%p^%K4uvME(4qkg5fYF{MGC02koE6t;PjH1&Ps)xHuiT14yL} zpWU)czI|Kok;@ZEFAhB%nS2PW&!{_bNa4?)ZuJ{95RxGlAFl|-j16!Wo5U2bY z_;-*Qyb+wEi6SptzjLP{p$~R9TH4vnvk`IZr%g50zi6PTp`l11)DpAA3xn9_y{0tx zS)+eO&>NT=n%VS0YpB+_LpPYS=uhnS!BT1Xp$zN3 z(Uf{Tt{4H03o@2InO=1R=mKr-l(wCH50zU&Mu!w)q4qvh(zh|_ES5}1@r$u*l}oMb zOB3=jv0H@6oD_d64z1nL7hxuvpG!(^r=$?dH#30zm~J?{Yu?S7>1H9f91a{%(5|f76DhQu8AaS5Yv~`wV?PU?Vb027zP@po z^+mlVNl7n#%-vv$>HjXr`^tX{$2%@7Q@d(nZkOT10|3f*cNo2C*Y4d%b_Y90CWl>N z1)P|ReCMdh6x8N4Fc^!#vfF%D47aP1oPVtLdp>7@iLWI}44b&-ZiGdQchF16)8^vX>k z1T$p-4i;lP8qm;_rAmf7|J$(hDBEke{R$Udem(*j^HP5+2Z(}YMQJpaze(SDN^f-< z{GA@Y1O{2QGvi_WD(@Y5#^{)CS+E;s8=P+T4j(_UvggjNTlIp@#|L8Bj-o*`cBX~E zsV!UH2Hx@BY_xMOWtFH(P;;@X_tb<{z5M-*yZ<{F{lAq3K51^ij>P%@w^6{es^=>x zw65bl%ZO?zTO!5QW8S>LJ43H}jO$DFq*MNFF5bO{49ti+)J9#)E>W=&OzDr09zML5 zdi=~kz~~S$tLxpuc8%AttK)CZR2eQ4-OrPNPO< z4`$FwiOUk;xp-ESkly?So;QEm{UklaVA}90?hmtbb8qrJ)1sFJ6*YbVfS!1K^>zBA z8_+Nn!uaN)?gOhVha?QX0)jqZf%Y!V<4KnkLOGOBIdml&fWMX{XpD4br~+MJ7R82? zD7-+6cEh@>o*rHFyC>08eodmiK2!44+uWel)SOMm_!!Xf3^(dPHw3Cxy7knjvnWpP zf`2BS8tcO-w`19->VXydW)|OrVlpDNQ%AQd&vRM0XF#tx2LrD0@x{R5$JY-C0*gXt z7E{)Xy0nn-Zu*qybHwf=+n`t%+Gjxg^0BzkZu7sv(gUXN5}4eixhro+yfK}uGe-*w zeKgH)u;bXV0+pL+`c?k-7dUOS)Ze*pw3k(?T3EYUwKo4n%jRcnY5LhXjh2 zrf)g)hN$^jJnuD3bSr=8D*z2sr{?nA#)|jiYHfDchUb-KG~LB zzv_8>HB7Y(yvOI@$n}+gzjj3D@iN?KZu_*PP3LKXs9!4{w|+Tq-PC8&-|6lhG&fz| zW0`RI@=M!{%Ii}3>27WLJ0CtsMbTQv?SfZ$<)=0(X{v$L~5VPJNTi~~@f44Ta1 z?3!RDva19tTyM^Al%uF(Igj8;!9)xy?{rMNsE6**X3OST{xhGNC^gKX^1ATHn5emT z;ZB^~U;g8HMG6m5o3RJd^B`7mt*mz?R}{DC$&D zbjE*!sR<^nb4A;Q+)H7&`>{qW_IIcz@d+a#bx zBf{)GP(kG?`?ev_RlQ4@!$+BjY{gp}$f7g0O&P@iF*AlZy~Ed0#c#-pSHQWoJTR?+ zEsgvQ^?#sS#VSG_z6U=s$xtY@Xd!fYL{U6!(hwj@P@pI13pt2uK86-W7LUW1oc=@R zIh7BB^MlVjai{7(h05mb)faUg!Ru~6td|BxD!s>RcI`zm~y4?$ZAx1$12VOy;eTMTkK%mjt_R**+0OI(13Q(U(Xy4+Z&~jBJUjK%h!M zP6bYKKIE$^vKd-tMI0q5f$&l7G<0YyAOeN(KBFyxu>&U;@`KSPA^Tx0|a^|=%*5fJ;-N_Yf#-K8)pQ#5h{ z@qkk8%&5F-&ZqSxYQ9=(TkkS!-Tcdv}Fa~aKql1iX1F<6% zVk`KDGv@{+TlgL8a1+g_gfU*zWGRMj21ifn}{x|!Ti&f-zt2|;C_)Rv@a&D;{4 zUqFi>@ZF?iMyx%Y0R+XP+{3@U8$BQ869w8O!#Z`^z-2v{kdUC2xz{Szc{iLw+0-5N z8q%%oF|ccOjwJ}kGG@hfM;ofeIhiul2{Nm7VhmzrhF0CJ;toN-fMFv()MD<-D~%om z^Ik>8BFDnPU3!=}BTk*$^;Y((SRc<*3dzmQEvECo_e->#0+jA! zHfhohuf-sbSc7atY!KqN*uf!%Nt`Tjmi>>^&Yj4mhv`&@3>1Yw_TD1x645W95h+{+ zrT^3-Ia1_k4C@RJFBn=bqCY8vqU>uac_z0*I6>W5gz9S-?h5YZ#qF$L53PJCEyhb? zc(v3=IdH>{b>Cs-%8%p|9)X=H@OZ?luCM9n-aWf?kPgxIkQJIz_DKtod?P#q0@z;? zzx*e=2)|N<&|E`h>w;zw8zOJ+eK!}6aVgs}4;Fy

Y&d%k8LdGS`A*#WfAU?{u%GKktJ@02udht#MmjOQBdh>W?A(MX6&(6@}t(<0B3E%f{L(_)}v{NHv9GM2B%+C!(A_ zC=?Ih$DE2ExU#8Y Date: Thu, 8 Apr 2021 13:49:13 -0400 Subject: [PATCH 05/41] Bit more lazy loading on start screen --- packages/core/ui/NewSessionCards.js | 186 ------------------ packages/core/ui/NewSessionCards.tsx | 109 ++++++++++ packages/core/ui/index.ts | 1 - products/jbrowse-desktop/src/JBrowse.js | 5 +- .../jbrowse-desktop/src}/StartScreen.tsx | 51 ++--- products/jbrowse-web/src/Loader.tsx | 11 +- products/jbrowse-web/src/StartScreen.tsx | 18 +- 7 files changed, 146 insertions(+), 235 deletions(-) delete mode 100644 packages/core/ui/NewSessionCards.js create mode 100644 packages/core/ui/NewSessionCards.tsx rename {packages/core/ui => products/jbrowse-desktop/src}/StartScreen.tsx (87%) diff --git a/packages/core/ui/NewSessionCards.js b/packages/core/ui/NewSessionCards.js deleted file mode 100644 index 2151d665ba..0000000000 --- a/packages/core/ui/NewSessionCards.js +++ /dev/null @@ -1,186 +0,0 @@ -import Card from '@material-ui/core/Card' -import CardMedia from '@material-ui/core/CardMedia' -import Container from '@material-ui/core/Container' -import { makeStyles } from '@material-ui/core/styles' -import Typography from '@material-ui/core/Typography' -import { PropTypes as MobxPropTypes } from 'mobx-react' -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import emptyIcon from './emptyIcon.png' -import linearGenomeViewIcon from './linearGenomeViewIcon.png' -import svInspectorIcon from './svInspectorIcon.png' - -const useStyles = makeStyles(theme => ({ - card: { - width: 200, - height: 150, - cursor: 'pointer', - }, - name: { - marginTop: theme.spacing(), - textAlign: 'center', - maxWidth: 200, - }, - media: { - height: 150, - }, -})) - -function NewSessionCard({ name, onClick, image }) { - const classes = useStyles() - const [hovered, setHovered] = useState(false) - return ( - - setHovered(true)} - onMouseOut={() => setHovered(false)} - onClick={onClick} - raised={Boolean(hovered)} - > - - - - {name} - - - ) -} - -NewSessionCard.propTypes = { - name: PropTypes.string.isRequired, - onClick: PropTypes.func, - image: PropTypes.string.isRequired, - style: PropTypes.shape({}), -} - -NewSessionCard.defaultProps = { - onClick: () => {}, - style: {}, -} - -const emptySessionSnapshot = { - name: `New session ${new Date().toLocaleString()}`, - connections: {}, -} - -export function NewEmptySession({ root }) { - function onClick() { - root.activateSession(emptySessionSnapshot) - } - return -} - -NewEmptySession.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export function ProceedEmptySession({ root }) { - function onClick() { - const snapshot = { - ...emptySessionSnapshot, - name: `New session ${new Date().toLocaleString()}`, - views: [], - } - root.setSession(snapshot) - } - return -} -ProceedEmptySession.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export function AddLinearGenomeViewToSession({ root }) { - const launchLGV = () => { - const snapshot = { - ...emptySessionSnapshot, - name: `New session ${new Date().toLocaleString()}`, - views: [{ type: 'LinearGenomeView' }], - } - root.setSession(snapshot) - } - - return ( - - ) -} -AddLinearGenomeViewToSession.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export function NewLinearGenomeViewSession({ root }) { - const launchLGVSession = () => { - const snapshot = { - ...emptySessionSnapshot, - name: `New session ${new Date().toLocaleString()}`, - views: [{ type: 'LinearGenomeView' }], - } - root.activateSession(snapshot) - } - - return ( - - ) -} - -NewLinearGenomeViewSession.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export function NewSVInspectorSession({ root }) { - const launchSVSession = () => { - const snapshot = { - ...emptySessionSnapshot, - name: `New session ${new Date().toLocaleString()}`, - views: [ - { - type: 'SvInspectorView', - }, - ], - } - root.activateSession(snapshot) - } - return ( - - ) -} - -NewSVInspectorSession.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export function AddSVInspectorToSession({ root }) { - const launchSVSession = () => { - const snapshot = { - ...emptySessionSnapshot, - name: `New session ${new Date().toLocaleString()}`, - views: [{ type: 'SvInspectorView' }], - } - root.setSession(snapshot) - } - return ( - - ) -} - -AddSVInspectorToSession.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} diff --git a/packages/core/ui/NewSessionCards.tsx b/packages/core/ui/NewSessionCards.tsx new file mode 100644 index 0000000000..67eebb42b9 --- /dev/null +++ b/packages/core/ui/NewSessionCards.tsx @@ -0,0 +1,109 @@ +import React, { useState } from 'react' +import { + Card, + CardMedia, + Container, + Typography, + makeStyles, +} from '@material-ui/core' + +// @ts-ignore +import emptyIcon from './emptyIcon.png' +// @ts-ignore +import linearGenomeViewIcon from './linearGenomeViewIcon.png' +// @ts-ignore +import svInspectorIcon from './svInspectorIcon.png' + +const useStyles = makeStyles(theme => ({ + card: { + width: 200, + height: 150, + cursor: 'pointer', + }, + name: { + marginTop: theme.spacing(), + textAlign: 'center', + maxWidth: 200, + }, + media: { + height: 150, + }, +})) + +const emptySessionSnapshot = { + name: `New session ${new Date().toLocaleString()}`, + connections: {}, +} + +function NewSessionCard({ + name, + onClick = () => {}, + image, +}: { + name: string + onClick: () => void + image: string +}) { + const classes = useStyles() + const [hovered, setHovered] = useState(false) + return ( + + setHovered(true)} + onMouseOut={() => setHovered(false)} + onClick={onClick} + raised={Boolean(hovered)} + > + + + + {name} + + + ) +} + +export function NewEmptySession({ root }: { root: any }) { + return ( + { + root.setSession(emptySessionSnapshot) + }} + image={emptyIcon} + /> + ) +} + +export function NewLinearGenomeViewSession({ root }: { root: any }) { + return ( + { + root.setSession({ + ...emptySessionSnapshot, + name: `New session ${new Date().toLocaleString()}`, + views: [{ type: 'LinearGenomeView' }], + }) + }} + image={linearGenomeViewIcon} + /> + ) +} + +export function NewSVInspectorSession({ root }: { root: any }) { + return ( + { + root.setSession({ + ...emptySessionSnapshot, + name: `New session ${new Date().toLocaleString()}`, + views: [{ type: 'SvInspectorView' }], + }) + }} + image={svInspectorIcon} + /> + ) +} diff --git a/packages/core/ui/index.ts b/packages/core/ui/index.ts index 4c789d2ca8..7d72e9b01d 100644 --- a/packages/core/ui/index.ts +++ b/packages/core/ui/index.ts @@ -4,7 +4,6 @@ export { default as App } from './App' export { default as FileSelector } from './FileSelector' export { default as PrerenderedCanvas } from './PrerenderedCanvas' export { default as ResizeHandle } from './ResizeHandle' -export { default as StartScreen } from './StartScreen' export { default as EditableTypography } from './EditableTypography' export { default as FactoryResetDialog } from './FactoryResetDialog' export { default as Tooltip } from './Tooltip' diff --git a/products/jbrowse-desktop/src/JBrowse.js b/products/jbrowse-desktop/src/JBrowse.js index d86f28012f..0b2fb12dbd 100644 --- a/products/jbrowse-desktop/src/JBrowse.js +++ b/products/jbrowse-desktop/src/JBrowse.js @@ -1,13 +1,12 @@ import { getConf } from '@jbrowse/core/configuration' -import { App, StartScreen, createJBrowseTheme } from '@jbrowse/core/ui' - +import { App, createJBrowseTheme } from '@jbrowse/core/ui' import CssBaseline from '@material-ui/core/CssBaseline' - import { ThemeProvider } from '@material-ui/core/styles' import { observer } from 'mobx-react' import { onSnapshot } from 'mobx-state-tree' import React, { useEffect, useState } from 'react' +import StartScreen from './StartScreen' import factoryReset from './factoryReset' const debounceMs = 1000 diff --git a/packages/core/ui/StartScreen.tsx b/products/jbrowse-desktop/src/StartScreen.tsx similarity index 87% rename from packages/core/ui/StartScreen.tsx rename to products/jbrowse-desktop/src/StartScreen.tsx index 858eb0f896..f4340987c3 100644 --- a/packages/core/ui/StartScreen.tsx +++ b/products/jbrowse-desktop/src/StartScreen.tsx @@ -1,26 +1,26 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import Button from '@material-ui/core/Button' -import CircularProgress from '@material-ui/core/CircularProgress' -import Container from '@material-ui/core/Container' -import Dialog from '@material-ui/core/Dialog' -import DialogActions from '@material-ui/core/DialogActions' -import DialogContent from '@material-ui/core/DialogContent' -import DialogContentText from '@material-ui/core/DialogContentText' -import DialogTitle from '@material-ui/core/DialogTitle' -import Grid from '@material-ui/core/Grid' +import React, { useEffect, useState } from 'react' +import { + Button, + CircularProgress, + Container, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Grid, + IconButton, + Input, + ListSubheader, + Menu, + MenuItem, + Typography, + makeStyles, +} from '@material-ui/core' import WarningIcon from '@material-ui/icons/Warning' import SettingsIcon from '@material-ui/icons/Settings' -import IconButton from '@material-ui/core/IconButton' -import Input from '@material-ui/core/Input' import ListItemIcon from '@material-ui/core/ListItemIcon' -import ListSubheader from '@material-ui/core/ListSubheader' -import Menu from '@material-ui/core/Menu' -import MenuItem from '@material-ui/core/MenuItem' -import { makeStyles } from '@material-ui/core/styles' -import Typography from '@material-ui/core/Typography' -import { PropTypes as MobxPropTypes } from 'mobx-react' -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' import { LogoFull } from './Logo' import { inDevelopment } from '../util' import { @@ -73,13 +73,9 @@ const DeleteSessionDialog = ({ return (

onClose(false)}> - - {`Delete session "${sessionToDelete}"?`} - + {`Delete session "${sessionToDelete}"?`} - - This action cannot be undone - + This action cannot be undone
}> + + + ) } return } diff --git a/products/jbrowse-web/src/StartScreen.tsx b/products/jbrowse-web/src/StartScreen.tsx index af66545a01..32f16e33dd 100644 --- a/products/jbrowse-web/src/StartScreen.tsx +++ b/products/jbrowse-web/src/StartScreen.tsx @@ -18,13 +18,12 @@ import Menu from '@material-ui/core/Menu' import MenuItem from '@material-ui/core/MenuItem' import { makeStyles } from '@material-ui/core/styles' import Typography from '@material-ui/core/Typography' -import { PropTypes as MobxPropTypes } from 'mobx-react' import React, { useEffect, useState } from 'react' import { LogoFull, FactoryResetDialog } from '@jbrowse/core/ui' import { - ProceedEmptySession, - AddLinearGenomeViewToSession, - AddSVInspectorToSession, + NewEmptySession, + NewLinearGenomeViewSession, + NewSVInspectorSession, } from '@jbrowse/core/ui/NewSessionCards' import RecentSessionCard from './RecentSessionCard' @@ -196,13 +195,13 @@ export default function StartScreen({ - + - + - +
@@ -235,7 +234,6 @@ export default function StartScreen({ ) } - -StartScreen.propTypes = { - root: MobxPropTypes.objectOrObservableObject.isRequired, -} From 3239cf9f012219b751a214a273874e063c7d4472 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 15:13:26 -0400 Subject: [PATCH 06/41] Lazy react-ify spreadsheet view --- .../SpreadsheetView/SpreadsheetViewType.ts | 12 - .../components/ColumnFilterControls.js | 139 ++-- .../SpreadsheetView/components/ColumnMenu.js | 323 +++++---- .../components/GlobalFilterControls.js | 129 ++-- .../components/ImportWizard.js | 402 ++++++----- .../SpreadsheetView/components/RowMenu.tsx | 98 ++- .../SpreadsheetView/components/Spreadsheet.js | 640 +++++++++--------- .../components/SpreadsheetView.js | 461 +++++++------ .../SpreadsheetView/models/ImportWizard.ts | 31 +- plugins/spreadsheet-view/src/index.ts | 16 +- 10 files changed, 1087 insertions(+), 1164 deletions(-) delete mode 100644 plugins/spreadsheet-view/src/SpreadsheetView/SpreadsheetViewType.ts diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/SpreadsheetViewType.ts b/plugins/spreadsheet-view/src/SpreadsheetView/SpreadsheetViewType.ts deleted file mode 100644 index 6b4ca9d2d8..0000000000 --- a/plugins/spreadsheet-view/src/SpreadsheetView/SpreadsheetViewType.ts +++ /dev/null @@ -1,12 +0,0 @@ -import PluginManager from '@jbrowse/core/PluginManager' -import ReactComponentFactory from './components/SpreadsheetView' -import stateModelFactory from './models/SpreadsheetView' - -export default ({ jbrequire }: PluginManager) => { - const ViewType = jbrequire('@jbrowse/core/pluggableElementTypes/ViewType') - - const ReactComponent = jbrequire(ReactComponentFactory) - const stateModel = jbrequire(stateModelFactory) - - return new ViewType({ name: 'SpreadsheetView', stateModel, ReactComponent }) -} diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js index edbab5dc36..79cb11719c 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js @@ -1,88 +1,71 @@ import FilterIcon from '@material-ui/icons/FilterList' import CloseIcon from '@material-ui/icons/Close' +/* eslint-disable react/prop-types */ +import { observer } from 'mobx-react' +import { getParent } from 'mobx-state-tree' +import React from 'react' +import { Grid, IconButton, Typography, makeStyles } from '@material-ui/core' -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer, PropTypes: MobxPropTypes } = jbrequire('mobx-react') - const { getParent } = jbrequire('mobx-state-tree') - const React = jbrequire('react') - - const { makeStyles } = jbrequire('@material-ui/core/styles') - - const Grid = jbrequire('@material-ui/core/Grid') - const IconButton = jbrequire('@material-ui/core/IconButton') - const Typography = jbrequire('@material-ui/core/Typography') - - const useStyles = makeStyles(theme => { - return { - columnName: { verticalAlign: 'middle', paddingRight: '0.3em' }, - columnFilter: { - overflow: 'hidden', - whiteSpace: 'nowrap', - boxSizing: 'border-box', - width: '100%', - position: 'relative', - }, - filterIcon: { position: 'relative', top: '12px' }, - filterIconBg: { - background: theme.palette.tertiary.main, - color: 'white', - padding: [[0, theme.spacing(1.5)]], - }, - } - }) - - function FilterOperations({ filterModel }) { - if (filterModel) { - return - } - return null +const useStyles = makeStyles(theme => { + return { + columnName: { verticalAlign: 'middle', paddingRight: '0.3em' }, + columnFilter: { + overflow: 'hidden', + whiteSpace: 'nowrap', + boxSizing: 'border-box', + width: '100%', + position: 'relative', + }, + filterIcon: { position: 'relative', top: '12px' }, + filterIconBg: { + background: theme.palette.tertiary.main, + color: 'white', + padding: [[0, theme.spacing(1.5)]], + }, } - FilterOperations.propTypes = { - filterModel: MobxPropTypes.observableObject.isRequired, +}) + +function FilterOperations({ filterModel }) { + if (filterModel) { + return } + return null +} - const ColumnFilterControls = observer( - ({ viewModel, filterModel, columnNumber, height }) => { - const classes = useStyles() +export default observer(({ viewModel, filterModel, columnNumber, height }) => { + const classes = useStyles() - const removeFilter = () => { - const filterControls = getParent(filterModel, 2) - filterControls.removeColumnFilter(filterModel) - } + const removeFilter = () => { + const filterControls = getParent(filterModel, 2) + filterControls.removeColumnFilter(filterModel) + } - const columnDefinition = viewModel.spreadsheet.columns[columnNumber] - if (!columnDefinition) - throw new Error( - 'no column definition! filters are probably out of date', - ) - return ( - + + + + + - - - - - - - - - {columnDefinition.name} - {' '} - - - - ) - }, + + + + {columnDefinition.name} + {' '} + + + ) - - return ColumnFilterControls -} +}) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js index a6ec4a3b06..d05e9b6745 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js @@ -1,188 +1,173 @@ +import React from 'react' +import { observer } from 'mobx-react' +import { iterMap } from '@jbrowse/core/util' +import { Menu } from '@jbrowse/core/ui' + +// icons import CheckIcon from '@material-ui/icons/Check' import FilterListIcon from '@material-ui/icons/FilterList' import PermDataSettingIcon from '@material-ui/icons/PermDataSetting' import SortIcon from '@material-ui/icons/Sort' -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer } = jbrequire('mobx-react') - const React = jbrequire('react') +export default observer( + ({ viewModel, spreadsheetModel, currentColumnMenu, setColumnMenu }) => { + const columnMenuClose = () => { + setColumnMenu(null) + } - const { iterMap } = jbrequire('@jbrowse/core/util') - const { Menu } = jbrequire('@jbrowse/core/ui') + function handleMenuItemClick(event, callback) { + callback() + columnMenuClose(null) + } - const ColumnMenu = observer( - ({ viewModel, spreadsheetModel, currentColumnMenu, setColumnMenu }) => { - const columnMenuClose = () => { - setColumnMenu(null) - } + const columnNumber = currentColumnMenu && currentColumnMenu.colNumber - function handleMenuItemClick(event, callback) { - callback() - columnMenuClose(null) - } + const sortMenuClick = descending => { + spreadsheetModel.setSortColumns([ + { + columnNumber, + descending, + }, + ]) + } - const columnNumber = currentColumnMenu && currentColumnMenu.colNumber + const filterMenuClick = () => { + viewModel.filterControls.addBlankColumnFilter(columnNumber) + } - const sortMenuClick = descending => { - spreadsheetModel.setSortColumns([ - { - columnNumber, - descending, - }, - ]) - } + const { dataTypeChoices } = spreadsheetModel - const filterMenuClick = () => { - viewModel.filterControls.addBlankColumnFilter(columnNumber) + // make a Map of categoryName => [entry...] + const dataTypeTopLevelMenu = new Map() + dataTypeChoices.forEach(dataTypeRecord => { + const { displayName, categoryName } = dataTypeRecord + if (categoryName) { + if (!dataTypeTopLevelMenu.has(categoryName)) { + dataTypeTopLevelMenu.set(categoryName, { + isCategory: true, + subMenuItems: [], + }) + } + dataTypeTopLevelMenu.get(categoryName).subMenuItems.push(dataTypeRecord) + } else { + dataTypeTopLevelMenu.set(displayName, dataTypeRecord) } + }) - const { dataTypeChoices } = spreadsheetModel - - // make a Map of categoryName => [entry...] - const dataTypeTopLevelMenu = new Map() - dataTypeChoices.forEach(dataTypeRecord => { - const { displayName, categoryName } = dataTypeRecord - if (categoryName) { - if (!dataTypeTopLevelMenu.has(categoryName)) { - dataTypeTopLevelMenu.set(categoryName, { - isCategory: true, - subMenuItems: [], - }) - } - dataTypeTopLevelMenu - .get(categoryName) - .subMenuItems.push(dataTypeRecord) - } else { - dataTypeTopLevelMenu.set(displayName, dataTypeRecord) - } - }) + const dataType = + currentColumnMenu && spreadsheetModel.columns[columnNumber].dataType + const dataTypeName = (dataType && dataType.type) || '' + const dataTypeDisplayName = + (currentColumnMenu && + spreadsheetModel.columns[columnNumber].dataType.displayName) || + '' - const dataType = - currentColumnMenu && spreadsheetModel.columns[columnNumber].dataType - const dataTypeName = (dataType && dataType.type) || '' - const dataTypeDisplayName = - (currentColumnMenu && - spreadsheetModel.columns[columnNumber].dataType.displayName) || - '' + const isSortingAscending = Boolean( + spreadsheetModel.sortColumns.length && + currentColumnMenu && + spreadsheetModel.sortColumns.find( + col => + col.columnNumber === currentColumnMenu.colNumber && !col.descending, + ), + ) + const isSortingDescending = Boolean( + spreadsheetModel.sortColumns.length && + currentColumnMenu && + spreadsheetModel.sortColumns.find( + col => + col.columnNumber === currentColumnMenu.colNumber && col.descending, + ), + ) + function stopSortingClick() { + columnMenuClose() + spreadsheetModel.setSortColumns([]) + } - const isSortingAscending = Boolean( - spreadsheetModel.sortColumns.length && - currentColumnMenu && - spreadsheetModel.sortColumns.find( - col => - col.columnNumber === currentColumnMenu.colNumber && - !col.descending, - ), - ) - const isSortingDescending = Boolean( - spreadsheetModel.sortColumns.length && - currentColumnMenu && - spreadsheetModel.sortColumns.find( - col => - col.columnNumber === currentColumnMenu.colNumber && - col.descending, - ), - ) - function stopSortingClick() { - columnMenuClose() - spreadsheetModel.setSortColumns([]) - } - - const menuItems = [ - // top-level column menu - { - label: 'Sort ascending', - icon: SortIcon, - type: 'radio', - checked: isSortingAscending, - onClick: isSortingAscending - ? stopSortingClick - : sortMenuClick.bind(null, false), - }, - { - label: 'Sort descending', - icon: SortIcon, - type: 'radio', - checked: isSortingDescending, - onClick: isSortingDescending - ? stopSortingClick - : sortMenuClick.bind(null, true), - }, - // data type menu - { - label: `Type: ${dataTypeDisplayName}`, - icon: PermDataSettingIcon, - subMenu: iterMap( - dataTypeTopLevelMenu.entries(), - ([displayName, record]) => { - const { subMenuItems, typeName } = record - if (typeName) { - const menuEntry = { - label: displayName || typeName, - onClick: () => { - spreadsheetModel.setColumnType(columnNumber, typeName) - }, - } - if (dataTypeName === typeName) { - menuEntry.icon = CheckIcon - } - return menuEntry + const menuItems = [ + // top-level column menu + { + label: 'Sort ascending', + icon: SortIcon, + type: 'radio', + checked: isSortingAscending, + onClick: isSortingAscending + ? stopSortingClick + : sortMenuClick.bind(null, false), + }, + { + label: 'Sort descending', + icon: SortIcon, + type: 'radio', + checked: isSortingDescending, + onClick: isSortingDescending + ? stopSortingClick + : sortMenuClick.bind(null, true), + }, + // data type menu + { + label: `Type: ${dataTypeDisplayName}`, + icon: PermDataSettingIcon, + subMenu: iterMap( + dataTypeTopLevelMenu.entries(), + ([displayName, record]) => { + const { subMenuItems, typeName } = record + if (typeName) { + const menuEntry = { + label: displayName || typeName, + onClick: () => { + spreadsheetModel.setColumnType(columnNumber, typeName) + }, } - if (subMenuItems) { - return { - label: displayName, - icon: subMenuItems.find(i => i.typeName === dataTypeName) - ? CheckIcon - : undefined, - subMenu: subMenuItems.map( - ({ - typeName: subTypeName, - displayName: subDisplayName, - }) => ({ - label: subDisplayName, - icon: - subTypeName === dataTypeName ? CheckIcon : undefined, - onClick: () => { - spreadsheetModel.setColumnType( - columnNumber, - subTypeName, - ) - }, - }), - ), - } + if (dataTypeName === typeName) { + menuEntry.icon = CheckIcon } - return null - }, - ).filter(Boolean), - }, - ] + return menuEntry + } + if (subMenuItems) { + return { + label: displayName, + icon: subMenuItems.find(i => i.typeName === dataTypeName) + ? CheckIcon + : undefined, + subMenu: subMenuItems.map( + ({ typeName: subTypeName, displayName: subDisplayName }) => ({ + label: subDisplayName, + icon: subTypeName === dataTypeName ? CheckIcon : undefined, + onClick: () => { + spreadsheetModel.setColumnType(columnNumber, subTypeName) + }, + }), + ), + } + } + return null + }, + ).filter(Boolean), + }, + ] - // don't display the filter item if this data type doesn't have filtering - // implemented - if (dataType && dataType.hasFilter) { - menuItems.push({ - label: 'Create filter', - icon: FilterListIcon, - onClick: filterMenuClick.bind(null, true), - }) - } + // don't display the filter item if this data type doesn't have filtering + // implemented + if (dataType && dataType.hasFilter) { + menuItems.push({ + label: 'Create filter', + icon: FilterListIcon, + onClick: filterMenuClick.bind(null, true), + }) + } - return ( - - ) - }, - ) - return ColumnMenu -} + return ( + + ) + }, +) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js index f913e04c0a..c277e1ad16 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js @@ -1,76 +1,69 @@ +import React, { useState, useEffect } from 'react' + +import { IconButton, InputAdornment, TextField } from '@material-ui/core' import ClearIcon from '@material-ui/icons/Clear' import FilterIcon from '@material-ui/icons/FilterList' -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer } = jbrequire('mobx-react') - const React = jbrequire('react') - const { useEffect, useState } = React - const { makeStyles } = jbrequire('@material-ui/core/styles') - const IconButton = jbrequire('@material-ui/core/IconButton') - const InputAdornment = jbrequire('@material-ui/core/InputAdornment') - const { useDebounce } = jbrequire('@jbrowse/core/util') - const TextField = jbrequire('@material-ui/core/TextField') +import { observer } from 'mobx-react' +import { makeStyles } from '@material-ui/core/styles' +import { useDebounce } from '@jbrowse/core/util' - const useStyles = makeStyles((/* theme */) => { - return { - textFilterControlEndAdornment: { marginRight: '-18px' }, - } - }) +const useStyles = makeStyles((/* theme */) => { + return { + textFilterControlEndAdornment: { marginRight: '-18px' }, + } +}) - const TextFilter = observer(({ textFilter }) => { - const classes = useStyles() - // this paragraph is silliness to debounce the text filter input - const [textFilterValue, setTextFilterValue] = useState( - textFilter.stringToFind, - ) - const debouncedTextFilter = useDebounce(textFilterValue, 500) - useEffect(() => { - textFilter.setString(debouncedTextFilter) - }, [debouncedTextFilter, textFilter]) +const TextFilter = observer(({ textFilter }) => { + const classes = useStyles() + // this paragraph is silliness to debounce the text filter input + const [textFilterValue, setTextFilterValue] = useState( + textFilter.stringToFind, + ) + const debouncedTextFilter = useDebounce(textFilterValue, 500) + useEffect(() => { + textFilter.setString(debouncedTextFilter) + }, [debouncedTextFilter, textFilter]) - return ( -
- setTextFilterValue(evt.target.value)} - className={classes.textFilterControl} - variant="outlined" - InputProps={{ - startAdornment: ( - - - - ), - endAdornment: ( - + setTextFilterValue(evt.target.value)} + className={classes.textFilterControl} + variant="outlined" + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: ( + + setTextFilterValue('')} + color="secondary" > - setTextFilterValue('')} - color="secondary" - > - - - - ), - }} - /> -
- ) - }) - - const FilterControls = observer(({ model }) => { - // const classes = useStyles() - const textFilter = model.filterControls.rowFullText - return - }) + + + + ), + }} + /> +
+ ) +}) - return FilterControls -} +export default observer(({ model }) => { + // const classes = useStyles() + const textFilter = model.filterControls.rowFullText + return +}) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js index 7e39815a9f..d8155ee859 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js @@ -1,219 +1,217 @@ -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer } = jbrequire('mobx-react') - const React = jbrequire('react') - const { useState, useEffect } = React - const { FileSelector } = jbrequire('@jbrowse/core/ui') - const { readConfObject } = jbrequire('@jbrowse/core/configuration') - const { makeStyles } = jbrequire('@material-ui/core/styles') - const Card = jbrequire('@material-ui/core/Card') - const CardContent = jbrequire('@material-ui/core/CardContent') - const FormControl = jbrequire('@material-ui/core/FormControl') - const FormGroup = jbrequire('@material-ui/core/FormGroup') - const FormLabel = jbrequire('@material-ui/core/FormLabel') - const FormControlLabel = jbrequire('@material-ui/core/FormControlLabel') - const Checkbox = jbrequire('@material-ui/core/Checkbox') - const RadioGroup = jbrequire('@material-ui/core/RadioGroup') - const Radio = jbrequire('@material-ui/core/Radio') - const Container = jbrequire('@material-ui/core/Container') - const Button = jbrequire('@material-ui/core/Button') - const Grid = jbrequire('@material-ui/core/Grid') - const Typography = jbrequire('@material-ui/core/Typography') - const TextField = jbrequire('@material-ui/core/TextField') - const MenuItem = jbrequire('@material-ui/core/MenuItem') - const Select = jbrequire('@material-ui/core/Select') +import React, { useState, useEffect } from 'react' - const useStyles = makeStyles(theme => { - return { - root: { - position: 'relative', - padding: theme.spacing(1), - background: 'white', - }, - errorCard: { - width: '50%', - margin: [[theme.spacing(2), 'auto']], - border: [['2px', 'solid', theme.palette.error.main]], - }, - buttonContainer: { marginTop: theme.spacing(1) }, - } - }) +import { + Card, + CardContent, + FormControl, + FormGroup, + FormLabel, + FormControlLabel, + Checkbox, + RadioGroup, + Radio, + Container, + Button, + Grid, + Typography, + TextField, + MenuItem, + Select, + makeStyles, +} from '@material-ui/core' - const NumberEditor = observer( - ({ model, disabled, modelPropName, modelSetterName }) => { - const [val, setVal] = useState(model[modelPropName]) - useEffect(() => { - const num = parseInt(val, 10) - if (!Number.isNaN(num)) { - if (num > 0) model[modelSetterName](num) - else setVal(1) - } - }, [model, modelSetterName, val]) - return ( - setVal(evt.target.value)} - style={{ width: '2rem', verticalAlign: 'baseline' }} - /> - ) - }, - ) - const ImportForm = observer(({ model }) => { - const classes = useStyles() - const showColumnNameRowControls = - model.fileType === 'CSV' || model.fileType === 'TSV' +import { observer } from 'mobx-react' +import { FileSelector } from '@jbrowse/core/ui' +import { readConfObject } from '@jbrowse/core/configuration' - const { - selectedAssemblyIdx, - setSelectedAssemblyIdx, - fileType, - fileTypes, - setFileType, - hasColumnNameLine, - toggleHasColumnNameLine, - assemblyChoices, - } = model +const useStyles = makeStyles(theme => { + return { + root: { + position: 'relative', + padding: theme.spacing(1), + background: 'white', + }, + errorCard: { + width: '50%', + margin: [[theme.spacing(2), 'auto']], + border: [['2px', 'solid', theme.palette.error.main]], + }, + buttonContainer: { marginTop: theme.spacing(1) }, + } +}) +const NumberEditor = observer( + ({ model, disabled, modelPropName, modelSetterName }) => { + const [val, setVal] = useState(model[modelPropName]) + useEffect(() => { + const num = parseInt(val, 10) + if (!Number.isNaN(num)) { + if (num > 0) model[modelSetterName](num) + else setVal(1) + } + }, [model, modelSetterName, val]) return ( - - - - - Tabular file - - - - - - - - File Type - - - {fileTypes.map(fileTypeName => { - return ( - - setFileType(fileTypeName)} - control={} - label={fileTypeName} - /> - - ) - })} - - - - - {showColumnNameRowControls ? ( - - - Column Names -
- - } - /> - -
-
-
- ) : null} - - - Associated with assembly - + + + +
+ {showColumnNameRowControls ? ( + + + Column Names +
+ + } + /> + +
- - {model.canCancel ? ( - - ) : null}{' '} + ) : null} + + + Associated with assembly + + + + + {model.canCancel ? ( - + ) : null}{' '} + -
- ) - }) - - const ErrorDisplay = observer(({ errorMessage }) => { - const classes = useStyles() - return ( - - - - {String(errorMessage)} - - - - ) - }) + + + ) +}) - const ImportWizard = observer(({ model }) => { - const classes = useStyles() - return ( - <> - {model.error ? ( - - - - ) : null} - - - ) - }) +const ErrorDisplay = observer(({ errorMessage }) => { + const classes = useStyles() + return ( + + + + {String(errorMessage)} + + + + ) +}) - return ImportWizard -} +export default observer(({ model }) => { + const classes = useStyles() + return ( + <> + {model.error ? ( + + + + ) : null} + + + ) +}) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx b/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx index c61f84adb8..eaba44a525 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx @@ -1,4 +1,5 @@ -import PluginManager from '@jbrowse/core/PluginManager' +import React from 'react' +import { observer } from 'mobx-react' import { Menu, MenuItem } from '@jbrowse/core/ui' import { InstanceOfModelReturnedBy } from '@jbrowse/core/util' @@ -10,56 +11,49 @@ export interface Props { spreadsheetModel: InstanceOfModelReturnedBy } -export default (pluginManager: PluginManager) => { - const { lib } = pluginManager - const { observer } = lib['mobx-react'] - const React = lib.react - - const RowMenu = observer(({ viewModel, spreadsheetModel }: Props) => { - const currentRowMenu = spreadsheetModel.rowMenuPosition - const { setRowMenuPosition } = spreadsheetModel - - const rowMenuClose = () => { - setRowMenuPosition(null) - } - - const rowNumber = spreadsheetModel.rowMenuPosition?.rowNumber - if (rowNumber === undefined) return null - - const row = spreadsheetModel.rowSet.rows[rowNumber - 1] - - function handleMenuItemClick(_event: unknown, callback: Function) { - callback(viewModel, spreadsheetModel, rowNumber, row) - rowMenuClose() +export default observer(({ viewModel, spreadsheetModel }: Props) => { + const currentRowMenu = spreadsheetModel.rowMenuPosition + const { setRowMenuPosition } = spreadsheetModel + + const rowMenuClose = () => { + setRowMenuPosition(null) + } + + const rowNumber = spreadsheetModel.rowMenuPosition?.rowNumber + if (rowNumber === undefined) return null + + const row = spreadsheetModel.rowSet.rows[rowNumber - 1] + + function handleMenuItemClick(_event: unknown, callback: Function) { + callback(viewModel, spreadsheetModel, rowNumber, row) + rowMenuClose() + } + + // got through and evaluate all the `disabled` callbacks of the menu items + const menuItems: MenuItem[] = viewModel.rowMenuItems.map(item => { + if (typeof item.disabled === 'function') { + const disabled = item.disabled( + viewModel, + spreadsheetModel, + rowNumber, + row, + ) + return { ...item, disabled } } - - // got through and evaluate all the `disabled` callbacks of the menu items - const menuItems: MenuItem[] = viewModel.rowMenuItems.map(item => { - if (typeof item.disabled === 'function') { - const disabled = item.disabled( - viewModel, - spreadsheetModel, - rowNumber, - row, - ) - return { ...item, disabled } - } - return item - }) - - return ( - - ) + return item }) - return RowMenu -} + + return ( + + ) +}) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js index d80f017876..24ccbbe56c 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js @@ -1,9 +1,20 @@ +import React, { useState } from 'react' +import { observer } from 'mobx-react' +import { getParent } from 'mobx-state-tree' +import { grey, indigo } from '@material-ui/core/colors' +import { + Checkbox, + IconButton, + Tooltip, + FormControlLabel, + makeStyles, +} from '@material-ui/core' import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp' import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown' import CropFreeIcon from '@material-ui/icons/CropFree' import ArrowDropDown from '@material-ui/icons/ArrowDropDown' -import ColumnMenuFactory from './ColumnMenu' -import RowMenuFactory from './RowMenu' +import ColumnMenu from './ColumnMenu' +import RowMenu from './RowMenu' /* eslint-disable react/prop-types */ export function numToColName(num) { @@ -22,354 +33,325 @@ export function numToColName(num) { throw new RangeError('column number out of range') } -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer, PropTypes: MobxPropTypes } = jbrequire('mobx-react') - const React = jbrequire('react') - const { useState } = React - const ReactPropTypes = jbrequire('prop-types') - const { getParent } = jbrequire('mobx-state-tree') - - const { makeStyles } = jbrequire('@material-ui/core/styles') - const { grey, indigo } = jbrequire('@material-ui/core/colors') - const Checkbox = jbrequire('@material-ui/core/Checkbox') - const IconButton = jbrequire('@material-ui/core/IconButton') - const Tooltip = jbrequire('@material-ui/core/Tooltip') - const FormControlLabel = jbrequire('@material-ui/core/FormControlLabel') - - const ColumnMenu = jbrequire(ColumnMenuFactory) - const RowMenu = jbrequire(RowMenuFactory) - - const useStyles = makeStyles(theme => { - return { - root: { - position: 'relative', - marginBottom: theme.spacing(1), - background: grey[500], - overflow: 'auto', - }, - dataTable: { - borderCollapse: 'collapse', - borderSpacing: 0, - boxSizing: 'border-box', - '& td': { - border: `1px solid ${grey[300]}`, - padding: '0.2rem', - maxWidth: '50em', - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - }, - dataTableBody: { - background: 'white', - }, - rowNumCell: { - background: grey[200], - textAlign: 'left', - border: `1px solid ${grey[300]}`, - position: 'relative', - padding: '0 2px 0 0', - whiteSpace: 'nowrap', - userSelect: 'none', - }, - rowNumber: { - fontWeight: 'normal', - display: 'inline-block', - flex: 'none', - paddingRight: '20px', - margin: 0, - whiteSpace: 'nowrap', - }, - rowMenuButton: { - padding: 0, - margin: 0, - position: 'absolute', - right: 0, - display: 'inline-block', - whiteSpace: 'nowrap', - flex: 'none', - }, - rowMenuButtonIcon: {}, - rowSelector: { - position: 'relative', - top: '-2px', - margin: 0, - padding: '0 0.2rem', - }, - columnHead: { - fontWeight: 'normal', - background: grey[200], +const useStyles = makeStyles(theme => { + return { + root: { + position: 'relative', + marginBottom: theme.spacing(1), + background: grey[500], + overflow: 'auto', + }, + dataTable: { + borderCollapse: 'collapse', + borderSpacing: 0, + boxSizing: 'border-box', + '& td': { border: `1px solid ${grey[300]}`, - position: 'sticky', - top: '-1px', - zIndex: 2, - whiteSpace: 'nowrap', - padding: [[0, theme.spacing(1)]], - }, - sortIndicator: { - position: 'relative', - top: '0.2rem', - fontSize: '1rem', - }, - columnButtonContainer: { - display: 'none', - position: 'absolute', - right: 0, - top: 0, - background: grey[100], - height: '100%', - boxSizing: 'border-box', - borderLeft: `1px solid ${grey[300]}`, + padding: '0.2rem', + maxWidth: '50em', + overflow: 'hidden', + textOverflow: 'ellipsis', }, - columnButton: { - padding: 0, - }, - topLeftCorner: { - background: grey[300], - position: 'sticky', - top: '-1px', - zIndex: 2, - minWidth: theme.spacing(2), - textAlign: 'left', - }, - dataRowSelected: { + }, + dataTableBody: { + background: 'white', + }, + rowNumCell: { + background: grey[200], + textAlign: 'left', + border: `1px solid ${grey[300]}`, + position: 'relative', + padding: '0 2px 0 0', + whiteSpace: 'nowrap', + userSelect: 'none', + }, + rowNumber: { + fontWeight: 'normal', + display: 'inline-block', + flex: 'none', + paddingRight: '20px', + margin: 0, + whiteSpace: 'nowrap', + }, + rowMenuButton: { + padding: 0, + margin: 0, + position: 'absolute', + right: 0, + display: 'inline-block', + whiteSpace: 'nowrap', + flex: 'none', + }, + rowMenuButtonIcon: {}, + rowSelector: { + position: 'relative', + top: '-2px', + margin: 0, + padding: '0 0.2rem', + }, + columnHead: { + fontWeight: 'normal', + background: grey[200], + border: `1px solid ${grey[300]}`, + position: 'sticky', + top: '-1px', + zIndex: 2, + whiteSpace: 'nowrap', + padding: [[0, theme.spacing(1)]], + }, + sortIndicator: { + position: 'relative', + top: '0.2rem', + fontSize: '1rem', + }, + columnButtonContainer: { + display: 'none', + position: 'absolute', + right: 0, + top: 0, + background: grey[100], + height: '100%', + boxSizing: 'border-box', + borderLeft: `1px solid ${grey[300]}`, + }, + columnButton: { + padding: 0, + }, + topLeftCorner: { + background: grey[300], + position: 'sticky', + top: '-1px', + zIndex: 2, + minWidth: theme.spacing(2), + textAlign: 'left', + }, + dataRowSelected: { + background: indigo[100], + '& th': { background: indigo[100], - '& th': { - background: indigo[100], - }, }, - emptyMessage: { captionSide: 'bottom' }, - } - }) + }, + emptyMessage: { captionSide: 'bottom' }, + } +}) - const CellData = observer(({ cell, spreadsheetModel, columnNumber }) => { - const { dataType } = spreadsheetModel.columns.get(columnNumber) - if (dataType.DataCellReactComponent) { - return ( - - ) - } +const CellData = observer(({ cell, spreadsheetModel, columnNumber }) => { + const { dataType } = spreadsheetModel.columns.get(columnNumber) + if (dataType.DataCellReactComponent) { + return ( + + ) + } - return cell.text - }) + return cell.text +}) - const DataRow = observer(({ rowModel, rowNumber, spreadsheetModel }) => { - const classes = useStyles() - const { hideRowSelection, columnDisplayOrder } = spreadsheetModel - let rowClass = classes.dataRow - if (rowModel.isSelected) rowClass += ` ${classes.dataRowSelected}` +const DataRow = observer(({ rowModel, rowNumber, spreadsheetModel }) => { + const classes = useStyles() + const { hideRowSelection, columnDisplayOrder } = spreadsheetModel + let rowClass = classes.dataRow + if (rowModel.isSelected) rowClass += ` ${classes.dataRowSelected}` - function labelClick(evt) { - rowModel.toggleSelect() - evt.stopPropagation() - evt.preventDefault() - } + function labelClick(evt) { + rowModel.toggleSelect() + evt.stopPropagation() + evt.preventDefault() + } - function rowButtonClick(event) { - spreadsheetModel.setRowMenuPosition({ - anchorEl: event.currentTarget, - rowNumber, - }) - event.preventDefault() - event.stopPropagation() - } + function rowButtonClick(event) { + spreadsheetModel.setRowMenuPosition({ + anchorEl: event.currentTarget, + rowNumber, + }) + event.preventDefault() + event.stopPropagation() + } - return ( - - - - ) - } - label={rowModel.id} + return ( + + + + ) + } + label={rowModel.id} + /> + + + + + {columnDisplayOrder.map(colNumber => ( + + - - - - - {columnDisplayOrder.map(colNumber => ( - - - - ))} - - ) - }) + + ))} + + ) +}) - function SortIndicator({ model, columnNumber }) { - const classes = useStyles() - const sortSpec = model.sortColumns.find( - c => c.columnNumber === columnNumber, - ) +function SortIndicator({ model, columnNumber }) { + const classes = useStyles() + const sortSpec = model.sortColumns.find(c => c.columnNumber === columnNumber) - if (sortSpec) { - const { descending } = sortSpec - return descending ? ( - - ) : ( - - ) - } - return null + if (sortSpec) { + const { descending } = sortSpec + return descending ? ( + + ) : ( + + ) } + return null +} - const DataTableBody = observer( - ({ rows, spreadsheetModel, page, rowsPerPage }) => { - const classes = useStyles() - return ( - - {rows.slice(rowsPerPage * page, rowsPerPage * (page + 1)).map(row => ( - - ))} - - ) - }, - ) - - const DataTable = observer(({ model, page, rowsPerPage }) => { - const { columnDisplayOrder, columns, hasColumnNames, rowSet } = model +const DataTableBody = observer( + ({ rows, spreadsheetModel, page, rowsPerPage }) => { const classes = useStyles() - - // column menu active state - const [currentColumnMenu, setColumnMenu] = useState(null) - function columnButtonClick(colNumber, evt) { - setColumnMenu({ - colNumber, - anchorEl: evt.currentTarget, - }) - } - - // column header hover state - const [currentHoveredColumn, setHoveredColumn] = useState(null) - function columnHeaderMouseOver(colNumber /* , evt */) { - setHoveredColumn(colNumber) - } - function columnHeaderMouseOut(/* colNumber, evt */) { - setHoveredColumn(null) - } - - const totalRows = rowSet.count - const rows = rowSet.sortedFilteredRows - return ( - <> - - - - - - - {columnDisplayOrder.map(colNumber => ( - - ))} - - - + {rows.slice(rowsPerPage * page, rowsPerPage * (page + 1)).map(row => ( + - {!rows.length ? ( - - ) : null} -
- - - - - - - - - - {(hasColumnNames && columns.get(colNumber).name) || - numToColName(colNumber)} -
- - - -
-
- {totalRows ? 'no rows match criteria' : 'no rows present'} -
- + ))} + ) - }) + }, +) - function Spreadsheet({ model, height, page, rowsPerPage }) { - const classes = useStyles() +const DataTable = observer(({ model, page, rowsPerPage }) => { + const { columnDisplayOrder, columns, hasColumnNames, rowSet } = model + const classes = useStyles() - return ( -
- {model && model.rowSet && model.rowSet.isLoaded && model.initialized ? ( - - ) : ( -
Loading...
- )} -
- ) + // column menu active state + const [currentColumnMenu, setColumnMenu] = useState(null) + function columnButtonClick(colNumber, evt) { + setColumnMenu({ + colNumber, + anchorEl: evt.currentTarget, + }) } - Spreadsheet.propTypes = { - model: MobxPropTypes.objectOrObservableObject, - height: ReactPropTypes.number.isRequired, + + // column header hover state + const [currentHoveredColumn, setHoveredColumn] = useState(null) + function columnHeaderMouseOver(colNumber /* , evt */) { + setHoveredColumn(colNumber) } - Spreadsheet.defaultProps = { - model: undefined, + function columnHeaderMouseOut(/* colNumber, evt */) { + setHoveredColumn(null) } - return observer(Spreadsheet) -} + + const totalRows = rowSet.count + const rows = rowSet.sortedFilteredRows + + return ( + <> + + + + + + + {columnDisplayOrder.map(colNumber => ( + + ))} + + + + {!rows.length ? ( + + ) : null} +
+ + + + + + + + + + {(hasColumnNames && columns.get(colNumber).name) || + numToColName(colNumber)} +
+ + + +
+
+ {totalRows ? 'no rows match criteria' : 'no rows present'} +
+ + ) +}) + +export default observer(({ model, height, page, rowsPerPage }) => { + const classes = useStyles() + + return ( +
+ {model && model.rowSet && model.rowSet.isLoaded && model.initialized ? ( + + ) : ( +
Loading...
+ )} +
+ ) +}) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js index 633ebf89f5..af86336a2a 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js @@ -1,254 +1,245 @@ +import React from 'react' +import { + FormGroup, + Grid, + IconButton, + TablePagination, + makeStyles, +} from '@material-ui/core' +import { observer } from 'mobx-react' +import { ResizeHandle } from '@jbrowse/core/ui' import FolderOpenIcon from '@material-ui/icons/FolderOpen' -import ImportWizardFactory from './ImportWizard' -import SpreadsheetFactory from './Spreadsheet' -import GlobalFilterControlsFactory from './GlobalFilterControls' -import ColumnFilterControlsFactory from './ColumnFilterControls' -export default pluginManager => { - const { jbrequire } = pluginManager - const { observer, PropTypes } = jbrequire('mobx-react') - const React = jbrequire('react') - const IconButton = jbrequire('@material-ui/core/IconButton') - const { FormGroup, TablePagination } = jbrequire('@material-ui/core') - const { makeStyles } = jbrequire('@material-ui/core/styles') - const Grid = jbrequire('@material-ui/core/Grid') - const { ResizeHandle } = jbrequire('@jbrowse/core/ui') - - const ImportWizard = jbrequire(ImportWizardFactory) - const Spreadsheet = jbrequire(SpreadsheetFactory) - const GlobalFilterControls = jbrequire(GlobalFilterControlsFactory) - const ColumnFilterControls = jbrequire(ColumnFilterControlsFactory) - - const headerHeight = 52 - const colFilterHeight = 46 - const statusBarHeight = 40 - - const useStyles = makeStyles(theme => ({ - root: { - position: 'relative', - marginBottom: theme.spacing(1), - background: 'white', - overflow: 'hidden', - }, - header: { - overflow: 'hidden', - whiteSpace: 'nowrap', - boxSizing: 'border-box', - height: headerHeight, - paddingLeft: theme.spacing(1), - }, - contentArea: { overflow: 'auto' }, - columnFilter: { - overflow: 'hidden', - whiteSpace: 'nowrap', - boxSizing: 'border-box', - height: headerHeight, - paddingLeft: theme.spacing(1), - }, - viewControls: { - margin: 0, - }, - rowCount: { - marginLeft: theme.spacing(1), - }, - statusBar: { - position: 'absolute', - background: theme.palette.background.light, - left: 0, - bottom: 0, - height: statusBarHeight, - width: '100%', - boxSizing: 'border-box', - borderTop: '1px outset #b1b1b1', - paddingLeft: theme.spacing(1), - }, - verticallyCenter: { - display: 'flex', - justifyContent: 'center', - flexDirection: 'column', - }, - spacer: { - flexGrow: 1, - }, - textFilterControlAdornment: { marginRight: '-18px' }, - })) - - const ViewControls = observer(({ model }) => { - const classes = useStyles() - return ( - - - model.setImportMode()} - className={classes.iconButton} - title="open a tabular file" - data-testid="spreadsheet_view_open" - color="secondary" - > - - - +import ImportWizard from './ImportWizard' +import Spreadsheet from './Spreadsheet' +import GlobalFilterControls from './GlobalFilterControls' +import ColumnFilterControls from './ColumnFilterControls' + +const headerHeight = 52 +const colFilterHeight = 46 +const statusBarHeight = 40 + +const useStyles = makeStyles(theme => ({ + root: { + position: 'relative', + marginBottom: theme.spacing(1), + background: 'white', + overflow: 'hidden', + }, + header: { + overflow: 'hidden', + whiteSpace: 'nowrap', + boxSizing: 'border-box', + height: headerHeight, + paddingLeft: theme.spacing(1), + }, + contentArea: { overflow: 'auto' }, + columnFilter: { + overflow: 'hidden', + whiteSpace: 'nowrap', + boxSizing: 'border-box', + height: headerHeight, + paddingLeft: theme.spacing(1), + }, + viewControls: { + margin: 0, + }, + rowCount: { + marginLeft: theme.spacing(1), + }, + statusBar: { + position: 'absolute', + background: theme.palette.background.light, + left: 0, + bottom: 0, + height: statusBarHeight, + width: '100%', + boxSizing: 'border-box', + borderTop: '1px outset #b1b1b1', + paddingLeft: theme.spacing(1), + }, + verticallyCenter: { + display: 'flex', + justifyContent: 'center', + flexDirection: 'column', + }, + spacer: { + flexGrow: 1, + }, + textFilterControlAdornment: { marginRight: '-18px' }, +})) + +const ViewControls = observer(({ model }) => { + const classes = useStyles() + return ( + + + model.setImportMode()} + className={classes.iconButton} + title="open a tabular file" + data-testid="spreadsheet_view_open" + color="secondary" + > + + - ) - }) - - const RowCountMessage = observer(({ spreadsheet }) => { - if (spreadsheet && spreadsheet.rowSet.isLoaded) { - const { - passingFiltersCount, - count, - selectedCount, - selectedAndPassingFiltersCount, - } = spreadsheet.rowSet - - let rowMessage - if (passingFiltersCount !== count) { - rowMessage = `${spreadsheet.rowSet.passingFiltersCount} rows of ${spreadsheet.rowSet.count} total` - if (selectedCount) { - rowMessage += `, ${selectedAndPassingFiltersCount} selected` - const selectedAndNotPassingFiltersCount = - selectedCount - selectedAndPassingFiltersCount - if (selectedAndNotPassingFiltersCount) { - rowMessage += ` (${selectedAndNotPassingFiltersCount} selected rows do not pass filters)` - } - } - } else { - rowMessage = `${spreadsheet.rowSet.count} rows` - if (selectedCount) { - rowMessage += `, ${selectedCount} selected` + + ) +}) + +const RowCountMessage = observer(({ spreadsheet }) => { + if (spreadsheet && spreadsheet.rowSet.isLoaded) { + const { + passingFiltersCount, + count, + selectedCount, + selectedAndPassingFiltersCount, + } = spreadsheet.rowSet + + let rowMessage + if (passingFiltersCount !== count) { + rowMessage = `${spreadsheet.rowSet.passingFiltersCount} rows of ${spreadsheet.rowSet.count} total` + if (selectedCount) { + rowMessage += `, ${selectedAndPassingFiltersCount} selected` + const selectedAndNotPassingFiltersCount = + selectedCount - selectedAndPassingFiltersCount + if (selectedAndNotPassingFiltersCount) { + rowMessage += ` (${selectedAndNotPassingFiltersCount} selected rows do not pass filters)` } } - return rowMessage + } else { + rowMessage = `${spreadsheet.rowSet.count} rows` + if (selectedCount) { + rowMessage += `, ${selectedCount} selected` + } } - return null - }) + return rowMessage + } + return null +}) - function SpreadsheetView({ model }) { - const classes = useStyles() +export default observer(({ model }) => { + const classes = useStyles() - const { spreadsheet, filterControls } = model + const { spreadsheet, filterControls } = model - const colFilterCount = filterControls.columnFilters.length - const [page, setPage] = React.useState(0) - const [rowsPerPage, setRowsPerPage] = React.useState(100) + const colFilterCount = filterControls.columnFilters.length + const [page, setPage] = React.useState(0) + const [rowsPerPage, setRowsPerPage] = React.useState(100) - const handleChangePage = (event, newPage) => { - setPage(newPage) - } + const handleChangePage = (event, newPage) => { + setPage(newPage) + } - const handleChangeRowsPerPage = event => { - setRowsPerPage(+event.target.value) - setPage(0) - } + const handleChangeRowsPerPage = event => { + setRowsPerPage(+event.target.value) + setPage(0) + } - return ( -
- - {model.hideViewControls ? null : ( - - - - )} - {model.mode !== 'display' || model.hideFilterControls ? null : ( - - - - )} - + return ( +
+ + {model.hideViewControls ? null : ( + + + + )} + {model.mode !== 'display' || model.hideFilterControls ? null : ( + + + + )} + - {model.mode !== 'display' || model.hideFilterControls - ? null - : model.filterControls.columnFilters.map((filter, filterNumber) => { - return ( - - ) - })} + {model.mode !== 'display' || model.hideFilterControls + ? null + : model.filterControls.columnFilters.map((filter, filterNumber) => { + return ( + + ) + })} +
+ {model.mode !== 'import' ? null : ( + + )}
- {model.mode !== 'import' ? null : ( - - )} -
- -
+
+
-
- {spreadsheet ? ( - -
- -
-
- -
- - ) : null} -
- {model.hideVerticalResizeHandle ? null : ( - - )} +
+ {spreadsheet ? ( + +
+ +
+
+ +
+ + ) : null}
- ) - } - SpreadsheetView.propTypes = { - model: PropTypes.objectOrObservableObject.isRequired, - } - return observer(SpreadsheetView) -} + {model.hideVerticalResizeHandle ? null : ( + + )} +
+ ) +}) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts b/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts index 4de0c51101..93a07b3de9 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts +++ b/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts @@ -1,10 +1,6 @@ import { getSession } from '@jbrowse/core/util' import PluginManager from '@jbrowse/core/PluginManager' import { unzip } from '@gmod/bgzf-filehandle' -import { parseCsvBuffer, parseTsvBuffer } from '../importAdapters/ImportUtils' -import { parseVcfBuffer } from '../importAdapters/VcfImport' -import { parseBedBuffer, parseBedPEBuffer } from '../importAdapters/BedImport' -import { parseSTARFusionBuffer } from '../importAdapters/STARFusionImport' // 30MB const IMPORT_SIZE_LIMIT = 30_000_000 @@ -17,12 +13,14 @@ export default (pluginManager: PluginManager) => { const fileTypes = ['CSV', 'TSV', 'VCF', 'BED', 'BEDPE', 'STAR-Fusion'] const fileTypeParsers = { - CSV: parseCsvBuffer, - TSV: parseTsvBuffer, - VCF: parseVcfBuffer, - BED: parseBedBuffer, - BEDPE: parseBedPEBuffer, - 'STAR-Fusion': parseSTARFusionBuffer, + CSV: import('../importAdapters/ImportUtils').then(r => r.parseCsvBuffer), + TSV: import('../importAdapters/ImportUtils').then(r => r.parseTsvBuffer), + VCF: import('../importAdapters/VcfImport').then(r => r.parseVcfBuffer), + BED: import('../importAdapters/BedImport').then(r => r.parseBedBuffer), + BEDPE: import('../importAdapters/BedImport').then(r => r.parseBedPEBuffer), + 'STAR-Fusion': import('../importAdapters/STARFusionImport').then( + r => r.parseSTARFusionBuffer, + ), } // regexp used to guess the type of a file or URL from its file extension const fileTypesRegexp = new RegExp( @@ -143,16 +141,19 @@ export default (pluginManager: PluginManager) => { // fetch and parse the file, make a new Spreadsheet model for it, // then set the parent to display it - import() { + async import() { try { if (!self.fileSource) return - const typeParser = - fileTypeParsers[self.fileType as keyof typeof fileTypeParsers] - if (!typeParser) - throw new Error(`cannot open files of type '${self.fileType}'`) + if (self.loading) throw new Error('cannot import, load already in progress') self.loading = true + const typeParser = await fileTypeParsers[ + self.fileType as keyof typeof fileTypeParsers + ] + if (!typeParser) { + throw new Error(`cannot open files of type '${self.fileType}'`) + } const filehandle = openLocation(self.fileSource) filehandle diff --git a/plugins/spreadsheet-view/src/index.ts b/plugins/spreadsheet-view/src/index.ts index cdaa177446..75a16f9752 100644 --- a/plugins/spreadsheet-view/src/index.ts +++ b/plugins/spreadsheet-view/src/index.ts @@ -1,16 +1,24 @@ +import { lazy } from 'react' import { AbstractSessionModel, isAbstractMenuManager } from '@jbrowse/core/util' import PluginManager from '@jbrowse/core/PluginManager' import Plugin from '@jbrowse/core/Plugin' import ViewComfyIcon from '@material-ui/icons/ViewComfy' -import SpreadsheetViewTypeFactory from './SpreadsheetView/SpreadsheetViewType' +import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType' +import stateModelFactory from './SpreadsheetView/models/SpreadsheetView' export default class SpreadsheetViewPlugin extends Plugin { name = 'SpreadsheetViewPlugin' install(pluginManager: PluginManager) { - pluginManager.addViewType(() => - pluginManager.jbrequire(SpreadsheetViewTypeFactory), - ) + pluginManager.addViewType(() => { + return new ViewType({ + name: 'SpreadsheetView', + stateModel: stateModelFactory(pluginManager), + LazyReactComponent: lazy( + () => import('./SpreadsheetView/components/SpreadsheetView'), + ), + }) + }) } configure(pluginManager: PluginManager) { From a2a9a26b7cb7398c93db3b16ae3736114ac65853 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 15:34:23 -0400 Subject: [PATCH 07/41] Import callback --- .../SpreadsheetView/models/ImportWizard.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts b/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts index 93a07b3de9..e1e2fc6022 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts +++ b/plugins/spreadsheet-view/src/SpreadsheetView/models/ImportWizard.ts @@ -13,14 +13,20 @@ export default (pluginManager: PluginManager) => { const fileTypes = ['CSV', 'TSV', 'VCF', 'BED', 'BEDPE', 'STAR-Fusion'] const fileTypeParsers = { - CSV: import('../importAdapters/ImportUtils').then(r => r.parseCsvBuffer), - TSV: import('../importAdapters/ImportUtils').then(r => r.parseTsvBuffer), - VCF: import('../importAdapters/VcfImport').then(r => r.parseVcfBuffer), - BED: import('../importAdapters/BedImport').then(r => r.parseBedBuffer), - BEDPE: import('../importAdapters/BedImport').then(r => r.parseBedPEBuffer), - 'STAR-Fusion': import('../importAdapters/STARFusionImport').then( - r => r.parseSTARFusionBuffer, - ), + CSV: () => + import('../importAdapters/ImportUtils').then(r => r.parseCsvBuffer), + TSV: () => + import('../importAdapters/ImportUtils').then(r => r.parseTsvBuffer), + VCF: () => + import('../importAdapters/VcfImport').then(r => r.parseVcfBuffer), + BED: () => + import('../importAdapters/BedImport').then(r => r.parseBedBuffer), + BEDPE: () => + import('../importAdapters/BedImport').then(r => r.parseBedPEBuffer), + 'STAR-Fusion': () => + import('../importAdapters/STARFusionImport').then( + r => r.parseSTARFusionBuffer, + ), } // regexp used to guess the type of a file or URL from its file extension const fileTypesRegexp = new RegExp( @@ -150,7 +156,7 @@ export default (pluginManager: PluginManager) => { self.loading = true const typeParser = await fileTypeParsers[ self.fileType as keyof typeof fileTypeParsers - ] + ]() if (!typeParser) { throw new Error(`cannot open files of type '${self.fileType}'`) } From cb569830b05915c14191ffec2fbc34a476bbe6aa Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 15:47:04 -0400 Subject: [PATCH 08/41] Fix some lint --- packages/core/ui/NewSessionCards.tsx | 3 +++ .../BaseChordDisplay/models/BaseChordDisplayModel.ts | 2 +- .../src/CircularView/components/CircularView.js | 2 +- products/jbrowse-desktop/src/StartScreen.tsx | 10 +++++----- products/jbrowse-desktop/src/declare.d.ts | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 products/jbrowse-desktop/src/declare.d.ts diff --git a/packages/core/ui/NewSessionCards.tsx b/packages/core/ui/NewSessionCards.tsx index 67eebb42b9..5f70bd041f 100644 --- a/packages/core/ui/NewSessionCards.tsx +++ b/packages/core/ui/NewSessionCards.tsx @@ -64,6 +64,7 @@ function NewSessionCard({ ) } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function NewEmptySession({ root }: { root: any }) { return ( {} } const useStyles = makeStyles(theme => ({ diff --git a/products/jbrowse-desktop/src/declare.d.ts b/products/jbrowse-desktop/src/declare.d.ts new file mode 100644 index 0000000000..b8d9f54f9e --- /dev/null +++ b/products/jbrowse-desktop/src/declare.d.ts @@ -0,0 +1 @@ +declare module '@jbrowse/core/ui/RecentSessionCard' From dde73255e226a681f13909366dc9f2b5c4b80645 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 18:21:05 -0400 Subject: [PATCH 09/41] Add drawer widgets to lazy --- packages/core/ui/DrawerWidget.js | 24 ++++++++++++++---------- plugins/config/src/index.ts | 9 +++++++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/core/ui/DrawerWidget.js b/packages/core/ui/DrawerWidget.js index f3a3e7fc90..3a64b7c615 100644 --- a/packages/core/ui/DrawerWidget.js +++ b/packages/core/ui/DrawerWidget.js @@ -1,18 +1,20 @@ -import Typography from '@material-ui/core/Typography' -import AppBar from '@material-ui/core/AppBar' -import IconButton from '@material-ui/core/IconButton' -import Toolbar from '@material-ui/core/Toolbar' -import Select from '@material-ui/core/Select' -import MenuItem from '@material-ui/core/MenuItem' +import React, { Suspense } from 'react' +import { + AppBar, + IconButton, + ListItemSecondaryAction, + MenuItem, + Select, + Toolbar, + Typography, + makeStyles, +} from '@material-ui/core' import DeleteIcon from '@material-ui/icons/Delete' import CloseIcon from '@material-ui/icons/Close' import MinimizeIcon from '@material-ui/icons/Minimize' -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction' -import { makeStyles } from '@material-ui/core/styles' import { fade } from '@material-ui/core/styles/colorManipulator' import { observer, PropTypes } from 'mobx-react' import { getEnv } from 'mobx-state-tree' -import React from 'react' import Drawer from './Drawer' const useStyles = makeStyles(theme => ({ @@ -149,7 +151,9 @@ const DrawerWidget = observer(props => {
- + Loading
}> + +
) diff --git a/plugins/config/src/index.ts b/plugins/config/src/index.ts index 6c7dd83c62..7a7f587778 100644 --- a/plugins/config/src/index.ts +++ b/plugins/config/src/index.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType' import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' import Plugin from '@jbrowse/core/Plugin' @@ -5,7 +6,6 @@ import PluginManager from '@jbrowse/core/PluginManager' import { configSchema as ConfigurationEditorConfigSchema, HeadingComponent as ConfigurationEditorHeadingComponent, - ReactComponent as ConfigurationEditorReactComponent, stateModelFactory as ConfigurationEditorStateModelFactory, } from './ConfigurationEditorWidget' import { @@ -64,7 +64,12 @@ export default class extends Plugin { HeadingComponent: ConfigurationEditorHeadingComponent, configSchema: ConfigurationEditorConfigSchema, stateModel: ConfigurationEditorStateModelFactory(pluginManager), - ReactComponent: ConfigurationEditorReactComponent, + ReactComponent: lazy( + () => + import( + './ConfigurationEditorWidget/components/ConfigurationEditor' + ), + ), }) }) } From faf24be890a6b4d53428774b1d1eaee8ee29af96 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 18:23:10 -0400 Subject: [PATCH 10/41] Remove LazyReactComponent, just use ReactComponent and require Suspense --- .../core/pluggableElementTypes/ViewType.ts | 28 +++++-------------- packages/core/ui/App.js | 14 ++-------- plugins/circular-view/src/index.ts | 2 +- plugins/dotplot-view/src/index.ts | 2 +- plugins/linear-genome-view/src/index.ts | 2 +- plugins/spreadsheet-view/src/index.ts | 2 +- 6 files changed, 14 insertions(+), 36 deletions(-) diff --git a/packages/core/pluggableElementTypes/ViewType.ts b/packages/core/pluggableElementTypes/ViewType.ts index f2eb64c89c..bb2716a1b7 100644 --- a/packages/core/pluggableElementTypes/ViewType.ts +++ b/packages/core/pluggableElementTypes/ViewType.ts @@ -12,33 +12,19 @@ type ViewReactComponent = React.ComponentType<{ export default class ViewType extends PluggableElementBase { ReactComponent?: ViewReactComponent - LazyReactComponent?: ViewReactComponent - stateModel: IAnyModelType displayTypes: DisplayType[] = [] - constructor( - stuff: - | { - name: string - ReactComponent: ViewReactComponent - stateModel: IAnyModelType - } - | { - name: string - LazyReactComponent: ViewReactComponent - stateModel: IAnyModelType - }, - ) { + constructor(stuff: { + name: string + ReactComponent: ViewReactComponent + stateModel: IAnyModelType + }) { super(stuff) - if ('ReactComponent' in stuff) { - this.ReactComponent = stuff.ReactComponent - } else { - this.LazyReactComponent = stuff.LazyReactComponent - } + this.ReactComponent = stuff.ReactComponent this.stateModel = stuff.stateModel - if (!this.ReactComponent && !this.LazyReactComponent) { + if (!this.ReactComponent) { throw new Error(`no ReactComponent defined for view ${this.name}`) } if (!this.stateModel) { diff --git a/packages/core/ui/App.js b/packages/core/ui/App.js index 3386df6a12..659ed078f9 100644 --- a/packages/core/ui/App.js +++ b/packages/core/ui/App.js @@ -144,28 +144,20 @@ function App({ session, HeaderButtons }) { if (!viewType) { throw new Error(`unknown view type ${view.type}`) } - const { LazyReactComponent, ReactComponent } = viewType + const { ReactComponent } = viewType return ( session.removeView(view)} > - {LazyReactComponent ? ( - Loading...
}> - - - ) : ( + Loading...
}> - )} + ) })} diff --git a/plugins/circular-view/src/index.ts b/plugins/circular-view/src/index.ts index 21a916962a..339b8fbe3b 100644 --- a/plugins/circular-view/src/index.ts +++ b/plugins/circular-view/src/index.ts @@ -13,7 +13,7 @@ export default class CircularViewPlugin extends Plugin { pluginManager.addViewType( () => new ViewType({ - LazyReactComponent: lazy( + ReactComponent: lazy( () => import('./CircularView/components/CircularView'), ), stateModel: stateModelFactory(pluginManager), diff --git a/plugins/dotplot-view/src/index.ts b/plugins/dotplot-view/src/index.ts index 7c08e25cfc..a804cf4178 100644 --- a/plugins/dotplot-view/src/index.ts +++ b/plugins/dotplot-view/src/index.ts @@ -119,7 +119,7 @@ export default class DotplotPlugin extends Plugin { return new ViewType({ name: 'DotplotView', stateModel: stateModelFactory(pluginManager), - LazyReactComponent: lazy( + ReactComponent: lazy( () => import('./DotplotView/components/DotplotView'), ), }) diff --git a/plugins/linear-genome-view/src/index.ts b/plugins/linear-genome-view/src/index.ts index 14807cd571..54716bcef4 100644 --- a/plugins/linear-genome-view/src/index.ts +++ b/plugins/linear-genome-view/src/index.ts @@ -117,7 +117,7 @@ export default class LinearGenomeViewPlugin extends Plugin { new ViewType({ name: 'LinearGenomeView', stateModel: linearGenomeViewStateModelFactory(pluginManager), - LazyReactComponent: lazy( + ReactComponent: lazy( () => import('./LinearGenomeView/components/LinearGenomeView'), ), }), diff --git a/plugins/spreadsheet-view/src/index.ts b/plugins/spreadsheet-view/src/index.ts index 75a16f9752..b18ea852ec 100644 --- a/plugins/spreadsheet-view/src/index.ts +++ b/plugins/spreadsheet-view/src/index.ts @@ -14,7 +14,7 @@ export default class SpreadsheetViewPlugin extends Plugin { return new ViewType({ name: 'SpreadsheetView', stateModel: stateModelFactory(pluginManager), - LazyReactComponent: lazy( + ReactComponent: lazy( () => import('./SpreadsheetView/components/SpreadsheetView'), ), }) From bb54c7d56fdbc65b27523a671b0737202dbf5427 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 18:38:37 -0400 Subject: [PATCH 11/41] Modularize widgets --- .../config/src/ConfigurationEditorWidget/index.js | 1 - plugins/config/src/index.ts | 14 +++++++------- .../components/ConfigureConnection.js | 15 +++------------ 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/plugins/config/src/ConfigurationEditorWidget/index.js b/plugins/config/src/ConfigurationEditorWidget/index.js index b7fa16df2d..2bc80b51a2 100644 --- a/plugins/config/src/ConfigurationEditorWidget/index.js +++ b/plugins/config/src/ConfigurationEditorWidget/index.js @@ -2,7 +2,6 @@ import { observer } from 'mobx-react' import { isStateTreeNode, getType } from 'mobx-state-tree' import { ConfigurationSchema } from '@jbrowse/core/configuration' -export { default as ReactComponent } from './components/ConfigurationEditor' export { default as stateModelFactory } from './model' export const configSchema = ConfigurationSchema('ConfigurationEditorWidget', {}) export const HeadingComponent = observer(({ model }) => { diff --git a/plugins/config/src/index.ts b/plugins/config/src/index.ts index 7a7f587778..e781393e05 100644 --- a/plugins/config/src/index.ts +++ b/plugins/config/src/index.ts @@ -21,6 +21,10 @@ import { configSchema as refNameAliasAdapterConfigSchema, } from './RefNameAliasAdapter' +const ConfigurationEditor = lazy( + () => import('./ConfigurationEditorWidget/components/ConfigurationEditor'), +) + export default class extends Plugin { name = 'ConfigurationPlugin' @@ -64,16 +68,12 @@ export default class extends Plugin { HeadingComponent: ConfigurationEditorHeadingComponent, configSchema: ConfigurationEditorConfigSchema, stateModel: ConfigurationEditorStateModelFactory(pluginManager), - ReactComponent: lazy( - () => - import( - './ConfigurationEditorWidget/components/ConfigurationEditor' - ), - ), + ReactComponent: ConfigurationEditor, }) }) } } -export { default as ConfigurationEditor } from './ConfigurationEditorWidget/components/ConfigurationEditor' export { default as JsonEditor } from './ConfigurationEditorWidget/components/JsonEditor' + +export { ConfigurationEditor } diff --git a/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js b/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js index d5649b6a3c..3250518441 100644 --- a/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js +++ b/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js @@ -1,9 +1,8 @@ import { ConfigurationEditor } from '@jbrowse/plugin-config' -import { observer, PropTypes as MobxPropTypes } from 'mobx-react' -import PropTypes from 'prop-types' +import { observer } from 'mobx-react' import React from 'react' -function ConfigureConnection(props) { +export default observer(props => { const { connectionType, model, setModelReady } = props const ConfigEditorComponent = connectionType.configEditorComponent || ConfigurationEditor @@ -14,12 +13,4 @@ function ConfigureConnection(props) { setModelReady={setModelReady} /> ) -} - -ConfigureConnection.propTypes = { - connectionType: PropTypes.shape().isRequired, - model: MobxPropTypes.observableObject.isRequired, - setModelReady: PropTypes.func.isRequired, -} - -export default observer(ConfigureConnection) +}) From 5ae5a7d23391dd72f56901e20f0ee4594ff695a3 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 18:48:44 -0400 Subject: [PATCH 12/41] Lazy drawer widgets --- packages/core/ui/App.js | 30 +++++++++++-------- packages/core/ui/DrawerWidget.js | 14 ++++----- .../components/ConfigurationEditor.js | 9 ++---- .../src/ConfigurationEditorWidget/model.js | 4 --- plugins/config/src/index.ts | 6 ++-- plugins/menus/src/AboutWidget/index.js | 2 -- plugins/menus/src/index.ts | 21 ++++++++----- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/core/ui/App.js b/packages/core/ui/App.js index 659ed078f9..5eb64a53ad 100644 --- a/packages/core/ui/App.js +++ b/packages/core/ui/App.js @@ -78,16 +78,22 @@ const useStyles = makeStyles(theme => ({ }, })) -function App({ session, HeaderButtons }) { +export default observer(({ session, HeaderButtons }) => { const classes = useStyles() const { pluginManager } = getEnv(session) - const { visibleWidget, drawerWidth, minimized, activeWidgets } = session + const { + visibleWidget, + drawerWidth, + minimized, + activeWidgets, + savedSessionNames, + name, + menus, + views, + } = session function handleNameChange(newName) { - if ( - session.savedSessionNames && - session.savedSessionNames.includes(newName) - ) { + if (savedSessionNames && savedSessionNames.includes(newName)) { session.notify( `Cannot rename session to "${newName}", a saved session with that name already exists`, 'warning', @@ -109,7 +115,7 @@ function App({ session, HeaderButtons }) {
- {session.menus.map(menu => ( + {menus.map(menu => (
- {session.views.map(view => { + {views.map(view => { const viewType = pluginManager.getViewType(view.type) if (!viewType) { throw new Error(`unknown view type ${view.type}`) @@ -161,6 +167,8 @@ function App({ session, HeaderButtons }) { ) })} + + {/* blank space at the bottom of screen allows scroll */}
@@ -187,6 +195,4 @@ function App({ session, HeaderButtons }) { ) -} - -export default observer(App) +}) diff --git a/packages/core/ui/DrawerWidget.js b/packages/core/ui/DrawerWidget.js index 3a64b7c615..3e88b85712 100644 --- a/packages/core/ui/DrawerWidget.js +++ b/packages/core/ui/DrawerWidget.js @@ -9,12 +9,13 @@ import { Typography, makeStyles, } from '@material-ui/core' -import DeleteIcon from '@material-ui/icons/Delete' -import CloseIcon from '@material-ui/icons/Close' -import MinimizeIcon from '@material-ui/icons/Minimize' import { fade } from '@material-ui/core/styles/colorManipulator' import { observer, PropTypes } from 'mobx-react' import { getEnv } from 'mobx-state-tree' +import DeleteIcon from '@material-ui/icons/Delete' +import CloseIcon from '@material-ui/icons/Close' +import MinimizeIcon from '@material-ui/icons/Minimize' + import Drawer from './Drawer' const useStyles = makeStyles(theme => ({ @@ -54,9 +55,8 @@ const useStyles = makeStyles(theme => ({ const DrawerWidget = observer(props => { const { session } = props const { visibleWidget, activeWidgets } = session - const { ReactComponent } = getEnv(session).pluginManager.getWidgetType( - visibleWidget.type, - ) + const { pluginManager } = getEnv(session) + const { ReactComponent } = pluginManager.getWidgetType(visibleWidget.type) const classes = useStyles() const handleChange = (e, option) => { @@ -151,7 +151,7 @@ const DrawerWidget = observer(props => { - Loading}> + Loading...}> diff --git a/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js b/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js index 6687d0fc15..efe29c9951 100644 --- a/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js +++ b/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js @@ -94,7 +94,7 @@ const useStyles = makeStyles(theme => ({ }, })) -function ConfigurationEditor({ model }) { +export default observer(({ model }) => { const classes = useStyles() // key forces a re-render, otherwise the same field can end up being used // for different tracks since only the backing model changes for example @@ -105,9 +105,4 @@ function ConfigurationEditor({ model }) { {!model.target ? 'no target set' : } ) -} -ConfigurationEditor.propTypes = { - model: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export default observer(ConfigurationEditor) +}) diff --git a/plugins/config/src/ConfigurationEditorWidget/model.js b/plugins/config/src/ConfigurationEditorWidget/model.js index f9e6016f8b..e18c78d8ab 100644 --- a/plugins/config/src/ConfigurationEditorWidget/model.js +++ b/plugins/config/src/ConfigurationEditorWidget/model.js @@ -12,12 +12,8 @@ export default pluginManager => pluginManager.pluggableConfigSchemaType('track'), ), }) - // .volatile(() => ({ - // target: undefined, - // })) .actions(self => ({ setTarget(newTarget) { self.target = newTarget }, })) -// .views(self => ({})) diff --git a/plugins/config/src/index.ts b/plugins/config/src/index.ts index e781393e05..7b1f1e425b 100644 --- a/plugins/config/src/index.ts +++ b/plugins/config/src/index.ts @@ -21,7 +21,7 @@ import { configSchema as refNameAliasAdapterConfigSchema, } from './RefNameAliasAdapter' -const ConfigurationEditor = lazy( +const ConfigurationEditorComponent = lazy( () => import('./ConfigurationEditorWidget/components/ConfigurationEditor'), ) @@ -68,7 +68,7 @@ export default class extends Plugin { HeadingComponent: ConfigurationEditorHeadingComponent, configSchema: ConfigurationEditorConfigSchema, stateModel: ConfigurationEditorStateModelFactory(pluginManager), - ReactComponent: ConfigurationEditor, + ReactComponent: ConfigurationEditorComponent, }) }) } @@ -76,4 +76,4 @@ export default class extends Plugin { export { default as JsonEditor } from './ConfigurationEditorWidget/components/JsonEditor' -export { ConfigurationEditor } +export { ConfigurationEditorComponent as ConfigurationEditor } diff --git a/plugins/menus/src/AboutWidget/index.js b/plugins/menus/src/AboutWidget/index.js index cdceff872b..3065d80769 100644 --- a/plugins/menus/src/AboutWidget/index.js +++ b/plugins/menus/src/AboutWidget/index.js @@ -8,5 +8,3 @@ export const stateModel = types.model('AboutWidget', { id: ElementId, type: types.literal('AboutWidget'), }) - -export { default as ReactComponent } from './components/AboutWidget' diff --git a/plugins/menus/src/index.ts b/plugins/menus/src/index.ts index a9180d9463..cfe2313393 100644 --- a/plugins/menus/src/index.ts +++ b/plugins/menus/src/index.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' import Plugin from '@jbrowse/core/Plugin' import PluginManager from '@jbrowse/core/PluginManager' @@ -17,22 +18,18 @@ import { saveAs } from 'file-saver' import { getSnapshot, IAnyStateTreeNode } from 'mobx-state-tree' import { configSchema as aboutConfigSchema, - ReactComponent as AboutReactComponent, stateModel as aboutStateModel, } from './AboutWidget' import { configSchema as helpConfigSchema, - ReactComponent as HelpReactComponent, stateModel as helpStateModel, } from './HelpWidget' import { configSchema as importSessionConfigSchema, - ReactComponent as ImportSessionReactComponent, stateModel as importSessionStateModel, } from './ImportSessionWidget' import { configSchema as sessionManagerConfigSchema, - ReactComponent as SessionManagerReactComponent, stateModel as sessionManagerStateModel, } from './SessionManager' @@ -46,7 +43,9 @@ export default class extends Plugin { heading: 'About', configSchema: aboutConfigSchema, stateModel: aboutStateModel, - ReactComponent: AboutReactComponent, + ReactComponent: lazy( + () => import('./AboutWidget/components/AboutWidget'), + ), }) }) @@ -56,7 +55,9 @@ export default class extends Plugin { heading: 'Help', configSchema: helpConfigSchema, stateModel: helpStateModel, - ReactComponent: HelpReactComponent, + ReactComponent: lazy( + () => import('./HelpWidget/components/HelpWidget'), + ), }) }) @@ -66,7 +67,9 @@ export default class extends Plugin { heading: 'Import session', configSchema: importSessionConfigSchema, stateModel: importSessionStateModel, - ReactComponent: ImportSessionReactComponent, + ReactComponent: lazy( + () => import('./ImportSessionWidget/components/ImportSessionWidget'), + ), }) }) @@ -76,7 +79,9 @@ export default class extends Plugin { heading: 'Sessions', configSchema: sessionManagerConfigSchema, stateModel: sessionManagerStateModel, - ReactComponent: SessionManagerReactComponent, + ReactComponent: lazy( + () => import('./SessionManager/components/SessionManager'), + ), }) }) } From c84db4b9763aea2829a12fc33b5310574ee96c3e Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 20:01:03 -0400 Subject: [PATCH 13/41] Remove concept of track based dialog --- .../models/BaseTrackModel.ts | 10 ----- packages/core/util/types/index.ts | 1 + .../components/ColorByTag.tsx | 6 +-- .../components/FilterByTag.tsx | 8 ++-- .../components/SetFeatureHeight.tsx | 10 ++--- .../components/SetMaxHeight.tsx | 8 ++-- .../components/SortByTag.tsx | 6 +-- .../src/LinearPileupDisplay/model.ts | 45 +++++++++---------- .../components/LinearGenomeView.tsx | 21 +++------ .../src/LinearGenomeView/index.ts | 17 +++---- .../components/SetColorDialog.tsx | 8 ++-- .../components/SetMinMaxDialog.tsx | 6 +-- .../src/LinearWiggleDisplay/models/model.ts | 15 ++++--- 13 files changed, 72 insertions(+), 89 deletions(-) diff --git a/packages/core/pluggableElementTypes/models/BaseTrackModel.ts b/packages/core/pluggableElementTypes/models/BaseTrackModel.ts index 7d2138619d..8c0dcbdf78 100644 --- a/packages/core/pluggableElementTypes/models/BaseTrackModel.ts +++ b/packages/core/pluggableElementTypes/models/BaseTrackModel.ts @@ -38,16 +38,6 @@ export function createBaseTrackModel( pluginManager.pluggableMstType('display', 'stateModel'), ), }) - .volatile(() => ({ - DialogComponent: undefined as React.FC | undefined, - DialogDisplay: undefined as any, - })) - .actions(self => ({ - setDialogComponent(dlg?: React.FC, context?: any) { - self.DialogComponent = dlg - self.DialogDisplay = context - }, - })) .views(self => ({ get rpcSessionId() { return self.configuration.trackId diff --git a/packages/core/util/types/index.ts b/packages/core/util/types/index.ts index c75551b0d0..629cd2f413 100644 --- a/packages/core/util/types/index.ts +++ b/packages/core/util/types/index.ts @@ -119,6 +119,7 @@ export interface AbstractViewModel { type: string width: number setWidth(width: number): void + setDialogComponent(dlg: unknown, display?: unknown): void } export function isViewModel(thing: unknown): thing is AbstractViewModel { return ( diff --git a/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx b/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx index 844966f28b..9fafa40006 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx @@ -22,11 +22,11 @@ const useStyles = makeStyles(theme => ({ })) export default function ColorByTagDlg(props: { - display: { setColorScheme: Function } + model: { setColorScheme: Function } handleClose: () => void }) { const classes = useStyles() - const { display, handleClose } = props + const { model, handleClose } = props const [tag, setTag] = useState('') const validTag = tag.match(/^[A-Za-z][A-Za-z0-9]$/) @@ -75,7 +75,7 @@ export default function ColorByTagDlg(props: { color="primary" style={{ marginLeft: 20 }} onClick={() => { - display.setColorScheme({ + model.setColorScheme({ type: 'tag', tag, }) diff --git a/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx b/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx index e41bd6bf93..eba4e50b74 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx @@ -82,7 +82,7 @@ function Bitmask(props: { flag?: number; setFlag: Function }) { export default observer( (props: { - display: { + model: { filterBy?: { flagExclude: number flagInclude: number @@ -93,9 +93,9 @@ export default observer( } handleClose: () => void }) => { - const { display, handleClose } = props + const { model, handleClose } = props const classes = useStyles() - const { filterBy } = display + const { filterBy } = model const [flagInclude, setFlagInclude] = useState(filterBy?.flagInclude) const [flagExclude, setFlagExclude] = useState(filterBy?.flagExclude) const [tag, setTag] = useState(filterBy?.tagFilter?.tag || '') @@ -196,7 +196,7 @@ export default observer( variant="contained" color="primary" onClick={() => { - display.setFilterBy({ + model.setFilterBy({ flagInclude, flagExclude, readName, diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx index 0957ced798..ae78b5ddc2 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx @@ -26,7 +26,7 @@ const useStyles = makeStyles(theme => ({ })) export default function SetMinMaxDlg(props: { - display: { + model: { minScore: number maxScore: number setMinScore: Function @@ -39,8 +39,8 @@ export default function SetMinMaxDlg(props: { handleClose: () => void }) { const classes = useStyles() - const { display, handleClose } = props - const { featureHeightSetting, noSpacing: noSpacingSetting } = display + const { model, handleClose } = props + const { featureHeightSetting, noSpacing: noSpacingSetting } = model const [height, setHeight] = useState(`${featureHeightSetting}`) const [noSpacing, setNoSpacing] = useState(noSpacingSetting) @@ -95,10 +95,10 @@ export default function SetMinMaxDlg(props: { style={{ marginLeft: 20 }} disabled={!ok} onClick={() => { - display.setFeatureHeight( + model.setFeatureHeight( height !== '' && !Number.isNaN(+height) ? +height : undefined, ) - display.setNoSpacing(noSpacing) + model.setNoSpacing(noSpacing) handleClose() }} diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx index 07ca487e41..ee2619d66c 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx @@ -27,15 +27,15 @@ const useStyles = makeStyles(theme => ({ export default observer( (props: { - display: { + model: { maxHeight?: number setMaxHeight: Function } handleClose: () => void }) => { - const { display, handleClose } = props + const { model, handleClose } = props const classes = useStyles() - const { maxHeight = '' } = display + const { maxHeight = '' } = model const [max, setMax] = useState(`${maxHeight}`) return ( @@ -71,7 +71,7 @@ export default observer( type="submit" style={{ marginLeft: 20 }} onClick={() => { - display.setMaxHeight( + model.setMaxHeight( max !== '' && !Number.isNaN(+max) ? +max : undefined, ) handleClose() diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx index 1d4335d4cf..173bbd04dd 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx @@ -23,11 +23,11 @@ const useStyles = makeStyles(theme => ({ })) export default function SortByTagDlg(props: { - display: { setSortedBy: Function } + model: { setSortedBy: Function } handleClose: () => void }) { const classes = useStyles() - const { display, handleClose } = props + const { model, handleClose } = props const [tag, setTag] = useState('') const validTag = tag.match(/^[A-Za-z][A-Za-z0-9]$/) return ( @@ -72,7 +72,7 @@ export default function SortByTagDlg(props: { variant="contained" color="primary" onClick={() => { - display.setSortedBy('tag', tag) + model.setSortedBy('tag', tag) handleClose() }} > diff --git a/plugins/alignments/src/LinearPileupDisplay/model.ts b/plugins/alignments/src/LinearPileupDisplay/model.ts index 79581ac8bc..ac8b21a3d6 100644 --- a/plugins/alignments/src/LinearPileupDisplay/model.ts +++ b/plugins/alignments/src/LinearPileupDisplay/model.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import { ConfigurationReference, readConfObject, @@ -11,7 +12,6 @@ import { getSession, isSessionModelWithWidgets, getContainingView, - getContainingTrack, } from '@jbrowse/core/util' import { BlockSet } from '@jbrowse/core/util/blockTypes' @@ -35,11 +35,12 @@ import { AnyConfigurationModel } from '@jbrowse/core/configuration/configuration import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain' import { LinearPileupDisplayConfigModel } from './configSchema' import LinearPileupDisplayBlurb from './components/LinearPileupDisplayBlurb' -import ColorByTagDlg from './components/ColorByTag' -import FilterByTagDlg from './components/FilterByTag' -import SortByTagDlg from './components/SortByTag' -import SetFeatureHeightDlg from './components/SetFeatureHeight' -import SetMaxHeightDlg from './components/SetMaxHeight' + +const ColorByTagDlg = lazy(() => import('./components/ColorByTag')) +const FilterByTagDlg = lazy(() => import('./components/FilterByTag')) +const SortByTagDlg = lazy(() => import('./components/SortByTag')) +const SetFeatureHeightDlg = lazy(() => import('./components/SetFeatureHeight')) +const SetMaxHeightDlg = lazy(() => import('./components/SetMaxHeight')) // using a map because it preserves order const rendererTypes = new Map([ @@ -456,10 +457,9 @@ const stateModelFactory = ( { label: 'Sort by tag...', onClick: () => - getContainingTrack(self).setDialogComponent( - SortByTagDlg, - self, - ), + getContainingView(self).setDialogComponent(SortByTagDlg, { + model: self, + }), }, { label: 'Clear sort', @@ -516,10 +516,9 @@ const stateModelFactory = ( { label: 'Color by tag...', onClick: () => { - getContainingTrack(self).setDialogComponent( - ColorByTagDlg, - self, - ) + getContainingView(self).setDialogComponent(ColorByTagDlg, { + model: self, + }) }, }, ], @@ -528,28 +527,26 @@ const stateModelFactory = ( label: 'Filter by', icon: FilterListIcon, onClick: () => { - getContainingTrack(self).setDialogComponent( - FilterByTagDlg, - self, - ) + getContainingView(self).setDialogComponent(FilterByTagDlg, { + model: self, + }) }, }, { label: 'Set feature height', onClick: () => { - getContainingTrack(self).setDialogComponent( + getContainingView(self).setDialogComponent( SetFeatureHeightDlg, - self, + { model: self }, ) }, }, { label: 'Set max height', onClick: () => { - getContainingTrack(self).setDialogComponent( - SetMaxHeightDlg, - self, - ) + getContainingView(self).setDialogComponent(SetMaxHeightDlg, { + model: self, + }) }, }, { diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx index f22eeff6a9..e99de2413a 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/LinearGenomeView.tsx @@ -1,3 +1,4 @@ +import React, { Suspense } from 'react' // material ui things import { Button, Paper, Typography, makeStyles } from '@material-ui/core' import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons' @@ -5,7 +6,6 @@ import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons' // misc import { observer } from 'mobx-react' import { Instance } from 'mobx-state-tree' -import React from 'react' // locals import { LinearGenomeViewStateModel } from '..' @@ -45,21 +45,14 @@ const LinearGenomeView = observer((props: { model: LGV }) => { return (
{model.DialogComponent ? ( - model.setDialogComponent(undefined)} - /> + }> + model.setDialogComponent(undefined, undefined)} + {...model.DialogProps} + /> + ) : null} - {dialogTrack ? ( - - dialogTrack.setDialogComponent(undefined, undefined) - } - /> - ) : null} {model.isSeqDialogDisplayed ? ( void; model: { clearView: Function } }> - | undefined, + DialogComponent: undefined as React.FC | undefined, + DialogProps: undefined as unknown, })) .views(self => ({ get width(): number { @@ -442,13 +441,9 @@ export function stateModelFactory(pluginManager: PluginManager) { }, })) .actions(self => ({ - setDialogComponent( - comp?: React.FC<{ - handleClose: () => void - model: { clearView: Function } - }>, - ) { + setDialogComponent(comp?: React.FC, props?: unknown) { self.DialogComponent = comp + self.DialogProps = props }, setWidth(newWidth: number) { self.volatileWidth = newWidth @@ -1167,7 +1162,9 @@ export function stateModelFactory(pluginManager: PluginManager) { { label: 'Return to import form', onClick: () => { - self.setDialogComponent(ReturnToImportFormDlg) + self.setDialogComponent( + ReturnToImportFormDlg as React.FC, + ) }, icon: FolderOpenIcon, }, diff --git a/plugins/wiggle/src/LinearWiggleDisplay/components/SetColorDialog.tsx b/plugins/wiggle/src/LinearWiggleDisplay/components/SetColorDialog.tsx index db4135a4f7..c10aa081cd 100644 --- a/plugins/wiggle/src/LinearWiggleDisplay/components/SetColorDialog.tsx +++ b/plugins/wiggle/src/LinearWiggleDisplay/components/SetColorDialog.tsx @@ -31,14 +31,14 @@ function serializeColor(color: Color) { } export default function SetColorDialog(props: { - display: { + model: { color: number setColor: Function } handleClose: () => void }) { const classes = useStyles() - const { display, handleClose } = props + const { model, handleClose } = props return ( { - display.setColor(serializeColor(event.rgb)) + model.setColor(serializeColor(event.rgb)) }} />
}> + + , ) await findByText(/Reference Sequence/) expect(container.firstChild).toMatchSnapshot() From 381cda922fda6f8b65ef476cff48a02b0e9b56cb Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 21:01:24 -0400 Subject: [PATCH 18/41] Suspense --- .../components/AddConnectionWidget.test.js | 76 +++++++++---------- .../components/ConfigureConnection.js | 12 +-- 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js b/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js index ec8bfb269c..48ad24c6aa 100644 --- a/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js +++ b/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js @@ -1,4 +1,5 @@ -import React, { Suspense } from 'react' +/* eslint curly:error*/ +import React from 'react' import { render, cleanup, fireEvent } from '@testing-library/react' import { createTestSession } from '@jbrowse/web/src/rootModel' import AddConnectionWidget from './AddConnectionWidget' @@ -37,11 +38,7 @@ describe('', () => { afterEach(cleanup) it('renders', () => { - const { container } = render( - Loading...
}> - -
, - ) + const { container } = render() expect(container.firstChild).toMatchSnapshot() }) @@ -49,7 +46,7 @@ describe('', () => { const mockFetch = url => { const urlText = url.href ? url.href : url let responseText = '' - if (urlText.endsWith('hub.txt')) + if (urlText.endsWith('hub.txt')) { responseText = `hub TestHub shortLabel Test Hub longLabel Test Genome Informatics Hub for human DNase and RNAseq data @@ -57,48 +54,52 @@ genomesFile genomes.txt email genome@test.com descriptionUrl test.html ` - else if (urlText.endsWith('genomes.txt')) + } else if (urlText.endsWith('genomes.txt')) { responseText = `genome volMyt1 trackDb hg19/trackDb.txt ` - else if (urlText.endsWith('trackDb.txt')) + } else if (urlText.endsWith('trackDb.txt')) { responseText = `track dnaseSignal bigDataUrl dnaseSignal.bigWig shortLabel DNAse Signal longLabel Depth of alignments of DNAse reads type bigWig ` + } return Promise.resolve(new Response(responseText, { url: urlText })) } jest.spyOn(global, 'fetch').mockImplementation(mockFetch) const { - getByTestId, - getAllByTestId, + findByTestId, + findAllByTestId, getAllByRole, + findAllByDisplayValue, findByText, - getAllByDisplayValue, - } = render( - Loading...}> - - , - ) + } = render() expect(session.connections.length).toBe(0) fireEvent.mouseDown(getAllByRole('button')[0]) fireEvent.click(await findByText('volMyt1')) fireEvent.mouseDown(getAllByRole('button')[1]) fireEvent.click(await findByText('UCSC Track Hub')) - fireEvent.click(getByTestId('addConnectionNext')) - fireEvent.change(getAllByDisplayValue('nameOfConnection')[1], { + fireEvent.click(await findByTestId('addConnectionNext')) + const res = await findAllByDisplayValue('nameOfConnection') + fireEvent.change(res[1], { target: { value: 'Test UCSC connection name' }, }) - fireEvent.change( - getAllByDisplayValue('http://mysite.com/path/to/hub.txt')[1], - { - target: { value: 'http://test.com/hub.txt' }, - }, + + // await waitFor(async () => { + // const r = await findAllByDisplayValue('http://mysite.com/path/to/hub.txt') + // expect(r.length).toBe(2) + // }) + const res2 = await findAllByDisplayValue( + 'http://mysite.com/path/to/hub.txt', ) - fireEvent.click(getAllByTestId('addConnectionNext')[1]) + + fireEvent.change(res2[0], { + target: { value: 'http://test.com/hub.txt' }, + }) + fireEvent.click((await findAllByTestId('addConnectionNext'))[0]) expect(session.sessionConnections.length).toBe(1) }) @@ -106,38 +107,37 @@ type bigWig const mockFetch = url => { const urlText = url.href ? url.href : url let responseText = '' - if (urlText.endsWith('trackList.json')) responseText = '{}' - else if (urlText.endsWith('refSeqs.json')) responseText = '[]' + if (urlText.endsWith('trackList.json')) { + responseText = '{}' + } else if (urlText.endsWith('refSeqs.json')) { + responseText = '[]' + } return Promise.resolve(new Response(responseText, { url: urlText })) } jest.spyOn(global, 'fetch').mockImplementation(mockFetch) const { - getByTestId, + findByTestId, getAllByTestId, getAllByRole, findByText, - getAllByDisplayValue, - } = render( - Loading...}> - - , - ) + findAllByDisplayValue, + } = render() expect(session.connections.length).toBe(0) fireEvent.mouseDown(getAllByRole('button')[0]) fireEvent.click(await findByText('volMyt1')) fireEvent.mouseDown(getAllByRole('button')[1]) fireEvent.click(await findByText('JBrowse 1 Data')) - fireEvent.click(getByTestId('addConnectionNext')) - fireEvent.change(getAllByDisplayValue('nameOfConnection')[1], { + fireEvent.click(await findByTestId('addConnectionNext')) + fireEvent.change((await findAllByDisplayValue('nameOfConnection'))[1], { target: { value: 'Test JBrowse 1 connection name' }, }) fireEvent.change( - getAllByDisplayValue('http://mysite.com/jbrowse/data/')[1], + (await findAllByDisplayValue('http://mysite.com/jbrowse/data/'))[0], { target: { value: 'http://test.com/jbrowse/data/' }, }, ) - fireEvent.click(getAllByTestId('addConnectionNext')[1]) + fireEvent.click(getAllByTestId('addConnectionNext')[0]) expect(session.sessionConnections.length).toBe(1) }) }) diff --git a/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js b/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js index 3250518441..6854862428 100644 --- a/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js +++ b/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js @@ -1,6 +1,6 @@ +import React, { Suspense } from 'react' import { ConfigurationEditor } from '@jbrowse/plugin-config' import { observer } from 'mobx-react' -import React from 'react' export default observer(props => { const { connectionType, model, setModelReady } = props @@ -8,9 +8,11 @@ export default observer(props => { connectionType.configEditorComponent || ConfigurationEditor return ( - + Loading...}> + + ) }) From 554dbdba9fd6ae03709233d9f234e05bca268e42 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 21:45:04 -0400 Subject: [PATCH 19/41] Update --- .../components/AddConnectionWidget.test.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js b/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js index 48ad24c6aa..14e3897f95 100644 --- a/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js +++ b/plugins/data-management/src/AddConnectionWidget/components/AddConnectionWidget.test.js @@ -83,22 +83,16 @@ type bigWig fireEvent.mouseDown(getAllByRole('button')[1]) fireEvent.click(await findByText('UCSC Track Hub')) fireEvent.click(await findByTestId('addConnectionNext')) - const res = await findAllByDisplayValue('nameOfConnection') - fireEvent.change(res[1], { + fireEvent.change((await findAllByDisplayValue('nameOfConnection'))[0], { target: { value: 'Test UCSC connection name' }, }) - // await waitFor(async () => { - // const r = await findAllByDisplayValue('http://mysite.com/path/to/hub.txt') - // expect(r.length).toBe(2) - // }) - const res2 = await findAllByDisplayValue( - 'http://mysite.com/path/to/hub.txt', + fireEvent.change( + (await findAllByDisplayValue('http://mysite.com/path/to/hub.txt'))[0], + { + target: { value: 'http://test.com/hub.txt' }, + }, ) - - fireEvent.change(res2[0], { - target: { value: 'http://test.com/hub.txt' }, - }) fireEvent.click((await findAllByTestId('addConnectionNext'))[0]) expect(session.sessionConnections.length).toBe(1) }) From 3d049962855132e6620d6b096291a4429b75eaa4 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 22:02:50 -0400 Subject: [PATCH 20/41] Couple more lazy --- .../src/AddConnectionWidget/index.js | 1 - .../src/AddTrackWidget/index.js | 1 - .../HierarchicalTrackSelectorWidget/index.js | 1 - plugins/data-management/src/index.ts | 19 +++++++++++++------ .../src/LinearComparativeView/index.ts | 7 ++++--- .../src/LinearSyntenyView/index.ts | 4 ++-- 6 files changed, 19 insertions(+), 14 deletions(-) diff --git a/plugins/data-management/src/AddConnectionWidget/index.js b/plugins/data-management/src/AddConnectionWidget/index.js index 73a02bb186..06584e401e 100644 --- a/plugins/data-management/src/AddConnectionWidget/index.js +++ b/plugins/data-management/src/AddConnectionWidget/index.js @@ -1,5 +1,4 @@ import { ConfigurationSchema } from '@jbrowse/core/configuration' -export { default as ReactComponent } from './components/AddConnectionWidget' export { default as stateModel } from './model' export const configSchema = ConfigurationSchema('AddConnectionWidget', {}) diff --git a/plugins/data-management/src/AddTrackWidget/index.js b/plugins/data-management/src/AddTrackWidget/index.js index 609648fa8a..ff1c8a2b87 100644 --- a/plugins/data-management/src/AddTrackWidget/index.js +++ b/plugins/data-management/src/AddTrackWidget/index.js @@ -1,5 +1,4 @@ import { ConfigurationSchema } from '@jbrowse/core/configuration' -export { default as ReactComponent } from './components/AddTrackWidget' export { default as stateModelFactory } from './model' export const configSchema = ConfigurationSchema('AddTrackWidget', {}) diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/index.js b/plugins/data-management/src/HierarchicalTrackSelectorWidget/index.js index f8ce743c9a..42cc609d12 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/index.js +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/index.js @@ -1,6 +1,5 @@ import { ConfigurationSchema } from '@jbrowse/core/configuration' -export { default as ReactComponent } from './components/HierarchicalTrackSelector' export { default as stateModelFactory } from './model' export const configSchema = ConfigurationSchema( 'HierarchicalTrackSelectorWidget', diff --git a/plugins/data-management/src/index.ts b/plugins/data-management/src/index.ts index 24fc6cac70..a8eaf515b1 100644 --- a/plugins/data-management/src/index.ts +++ b/plugins/data-management/src/index.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import ConnectionType from '@jbrowse/core/pluggableElementTypes/ConnectionType' import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' import Plugin from '@jbrowse/core/Plugin' @@ -10,17 +11,14 @@ import { modelFactory as ucscModelFactory, } from './ucsc-trackhub' import { - ReactComponent as AddTrackReactComponent, stateModelFactory as AddTrackStateModelFactory, configSchema as AddTrackConfigSchema, } from './AddTrackWidget' import { - ReactComponent as AddConnectionReactComponent, stateModel as AddConnectionStateModel, configSchema as AddConnectionConfigSchema, } from './AddConnectionWidget' import { - ReactComponent as HierarchicalTrackSelectorReactComponent, stateModelFactory as HierarchicalTrackSelectorStateModelFactory, configSchema as HierarchicalTrackSelectorConfigSchema, } from './HierarchicalTrackSelectorWidget' @@ -54,7 +52,12 @@ export default class extends Plugin { heading: 'Available tracks', configSchema: HierarchicalTrackSelectorConfigSchema, stateModel: HierarchicalTrackSelectorStateModelFactory(pluginManager), - ReactComponent: HierarchicalTrackSelectorReactComponent, + ReactComponent: lazy( + () => + import( + './HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector' + ), + ), }) }) @@ -64,7 +67,9 @@ export default class extends Plugin { heading: 'Add a track', configSchema: AddTrackConfigSchema, stateModel: AddTrackStateModelFactory(pluginManager), - ReactComponent: AddTrackReactComponent, + ReactComponent: lazy( + () => import('./AddTrackWidget/components/AddTrackWidget'), + ), }) }) @@ -74,7 +79,9 @@ export default class extends Plugin { heading: 'Add a connection', configSchema: AddConnectionConfigSchema, stateModel: AddConnectionStateModel, - ReactComponent: AddConnectionReactComponent, + ReactComponent: lazy( + () => import('./AddConnectionWidget/components/AddConnectionWidget'), + ), }) }) } diff --git a/plugins/linear-comparative-view/src/LinearComparativeView/index.ts b/plugins/linear-comparative-view/src/LinearComparativeView/index.ts index e147007883..345cb96c70 100644 --- a/plugins/linear-comparative-view/src/LinearComparativeView/index.ts +++ b/plugins/linear-comparative-view/src/LinearComparativeView/index.ts @@ -1,11 +1,12 @@ -import ReactComponent from './components/LinearComparativeView' +import { lazy } from 'react' +import PluginManager from '@jbrowse/core/PluginManager' import modelFactory from './model' -export default ({ jbrequire }: { jbrequire: Function }) => { +export default ({ jbrequire }: PluginManager) => { const ViewType = jbrequire('@jbrowse/core/pluggableElementTypes/ViewType') return new ViewType({ name: 'LinearComparativeView', stateModel: jbrequire(modelFactory), - ReactComponent, + ReactComponent: lazy(() => import('./components/LinearComparativeView')), }) } diff --git a/plugins/linear-comparative-view/src/LinearSyntenyView/index.ts b/plugins/linear-comparative-view/src/LinearSyntenyView/index.ts index fbddf9c426..e4cefa83e1 100644 --- a/plugins/linear-comparative-view/src/LinearSyntenyView/index.ts +++ b/plugins/linear-comparative-view/src/LinearSyntenyView/index.ts @@ -1,5 +1,5 @@ +import { lazy } from 'react' import PluginManager from '@jbrowse/core/PluginManager' -import ReactComponent from './components/LinearSyntenyView' import modelFactory from './model' export default (pluginManager: PluginManager) => { @@ -8,6 +8,6 @@ export default (pluginManager: PluginManager) => { return new ViewType({ name: 'LinearSyntenyView', stateModel: jbrequire(modelFactory), - ReactComponent, + ReactComponent: lazy(() => import('./components/LinearSyntenyView')), }) } From 172360eabbe3835de7d2ea2acb84435fafb60903 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 22:44:48 -0400 Subject: [PATCH 21/41] More wild stuff --- .../BaseFeatureWidget/BaseFeatureDetail.tsx | 30 +++++++++---------- packages/core/BaseFeatureWidget/index.ts | 1 - packages/core/CorePlugin.ts | 15 ++++++++++ packages/development-tools/index.js | 4 +++ .../BreakpointAlignmentsFeatureDetail.tsx | 23 ++++---------- .../index.js | 1 - plugins/breakpoint-split-view/src/index.ts | 9 ++++-- plugins/linear-genome-view/src/index.ts | 16 +--------- plugins/trackhub-registry/src/index.ts | 6 ++-- .../TrackHubRegistrySelect.js | 26 +++++++--------- plugins/variants/src/index.ts | 6 ++-- 11 files changed, 66 insertions(+), 71 deletions(-) diff --git a/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx b/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx index 178aa353a6..ad1c1684f0 100644 --- a/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx +++ b/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx @@ -421,21 +421,6 @@ function isEmpty(obj: Record) { return Object.keys(obj).length === 0 } -export const BaseFeatureDetails = observer((props: BaseInputProps) => { - const { model } = props - const { featureData } = model - - if (!featureData) { - return null - } - const feature = JSON.parse(JSON.stringify(featureData)) - - if (isEmpty(feature)) { - return null - } - return -}) - export const FeatureDetails = (props: { model: IAnyStateTreeNode feature: SimpleFeatureSerialized & { name?: string; id?: string } @@ -499,3 +484,18 @@ export const FeatureDetails = (props: { ) } + +export default observer((props: BaseInputProps) => { + const { model } = props + const { featureData } = model + + if (!featureData) { + return null + } + const feature = JSON.parse(JSON.stringify(featureData)) + + if (isEmpty(feature)) { + return null + } + return +}) diff --git a/packages/core/BaseFeatureWidget/index.ts b/packages/core/BaseFeatureWidget/index.ts index 4e889b114a..df7c2f757f 100644 --- a/packages/core/BaseFeatureWidget/index.ts +++ b/packages/core/BaseFeatureWidget/index.ts @@ -26,4 +26,3 @@ export default function stateModelFactory(pluginManager: PluginManager) { } export { configSchema, stateModelFactory } -export { BaseFeatureDetails as ReactComponent } from './BaseFeatureDetail' diff --git a/packages/core/CorePlugin.ts b/packages/core/CorePlugin.ts index 468d9e4278..37dc1fb5c8 100644 --- a/packages/core/CorePlugin.ts +++ b/packages/core/CorePlugin.ts @@ -1,6 +1,9 @@ +import { lazy } from 'react' +import { configSchema, stateModelFactory } from './BaseFeatureWidget' import Plugin from './Plugin' import PluginManager from './PluginManager' import * as coreRpcMethods from './rpc/coreRpcMethods' +import WidgetType from './pluggableElementTypes/WidgetType' /** the core plugin, which registers types that ALL JBrowse applications are expected to need. */ export default class CorePlugin extends Plugin { @@ -11,5 +14,17 @@ export default class CorePlugin extends Plugin { Object.values(coreRpcMethods).forEach(RpcMethod => { pluginManager.addRpcMethod(() => new RpcMethod(pluginManager)) }) + + pluginManager.addWidgetType(() => { + return new WidgetType({ + name: 'BaseFeatureWidget', + heading: 'Feature details', + configSchema, + stateModel: stateModelFactory(pluginManager), + ReactComponent: lazy( + () => import('./BaseFeatureWidget/BaseFeatureDetail'), + ), + }) + }) } } diff --git a/packages/development-tools/index.js b/packages/development-tools/index.js index 9ea81b7448..50bc7cd8c1 100644 --- a/packages/development-tools/index.js +++ b/packages/development-tools/index.js @@ -21,6 +21,10 @@ function createJBrowsePluginTsdxConfig(config, options, globals) { globals.forEach(global => { config.output.globals[global] = `JBrowseExports.${global}` }) + + // xref https://github.com/rollup/rollup/issues/2616 we use code splitting + // but plugins want single file output + config.inlineDynamicImports = true } return config } diff --git a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx index 5903f0408a..6fbfb47dfa 100644 --- a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx +++ b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx @@ -1,21 +1,13 @@ import Paper from '@material-ui/core/Paper' -import { observer, PropTypes as MobxPropTypes } from 'mobx-react' -import React, { FunctionComponent } from 'react' +import { observer } from 'mobx-react' +import React from 'react' import { BaseCoreDetails, BaseAttributes, } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' -interface AlnCardProps { - title?: string -} - -interface AlnInputProps extends AlnCardProps { - model: any // eslint-disable-line @typescript-eslint/no-explicit-any -} - -const AlignmentFeatureDetails: FunctionComponent = props => { - const { model } = props +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export default observer(({ model }: { model: any }) => { const { feature1, feature2 } = JSON.parse(JSON.stringify(model.featureData)) return ( @@ -25,9 +17,4 @@ const AlignmentFeatureDetails: FunctionComponent = props => { ) -} -AlignmentFeatureDetails.propTypes = { - model: MobxPropTypes.objectOrObservableObject.isRequired, -} - -export default observer(AlignmentFeatureDetails) +}) diff --git a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/index.js b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/index.js index e142e834b3..d826921e8b 100644 --- a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/index.js +++ b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/index.js @@ -20,4 +20,3 @@ const stateModel = types })) export { configSchema, stateModel } -export { default as ReactComponent } from './BreakpointAlignmentsFeatureDetail' diff --git a/plugins/breakpoint-split-view/src/index.ts b/plugins/breakpoint-split-view/src/index.ts index 2d03cd6459..ea7f6645c8 100644 --- a/plugins/breakpoint-split-view/src/index.ts +++ b/plugins/breakpoint-split-view/src/index.ts @@ -1,9 +1,9 @@ +import { lazy } from 'react' import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' import PluginManager from '@jbrowse/core/PluginManager' import Plugin from '@jbrowse/core/Plugin' import { configSchema as alignmentsFeatureDetailConfigSchema, - ReactComponent as AlignmentsFeatureDetailReactComponent, stateModel as alignmentsFeatureDetailStateModel, } from './BreakpointAlignmentsFeatureDetail' import BreakpointSplitViewFactory from './BreakpointSplitView' @@ -22,7 +22,12 @@ export default class BreakpointSplitViewPlugin extends Plugin { heading: 'Breakpoint feature details', configSchema: alignmentsFeatureDetailConfigSchema, stateModel: alignmentsFeatureDetailStateModel, - ReactComponent: AlignmentsFeatureDetailReactComponent, + ReactComponent: lazy( + () => + import( + './BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail' + ), + ), }), ) } diff --git a/plugins/linear-genome-view/src/index.ts b/plugins/linear-genome-view/src/index.ts index 54716bcef4..1bdcc2320d 100644 --- a/plugins/linear-genome-view/src/index.ts +++ b/plugins/linear-genome-view/src/index.ts @@ -1,9 +1,5 @@ import { lazy } from 'react' -import { - configSchema as baseFeatureWidgetConfigSchema, - ReactComponent as BaseFeatureWidgetReactComponent, - stateModelFactory as baseFeatureWidgetStateModelFactory, -} from '@jbrowse/core/BaseFeatureWidget' + import { ConfigurationSchema } from '@jbrowse/core/configuration' import { createBaseTrackConfig, @@ -122,16 +118,6 @@ export default class LinearGenomeViewPlugin extends Plugin { ), }), ) - pluginManager.addWidgetType( - () => - new WidgetType({ - name: 'BaseFeatureWidget', - heading: 'Feature details', - configSchema: baseFeatureWidgetConfigSchema, - stateModel: baseFeatureWidgetStateModelFactory(pluginManager), - ReactComponent: BaseFeatureWidgetReactComponent, - }), - ) } configure(pluginManager: PluginManager) { diff --git a/plugins/trackhub-registry/src/index.ts b/plugins/trackhub-registry/src/index.ts index d6fe3d6084..b544f0d78e 100644 --- a/plugins/trackhub-registry/src/index.ts +++ b/plugins/trackhub-registry/src/index.ts @@ -1,8 +1,8 @@ +import { lazy } from 'react' import ConnectionType from '@jbrowse/core/pluggableElementTypes/ConnectionType' import Plugin from '@jbrowse/core/Plugin' import PluginManager from '@jbrowse/core/PluginManager' import { configSchema, modelFactory } from './trackhub-registry' -import TrackHubRegistrySelect from './trackhub-registry/TrackHubRegistrySelect' export default class TrackHubRegistryPlugin extends Plugin { name = 'TrackHubRegistryPlugin' @@ -13,7 +13,9 @@ export default class TrackHubRegistryPlugin extends Plugin { new ConnectionType({ name: 'TheTrackHubRegistryConnection', configSchema, - configEditorComponent: TrackHubRegistrySelect, + configEditorComponent: lazy( + () => import('./trackhub-registry/TrackHubRegistrySelect'), + ), stateModel: modelFactory(pluginManager), displayName: 'The Track Hub Registry', description: 'A hub from The Track Hub Registry', diff --git a/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js b/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js index 3334ebe52c..ebcb6d7d05 100644 --- a/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js +++ b/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js @@ -1,15 +1,16 @@ import { openLocation } from '@jbrowse/core/util/io' -import FormControl from '@material-ui/core/FormControl' -import FormControlLabel from '@material-ui/core/FormControlLabel' -import FormLabel from '@material-ui/core/FormLabel' -import LinearProgress from '@material-ui/core/LinearProgress' -import Radio from '@material-ui/core/Radio' -import RadioGroup from '@material-ui/core/RadioGroup' -import { makeStyles } from '@material-ui/core/styles' -import Tooltip from '@material-ui/core/Tooltip' -import Typography from '@material-ui/core/Typography' +import { + FormControl, + FormControlLabel, + FormLabel, + LinearProgress, + Radio, + RadioGroup, + Tooltip, + Typography, + makeStyles, +} from '@material-ui/core' import SanitizedHTML from '@jbrowse/core/ui/SanitizedHTML' -import { PropTypes as MobxPropTypes } from 'mobx-react' import PropTypes from 'prop-types' import React, { useEffect, useState } from 'react' import HubDetails from './HubDetails' @@ -313,9 +314,4 @@ function TrackHubRegistrySelect({ model, setModelReady }) { return <>{renderItems} } -TrackHubRegistrySelect.propTypes = { - model: MobxPropTypes.objectOrObservableObject.isRequired, - setModelReady: PropTypes.func.isRequired, -} - export default TrackHubRegistrySelect diff --git a/plugins/variants/src/index.ts b/plugins/variants/src/index.ts index 0ffcb01137..5bcb52816b 100644 --- a/plugins/variants/src/index.ts +++ b/plugins/variants/src/index.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import { ConfigurationSchema } from '@jbrowse/core/configuration' import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType' import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType' @@ -18,7 +19,6 @@ import { import StructuralVariantChordRendererFactory from './StructuralVariantChordRenderer' import { configSchema as variantFeatureWidgetConfigSchema, - ReactComponent as VariantFeatureWidgetReactComponent, stateModelFactory as variantFeatureWidgetStateModelFactory, } from './VariantFeatureWidget' import { @@ -84,7 +84,9 @@ export default class VariantsPlugin extends Plugin { heading: 'Feature details', configSchema: variantFeatureWidgetConfigSchema, stateModel: variantFeatureWidgetStateModelFactory(pluginManager), - ReactComponent: VariantFeatureWidgetReactComponent, + ReactComponent: lazy( + () => import('./VariantFeatureWidget/VariantFeatureWidget'), + ), }), ) } From 4ec8f6283615f73b9bc13687f0f02d96d6c3f0c9 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 8 Apr 2021 22:50:19 -0400 Subject: [PATCH 22/41] Updates --- packages/core/BaseFeatureWidget/index.test.js | 8 +++++--- packages/development-tools/index.js | 2 +- plugins/linear-genome-view/src/index.ts | 1 - .../src/trackhub-registry/TrackHubRegistrySelect.js | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/core/BaseFeatureWidget/index.test.js b/packages/core/BaseFeatureWidget/index.test.js index a8efc1aeb3..55df83d516 100644 --- a/packages/core/BaseFeatureWidget/index.test.js +++ b/packages/core/BaseFeatureWidget/index.test.js @@ -1,10 +1,10 @@ +import React, { Suspense } from 'react' import { render } from '@testing-library/react' -import React from 'react' import { types } from 'mobx-state-tree' import { ConfigurationSchema } from '@jbrowse/core/configuration' import PluginManager from '../PluginManager' import { stateModelFactory } from '.' -import { BaseFeatureDetails as ReactComponent } from './BaseFeatureDetail' +import BaseFeatureDetails from './BaseFeatureDetail' test('open up a widget', () => { console.warn = jest.fn() @@ -20,7 +20,9 @@ test('open up a widget', () => { widget: { type: 'BaseFeatureWidget' }, }) const { container, getByText } = render( - , + Loading...}> + + , ) model.widget.setFeatureData({ start: 2, diff --git a/packages/development-tools/index.js b/packages/development-tools/index.js index 50bc7cd8c1..e31be04818 100644 --- a/packages/development-tools/index.js +++ b/packages/development-tools/index.js @@ -24,8 +24,8 @@ function createJBrowsePluginTsdxConfig(config, options, globals) { // xref https://github.com/rollup/rollup/issues/2616 we use code splitting // but plugins want single file output - config.inlineDynamicImports = true } + config.inlineDynamicImports = true return config } diff --git a/plugins/linear-genome-view/src/index.ts b/plugins/linear-genome-view/src/index.ts index 1bdcc2320d..84d0ed2a46 100644 --- a/plugins/linear-genome-view/src/index.ts +++ b/plugins/linear-genome-view/src/index.ts @@ -8,7 +8,6 @@ import { import TrackType from '@jbrowse/core/pluggableElementTypes/TrackType' import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType' import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType' -import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType' import Plugin from '@jbrowse/core/Plugin' import PluginManager from '@jbrowse/core/PluginManager' import { AbstractSessionModel, isAbstractMenuManager } from '@jbrowse/core/util' diff --git a/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js b/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js index ebcb6d7d05..dafee71aeb 100644 --- a/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js +++ b/plugins/trackhub-registry/src/trackhub-registry/TrackHubRegistrySelect.js @@ -1,3 +1,4 @@ +/* eslint-disable react/prop-types */ import { openLocation } from '@jbrowse/core/util/io' import { FormControl, From 36c6893070fdb23805f8738cc201674904c87b84 Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 9 Apr 2021 11:00:22 -0400 Subject: [PATCH 23/41] Assemblymanager/variant detail widget code lazified --- .../src/AssemblyManager/AssemblyAddForm.tsx | 18 +++++++++------ plugins/data-management/src/index.ts | 8 +++++-- .../src/VariantFeatureWidget/index.js | 2 -- products/jbrowse-desktop/src/JBrowse.js | 22 +++++++++---------- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx b/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx index 07d11f1b40..c58b56e5ff 100644 --- a/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx +++ b/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx @@ -1,11 +1,17 @@ import React, { useState } from 'react' import { observer } from 'mobx-react' -import TextField from '@material-ui/core/TextField' import FileSelector from '@jbrowse/core/ui/FileSelector' import { FileLocation } from '@jbrowse/core/util/types' -import { Grid, MenuItem, Paper } from '@material-ui/core' -import { makeStyles, createStyles, Theme } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' +import { + Button, + Grid, + MenuItem, + Paper, + TextField, + Theme, + makeStyles, + createStyles, +} from '@material-ui/core' import AddIcon from '@material-ui/icons/Add' const useStyles = makeStyles((theme: Theme) => @@ -133,7 +139,7 @@ const AdapterInput = observer( }, ) -const AssemblyAddForm = observer( +export default observer( ({ rootModel, setFormOpen, @@ -252,5 +258,3 @@ const AssemblyAddForm = observer( ) }, ) - -export default AssemblyAddForm diff --git a/plugins/data-management/src/index.ts b/plugins/data-management/src/index.ts index a8eaf515b1..38577a83bd 100644 --- a/plugins/data-management/src/index.ts +++ b/plugins/data-management/src/index.ts @@ -22,8 +22,10 @@ import { stateModelFactory as HierarchicalTrackSelectorStateModelFactory, configSchema as HierarchicalTrackSelectorConfigSchema, } from './HierarchicalTrackSelectorWidget' -import AssemblyManager from './AssemblyManager' -import SetDefaultSession from './SetDefaultSession' + +const SetDefaultSession = lazy(() => import('./SetDefaultSession')) + +const AssemblyManager = lazy(() => import('./AssemblyManager')) export default class extends Plugin { name = 'DataManagementPlugin' @@ -123,3 +125,5 @@ export default class extends Plugin { } } } + +export { AssemblyManager, SetDefaultSession } diff --git a/plugins/variants/src/VariantFeatureWidget/index.js b/plugins/variants/src/VariantFeatureWidget/index.js index 1faf398beb..2df44a5208 100644 --- a/plugins/variants/src/VariantFeatureWidget/index.js +++ b/plugins/variants/src/VariantFeatureWidget/index.js @@ -24,5 +24,3 @@ export function stateModelFactory(pluginManager) { }, })) } - -export { default as ReactComponent } from './VariantFeatureWidget' diff --git a/products/jbrowse-desktop/src/JBrowse.js b/products/jbrowse-desktop/src/JBrowse.js index 0b2fb12dbd..c6611b7306 100644 --- a/products/jbrowse-desktop/src/JBrowse.js +++ b/products/jbrowse-desktop/src/JBrowse.js @@ -1,3 +1,4 @@ +import React, { useEffect, useState, Suspense } from 'react' import { getConf } from '@jbrowse/core/configuration' import { App, createJBrowseTheme } from '@jbrowse/core/ui' import CssBaseline from '@material-ui/core/CssBaseline' @@ -5,7 +6,7 @@ import { ThemeProvider } from '@material-ui/core/styles' import { observer } from 'mobx-react' import { onSnapshot } from 'mobx-state-tree' -import React, { useEffect, useState } from 'react' +import { AssemblyManager } from '@jbrowse/plugin-data-management' import StartScreen from './StartScreen' import factoryReset from './factoryReset' @@ -89,9 +90,6 @@ const JBrowse = observer(({ pluginManager }) => { } const theme = getConf(rootModel.jbrowse, 'theme') - const { AssemblyManager } = pluginManager.getPlugin( - 'DataManagementPlugin', - ).exports return ( @@ -99,13 +97,15 @@ const JBrowse = observer(({ pluginManager }) => { {rootModel.session ? ( <> - { - rootModel.setAssemblyEditing(false) - }} - /> + }> + { + rootModel.setAssemblyEditing(false) + }} + /> + ) : ( Date: Fri, 9 Apr 2021 19:31:12 -0400 Subject: [PATCH 24/41] Add some tsdx configs to disable splitting bundle --- packages/development-tools/index.js | 4 ---- plugins/alignments/tsdx.config.js | 8 ++++++++ plugins/bed/tsdx.config.js | 8 ++++++++ plugins/breakpoint-split-view/tsdx.config.js | 8 ++++++++ plugins/circular-view/tsdx.config.js | 8 ++++++++ plugins/config/tsdx.config.js | 8 ++++++++ plugins/data-management/tsdx.config.js | 8 ++++++++ plugins/dotplot-view/tsdx.config.js | 8 ++++++++ plugins/filtering/tsdx.config.js | 8 ++++++++ plugins/gff3/tsdx.config.js | 8 ++++++++ plugins/hic/tsdx.config.js | 8 ++++++++ plugins/legacy-jbrowse/tsdx.config.js | 8 ++++++++ plugins/linear-comparative-view/tsdx.config.js | 8 ++++++++ plugins/linear-genome-view/tsdx.config.js | 8 ++++++++ plugins/lollipop/tsdx.config.js | 8 ++++++++ plugins/menus/tsdx.config.js | 8 ++++++++ plugins/protein/tsdx.config.js | 8 ++++++++ plugins/rdf/tsdx.config.js | 8 ++++++++ plugins/sequence/tsdx.config.js | 8 ++++++++ plugins/spreadsheet-view/tsdx.config.js | 8 ++++++++ plugins/sv-inspector/tsdx.config.js | 8 ++++++++ plugins/svg/tsdx.config.js | 8 ++++++++ plugins/trackhub-registry/tsdx.config.js | 8 ++++++++ plugins/variants/tsdx.config.js | 8 ++++++++ plugins/wiggle/tsdx.config.js | 8 ++++++++ 25 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 plugins/alignments/tsdx.config.js create mode 100644 plugins/bed/tsdx.config.js create mode 100644 plugins/breakpoint-split-view/tsdx.config.js create mode 100644 plugins/circular-view/tsdx.config.js create mode 100644 plugins/config/tsdx.config.js create mode 100644 plugins/data-management/tsdx.config.js create mode 100644 plugins/dotplot-view/tsdx.config.js create mode 100644 plugins/filtering/tsdx.config.js create mode 100644 plugins/gff3/tsdx.config.js create mode 100644 plugins/hic/tsdx.config.js create mode 100644 plugins/legacy-jbrowse/tsdx.config.js create mode 100644 plugins/linear-comparative-view/tsdx.config.js create mode 100644 plugins/linear-genome-view/tsdx.config.js create mode 100644 plugins/lollipop/tsdx.config.js create mode 100644 plugins/menus/tsdx.config.js create mode 100644 plugins/protein/tsdx.config.js create mode 100644 plugins/rdf/tsdx.config.js create mode 100644 plugins/sequence/tsdx.config.js create mode 100644 plugins/spreadsheet-view/tsdx.config.js create mode 100644 plugins/sv-inspector/tsdx.config.js create mode 100644 plugins/svg/tsdx.config.js create mode 100644 plugins/trackhub-registry/tsdx.config.js create mode 100644 plugins/variants/tsdx.config.js create mode 100644 plugins/wiggle/tsdx.config.js diff --git a/packages/development-tools/index.js b/packages/development-tools/index.js index e31be04818..9ea81b7448 100644 --- a/packages/development-tools/index.js +++ b/packages/development-tools/index.js @@ -21,11 +21,7 @@ function createJBrowsePluginTsdxConfig(config, options, globals) { globals.forEach(global => { config.output.globals[global] = `JBrowseExports.${global}` }) - - // xref https://github.com/rollup/rollup/issues/2616 we use code splitting - // but plugins want single file output } - config.inlineDynamicImports = true return config } diff --git a/plugins/alignments/tsdx.config.js b/plugins/alignments/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/alignments/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/bed/tsdx.config.js b/plugins/bed/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/bed/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/breakpoint-split-view/tsdx.config.js b/plugins/breakpoint-split-view/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/breakpoint-split-view/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/circular-view/tsdx.config.js b/plugins/circular-view/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/circular-view/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/config/tsdx.config.js b/plugins/config/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/config/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/data-management/tsdx.config.js b/plugins/data-management/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/data-management/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/dotplot-view/tsdx.config.js b/plugins/dotplot-view/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/dotplot-view/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/filtering/tsdx.config.js b/plugins/filtering/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/filtering/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/gff3/tsdx.config.js b/plugins/gff3/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/gff3/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/hic/tsdx.config.js b/plugins/hic/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/hic/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/legacy-jbrowse/tsdx.config.js b/plugins/legacy-jbrowse/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/legacy-jbrowse/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/linear-comparative-view/tsdx.config.js b/plugins/linear-comparative-view/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/linear-comparative-view/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/linear-genome-view/tsdx.config.js b/plugins/linear-genome-view/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/linear-genome-view/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/lollipop/tsdx.config.js b/plugins/lollipop/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/lollipop/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/menus/tsdx.config.js b/plugins/menus/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/menus/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/protein/tsdx.config.js b/plugins/protein/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/protein/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/rdf/tsdx.config.js b/plugins/rdf/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/rdf/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/sequence/tsdx.config.js b/plugins/sequence/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/sequence/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/spreadsheet-view/tsdx.config.js b/plugins/spreadsheet-view/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/spreadsheet-view/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/sv-inspector/tsdx.config.js b/plugins/sv-inspector/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/sv-inspector/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/svg/tsdx.config.js b/plugins/svg/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/svg/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/trackhub-registry/tsdx.config.js b/plugins/trackhub-registry/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/trackhub-registry/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/variants/tsdx.config.js b/plugins/variants/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/variants/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} diff --git a/plugins/wiggle/tsdx.config.js b/plugins/wiggle/tsdx.config.js new file mode 100644 index 0000000000..02db3c28c4 --- /dev/null +++ b/plugins/wiggle/tsdx.config.js @@ -0,0 +1,8 @@ +// Not transpiled with TypeScript or Babel, so use plain Es6/Node.js! +module.exports = { + // This function will run for each entry/format/env combination + rollup(config) { + config.inlineDynamicImports = true + return config // always return a config. + }, +} From 01e68ff9ad28cce8c21357d603075e5c0e535a97 Mon Sep 17 00:00:00 2001 From: Colin Date: Mon, 12 Apr 2021 22:52:06 -0400 Subject: [PATCH 25/41] Use a specific build of jbrowse-web to get lazy loading instead of full repo build --- .github/workflows/push.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 84de0e3a63..50d30f28f5 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -38,8 +38,8 @@ jobs: run: | cd website/ yarn build - buildjbrowseweb: - name: Build jbrowse-web on node 10.x and ubuntu-latest + buildwholerepo: + name: Build whole repo on node 10.x and ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -53,6 +53,24 @@ jobs: run: yarn build - name: Test build run: BUILT_TESTS=1 yarn built-test-ci + + buildjbrowseweb: + name: Build only jbrowse-web and upload to s3 on node 10.x and ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 10.x + uses: actions/setup-node@v1 + with: + node-version: 10.x + - name: Install deps (with cache) + uses: bahmutov/npm-install@v1 + - name: Build project + run: | + echo $RELEASE_VERSION + cd products/jbrowse-web/ + NODE_OPTIONS='--max-old-space-size=6500' yarn build + cd ../../ - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: From e404b82b03b1aa6a3cc238a814e71b2c245da059 Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 13 Apr 2021 00:10:25 -0400 Subject: [PATCH 26/41] Increase timeout --- products/jbrowse-web/src/tests/JBrowse.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/products/jbrowse-web/src/tests/JBrowse.test.js b/products/jbrowse-web/src/tests/JBrowse.test.js index ce79a04b2a..e98c38f92a 100644 --- a/products/jbrowse-web/src/tests/JBrowse.test.js +++ b/products/jbrowse-web/src/tests/JBrowse.test.js @@ -299,8 +299,10 @@ test('wrong assembly', async () => { view.showTrack('volvox_wrong_assembly') await findAllByText( 'Error: region assembly (volvox) does not match track assemblies (wombat)', + {}, + { timeout: 10000 }, ) -}) +}, 15000) test('looks at about this track dialog', async () => { const pluginManager = getPluginManager() From a8606b0ae884b25caa258695a8410d802d6c1cb9 Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 13 Apr 2021 19:06:38 -0400 Subject: [PATCH 27/41] Add a suspense on embedded mode but sometimes only menu shows up --- .../JBrowseLinearGenomeView/ViewContainer.tsx | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx index 84e101a6c3..c48bf47d65 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx @@ -1,18 +1,20 @@ +import React, { useEffect, useState, Suspense } from 'react' import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models/BaseViewModel' import { Menu, Logomark } from '@jbrowse/core/ui' import { getSession } from '@jbrowse/core/util' -import IconButton, { +import { + IconButton, IconButtonProps as IconButtonPropsType, -} from '@material-ui/core/IconButton' -import Paper from '@material-ui/core/Paper' -import { makeStyles, useTheme } from '@material-ui/core/styles' -import { fade } from '@material-ui/core/styles/colorManipulator' -import { SvgIconProps } from '@material-ui/core/SvgIcon' + Paper, + makeStyles, + useTheme, + SvgIconProps, + fade, +} from '@material-ui/core' import Typography from '@material-ui/core/Typography' import MenuIcon from '@material-ui/icons/Menu' import { observer } from 'mobx-react' import { isAlive } from 'mobx-state-tree' -import React, { useEffect, useState } from 'react' import useDimensions from 'react-use-dimensions' import AboutDialog from '@jbrowse/core/ui/AboutDialog' @@ -122,34 +124,36 @@ function ViewContainer({ }, [padWidth, view, width]) return ( - -
- -
- {view.displayName ? ( - - {view.displayName} - - ) : null} -
-
- + Loading...
}> + +
+ +
+ {view.displayName ? ( + + {view.displayName} + + ) : null} +
+
+ +
-
- {children} - + {children} + + ) } From f6af519d89792d62d39f59d8900a46624ac91c52 Mon Sep 17 00:00:00 2001 From: Colin Date: Wed, 14 Apr 2021 22:54:00 -0400 Subject: [PATCH 28/41] Fix some dialog props --- plugins/linear-comparative-view/src/index.tsx | 8 +++----- plugins/linear-genome-view/src/LinearGenomeView/index.ts | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/linear-comparative-view/src/index.tsx b/plugins/linear-comparative-view/src/index.tsx index a72e0e9ae4..7f9971cc57 100644 --- a/plugins/linear-comparative-view/src/index.tsx +++ b/plugins/linear-comparative-view/src/index.tsx @@ -31,6 +31,7 @@ import { AbstractSessionModel, getSession, getContainingView, + getContainingTrack, isAbstractMenuManager, } from '@jbrowse/core/util' @@ -163,11 +164,7 @@ function WindowSizeDlg(props: { track: any }) { const classes = useStyles() - const { - track, - display: { feature: preFeature }, - handleClose, - } = props + const { track, feature: preFeature, handleClose } = props const [window, setWindowSize] = useState('0') const [error, setError] = useState() const windowSize = +window @@ -624,6 +621,7 @@ export default class extends Plugin { icon: AddIcon, onClick: () => { getSession(display).setDialogComponent(WindowSizeDlg, { + track: getContainingTrack(display), feature, }) }, diff --git a/plugins/linear-genome-view/src/LinearGenomeView/index.ts b/plugins/linear-genome-view/src/LinearGenomeView/index.ts index 0420add869..4df85bf73a 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/index.ts +++ b/plugins/linear-genome-view/src/LinearGenomeView/index.ts @@ -1160,7 +1160,9 @@ export function stateModelFactory(pluginManager: PluginManager) { { label: 'Return to import form', onClick: () => { - getSession(self).setDialogComponent(ReturnToImportFormDlg) + getSession(self).setDialogComponent(ReturnToImportFormDlg, { + model: self, + }) }, icon: FolderOpenIcon, }, From 6e1a9d8da8f74dcc732cc61c9e318d8950baa433 Mon Sep 17 00:00:00 2001 From: Colin Date: Wed, 14 Apr 2021 23:16:16 -0400 Subject: [PATCH 29/41] Small refactors --- .../JBrowseLinearGenomeView.tsx | 62 ++++++------- .../JBrowseLinearGenomeView/ModalWidget.tsx | 86 ++++++++++--------- .../JBrowseLinearGenomeView/ViewContainer.tsx | 52 +++++------ .../JBrowseLinearGenomeView.test.tsx.snap | 2 +- .../src/createModel/createSessionModel.ts | 1 + 5 files changed, 101 insertions(+), 102 deletions(-) diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx index 5bb4720030..58d6b779d4 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx @@ -1,34 +1,38 @@ +import React, { Suspense } from 'react' import { observer } from 'mobx-react' -import { Instance, getEnv } from 'mobx-state-tree' -import React from 'react' -import createSessionModel from '../createModel/createSessionModel' +import { getEnv } from 'mobx-state-tree' +import { makeStyles } from '@material-ui/core' +import { SessionModel } from '../createModel/createSessionModel' import ModalWidget from './ModalWidget' import ViewContainer from './ViewContainer' -type Session = Instance> -function JBrowseLinearGenomeView({ - viewState, -}: { - viewState: { session: Session } -}) { - const { session } = viewState - const { view } = session - const viewType = getEnv(session).pluginManager.getViewType(view.type) - if (!viewType) { - throw new Error(`unknown view type ${view.type}`) - } - const { ReactComponent } = viewType +const useStyles = makeStyles(() => ({ + avoidParentStyle: { + all: 'initial', + }, +})) +export default observer( + ({ viewState }: { viewState: { session: SessionModel } }) => { + const classes = useStyles() + const { session } = viewState + const { view } = session + const viewType = getEnv(session).pluginManager.getViewType(view.type) + if (!viewType) { + throw new Error(`unknown view type ${view.type}`) + } + const { ReactComponent } = viewType - return ( - // avoid parent styles getting into this div - // https://css-tricks.com/almanac/properties/a/all/ -
- - - - -
- ) -} - -export default observer(JBrowseLinearGenomeView) + return ( + // avoid parent styles getting into this div + // https://css-tricks.com/almanac/properties/a/all/ +
+ + Wow
}> + + + + +
+ ) + }, +) diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx index 3133d38bb1..c2ed45e2ed 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx @@ -1,15 +1,15 @@ -import AppBar from '@material-ui/core/AppBar' -import Modal from '@material-ui/core/Modal' -import Paper from '@material-ui/core/Paper' -import Toolbar from '@material-ui/core/Toolbar' -import Typography from '@material-ui/core/Typography' -import { makeStyles } from '@material-ui/core/styles' +import React, { Suspense } from 'react' +import { + AppBar, + Modal, + Paper, + Toolbar, + Typography, + makeStyles, +} from '@material-ui/core' import { observer } from 'mobx-react' -import { Instance, getEnv } from 'mobx-state-tree' -import React from 'react' -import createSessionModel from '../createModel/createSessionModel' - -type Session = Instance> +import { getEnv } from 'mobx-state-tree' +import { SessionModel } from '../createModel/createSessionModel' const useStyles = makeStyles({ paper: { @@ -23,37 +23,41 @@ const useStyles = makeStyles({ }, }) -const ModalWidgetContents = observer(({ session }: { session: Session }) => { - const { visibleWidget } = session - if (!visibleWidget) { +const ModalWidgetContents = observer( + ({ session }: { session: SessionModel }) => { + const { visibleWidget } = session + if (!visibleWidget) { + return ( + + + + ) + } + const { ReactComponent, HeadingComponent, heading } = getEnv( + session, + ).pluginManager.getWidgetType(visibleWidget.type) return ( - - - + <> + + + {HeadingComponent ? ( + + ) : ( + {heading} + )} + + + {visibleWidget && ReactComponent ? ( + Loading...
}> + + + ) : null} + ) - } - const { ReactComponent, HeadingComponent, heading } = getEnv( - session, - ).pluginManager.getWidgetType(visibleWidget.type) - return ( - <> - - - {HeadingComponent ? ( - - ) : ( - {heading} - )} - - - {visibleWidget && ReactComponent ? ( - - ) : null} - - ) -}) + }, +) -function ModalWidget({ session }: { session: Session }) { +export default observer(({ session }: { session: SessionModel }) => { const classes = useStyles() const { visibleWidget, hideAllWidgets } = session return ( @@ -63,6 +67,4 @@ function ModalWidget({ session }: { session: Session }) { ) -} - -export default observer(ModalWidget) +}) diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx index c48bf47d65..ce18f71531 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx @@ -1,22 +1,22 @@ -import React, { useEffect, useState, Suspense } from 'react' -import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models/BaseViewModel' -import { Menu, Logomark } from '@jbrowse/core/ui' -import { getSession } from '@jbrowse/core/util' +import React, { useEffect, useState } from 'react' import { IconButton, IconButtonProps as IconButtonPropsType, Paper, + SvgIconProps, + Typography, makeStyles, useTheme, - SvgIconProps, fade, } from '@material-ui/core' -import Typography from '@material-ui/core/Typography' import MenuIcon from '@material-ui/icons/Menu' import { observer } from 'mobx-react' import { isAlive } from 'mobx-state-tree' import useDimensions from 'react-use-dimensions' import AboutDialog from '@jbrowse/core/ui/AboutDialog' +import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models/BaseViewModel' +import { Menu, Logomark } from '@jbrowse/core/ui' +import { getSession } from '@jbrowse/core/util' const useStyles = makeStyles(theme => ({ viewContainer: { @@ -102,29 +102,23 @@ const ViewMenu = observer( }, ) -function ViewContainer({ - view, - children, -}: { - view: IBaseViewModel - children: React.ReactNode -}) { - const classes = useStyles() - const theme = useTheme() - const [measureRef, { width }] = useDimensions() +export default observer( + ({ view, children }: { view: IBaseViewModel; children: React.ReactNode }) => { + const classes = useStyles() + const theme = useTheme() + const [measureRef, { width }] = useDimensions() - const padWidth = theme.spacing(1) + const padWidth = theme.spacing(1) - useEffect(() => { - if (width) { - if (isAlive(view)) { - view.setWidth(width - padWidth * 2) + useEffect(() => { + if (width) { + if (isAlive(view)) { + view.setWidth(width - padWidth * 2) + } } - } - }, [padWidth, view, width]) + }, [padWidth, view, width]) - return ( - Loading...
}> + return ( {children} - - ) -} - -export default observer(ViewContainer) + ) + }, +) diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap index 9235f9f7e1..26c53ceb04 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/__snapshots__/JBrowseLinearGenomeView.test.tsx.snap @@ -2,7 +2,7 @@ exports[` renders successfully 1`] = `
+export type SessionModel = Instance /* eslint-disable @typescript-eslint/no-unused-vars */ // @ts-ignore From 76a7469f85ba13ceb73627263b27cc41a66ddcf1 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 15 Apr 2021 17:22:47 -0400 Subject: [PATCH 30/41] More lazy --- .../core/pluggableElementTypes/ViewType.ts | 2 +- .../components/HierarchicalTrackSelector.js | 86 ++++++++++--------- .../components/SetMaxHeight.tsx | 8 +- .../src/LinearBasicDisplay/model.ts | 8 +- ...rningModal.tsx => ConfigWarningDialog.tsx} | 0 products/jbrowse-web/src/Loader.tsx | 66 +++++++------- ...ningModal.tsx => SessionWarningDialog.tsx} | 0 7 files changed, 91 insertions(+), 79 deletions(-) rename products/jbrowse-web/src/{configWarningModal.tsx => ConfigWarningDialog.tsx} (100%) rename products/jbrowse-web/src/{sessionWarningModal.tsx => SessionWarningDialog.tsx} (100%) diff --git a/packages/core/pluggableElementTypes/ViewType.ts b/packages/core/pluggableElementTypes/ViewType.ts index bb2716a1b7..f259ee7e99 100644 --- a/packages/core/pluggableElementTypes/ViewType.ts +++ b/packages/core/pluggableElementTypes/ViewType.ts @@ -10,7 +10,7 @@ type ViewReactComponent = React.ComponentType<{ }> export default class ViewType extends PluggableElementBase { - ReactComponent?: ViewReactComponent + ReactComponent: ViewReactComponent stateModel: IAnyModelType diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js index 1befc2341a..c7ed9d4735 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js @@ -1,5 +1,12 @@ /* eslint-disable react/prop-types,no-nested-ternary,jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events */ -import React, { useCallback, useState, useRef, useEffect } from 'react' +import React, { + Suspense, + lazy, + useCallback, + useState, + useRef, + useEffect, +} from 'react' import { Checkbox, Fab, @@ -30,10 +37,10 @@ import { readConfObject } from '@jbrowse/core/configuration' import { observer } from 'mobx-react' import { VariableSizeTree } from 'react-vtree' -import CloseConnectionDialog from './CloseConnectionDialog' -import DeleteConnectionDialog from './DeleteConnectionDialog' -import ManageConnectionsDialog from './ManageConnectionsDialog' -import ToggleConnectionsDialog from './ToggleConnectionsDialog' +const CloseConnectionDialog = lazy(() => import('./CloseConnectionDialog')) +const DeleteConnectionDialog = lazy(() => import('./DeleteConnectionDialog')) +const ManageConnectionsDialog = lazy(() => import('./ManageConnectionsDialog')) +const ToggleConnectionsDialog = lazy(() => import('./ToggleConnectionsDialog')) const useStyles = makeStyles(theme => ({ searchBox: { @@ -196,12 +203,6 @@ const HierarchicalTree = observer(({ height, tree, model }) => { const session = getSession(model) const { filterText } = model - // const rootNode = { - // name: 'Tracks', - // id: 'Tracks', - // children: tree, - // } - const extra = { onChange: trackId => model.view.toggleTrack(trackId), onMoreInfo: setMoreInfo, @@ -494,36 +495,39 @@ const HierarchicalTrackSelectorHeader = observer( }} menuItems={menuItems} /> - {modalInfo ? ( - - ) : deleteDialogDetails ? ( - { - setDeleteDialogDetails(undefined) - }} - deleteDialogDetails={deleteDialogDetails} - session={session} - /> - ) : null} - {connectionManagerOpen ? ( - setConnectionManagerOpen(false)} - breakConnection={breakConnection} - session={session} - /> - ) : null} - {connectionToggleOpen ? ( - setConnectionToggleOpen(false)} - session={session} - breakConnection={breakConnection} - assemblyName={assemblyName} - /> - ) : null} + + }> + {modalInfo ? ( + + ) : deleteDialogDetails ? ( + { + setDeleteDialogDetails(undefined) + }} + deleteDialogDetails={deleteDialogDetails} + session={session} + /> + ) : null} + {connectionManagerOpen ? ( + setConnectionManagerOpen(false)} + breakConnection={breakConnection} + session={session} + /> + ) : null} + {connectionToggleOpen ? ( + setConnectionToggleOpen(false)} + session={session} + breakConnection={breakConnection} + assemblyName={assemblyName} + /> + ) : null} +
) }, diff --git a/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx b/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx index d10c2c9611..1320b820a4 100644 --- a/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx +++ b/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx @@ -27,15 +27,15 @@ const useStyles = makeStyles(theme => ({ export default observer( (props: { - display: { + model: { maxHeight?: number setMaxHeight: Function } handleClose: () => void }) => { - const { display, handleClose } = props + const { model, handleClose } = props const classes = useStyles() - const { maxHeight = '' } = display + const { maxHeight = '' } = model const [max, setMax] = useState(`${maxHeight}`) return ( @@ -71,7 +71,7 @@ export default observer( type="submit" style={{ marginLeft: 20 }} onClick={() => { - display.setMaxHeight( + model.setMaxHeight( max !== '' && !Number.isNaN(+max) ? +max : undefined, ) handleClose() diff --git a/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts b/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts index b473347f8f..73bbc288af 100644 --- a/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts +++ b/plugins/linear-genome-view/src/LinearBasicDisplay/model.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import { ConfigurationReference, getConf } from '@jbrowse/core/configuration' import { getParentRenderProps } from '@jbrowse/core/util/tracks' import { getSession } from '@jbrowse/core/util' @@ -6,7 +7,8 @@ import VisibilityIcon from '@material-ui/icons/Visibility' import { types, Instance } from 'mobx-state-tree' import { AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema' import { BaseLinearDisplay } from '../BaseLinearDisplay' -import SetMaxHeightDlg from './components/SetMaxHeight' + +const SetMaxHeightDlg = lazy(() => import('./components/SetMaxHeight')) const stateModelFactory = (configSchema: AnyConfigurationSchemaType) => types @@ -112,7 +114,9 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) => { label: 'Set max height', onClick: () => { - getSession(self).setDialogComponent(SetMaxHeightDlg, self) + getSession(self).setDialogComponent(SetMaxHeightDlg, { + model: self, + }) }, }, ] diff --git a/products/jbrowse-web/src/configWarningModal.tsx b/products/jbrowse-web/src/ConfigWarningDialog.tsx similarity index 100% rename from products/jbrowse-web/src/configWarningModal.tsx rename to products/jbrowse-web/src/ConfigWarningDialog.tsx diff --git a/products/jbrowse-web/src/Loader.tsx b/products/jbrowse-web/src/Loader.tsx index 16c247e305..2ca9bba53f 100644 --- a/products/jbrowse-web/src/Loader.tsx +++ b/products/jbrowse-web/src/Loader.tsx @@ -8,8 +8,8 @@ import { openLocation } from '@jbrowse/core/util/io' import ErrorBoundary from 'react-error-boundary' import { StringParam, - useQueryParam, QueryParamProvider, + useQueryParam, } from 'use-query-params' import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema' import { types, addDisposer, Instance, SnapshotOut } from 'mobx-state-tree' @@ -33,8 +33,9 @@ import JBrowse from './JBrowse' import JBrowseRootModelFactory from './rootModel' import packagedef from '../package.json' import factoryReset from './factoryReset' -import SessionWarningModal from './sessionWarningModal' -import ConfigWarningModal from './configWarningModal' + +const SessionWarningDialog = lazy(() => import('./SessionWarningDialog')) +const ConfigWarningDialog = lazy(() => import('./ConfigWarningDialog')) const StartScreen = lazy(() => import('./StartScreen')) @@ -566,7 +567,6 @@ const Renderer = observer( {`${err}`} {snapshotError ? ( <> - {' '} ... Failed element had snapshot:
 {
-            const session = JSON.parse(
-              JSON.stringify(loader.sessionTriaged.snap),
-            )
-            loader.setSessionSnapshot({ ...session, id: shortid() })
-            handleClose()
-          }}
-          onCancel={() => {
-            loader.setBlankSession(true)
-            handleClose()
-          }}
-        />
+        }>
+           {
+              const session = JSON.parse(
+                JSON.stringify(loader.sessionTriaged.snap),
+              )
+              loader.setSessionSnapshot({ ...session, id: shortid() })
+              handleClose()
+            }}
+            onCancel={() => {
+              loader.setBlankSession(true)
+              handleClose()
+            }}
+          />
+        
       ) : (
-         {
-            const session = JSON.parse(
-              JSON.stringify(loader.sessionTriaged.snap),
-            )
-            await loader.fetchPlugins(session)
-            loader.setConfigSnapshot({ ...session, id: shortid() })
-            handleClose()
-          }}
-          onCancel={() => {
-            factoryReset()
-            handleClose()
-          }}
-        />
+        }>
+           {
+              const session = JSON.parse(
+                JSON.stringify(loader.sessionTriaged.snap),
+              )
+              await loader.fetchPlugins(session)
+              loader.setConfigSnapshot({ ...session, id: shortid() })
+              handleClose()
+            }}
+            onCancel={() => {
+              factoryReset()
+              handleClose()
+            }}
+          />
+        
       )
     }
     if (pm) {
diff --git a/products/jbrowse-web/src/sessionWarningModal.tsx b/products/jbrowse-web/src/SessionWarningDialog.tsx
similarity index 100%
rename from products/jbrowse-web/src/sessionWarningModal.tsx
rename to products/jbrowse-web/src/SessionWarningDialog.tsx

From 048db55b7bb4f1e6937baaa298817022952a08c9 Mon Sep 17 00:00:00 2001
From: Colin 
Date: Thu, 15 Apr 2021 21:50:14 -0400
Subject: [PATCH 31/41] Better suspense fallback loading message

---
 .../src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx
index 58d6b779d4..36d2ea8e44 100644
--- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx
+++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx
@@ -7,6 +7,8 @@ import ModalWidget from './ModalWidget'
 import ViewContainer from './ViewContainer'
 
 const useStyles = makeStyles(() => ({
+  // avoid parent styles getting into this div
+  // https://css-tricks.com/almanac/properties/a/all/
   avoidParentStyle: {
     all: 'initial',
   },
@@ -23,11 +25,9 @@ export default observer(
     const { ReactComponent } = viewType
 
     return (
-      // avoid parent styles getting into this div
-      // https://css-tricks.com/almanac/properties/a/all/
       
- Wow
}> + Loading...
}> From a38f469c0c955fa0d38dc3e14d4cf85328c033d8 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 15 Apr 2021 22:36:20 -0400 Subject: [PATCH 32/41] Export name --- .../BaseFeatureWidget/BaseFeatureDetail.tsx | 20 ++++++++----------- .../core/pluggableElementTypes/ViewType.ts | 14 +++++++------ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx b/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx index ad1c1684f0..29792d6fc7 100644 --- a/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx +++ b/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx @@ -429,23 +429,17 @@ export const FeatureDetails = (props: { formatter?: (val: unknown, key: string) => JSX.Element }) => { const { omit = [], model, feature, depth = 0 } = props - const { name, id, type, subfeatures } = feature - const displayName = (name || id) as string | undefined - const ellipsedDisplayName = - displayName && displayName.length > 20 ? '' : displayName + const { name, id, type = '', subfeatures } = feature + const slug = name || id + const shortName = slug && slug.length > 20 ? `${slug}...` : slug + const title = `${shortName ? `${shortName} - ` : ''} - ${type}` const session = getSession(model) const defSeqTypes = ['mRNA', 'transcript'] const sequenceTypes = getConf(session, ['featureDetails', 'sequenceTypes']) || defSeqTypes return ( - +
Core details
@@ -485,7 +479,7 @@ export const FeatureDetails = (props: { ) } -export default observer((props: BaseInputProps) => { +const BaseFeatureDetails = observer((props: BaseInputProps) => { const { model } = props const { featureData } = model @@ -499,3 +493,5 @@ export default observer((props: BaseInputProps) => { } return }) + +export default BaseFeatureDetails diff --git a/packages/core/pluggableElementTypes/ViewType.ts b/packages/core/pluggableElementTypes/ViewType.ts index f259ee7e99..9169f5754f 100644 --- a/packages/core/pluggableElementTypes/ViewType.ts +++ b/packages/core/pluggableElementTypes/ViewType.ts @@ -2,12 +2,14 @@ import { IAnyModelType, IAnyStateTreeNode } from 'mobx-state-tree' import PluggableElementBase from './PluggableElementBase' import DisplayType from './DisplayType' -type ViewReactComponent = React.ComponentType<{ - // TODO: can we use AbstractViewModel here? - // eslint-disable-next-line @typescript-eslint/no-explicit-any - model: any - session?: IAnyStateTreeNode -}> +type ViewReactComponent = React.LazyExoticComponent< + React.ComponentType<{ + // TODO: can we use AbstractViewModel here? + // eslint-disable-next-line @typescript-eslint/no-explicit-any + model: any + session?: IAnyStateTreeNode + }> +> export default class ViewType extends PluggableElementBase { ReactComponent: ViewReactComponent From a0763458da2498387acb04a7e0e769331897011d Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 15 Apr 2021 23:01:17 -0400 Subject: [PATCH 33/41] Give display names to default exported react components, and add LazyExoticComponent wrappers to View/Widget types --- .../core/pluggableElementTypes/WidgetType.ts | 6 +- packages/core/ui/App.js | 4 +- packages/core/ui/Drawer.js | 2 +- packages/core/ui/DrawerWidget.js | 4 +- packages/core/ui/Tooltip.tsx | 19 +- .../components/ConfigurationEditor.js | 21 +- plugins/menus/src/HelpWidget/index.js | 2 - .../menus/src/ImportSessionWidget/index.ts | 2 - .../components/SessionManager.js | 4 +- plugins/menus/src/SessionManager/index.js | 2 - .../components/ColumnFilterControls.js | 72 +++---- .../SpreadsheetView/components/ColumnMenu.js | 4 +- .../components/GlobalFilterControls.js | 4 +- .../components/ImportWizard.js | 4 +- .../SpreadsheetView/components/RowMenu.tsx | 4 +- .../SpreadsheetView/components/Spreadsheet.js | 4 +- .../components/SpreadsheetView.js | 4 +- .../BreakendOptionDialog.tsx | 187 +++++++++--------- .../JBrowseLinearGenomeView.tsx | 5 +- .../JBrowseLinearGenomeView/ModalWidget.tsx | 4 +- .../JBrowseLinearGenomeView/ViewContainer.tsx | 4 +- 21 files changed, 185 insertions(+), 177 deletions(-) diff --git a/packages/core/pluggableElementTypes/WidgetType.ts b/packages/core/pluggableElementTypes/WidgetType.ts index 90b24d3faf..09e20bb8cb 100644 --- a/packages/core/pluggableElementTypes/WidgetType.ts +++ b/packages/core/pluggableElementTypes/WidgetType.ts @@ -1,4 +1,4 @@ -import { ComponentType } from 'react' +import { ComponentType, LazyExoticComponent } from 'react' import { IAnyModelType, IAnyStateTreeNode } from 'mobx-state-tree' import PluggableElementBase from './PluggableElementBase' import { AnyConfigurationSchemaType } from '../configuration/configurationSchema' @@ -11,7 +11,7 @@ export default class WidgetType extends PluggableElementBase { HeadingComponent?: ComponentType<{ model: IAnyStateTreeNode }> // eslint-disable-next-line @typescript-eslint/no-explicit-any - ReactComponent: React.FC + ReactComponent: LazyExoticComponent> stateModel: IAnyModelType @@ -22,7 +22,7 @@ export default class WidgetType extends PluggableElementBase { configSchema: AnyConfigurationSchemaType stateModel: IAnyModelType // eslint-disable-next-line @typescript-eslint/no-explicit-any - ReactComponent: React.FC + ReactComponent: LazyExoticComponent> }) { super(stuff) this.heading = stuff.heading diff --git a/packages/core/ui/App.js b/packages/core/ui/App.js index fcf4f27823..96850394ef 100644 --- a/packages/core/ui/App.js +++ b/packages/core/ui/App.js @@ -77,7 +77,7 @@ const useStyles = makeStyles(theme => ({ }, })) -export default observer(({ session, HeaderButtons }) => { +const App = observer(({ session, HeaderButtons }) => { const classes = useStyles() const { pluginManager } = getEnv(session) const { @@ -203,3 +203,5 @@ export default observer(({ session, HeaderButtons }) => {
) }) + +export default App diff --git a/packages/core/ui/Drawer.js b/packages/core/ui/Drawer.js index 1dea9dda04..10275be869 100644 --- a/packages/core/ui/Drawer.js +++ b/packages/core/ui/Drawer.js @@ -1,8 +1,8 @@ +import React from 'react' import Paper from '@material-ui/core/Paper' import { makeStyles } from '@material-ui/core/styles' import { observer, PropTypes as MobxPropTypes } from 'mobx-react' import PropTypes from 'prop-types' -import React from 'react' import ResizeHandle from './ResizeHandle' const useStyles = makeStyles(theme => ({ diff --git a/packages/core/ui/DrawerWidget.js b/packages/core/ui/DrawerWidget.js index f0ba8fa61b..e467d694ff 100644 --- a/packages/core/ui/DrawerWidget.js +++ b/packages/core/ui/DrawerWidget.js @@ -149,7 +149,7 @@ const DrawerHeader = observer(props => { ) }) -export default observer(({ session }) => { +const DrawerWidget = observer(({ session }) => { const { visibleWidget, activeWidgets } = session const { pluginManager } = getEnv(session) const { ReactComponent } = pluginManager.getWidgetType(visibleWidget.type) @@ -172,3 +172,5 @@ export default observer(({ session }) => { ) }) + +export default DrawerWidget diff --git a/packages/core/ui/Tooltip.tsx b/packages/core/ui/Tooltip.tsx index 02a69b9b24..a021555595 100644 --- a/packages/core/ui/Tooltip.tsx +++ b/packages/core/ui/Tooltip.tsx @@ -1,8 +1,6 @@ -/* eslint-disable react/require-default-props */ +import React, { useEffect, useState } from 'react' import { makeStyles } from '@material-ui/core/styles' import { observer } from 'mobx-react' -import React, { useEffect, useState } from 'react' -import ReactPropTypes from 'prop-types' import { Feature } from '../util/simpleFeature' import { readConfObject } from '../configuration' import { AnyConfigurationModel } from '../configuration/configurationSchema' @@ -31,9 +29,10 @@ const Tooltip = ({ timeout?: number }) => { const classes = useStyles() - // only show the loading message after 400ms to prevent excessive flickering const [shown, setShown] = useState(false) useEffect(() => { + // only show the loading message after short timeout to prevent excessive + // flickering const handle = setTimeout(() => setShown(true), timeout) return () => clearTimeout(handle) }) @@ -51,16 +50,4 @@ const Tooltip = ({ return null } -Tooltip.propTypes = { - configuration: ReactPropTypes.shape({}).isRequired, - offsetX: ReactPropTypes.number.isRequired, - offsetY: ReactPropTypes.number.isRequired, - feature: ReactPropTypes.shape({}), - timeout: ReactPropTypes.number, -} - -Tooltip.defaultProps = { - feature: undefined, -} - export default observer(Tooltip) diff --git a/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js b/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js index 363300d394..08712c53ff 100644 --- a/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js +++ b/plugins/config/src/ConfigurationEditorWidget/components/ConfigurationEditor.js @@ -16,17 +16,22 @@ import React from 'react' import SlotEditor from './SlotEditor' import TypeSelector from './TypeSelector' -const useMemberStyles = makeStyles(theme => ({ +const useStyles = makeStyles(theme => ({ subSchemaContainer: { marginLeft: theme.spacing(1), borderLeft: `1px solid ${theme.palette.secondary.main}`, paddingLeft: theme.spacing(1), marginBottom: theme.spacing(1), }, + root: { + padding: theme.spacing(1, 3, 1, 1), + background: theme.palette.background.default, + overflowX: 'hidden', + }, })) const Member = observer(props => { - const classes = useMemberStyles() + const classes = useStyles() const { slotName, slotSchema, schema, slot = schema[slotName] } = props let typeSelector if (isConfigurationSchemaType(slotSchema)) { @@ -86,15 +91,7 @@ const Schema = observer(({ schema }) => { ) }) -const useStyles = makeStyles(theme => ({ - root: { - padding: theme.spacing(1, 3, 1, 1), - background: theme.palette.background.default, - overflowX: 'hidden', - }, -})) - -export default observer(({ model }) => { +const ConfigurationEditor = observer(({ model }) => { const classes = useStyles() // key forces a re-render, otherwise the same field can end up being used // for different tracks since only the backing model changes for example @@ -106,3 +103,5 @@ export default observer(({ model }) => { ) }) + +export default ConfigurationEditor diff --git a/plugins/menus/src/HelpWidget/index.js b/plugins/menus/src/HelpWidget/index.js index febd7e3369..36680605fe 100644 --- a/plugins/menus/src/HelpWidget/index.js +++ b/plugins/menus/src/HelpWidget/index.js @@ -8,5 +8,3 @@ export const stateModel = types.model('HelpWidget', { id: ElementId, type: types.literal('HelpWidget'), }) - -export { default as ReactComponent } from './components/HelpWidget' diff --git a/plugins/menus/src/ImportSessionWidget/index.ts b/plugins/menus/src/ImportSessionWidget/index.ts index 3e7402196f..7b325241eb 100644 --- a/plugins/menus/src/ImportSessionWidget/index.ts +++ b/plugins/menus/src/ImportSessionWidget/index.ts @@ -8,5 +8,3 @@ export const stateModel = types.model('ImportSessionWidget', { id: ElementId, type: types.literal('ImportSessionWidget'), }) - -export { default as ReactComponent } from './components/ImportSessionWidget' diff --git a/plugins/menus/src/SessionManager/components/SessionManager.js b/plugins/menus/src/SessionManager/components/SessionManager.js index b6818b0e94..a7838a1783 100644 --- a/plugins/menus/src/SessionManager/components/SessionManager.js +++ b/plugins/menus/src/SessionManager/components/SessionManager.js @@ -66,7 +66,7 @@ const AutosaveEntry = observer(({ session }) => { ) : null }) -export default observer(({ session }) => { +const SessionManager = observer(({ session }) => { const classes = useStyles() const [sessionIndexToDelete, setSessionIndexToDelete] = useState(null) const [open, setOpen] = useState(false) @@ -171,3 +171,5 @@ export default observer(({ session }) => { ) }) + +export default SessionManager diff --git a/plugins/menus/src/SessionManager/index.js b/plugins/menus/src/SessionManager/index.js index ef433ba0bc..04627ed29b 100644 --- a/plugins/menus/src/SessionManager/index.js +++ b/plugins/menus/src/SessionManager/index.js @@ -8,5 +8,3 @@ export const stateModel = types.model('SessionManager', { id: ElementId, type: types.literal('SessionManager'), }) - -export { default as ReactComponent } from './components/SessionManager' diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js index 79cb11719c..a689f370f8 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnFilterControls.js @@ -32,40 +32,44 @@ function FilterOperations({ filterModel }) { return null } -export default observer(({ viewModel, filterModel, columnNumber, height }) => { - const classes = useStyles() +const ColumnFilterControls = observer( + ({ viewModel, filterModel, columnNumber, height }) => { + const classes = useStyles() - const removeFilter = () => { - const filterControls = getParent(filterModel, 2) - filterControls.removeColumnFilter(filterModel) - } + const removeFilter = () => { + const filterControls = getParent(filterModel, 2) + filterControls.removeColumnFilter(filterModel) + } - const columnDefinition = viewModel.spreadsheet.columns[columnNumber] - if (!columnDefinition) - throw new Error('no column definition! filters are probably out of date') - return ( - - - - - - - - - - {columnDefinition.name} - {' '} - + const columnDefinition = viewModel.spreadsheet.columns[columnNumber] + if (!columnDefinition) + throw new Error('no column definition! filters are probably out of date') + return ( + + + + + + + + + + {columnDefinition.name} + {' '} + + - - ) -}) + ) + }, +) + +export default ColumnFilterControls diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js index d05e9b6745..a54b048e54 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/ColumnMenu.js @@ -9,7 +9,7 @@ import FilterListIcon from '@material-ui/icons/FilterList' import PermDataSettingIcon from '@material-ui/icons/PermDataSetting' import SortIcon from '@material-ui/icons/Sort' -export default observer( +const ColumnMenu = observer( ({ viewModel, spreadsheetModel, currentColumnMenu, setColumnMenu }) => { const columnMenuClose = () => { setColumnMenu(null) @@ -171,3 +171,5 @@ export default observer( ) }, ) + +export default ColumnMenu diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js index c277e1ad16..1fcc573077 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/GlobalFilterControls.js @@ -62,8 +62,10 @@ const TextFilter = observer(({ textFilter }) => { ) }) -export default observer(({ model }) => { +const GlobalFilterControls = observer(({ model }) => { // const classes = useStyles() const textFilter = model.filterControls.rowFullText return }) + +export default GlobalFilterControls diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js index d8155ee859..7de138db99 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/ImportWizard.js @@ -202,7 +202,7 @@ const ErrorDisplay = observer(({ errorMessage }) => { ) }) -export default observer(({ model }) => { +const ImportWizard = observer(({ model }) => { const classes = useStyles() return ( <> @@ -215,3 +215,5 @@ export default observer(({ model }) => { ) }) + +export default ImportWizard diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx b/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx index eaba44a525..6aecf8eae0 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/RowMenu.tsx @@ -11,7 +11,7 @@ export interface Props { spreadsheetModel: InstanceOfModelReturnedBy } -export default observer(({ viewModel, spreadsheetModel }: Props) => { +const RowMenu = observer(({ viewModel, spreadsheetModel }: Props) => { const currentRowMenu = spreadsheetModel.rowMenuPosition const { setRowMenuPosition } = spreadsheetModel @@ -57,3 +57,5 @@ export default observer(({ viewModel, spreadsheetModel }: Props) => { /> ) }) + +export default RowMenu diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js index 24ccbbe56c..bea063e430 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/Spreadsheet.js @@ -342,7 +342,7 @@ const DataTable = observer(({ model, page, rowsPerPage }) => { ) }) -export default observer(({ model, height, page, rowsPerPage }) => { +const Spreadsheet = observer(({ model, height, page, rowsPerPage }) => { const classes = useStyles() return ( @@ -355,3 +355,5 @@ export default observer(({ model, height, page, rowsPerPage }) => { ) }) + +export default Spreadsheet diff --git a/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js b/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js index af86336a2a..5e4ee8a96e 100644 --- a/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js +++ b/plugins/spreadsheet-view/src/SpreadsheetView/components/SpreadsheetView.js @@ -125,7 +125,7 @@ const RowCountMessage = observer(({ spreadsheet }) => { return null }) -export default observer(({ model }) => { +const SpreadsheetView = observer(({ model }) => { const classes = useStyles() const { spreadsheet, filterControls } = model @@ -243,3 +243,5 @@ export default observer(({ model }) => { ) }) + +export default SpreadsheetView diff --git a/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx b/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx index f6e916d480..4dc44e3d2a 100644 --- a/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx +++ b/plugins/variants/src/VariantFeatureWidget/BreakendOptionDialog.tsx @@ -30,103 +30,102 @@ const useStyles = makeStyles(theme => ({ }, })) -export default observer( - ({ - model, - handleClose, - feature, - viewType, - }: { - model: any - handleClose: () => void - feature: Feature - viewType: any - }) => { - const classes = useStyles() - const [copyTracks, setCopyTracks] = useState(true) - const [mirrorTracks, setMirrorTracks] = useState(true) +function BreakendOptionDialog({ + model, + handleClose, + feature, + viewType, +}: { + model: any + handleClose: () => void + feature: Feature + viewType: any +}) { + const classes = useStyles() + const [copyTracks, setCopyTracks] = useState(true) + const [mirrorTracks, setMirrorTracks] = useState(true) - return ( - - - Breakpoint split view options - {handleClose ? ( - { - handleClose() - }} - > - - - ) : null} - - + return ( + + + Breakpoint split view options + {handleClose ? ( + { + handleClose() + }} + > + + + ) : null} + + - - setCopyTracks(val => !val)} - /> - } - label="Copy tracks into the new view" - /> + + setCopyTracks(val => !val)} + /> + } + label="Copy tracks into the new view" + /> - setMirrorTracks(val => !val)} - /> - } - label="Mirror tracks vertically in vertically stacked view" - /> - - - - - - - ) - }, -) + handleClose() + }} + variant="contained" + color="primary" + autoFocus + > + OK + + + + + ) +} +export default observer(BreakendOptionDialog) diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx index 36d2ea8e44..4485ed1a22 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/JBrowseLinearGenomeView.tsx @@ -13,7 +13,8 @@ const useStyles = makeStyles(() => ({ all: 'initial', }, })) -export default observer( + +const JBrowseLinearGenomeView = observer( ({ viewState }: { viewState: { session: SessionModel } }) => { const classes = useStyles() const { session } = viewState @@ -36,3 +37,5 @@ export default observer( ) }, ) + +export default JBrowseLinearGenomeView diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx index 53a7237197..6c015fadc3 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ModalWidget.tsx @@ -58,7 +58,7 @@ const ModalWidgetContents = observer( }, ) -export default observer(({ session }: { session: SessionModel }) => { +const ModalWidget = observer(({ session }: { session: SessionModel }) => { const classes = useStyles() const { visibleWidget, hideAllWidgets } = session return ( @@ -73,3 +73,5 @@ export default observer(({ session }: { session: SessionModel }) => { ) }) + +export default ModalWidget diff --git a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx index ce18f71531..b3347dd5a8 100644 --- a/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx +++ b/products/jbrowse-react-linear-genome-view/src/JBrowseLinearGenomeView/ViewContainer.tsx @@ -102,7 +102,7 @@ const ViewMenu = observer( }, ) -export default observer( +const ViewContainer = observer( ({ view, children }: { view: IBaseViewModel; children: React.ReactNode }) => { const classes = useStyles() const theme = useTheme() @@ -150,3 +150,5 @@ export default observer( ) }, ) + +export default ViewContainer From 37fe032a8e37b027b3bd909cf349810fa1305a56 Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 15 Apr 2021 23:25:01 -0400 Subject: [PATCH 34/41] Replace export default observer(() => {}) with expanded named function forms --- .../core/pluggableElementTypes/ViewType.ts | 15 +- .../core/pluggableElementTypes/WidgetType.ts | 4 +- .../src/AlignmentsFeatureDetail/index.js | 1 - .../components/AlignmentsDisplay.tsx | 8 +- .../components/ColorByTag.tsx | 32 +- .../components/FilterByTag.tsx | 286 +++++++++--------- .../components/SetFeatureHeight.tsx | 16 +- .../components/SetMaxHeight.tsx | 129 ++++---- .../components/SortByTag.tsx | 32 +- plugins/alignments/src/index.ts | 6 +- .../BreakpointAlignmentsFeatureDetail.tsx | 28 +- .../CircularView/components/CircularView.js | 4 +- .../src/CircularView/components/Ruler.js | 4 +- .../components/ConfigureConnection.js | 4 +- .../src/AssemblyManager/AssemblyAddForm.tsx | 4 +- .../components/CloseConnectionDialog.tsx | 108 +++---- .../components/DeleteConnectionDialog.tsx | 80 ++--- .../components/ManageConnectionsDialog.tsx | 133 ++++---- .../components/ToggleConnectionsDialog.tsx | 155 +++++----- .../src/DotplotView/components/Controls.tsx | 4 +- .../DotplotView/components/DotplotView.tsx | 8 +- .../src/DotplotView/components/ImportForm.tsx | 4 +- .../src/ServerSideRenderedContent.js | 7 +- .../components/SetMaxHeight.tsx | 135 +++++---- .../LinearGenomeView/components/Header.tsx | 29 +- .../components/WiggleDisplayComponent.tsx | 4 +- 26 files changed, 634 insertions(+), 606 deletions(-) diff --git a/packages/core/pluggableElementTypes/ViewType.ts b/packages/core/pluggableElementTypes/ViewType.ts index 9169f5754f..a58ccc3f90 100644 --- a/packages/core/pluggableElementTypes/ViewType.ts +++ b/packages/core/pluggableElementTypes/ViewType.ts @@ -2,14 +2,13 @@ import { IAnyModelType, IAnyStateTreeNode } from 'mobx-state-tree' import PluggableElementBase from './PluggableElementBase' import DisplayType from './DisplayType' -type ViewReactComponent = React.LazyExoticComponent< - React.ComponentType<{ - // TODO: can we use AbstractViewModel here? - // eslint-disable-next-line @typescript-eslint/no-explicit-any - model: any - session?: IAnyStateTreeNode - }> -> +type BasicView = React.ComponentType<{ + // TODO: can we use AbstractViewModel here? + // eslint-disable-next-line @typescript-eslint/no-explicit-any + model: any + session?: IAnyStateTreeNode +}> +type ViewReactComponent = React.LazyExoticComponent | BasicView export default class ViewType extends PluggableElementBase { ReactComponent: ViewReactComponent diff --git a/packages/core/pluggableElementTypes/WidgetType.ts b/packages/core/pluggableElementTypes/WidgetType.ts index 09e20bb8cb..90e5454357 100644 --- a/packages/core/pluggableElementTypes/WidgetType.ts +++ b/packages/core/pluggableElementTypes/WidgetType.ts @@ -11,7 +11,7 @@ export default class WidgetType extends PluggableElementBase { HeadingComponent?: ComponentType<{ model: IAnyStateTreeNode }> // eslint-disable-next-line @typescript-eslint/no-explicit-any - ReactComponent: LazyExoticComponent> + ReactComponent: LazyExoticComponent> | React.FC stateModel: IAnyModelType @@ -22,7 +22,7 @@ export default class WidgetType extends PluggableElementBase { configSchema: AnyConfigurationSchemaType stateModel: IAnyModelType // eslint-disable-next-line @typescript-eslint/no-explicit-any - ReactComponent: LazyExoticComponent> + ReactComponent: LazyExoticComponent> | React.FC }) { super(stuff) this.heading = stuff.heading diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/index.js b/plugins/alignments/src/AlignmentsFeatureDetail/index.js index 0cb9ef8acd..cdec106636 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/index.js +++ b/plugins/alignments/src/AlignmentsFeatureDetail/index.js @@ -25,4 +25,3 @@ export default function stateModelFactory(pluginManager) { } export { configSchema, stateModelFactory } -export { default as ReactComponent } from './AlignmentsFeatureDetail' diff --git a/plugins/alignments/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx b/plugins/alignments/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx index 4e863fa192..59370199a6 100644 --- a/plugins/alignments/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx +++ b/plugins/alignments/src/LinearAlignmentsDisplay/components/AlignmentsDisplay.tsx @@ -1,10 +1,10 @@ +import React from 'react' import { observer } from 'mobx-react' import { getConf } from '@jbrowse/core/configuration' -import React from 'react' import { ResizeHandle } from '@jbrowse/core/ui' import { AlignmentsDisplayModel } from '../models/model' -export default observer(({ model }: { model: AlignmentsDisplayModel }) => { +function AlignmentsDisplay({ model }: { model: AlignmentsDisplayModel }) { const { PileupDisplay, SNPCoverageDisplay, showPileup, showCoverage } = model return (
{
) -}) +} + +export default observer(AlignmentsDisplay) diff --git a/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx b/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx index 9fafa40006..d091f7cbd9 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/ColorByTag.tsx @@ -1,12 +1,15 @@ import React, { useState } from 'react' -import { makeStyles } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' -import TextField from '@material-ui/core/TextField' -import Typography from '@material-ui/core/Typography' -import Dialog from '@material-ui/core/Dialog' -import DialogContent from '@material-ui/core/DialogContent' -import DialogTitle from '@material-ui/core/DialogTitle' -import IconButton from '@material-ui/core/IconButton' +import { observer } from 'mobx-react' +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + TextField, + Typography, + makeStyles, +} from '@material-ui/core' import CloseIcon from '@material-ui/icons/Close' const useStyles = makeStyles(theme => ({ @@ -21,7 +24,7 @@ const useStyles = makeStyles(theme => ({ }, })) -export default function ColorByTagDlg(props: { +function ColorByTagDlg(props: { model: { setColorScheme: Function } handleClose: () => void }) { @@ -31,13 +34,8 @@ export default function ColorByTagDlg(props: { const validTag = tag.match(/^[A-Za-z][A-Za-z0-9]$/) return ( - - + + Color by tag ) } + +export default observer(ColorByTagDlg) diff --git a/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx b/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx index eba4e50b74..588b5f7352 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/FilterByTag.tsx @@ -1,16 +1,19 @@ /* eslint-disable no-bitwise */ import React, { useState } from 'react' import { observer } from 'mobx-react' -import { makeStyles } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' -import TextField from '@material-ui/core/TextField' -import Typography from '@material-ui/core/Typography' -import Link from '@material-ui/core/Link' -import Dialog from '@material-ui/core/Dialog' -import DialogContent from '@material-ui/core/DialogContent' -import Paper from '@material-ui/core/Paper' -import DialogTitle from '@material-ui/core/DialogTitle' -import IconButton from '@material-ui/core/IconButton' +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + Link, + Paper, + TextField, + Typography, + makeStyles, +} from '@material-ui/core' + import CloseIcon from '@material-ui/icons/Close' const useStyles = makeStyles(theme => ({ @@ -80,142 +83,137 @@ function Bitmask(props: { flag?: number; setFlag: Function }) { ) } -export default observer( - (props: { - model: { - filterBy?: { - flagExclude: number - flagInclude: number - readName?: string - tagFilter?: { tag: string; value: string } - } - setFilterBy: Function +function FilterByTagDlg(props: { + model: { + filterBy?: { + flagExclude: number + flagInclude: number + readName?: string + tagFilter?: { tag: string; value: string } } - handleClose: () => void - }) => { - const { model, handleClose } = props - const classes = useStyles() - const { filterBy } = model - const [flagInclude, setFlagInclude] = useState(filterBy?.flagInclude) - const [flagExclude, setFlagExclude] = useState(filterBy?.flagExclude) - const [tag, setTag] = useState(filterBy?.tagFilter?.tag || '') - const [tagValue, setTagValue] = useState(filterBy?.tagFilter?.value || '') - const [readName, setReadName] = useState(filterBy?.readName || '') - const validTag = tag.match(/^[A-Za-z][A-Za-z0-9]$/) + setFilterBy: Function + } + handleClose: () => void +}) { + const { model, handleClose } = props + const classes = useStyles() + const { filterBy } = model + const [flagInclude, setFlagInclude] = useState(filterBy?.flagInclude) + const [flagExclude, setFlagExclude] = useState(filterBy?.flagExclude) + const [tag, setTag] = useState(filterBy?.tagFilter?.tag || '') + const [tagValue, setTagValue] = useState(filterBy?.tagFilter?.value || '') + const [readName, setReadName] = useState(filterBy?.readName || '') + const validTag = tag.match(/^[A-Za-z][A-Za-z0-9]$/) - const site = 'https://broadinstitute.github.io/picard/explain-flags.html' + const site = 'https://broadinstitute.github.io/picard/explain-flags.html' - return ( - - - Filter options - - - - - - - Set filter bitmask options. Refer to {site}{' '} - for details - -
- -
-
- Read must have ALL these flags - -
-
- Read must have NONE of these flags - -
+ return ( + + + Filter options + + + + + + + Set filter bitmask options. Refer to {site}{' '} + for details + +
+ +
+
+ Read must have ALL these flags +
- - - - Filter by tag name and value. Use * in the value field to get - all reads containing any value for that tag. Example: filter tag - name SA with value * to get all split/supplementary reads. Other - examples include HP for haplotype, or RG for read group - - { - setTag(event.target.value) - }} - placeholder="Enter tag name" - inputProps={{ - maxLength: 2, - 'data-testid': 'color-tag-name-input', - }} - error={tag.length === 2 && !validTag} - helperText={ - tag.length === 2 && !validTag ? 'Not a valid tag' : '' - } - data-testid="color-tag-name" - /> - { - setTagValue(event.target.value) - }} - placeholder="Enter tag value" - inputProps={{ - 'data-testid': 'color-tag-name-input', - }} - data-testid="color-tag-value" - /> - - - Filter by read name - { - setReadName(event.target.value) - }} - placeholder="Enter read name" - inputProps={{ - 'data-testid': 'color-tag-readname-input', - }} - data-testid="color-tag-readname" - /> - -
+
+ + + Filter by tag name and value. Use * in the value field to get all + reads containing any value for that tag. Example: filter tag name + SA with value * to get all split/supplementary reads. Other + examples include HP for haplotype, or RG for read group + + { + setTag(event.target.value) }} - > - Submit - -
-
-
- ) - }, -) + placeholder="Enter tag name" + inputProps={{ + maxLength: 2, + 'data-testid': 'color-tag-name-input', + }} + error={tag.length === 2 && !validTag} + helperText={ + tag.length === 2 && !validTag ? 'Not a valid tag' : '' + } + data-testid="color-tag-name" + /> + { + setTagValue(event.target.value) + }} + placeholder="Enter tag value" + inputProps={{ + 'data-testid': 'color-tag-name-input', + }} + data-testid="color-tag-value" + /> + + + Filter by read name + { + setReadName(event.target.value) + }} + placeholder="Enter read name" + inputProps={{ + 'data-testid': 'color-tag-readname-input', + }} + data-testid="color-tag-readname" + /> + + +
+ +
+ ) +} + +export default observer(FilterByTagDlg) diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx index ae78b5ddc2..07d106134a 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SetFeatureHeight.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react' -import { makeStyles } from '@material-ui/core/styles' +import { observer } from 'mobx-react' import { Button, TextField, @@ -10,6 +10,7 @@ import { DialogTitle, Checkbox, FormControlLabel, + makeStyles, } from '@material-ui/core' import CloseIcon from '@material-ui/icons/Close' @@ -25,7 +26,7 @@ const useStyles = makeStyles(theme => ({ }, })) -export default function SetMinMaxDlg(props: { +function SetFeatureHeightDlg(props: { model: { minScore: number maxScore: number @@ -48,13 +49,8 @@ export default function SetMinMaxDlg(props: { const ok = height !== '' && !Number.isNaN(+height) return ( - - + + Set feature height ) } + +export default observer(SetFeatureHeightDlg) diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx index ee2619d66c..615b5b6354 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SetMaxHeight.tsx @@ -1,13 +1,15 @@ import React, { useState } from 'react' import { observer } from 'mobx-react' -import { makeStyles } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' -import TextField from '@material-ui/core/TextField' -import Typography from '@material-ui/core/Typography' -import Dialog from '@material-ui/core/Dialog' -import DialogContent from '@material-ui/core/DialogContent' -import DialogTitle from '@material-ui/core/DialogTitle' -import IconButton from '@material-ui/core/IconButton' +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + TextField, + Typography, + makeStyles, +} from '@material-ui/core' import CloseIcon from '@material-ui/icons/Close' const useStyles = makeStyles(theme => ({ @@ -25,63 +27,58 @@ const useStyles = makeStyles(theme => ({ }, })) -export default observer( - (props: { - model: { - maxHeight?: number - setMaxHeight: Function - } - handleClose: () => void - }) => { - const { model, handleClose } = props - const classes = useStyles() - const { maxHeight = '' } = model - const [max, setMax] = useState(`${maxHeight}`) +function SetMaxHeightDlg(props: { + model: { + maxHeight?: number + setMaxHeight: Function + } + handleClose: () => void +}) { + const { model, handleClose } = props + const classes = useStyles() + const { maxHeight = '' } = model + const [max, setMax] = useState(`${maxHeight}`) - return ( - - - Filter options - + + Filter options + + + + + +
+ Set max height for the track + { + setMax(event.target.value) + }} + placeholder="Enter max height for layout" + /> + -
-
-
- ) - }, -) + Submit + + + +
+ ) +} + +export default observer(SetMaxHeightDlg) diff --git a/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx b/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx index 173bbd04dd..f5c0e680de 100644 --- a/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx +++ b/plugins/alignments/src/LinearPileupDisplay/components/SortByTag.tsx @@ -1,12 +1,16 @@ import React, { useState } from 'react' -import { makeStyles } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' -import TextField from '@material-ui/core/TextField' -import Typography from '@material-ui/core/Typography' -import Dialog from '@material-ui/core/Dialog' -import DialogContent from '@material-ui/core/DialogContent' -import DialogTitle from '@material-ui/core/DialogTitle' -import IconButton from '@material-ui/core/IconButton' +import { observer } from 'mobx-react' +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + TextField, + Typography, + makeStyles, +} from '@material-ui/core' + import CloseIcon from '@material-ui/icons/Close' const useStyles = makeStyles(theme => ({ @@ -22,7 +26,7 @@ const useStyles = makeStyles(theme => ({ }, })) -export default function SortByTagDlg(props: { +function SortByTagDlg(props: { model: { setSortedBy: Function } handleClose: () => void }) { @@ -31,13 +35,8 @@ export default function SortByTagDlg(props: { const [tag, setTag] = useState('') const validTag = tag.match(/^[A-Za-z][A-Za-z0-9]$/) return ( - - + + Sort by tag ) } +export default observer(SortByTagDlg) diff --git a/plugins/alignments/src/index.ts b/plugins/alignments/src/index.ts index 2b86604e6b..8f31d842c5 100644 --- a/plugins/alignments/src/index.ts +++ b/plugins/alignments/src/index.ts @@ -1,3 +1,4 @@ +import { lazy } from 'react' import { ConfigurationSchema } from '@jbrowse/core/configuration' import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType' import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType' @@ -13,7 +14,6 @@ import { BaseLinearDisplayComponent } from '@jbrowse/plugin-linear-genome-view' import { LinearWiggleDisplayReactComponent } from '@jbrowse/plugin-wiggle' import { configSchema as alignmentsFeatureDetailConfigSchema, - ReactComponent as AlignmentsFeatureDetailReactComponent, stateModelFactory as alignmentsFeatureDetailStateModelFactory, } from './AlignmentsFeatureDetail' import BamAdapterF from './BamAdapter' @@ -127,7 +127,9 @@ export default class AlignmentsPlugin extends Plugin { heading: 'Feature details', configSchema: alignmentsFeatureDetailConfigSchema, stateModel: alignmentsFeatureDetailStateModelFactory(pluginManager), - ReactComponent: AlignmentsFeatureDetailReactComponent, + ReactComponent: lazy( + () => import('./AlignmentsFeatureDetail/AlignmentsFeatureDetail'), + ), }), ) pluginManager.addAdapterType( diff --git a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx index 6fbfb47dfa..6e0d4895bf 100644 --- a/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx +++ b/plugins/breakpoint-split-view/src/BreakpointAlignmentsFeatureDetail/BreakpointAlignmentsFeatureDetail.tsx @@ -6,15 +6,19 @@ import { BaseAttributes, } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail' -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export default observer(({ model }: { model: any }) => { - const { feature1, feature2 } = JSON.parse(JSON.stringify(model.featureData)) - return ( - - - - - - - ) -}) +const BreakpointAlignmentsFeatureDetail = observer( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ({ model }: { model: any }) => { + const { feature1, feature2 } = JSON.parse(JSON.stringify(model.featureData)) + return ( + + + + + + + ) + }, +) + +export default BreakpointAlignmentsFeatureDetail diff --git a/plugins/circular-view/src/CircularView/components/CircularView.js b/plugins/circular-view/src/CircularView/components/CircularView.js index 61acafeb22..9dc975cdca 100644 --- a/plugins/circular-view/src/CircularView/components/CircularView.js +++ b/plugins/circular-view/src/CircularView/components/CircularView.js @@ -231,7 +231,7 @@ const ImportForm = observer(({ model }) => { ) }) -export default observer(({ model }) => { +const CircularView = observer(({ model }) => { const classes = useStyles() const initialized = !!model.displayedRegions.length && model.figureWidth && model.figureHeight @@ -311,3 +311,5 @@ export default observer(({ model }) => { ) }) + +export default CircularView diff --git a/plugins/circular-view/src/CircularView/components/Ruler.js b/plugins/circular-view/src/CircularView/components/Ruler.js index a9d410b917..ca2c0ae894 100644 --- a/plugins/circular-view/src/CircularView/components/Ruler.js +++ b/plugins/circular-view/src/CircularView/components/Ruler.js @@ -195,7 +195,7 @@ const RegionRulerArc = observer(({ model, slice }) => { ) }) -export default observer(function Ruler({ model, slice }) { +const CircularRuler = observer(function Ruler({ model, slice }) { if (slice.region.elided) { return ( ) }) + +export default CircularRuler diff --git a/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js b/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js index 6854862428..aaa9310df9 100644 --- a/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js +++ b/plugins/data-management/src/AddConnectionWidget/components/ConfigureConnection.js @@ -2,7 +2,7 @@ import React, { Suspense } from 'react' import { ConfigurationEditor } from '@jbrowse/plugin-config' import { observer } from 'mobx-react' -export default observer(props => { +const ConfigureConnection = observer(props => { const { connectionType, model, setModelReady } = props const ConfigEditorComponent = connectionType.configEditorComponent || ConfigurationEditor @@ -16,3 +16,5 @@ export default observer(props => { ) }) + +export default ConfigureConnection diff --git a/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx b/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx index c58b56e5ff..9f6542cb6e 100644 --- a/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx +++ b/plugins/data-management/src/AssemblyManager/AssemblyAddForm.tsx @@ -139,7 +139,7 @@ const AdapterInput = observer( }, ) -export default observer( +const AssemblyAddForm = observer( ({ rootModel, setFormOpen, @@ -258,3 +258,5 @@ export default observer( ) }, ) + +export default AssemblyAddForm diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/CloseConnectionDialog.tsx b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/CloseConnectionDialog.tsx index d335b3b001..5a6f51ad35 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/CloseConnectionDialog.tsx +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/CloseConnectionDialog.tsx @@ -11,55 +11,61 @@ import { } from '@material-ui/core' import { observer } from 'mobx-react' -export default observer( +function CloseConnectionDialog({ + modalInfo = {}, + setModalInfo, +}: { // eslint-disable-next-line @typescript-eslint/no-explicit-any - ({ modalInfo = {}, setModalInfo }: { modalInfo: any; setModalInfo: any }) => { - const { name, dereferenceTypeCount, safelyBreakConnection } = modalInfo - return ( - - Close connection "{name}" - - {dereferenceTypeCount ? ( - <> - - Closing this connection will close: - - - {Object.entries(dereferenceTypeCount).map(([key, value]) => ( - {`${value} ${key}`} - ))} - - - ) : null} - - Are you sure you want to close this connection? - - - - - - - - ) - }, -) + modalInfo: any + setModalInfo: Function +}) { + const { name, dereferenceTypeCount, safelyBreakConnection } = modalInfo + return ( + + Close connection "{name}" + + {dereferenceTypeCount ? ( + <> + + Closing this connection will close: + + + {Object.entries(dereferenceTypeCount).map(([key, value]) => ( + {`${value} ${key}`} + ))} + + + ) : null} + + Are you sure you want to close this connection? + + + + + + + + ) +} + +export default observer(CloseConnectionDialog) diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/DeleteConnectionDialog.tsx b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/DeleteConnectionDialog.tsx index 3afee4867b..3dea95280d 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/DeleteConnectionDialog.tsx +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/DeleteConnectionDialog.tsx @@ -11,43 +11,43 @@ import { observer } from 'mobx-react' import { AnyConfigurationModel } from '@jbrowse/core/configuration/configurationSchema' import { AbstractSessionModel } from '@jbrowse/core/util' -export default observer( - ({ - deleteDialogDetails, - session, - handleClose, - }: { - deleteDialogDetails: { name: string; connectionConf: AnyConfigurationModel } - session: AbstractSessionModel - handleClose: Function - }) => { - const { connectionConf, name } = deleteDialogDetails - return ( - - Delete connection "{name}" - - - Are you sure you want to delete this connection? - - - - - - - - ) - }, -) +function DeleteConnectionDialog({ + deleteDialogDetails, + session, + handleClose, +}: { + deleteDialogDetails: { name: string; connectionConf: AnyConfigurationModel } + session: AbstractSessionModel + handleClose: Function +}) { + const { connectionConf, name } = deleteDialogDetails + return ( + + Delete connection "{name}" + + + Are you sure you want to delete this connection? + + + + + + + + ) +} + +export default observer(DeleteConnectionDialog) diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ManageConnectionsDialog.tsx b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ManageConnectionsDialog.tsx index 6a36d63c9b..6d34cde5ee 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ManageConnectionsDialog.tsx +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ManageConnectionsDialog.tsx @@ -29,71 +29,70 @@ const useStyles = makeStyles(theme => ({ }, })) -export default observer( - ({ - session, - handleClose, - breakConnection, - }: { - handleClose: () => void - session: AbstractSessionModel - breakConnection: Function - }) => { - const classes = useStyles() - const { adminMode, connections, sessionConnections } = session - return ( - - - Manage connections - handleClose()} - > - - - - - - Click the X icon to delete the connection from your config - completely - -
- {connections.map(conf => { - const name = readConfObject(conf, 'name') - return ( -
- - {adminMode || sessionConnections?.includes(conf) ? ( - breakConnection(conf, true)}> - +function ManageConnectionsDlg({ + session, + handleClose, + breakConnection, +}: { + handleClose: () => void + session: AbstractSessionModel + breakConnection: Function +}) { + const classes = useStyles() + const { adminMode, connections, sessionConnections } = session + return ( + + + Manage connections + handleClose()} + > + + + + + + Click the X icon to delete the connection from your config completely + +
+ {connections.map(conf => { + const name = readConfObject(conf, 'name') + return ( +
+ + {adminMode || sessionConnections?.includes(conf) ? ( + breakConnection(conf, true)}> + + + ) : ( + + + - ) : ( - - - - - - )} - {name} - -
- ) - })} - {!connections.length ? ( - No connections found - ) : null} -
-
- - - -
- ) - }, -) + + )} + {name} +
+
+ ) + })} + {!connections.length ? ( + No connections found + ) : null} +
+
+ + + +
+ ) +} + +export default observer(ManageConnectionsDlg) diff --git a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ToggleConnectionsDialog.tsx b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ToggleConnectionsDialog.tsx index fc3b982c92..a80c0905f8 100644 --- a/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ToggleConnectionsDialog.tsx +++ b/plugins/data-management/src/HierarchicalTrackSelectorWidget/components/ToggleConnectionsDialog.tsx @@ -30,84 +30,81 @@ const useStyles = makeStyles(theme => ({ }, })) -export default observer( - ({ - session, - handleClose, - assemblyName, - breakConnection, - }: { - handleClose: () => void - session: AbstractSessionModel - assemblyName: string - breakConnection: Function - }) => { - const classes = useStyles() - const { connections, connectionInstances } = session - const assemblySpecificConnections = connections.filter(c => - readConfObject(c, 'assemblyNames').includes(assemblyName), - ) - return ( - - - Turn on/off connections - handleClose()} - > - - - - - Use the checkbox to turn on/off connections -
- {assemblySpecificConnections.map(conf => { - const name = readConfObject(conf, 'name') - return ( -
- name === conn.name, +function ToggleConnectionDialog({ + session, + handleClose, + assemblyName, + breakConnection, +}: { + handleClose: () => void + session: AbstractSessionModel + assemblyName: string + breakConnection: Function +}) { + const classes = useStyles() + const { connections, connectionInstances } = session + const assemblySpecificConnections = connections.filter(c => + readConfObject(c, 'assemblyNames').includes(assemblyName), + ) + return ( + + + Turn on/off connections + handleClose()} + > + + + + + Use the checkbox to turn on/off connections +
+ {assemblySpecificConnections.map(conf => { + const name = readConfObject(conf, 'name') + return ( +
+ name === conn.name) + } + onChange={() => { + if ( + connectionInstances?.find( + conn => conn.name === readConfObject(conf, 'name'), ) + ) { + breakConnection(conf) + } else { + session.makeConnection?.(conf) } - onChange={() => { - if ( - connectionInstances?.find( - conn => - conn.name === readConfObject(conf, 'name'), - ) - ) { - breakConnection(conf) - } else { - session.makeConnection?.(conf) - } - }} - color="primary" - /> - } - label={name} - /> -
- ) - })} - {!assemblySpecificConnections.length ? ( - No connections found for {assemblyName} - ) : null} -
-
- - - -
- ) - }, -) + }} + color="primary" + /> + } + label={name} + /> +
+ ) + })} + {!assemblySpecificConnections.length ? ( + No connections found for {assemblyName} + ) : null} +
+
+ + + +
+ ) +} + +export default observer(ToggleConnectionDialog) diff --git a/plugins/dotplot-view/src/DotplotView/components/Controls.tsx b/plugins/dotplot-view/src/DotplotView/components/Controls.tsx index d3d03df026..bfa7e9c25a 100644 --- a/plugins/dotplot-view/src/DotplotView/components/Controls.tsx +++ b/plugins/dotplot-view/src/DotplotView/components/Controls.tsx @@ -31,7 +31,7 @@ const useStyles = makeStyles({ }, }) -export default observer(({ model }: { model: DotplotViewModel }) => { +const DotplotControls = observer(({ model }: { model: DotplotViewModel }) => { const classes = useStyles() return (
@@ -105,3 +105,5 @@ export default observer(({ model }: { model: DotplotViewModel }) => {
) }) + +export default DotplotControls diff --git a/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx b/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx index 2372d58532..61857500ec 100644 --- a/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx +++ b/plugins/dotplot-view/src/DotplotView/components/DotplotView.tsx @@ -422,10 +422,12 @@ const Grid = observer( ) }, ) -// produces offsetX/offsetY coordinates from a clientX and an element's getBoundingClientRect +// produces offsetX/offsetY coordinates from a clientX and an element's +// getBoundingClientRect function getOffset(coord: Coord, rect: Rect) { return coord && ([coord[0] - rect.left, coord[1] - rect.top] as Coord) } + const DotplotViewInternal = observer( ({ model }: { model: DotplotViewModel }) => { const { hview, vview, viewHeight } = model @@ -694,7 +696,7 @@ const DotplotViewInternal = observer( ) }, ) -export default observer(({ model }: { model: DotplotViewModel }) => { +const DotplotView = observer(({ model }: { model: DotplotViewModel }) => { const { initialized, loading, error } = model const classes = useStyles() @@ -716,3 +718,5 @@ export default observer(({ model }: { model: DotplotViewModel }) => { return }) + +export default DotplotView diff --git a/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx b/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx index b240f8a4ad..90847c0ad6 100644 --- a/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx +++ b/plugins/dotplot-view/src/DotplotView/components/ImportForm.tsx @@ -65,7 +65,7 @@ const FormRow = observer( ) }, ) -export default observer(({ model }: { model: DotplotViewModel }) => { +const DotplotImportForm = observer(({ model }: { model: DotplotViewModel }) => { const classes = useStyles() const [numRows] = useState(2) const [selected, setSelected] = useState([0, 0]) @@ -176,3 +176,5 @@ export default observer(({ model }: { model: DotplotViewModel }) => { ) }) + +export default DotplotImportForm diff --git a/plugins/linear-comparative-view/src/ServerSideRenderedContent.js b/plugins/linear-comparative-view/src/ServerSideRenderedContent.js index a55ad890fa..2a3929cfa8 100644 --- a/plugins/linear-comparative-view/src/ServerSideRenderedContent.js +++ b/plugins/linear-comparative-view/src/ServerSideRenderedContent.js @@ -1,7 +1,8 @@ +/* eslint-disable react/prop-types */ import React from 'react' import { observer } from 'mobx-react' -export default observer(function ServerSideRenderedContent(props) { +function ServerSideRenderedContent(props) { const { model } = props const { data, renderProps, renderingComponent: RenderingComponent } = model @@ -10,4 +11,6 @@ export default observer(function ServerSideRenderedContent(props) { ) : (

Loading

) -}) +} + +export default observer(ServerSideRenderedContent) diff --git a/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx b/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx index 1320b820a4..1e772c677d 100644 --- a/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx +++ b/plugins/linear-genome-view/src/LinearBasicDisplay/components/SetMaxHeight.tsx @@ -1,13 +1,16 @@ import React, { useState } from 'react' import { observer } from 'mobx-react' -import { makeStyles } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' -import TextField from '@material-ui/core/TextField' -import Typography from '@material-ui/core/Typography' -import Dialog from '@material-ui/core/Dialog' -import DialogContent from '@material-ui/core/DialogContent' -import DialogTitle from '@material-ui/core/DialogTitle' -import IconButton from '@material-ui/core/IconButton' +import { + Button, + Dialog, + DialogContent, + DialogTitle, + IconButton, + Typography, + TextField, + makeStyles, +} from '@material-ui/core' + import CloseIcon from '@material-ui/icons/Close' const useStyles = makeStyles(theme => ({ @@ -25,63 +28,63 @@ const useStyles = makeStyles(theme => ({ }, })) -export default observer( - (props: { - model: { - maxHeight?: number - setMaxHeight: Function - } - handleClose: () => void - }) => { - const { model, handleClose } = props - const classes = useStyles() - const { maxHeight = '' } = model - const [max, setMax] = useState(`${maxHeight}`) +function SetMaxHeightDlg(props: { + model: { + maxHeight?: number + setMaxHeight: Function + } + handleClose: () => void +}) { + const { model, handleClose } = props + const classes = useStyles() + const { maxHeight = '' } = model + const [max, setMax] = useState(`${maxHeight}`) - return ( - - - Filter options - + + Filter options + + + + + +
+ Set max height for the track + { + setMax(event.target.value) + }} + placeholder="Enter max score" + /> + -
-
-
- ) - }, -) + Submit + + + +
+ ) +} + +export default observer(SetMaxHeightDlg) diff --git a/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx b/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx index 6e373bb47e..5b603074a2 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx +++ b/plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx @@ -94,7 +94,17 @@ function PanControls({ model }: { model: LGV }) { ) } -export default observer(({ model }: { model: LGV }) => { +const RegionWidth = observer(({ model }: { model: LGV }) => { + const classes = useStyles() + const { coarseTotalBp } = model + return ( + + {`${Math.round(coarseTotalBp).toLocaleString('en-US')} bp`} + + ) +}) + +const LinearGenomeViewHeader = observer(({ model }: { model: LGV }) => { const classes = useStyles() const theme = useTheme() const session = getSession(model) @@ -107,11 +117,12 @@ export default observer(({ model }: { model: LGV }) => { const newRegion: Region | undefined = model.displayedRegions.find( region => newRegionValue === region.refName, ) - // navigate to region or if region not found try navigating to locstring + // navigate to region or if region not found try navigating to + // locstring. note: we use showAllRegions after setDisplayedRegions + // to make the entire region visible, xref #1703 if (newRegion) { model.setDisplayedRegions([newRegion]) - // we use showAllRegions after setDisplayedRegions to make the entire - // region visible, xref #1703 + model.showAllRegions() } else { newRegionValue && model.navToLocString(newRegionValue) @@ -165,12 +176,4 @@ export default observer(({ model }: { model: LGV }) => { return {controls} }) -const RegionWidth = observer(({ model }: { model: LGV }) => { - const classes = useStyles() - const { coarseTotalBp } = model - return ( - - {`${Math.round(coarseTotalBp).toLocaleString('en-US')} bp`} - - ) -}) +export default LinearGenomeViewHeader diff --git a/plugins/wiggle/src/LinearWiggleDisplay/components/WiggleDisplayComponent.tsx b/plugins/wiggle/src/LinearWiggleDisplay/components/WiggleDisplayComponent.tsx index defb3cf465..7bb98c9bde 100644 --- a/plugins/wiggle/src/LinearWiggleDisplay/components/WiggleDisplayComponent.tsx +++ b/plugins/wiggle/src/LinearWiggleDisplay/components/WiggleDisplayComponent.tsx @@ -24,7 +24,7 @@ export const YScaleBar = observer( }, ) -export default observer((props: { model: WiggleDisplayModel }) => { +const LinearWiggleDisplay = observer((props: { model: WiggleDisplayModel }) => { const { model } = props const { ready, stats, height, needsScalebar } = model return ( @@ -47,3 +47,5 @@ export default observer((props: { model: WiggleDisplayModel }) => { ) }) + +export default LinearWiggleDisplay From 9bdaf34c921fe68c61e05c104f9e2e6d548b06da Mon Sep 17 00:00:00 2001 From: Colin Date: Thu, 15 Apr 2021 23:37:47 -0400 Subject: [PATCH 35/41] Update snaps --- packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx | 6 +++--- .../__snapshots__/index.test.js.snap | 2 +- .../__snapshots__/VariantFeatureWidget.test.js.snap | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx b/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx index 29792d6fc7..bb7694ed95 100644 --- a/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx +++ b/packages/core/BaseFeatureWidget/BaseFeatureDetail.tsx @@ -430,9 +430,9 @@ export const FeatureDetails = (props: { }) => { const { omit = [], model, feature, depth = 0 } = props const { name, id, type = '', subfeatures } = feature - const slug = name || id - const shortName = slug && slug.length > 20 ? `${slug}...` : slug - const title = `${shortName ? `${shortName} - ` : ''} - ${type}` + const slug = name || id || '' + const shortName = slug.length > 20 ? `${slug}...` : slug + const title = `${shortName}${type ? ` - ${type}` : ''}` const session = getSession(model) const defSeqTypes = ['mRNA', 'transcript'] const sequenceTypes = diff --git a/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap b/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap index b7a6e767e1..727506af13 100644 --- a/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap +++ b/plugins/alignments/src/AlignmentsFeatureDetail/__snapshots__/index.test.js.snap @@ -22,7 +22,7 @@ exports[`open up a widget 1`] = ` class="MuiTypography-root MuiTypography-button" > - match + ctgA_3_555_0:0:0_2:0:0_102d... - match
- rs123 - undefined + rs123
Date: Thu, 15 Apr 2021 23:39:13 -0400 Subject: [PATCH 36/41] No warn --- plugins/rdf/src/index.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/rdf/src/index.test.js b/plugins/rdf/src/index.test.js index f5e70b57ed..ab753feaa5 100644 --- a/plugins/rdf/src/index.test.js +++ b/plugins/rdf/src/index.test.js @@ -3,6 +3,7 @@ import { getSnapshot } from 'mobx-state-tree' import ThisPlugin from './index' test('plugin in a stock JBrowse', () => { + console.warn = jest.fn() const pluginManager = new PluginManager([new ThisPlugin()]) pluginManager.createPluggableElements() pluginManager.configure() From 9a5ed0abf78c83b23afa8ffe49d880dc73203219 Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 16 Apr 2021 09:14:23 -0400 Subject: [PATCH 37/41] Silence warnings because BaseFeatureWidget uses safeReference to view->stateModel which triggers the no pluggable elements found in tests that dont register a view --- packages/core/PluginManager.ts | 4 +++- plugins/bed/src/index.test.js | 1 + plugins/menus/src/index.test.js | 7 +++---- plugins/protein/src/index.test.js | 1 + plugins/sequence/src/index.test.js | 1 + 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/core/PluginManager.ts b/packages/core/PluginManager.ts index babd16c5ac..8d17339c67 100644 --- a/packages/core/PluginManager.ts +++ b/packages/core/PluginManager.ts @@ -325,7 +325,9 @@ export default class PluginManager { pluggableTypes.push(thing) } }) - // try to smooth over the case when no types are registered, mostly encountered in tests + + // try to smooth over the case when no types are registered, mostly + // encountered in tests if (pluggableTypes.length === 0) { console.warn( `No JBrowse pluggable types found matching ('${typeGroup}','${fieldName}')`, diff --git a/plugins/bed/src/index.test.js b/plugins/bed/src/index.test.js index 3789ceda33..18ee27b8d3 100644 --- a/plugins/bed/src/index.test.js +++ b/plugins/bed/src/index.test.js @@ -3,6 +3,7 @@ import { getSnapshot } from 'mobx-state-tree' import ThisPlugin from '.' test('plugin in a stock JBrowse', () => { + console.warn = jest.fn() const pluginManager = new PluginManager([new ThisPlugin()]) pluginManager.createPluggableElements() pluginManager.configure() diff --git a/plugins/menus/src/index.test.js b/plugins/menus/src/index.test.js index 485fa25480..3eb2fe7a72 100644 --- a/plugins/menus/src/index.test.js +++ b/plugins/menus/src/index.test.js @@ -1,16 +1,15 @@ import PluginManager from '@jbrowse/core/PluginManager' import ThisPlugin from '.' -describe('Data management', () => { +describe('menus', () => { let pluginManager - beforeAll(() => { + it("won't add if already added", () => { + console.warn = jest.fn() pluginManager = new PluginManager([new ThisPlugin()]) pluginManager.createPluggableElements() pluginManager.configure() - }) - it("won't add if already added", () => { expect(() => pluginManager.addPlugin(new ThisPlugin())).toThrow( /JBrowse already configured, cannot add plugins/, ) diff --git a/plugins/protein/src/index.test.js b/plugins/protein/src/index.test.js index 1c5c7996de..78f739ac8a 100644 --- a/plugins/protein/src/index.test.js +++ b/plugins/protein/src/index.test.js @@ -2,6 +2,7 @@ import PluginManager from '@jbrowse/core/PluginManager' import ThisPlugin from '.' test('plugin in a stock JBrowse', () => { + console.warn = jest.fn() const pluginManager = new PluginManager([new ThisPlugin()]) pluginManager.createPluggableElements() pluginManager.configure() diff --git a/plugins/sequence/src/index.test.js b/plugins/sequence/src/index.test.js index 99a2409089..24a4ba7686 100644 --- a/plugins/sequence/src/index.test.js +++ b/plugins/sequence/src/index.test.js @@ -3,6 +3,7 @@ import { getSnapshot } from 'mobx-state-tree' import ThisPlugin from '.' test('plugin in a stock JBrowse', () => { + console.warn = jest.fn() const pluginManager = new PluginManager([new ThisPlugin()]) pluginManager.createPluggableElements() pluginManager.configure() From 8a8c03981eb25b8345679216c826ecb4d8b909e6 Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 16 Apr 2021 09:24:23 -0400 Subject: [PATCH 38/41] Silence some warnings during tests --- .../src/LinearGenomeView/index.test.ts | 187 ++++++++++-------- plugins/wiggle/src/index.test.js | 1 + 2 files changed, 104 insertions(+), 84 deletions(-) diff --git a/plugins/linear-genome-view/src/LinearGenomeView/index.test.ts b/plugins/linear-genome-view/src/LinearGenomeView/index.test.ts index 36850264ef..0c18289713 100644 --- a/plugins/linear-genome-view/src/LinearGenomeView/index.test.ts +++ b/plugins/linear-genome-view/src/LinearGenomeView/index.test.ts @@ -13,95 +13,102 @@ import { BaseLinearDisplayComponent } from '..' import { stateModelFactory as LinearBasicDisplayStateModelFactory } from '../LinearBareDisplay' import hg38DisplayedRegions from './hg38DisplayedRegions.json' -// a stub linear genome view state model that only accepts base track types. -// used in unit tests. -const stubManager = new PluginManager() -stubManager.addTrackType(() => { - const configSchema = ConfigurationSchema( - 'BasicTrack', - {}, - { - baseConfiguration: createBaseTrackConfig(stubManager), - explicitIdentifier: 'trackId', - }, - ) - return new TrackType({ - name: 'BasicTrack', - configSchema, - stateModel: createBaseTrackModel(stubManager, 'BasicTrack', configSchema), +// use initializer function to avoid having console.warn jest.fn in a global +function initialize() { + console.warn = jest.fn() + // a stub linear genome view state model that only accepts base track types. + // used in unit tests. + const stubManager = new PluginManager() + stubManager.addTrackType(() => { + const configSchema = ConfigurationSchema( + 'BasicTrack', + {}, + { + baseConfiguration: createBaseTrackConfig(stubManager), + explicitIdentifier: 'trackId', + }, + ) + return new TrackType({ + name: 'BasicTrack', + configSchema, + stateModel: createBaseTrackModel(stubManager, 'BasicTrack', configSchema), + }) }) -}) -stubManager.addDisplayType(() => { - const configSchema = ConfigurationSchema( - 'LinearBareDisplay', - {}, - { explicitlyTyped: true }, - ) - return new DisplayType({ - name: 'LinearBareDisplay', - configSchema, - stateModel: LinearBasicDisplayStateModelFactory(configSchema), - trackType: 'BasicTrack', - viewType: 'LinearGenomeView', - ReactComponent: BaseLinearDisplayComponent, + stubManager.addDisplayType(() => { + const configSchema = ConfigurationSchema( + 'LinearBareDisplay', + {}, + { explicitlyTyped: true }, + ) + return new DisplayType({ + name: 'LinearBareDisplay', + configSchema, + stateModel: LinearBasicDisplayStateModelFactory(configSchema), + trackType: 'BasicTrack', + viewType: 'LinearGenomeView', + ReactComponent: BaseLinearDisplayComponent, + }) }) -}) -stubManager.createPluggableElements() -stubManager.configure() -const LinearGenomeModel = stateModelFactory(stubManager) + stubManager.createPluggableElements() + stubManager.configure() -const Assembly = types - .model({ - regions: types.array(Region), - }) - .views(() => ({ - getCanonicalRefName(refName: string) { - if (refName === 'contigA') { - return 'ctgA' - } - return refName - }, - })) -const Session = types - .model({ - name: 'testSession', - rpcManager: 'rpcManagerExists', - view: types.maybe(LinearGenomeModel), - configuration: types.map(types.string), - }) - .actions(self => ({ - setView(view: Instance) { - self.view = view - return view - }, - })) - .volatile((/* self */) => ({ - assemblyManager: new Map([ - [ - 'volvox', - Assembly.create({ - regions: [ - { - assemblyName: 'volvox', - start: 0, - end: 50001, - refName: 'ctgA', - reversed: false, - }, - { - assemblyName: 'volvox', - start: 0, - end: 6079, - refName: 'ctgB', - reversed: false, - }, - ], - }), - ], - ]), - })) + const Assembly = types + .model({ + regions: types.array(Region), + }) + .views(() => ({ + getCanonicalRefName(refName: string) { + if (refName === 'contigA') { + return 'ctgA' + } + return refName + }, + })) + const LinearGenomeModel = stateModelFactory(stubManager) + const Session = types + .model({ + name: 'testSession', + rpcManager: 'rpcManagerExists', + view: types.maybe(LinearGenomeModel), + configuration: types.map(types.string), + }) + .actions(self => ({ + setView(view: Instance) { + self.view = view + return view + }, + })) + .volatile((/* self */) => ({ + assemblyManager: new Map([ + [ + 'volvox', + Assembly.create({ + regions: [ + { + assemblyName: 'volvox', + start: 0, + end: 50001, + refName: 'ctgA', + reversed: false, + }, + { + assemblyName: 'volvox', + start: 0, + end: 6079, + refName: 'ctgB', + reversed: false, + }, + ], + }), + ], + ]), + })) + + return { Session, LinearGenomeModel, Assembly } +} test('can instantiate a mostly empty model and read a default configuration value', () => { + const { Session, LinearGenomeModel } = initialize() const model = Session.create({ configuration: {}, }).setView( @@ -116,6 +123,7 @@ test('can instantiate a mostly empty model and read a default configuration valu }) test('can instantiate a model that lets you navigate', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -162,6 +170,7 @@ test('can instantiate a model that lets you navigate', () => { }) test('can instantiate a model that has multiple displayed regions', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -188,6 +197,7 @@ test('can instantiate a model that has multiple displayed regions', () => { }) test('can instantiate a model that tests navTo/moveTo', async () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -246,6 +256,7 @@ test('can instantiate a model that tests navTo/moveTo', async () => { }) test('can navToMultiple', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -316,6 +327,7 @@ test('can navToMultiple', () => { }) describe('Zoom to selected displayed regions', () => { + const { Session, LinearGenomeModel } = initialize() let model: Instance> let largestBpPerPx: number beforeEach(() => { @@ -508,6 +520,7 @@ describe('Zoom to selected displayed regions', () => { }) test('can instantiate a model that >2 regions', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -563,6 +576,7 @@ test('can instantiate a model that >2 regions', () => { }) test('can perform bpToPx in a way that makes sense on things that happen outside', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -621,6 +635,7 @@ test('can perform bpToPx in a way that makes sense on things that happen outside // this test is important because interregionpadding blocks outside the current // view should not be taken into account test('can perform pxToBp on human genome things with ellided blocks (zoomed in)', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -645,6 +660,7 @@ test('can perform pxToBp on human genome things with ellided blocks (zoomed in)' // this tests some places on hg38 when zoomed to whole genome, so inter-region // padding blocks and elided blocks matter test('can perform pxToBp on human genome things with ellided blocks (zoomed out)', () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -678,6 +694,7 @@ test('can perform pxToBp on human genome things with ellided blocks (zoomed out) }) test('can showAllRegionsInAssembly', async () => { + const { Session, LinearGenomeModel } = initialize() const session = Session.create({ configuration: {}, }) @@ -698,6 +715,7 @@ test('can showAllRegionsInAssembly', async () => { }) describe('Get Sequence for selected displayed regions', () => { + const { Session, LinearGenomeModel } = initialize() /* the start of all the results should be +1 the sequence dialog then handles converting from 1-based closed to interbase */ @@ -921,6 +939,7 @@ describe('Get Sequence for selected displayed regions', () => { }) test('navToLocString with human assembly', () => { + const { LinearGenomeModel } = initialize() const HumanAssembly = types .model({ regions: types.array(Region), diff --git a/plugins/wiggle/src/index.test.js b/plugins/wiggle/src/index.test.js index 5bb702ad83..6be0c4d51f 100644 --- a/plugins/wiggle/src/index.test.js +++ b/plugins/wiggle/src/index.test.js @@ -3,6 +3,7 @@ import { getSnapshot } from 'mobx-state-tree' import ThisPlugin from '.' test('plugin in a stock JBrowse', () => { + console.warn = jest.fn() const pluginManager = new PluginManager([new ThisPlugin()]) pluginManager.createPluggableElements() pluginManager.configure() From 1db29ef76852cf8004b7d29f4cfd93f5935310fe Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 16 Apr 2021 10:01:09 -0400 Subject: [PATCH 39/41] Add some timeout --- products/jbrowse-web/src/tests/Alignments.test.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/products/jbrowse-web/src/tests/Alignments.test.js b/products/jbrowse-web/src/tests/Alignments.test.js index 987abd030b..352f1cea47 100644 --- a/products/jbrowse-web/src/tests/Alignments.test.js +++ b/products/jbrowse-web/src/tests/Alignments.test.js @@ -148,7 +148,11 @@ describe('alignments track', () => { // load track fireEvent.click(await findByTestId('htsTrackEntry-volvox-long-reads-cram')) - await findByTestId('display-volvox-long-reads-cram-LinearAlignmentsDisplay') + await findByTestId( + 'display-volvox-long-reads-cram-LinearAlignmentsDisplay', + {}, + { timeout: 10000 }, + ) expect(state.session.views[0].tracks[0]).toBeTruthy() // opens the track menu From e891114566e1b5d3ab86e40efcc6cf0050b575d6 Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 16 Apr 2021 10:12:45 -0400 Subject: [PATCH 40/41] Allow performance label for the github label triager --- .github/workflows/pull_request_label.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml index 5712cd1733..f62fd853d9 100644 --- a/.github/workflows/pull_request_label.yml +++ b/.github/workflows/pull_request_label.yml @@ -10,7 +10,8 @@ jobs: !contains(github.event.pull_request.labels.*.name, 'enhancement') && !contains(github.event.pull_request.labels.*.name, 'bug') && !contains(github.event.pull_request.labels.*.name, 'documentation') && - !contains(github.event.pull_request.labels.*.name, 'internal') + !contains(github.event.pull_request.labels.*.name, 'internal') && + !contains(github.event.pull_request.labels.*.name, 'performance') steps: - uses: actions/labeler@main with: From 2a985e0c415bd780a91696f3afb0025d0e63ec6e Mon Sep 17 00:00:00 2001 From: Colin Date: Tue, 20 Apr 2021 17:53:19 -0400 Subject: [PATCH 41/41] Unused suspense in test, clean up some types --- packages/core/BaseFeatureWidget/index.test.js | 6 ++---- packages/core/ui/NewSessionCards.tsx | 16 +++++++--------- packages/core/ui/declare.d.ts | 1 + 3 files changed, 10 insertions(+), 13 deletions(-) create mode 100644 packages/core/ui/declare.d.ts diff --git a/packages/core/BaseFeatureWidget/index.test.js b/packages/core/BaseFeatureWidget/index.test.js index 55df83d516..593302129f 100644 --- a/packages/core/BaseFeatureWidget/index.test.js +++ b/packages/core/BaseFeatureWidget/index.test.js @@ -1,4 +1,4 @@ -import React, { Suspense } from 'react' +import React from 'react' import { render } from '@testing-library/react' import { types } from 'mobx-state-tree' import { ConfigurationSchema } from '@jbrowse/core/configuration' @@ -20,9 +20,7 @@ test('open up a widget', () => { widget: { type: 'BaseFeatureWidget' }, }) const { container, getByText } = render( - Loading...
}> - - , + , ) model.widget.setFeatureData({ start: 2, diff --git a/packages/core/ui/NewSessionCards.tsx b/packages/core/ui/NewSessionCards.tsx index 5f70bd041f..1267a6bd40 100644 --- a/packages/core/ui/NewSessionCards.tsx +++ b/packages/core/ui/NewSessionCards.tsx @@ -7,11 +7,8 @@ import { makeStyles, } from '@material-ui/core' -// @ts-ignore import emptyIcon from './emptyIcon.png' -// @ts-ignore import linearGenomeViewIcon from './linearGenomeViewIcon.png' -// @ts-ignore import svInspectorIcon from './svInspectorIcon.png' const useStyles = makeStyles(theme => ({ @@ -64,8 +61,11 @@ function NewSessionCard({ ) } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function NewEmptySession({ root }: { root: any }) { +interface RootModel { + setSession: Function +} + +export function NewEmptySession({ root }: { root: RootModel }) { return (