Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability for views to pin elements, such as headers and tracks #4237

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
16 changes: 10 additions & 6 deletions packages/app-core/src/ui/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { MenuItem as JBMenuItem } from '@jbrowse/core/ui/Menu'
// locals
import AppToolbar from './AppToolbar'
import ViewLauncher from './ViewLauncher'
import ViewPanel from './ViewPanel'
import ViewContainer from './ViewContainer'
import DialogQueue from './DialogQueue'
import AppFab from './AppFab'

Expand All @@ -29,7 +29,7 @@ const useStyles = makeStyles()(theme => ({
gridTemplateRows: '[menubar] min-content [components] auto',
height: '100vh',
},
viewContainer: {
viewsContainer: {
overflowY: 'auto',
gridRow: 'components',
},
Expand Down Expand Up @@ -59,15 +59,19 @@ const LazyDrawerWidget = observer(function (props: Props) {
)
})

const ViewContainer = observer(function (props: Props) {
const ViewsContainer = observer(function (props: Props) {
const { session } = props
const { views } = session
const { classes } = useStyles()
return (
<div className={classes.viewContainer}>
<div className={classes.viewsContainer}>
{views.length > 0 ? (
views.map(view => (
<ViewPanel key={`view-${view.id}`} view={view} session={session} />
<ViewContainer
key={`view-${view.id}`}
view={view}
session={session}
/>
))
) : (
<ViewLauncher {...props} />
Expand Down Expand Up @@ -101,7 +105,7 @@ const App = observer(function (props: Props) {
<AppBar className={classes.appBar} position="static">
<AppToolbar {...props} />
</AppBar>
<ViewContainer {...props} />
<ViewsContainer {...props} />
</div>
<AppFab session={session} />

Expand Down
59 changes: 34 additions & 25 deletions packages/app-core/src/ui/App/ViewContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ import React, { useEffect } from 'react'
import { Paper, useTheme } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import { getSession, useWidthSetter } from '@jbrowse/core/util'
import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
import { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util'
import {
AbstractViewModel,
SessionWithFocusedViewAndDrawerWidgets,
useWidthSetter,
} from '@jbrowse/core/util'

// locals
import ViewHeader from './ViewHeader'
import ViewWrapper from './ViewWrapper'

const useStyles = makeStyles()(theme => ({
viewContainer: {
overflow: 'hidden',
margin: theme.spacing(0.5),
padding: `0 ${theme.spacing(1)} ${theme.spacing(1)}`,
overflow: 'clip',
// xref https://stackoverflow.com/questions/43909940/why-does-overflowhidden-prevent-positionsticky-from-working
// note that contain:paint also seems to work
},
focusedView: {
background: theme.palette.secondary.main,
Expand All @@ -25,19 +30,14 @@ const useStyles = makeStyles()(theme => ({

const ViewContainer = observer(function ({
view,
onClose,
onMinimize,
children,
session,
}: {
view: IBaseViewModel
onClose: () => void
onMinimize: () => void
children: React.ReactNode
view: AbstractViewModel
session: SessionWithFocusedViewAndDrawerWidgets
}) {
const theme = useTheme()
const ref = useWidthSetter(view, theme.spacing(1))
const { classes, cx } = useStyles()
const session = getSession(view) as SessionWithFocusedViewAndDrawerWidgets

useEffect(() => {
function handleSelectView(e: Event) {
Expand All @@ -56,20 +56,29 @@ const ViewContainer = observer(function ({
}
}, [ref, session, view])

const backgroundColorClassName =
session.focusedViewId === view.id
? classes.focusedView
: classes.unfocusedView
const viewContainerClassName = cx(
classes.viewContainer,
backgroundColorClassName,
)

return (
<Paper
ref={ref}
elevation={12}
className={cx(
classes.viewContainer,
session.focusedViewId === view.id
? classes.focusedView
: classes.unfocusedView,
)}
>
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
<Paper>{children}</Paper>
</Paper>
<>
<Paper ref={ref} elevation={12} className={viewContainerClassName}>
<ViewHeader
view={view}
onClose={() => session.removeView(view)}
onMinimize={() => view.setMinimized(!view.minimized)}
className={backgroundColorClassName}
/>
<Paper elevation={0}>
<ViewWrapper view={view} session={session} />
</Paper>
</Paper>
</>
)
})

Expand Down
12 changes: 10 additions & 2 deletions packages/app-core/src/ui/App/ViewHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IconButton } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
import { VIEW_HEADER_HEIGHT } from '@jbrowse/core/ui'

// icons
import CloseIcon from '@mui/icons-material/Close'
Expand All @@ -24,6 +25,11 @@ const useStyles = makeStyles()(theme => ({
},
viewHeader: {
display: 'flex',
height: VIEW_HEADER_HEIGHT,
position: 'sticky',
top: 0,
zIndex: 7,
background: theme.palette.secondary.main,
},
viewTitle: {
display: 'flex',
Expand Down Expand Up @@ -61,12 +67,14 @@ const ViewHeader = observer(function ({
view,
onClose,
onMinimize,
className,
}: {
view: IBaseViewModel
onClose: () => void
onMinimize: () => void
className?: string
}) {
const { classes } = useStyles()
const { classes, cx } = useStyles()
const scrollRef = useRef<HTMLDivElement>(null)
const session = getSession(view)

Expand All @@ -76,7 +84,7 @@ const ViewHeader = observer(function ({
scrollRef.current?.scrollIntoView?.({ block: 'center' })
}, [])
return (
<div ref={scrollRef} className={classes.viewHeader}>
<div ref={scrollRef} className={cx(classes.viewHeader, className)}>
<ViewMenu model={view} IconProps={{ className: classes.icon }} />
<div className={classes.grow} />
<div className={classes.viewTitle}>
Expand Down
63 changes: 0 additions & 63 deletions packages/app-core/src/ui/App/ViewPanel.tsx

This file was deleted.

44 changes: 44 additions & 0 deletions packages/app-core/src/ui/App/ViewWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { observer } from 'mobx-react'

// locals
import {
getEnv,
AbstractViewModel,
SessionWithFocusedViewAndDrawerWidgets,
} from '@jbrowse/core/util'

// ui elements
import ErrorMessage from '@jbrowse/core/ui/ErrorMessage'
import LoadingEllipses from '@jbrowse/core/ui/LoadingEllipses'

const ViewWrapper = observer(function ({
view,
session,
}: {
view: AbstractViewModel
session: SessionWithFocusedViewAndDrawerWidgets
}) {
const { pluginManager } = getEnv(session)
const viewType = pluginManager.getViewType(view.type)
if (!viewType) {
throw new Error(`unknown view type ${view.type}`)
}
const { ReactComponent } = viewType
if (view.minimized) {
return null
}

return (
<ErrorBoundary
FallbackComponent={({ error }) => <ErrorMessage error={error} />}
>
<Suspense fallback={<LoadingEllipses variant="h6" />}>
<ReactComponent model={view} session={session} />
</Suspense>
</ErrorBoundary>
)
})

export default ViewWrapper
10 changes: 10 additions & 0 deletions packages/core/pluggableElementTypes/models/BaseTrackModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export function createBaseTrackModel(
* #property
*/
minimized: false,
/**
* #property
*/
pinned: false,
/**
* #property
*/
Expand Down Expand Up @@ -122,6 +126,12 @@ export function createBaseTrackModel(
},
}))
.actions(self => ({
/**
* #action
*/
setPinned(flag: boolean) {
self.pinned = flag
},
/**
* #action
*/
Expand Down
1 change: 1 addition & 0 deletions packages/core/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { default as ReturnToImportFormDialog } from './ReturnToImportFormDialog'
export { default as ResizeHandle } from './ResizeHandle'
export { default as SanitizedHTML } from './SanitizedHTML'
export * from './Menu'
export const VIEW_HEADER_HEIGHT = 28
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const useStyles = makeStyles()(theme => ({
centerLineContainer: {
background: 'transparent',
height: '100%',
zIndex: 5, // above the track but under menu
zIndex: 4, // above the track but under menu
position: 'absolute',
border: `1px ${theme.palette.action.active} dashed`,
borderTop: 'none',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ type LGV = LinearGenomeViewModel
const useStyles = makeStyles()(theme => ({
verticalGuidesZoomContainer: {
position: 'absolute',
top: 0,
height: '100%',
width: '100%',
zIndex: 1,
pointerEvents: 'none',
},
verticalGuidesContainer: {
position: 'absolute',
height: '100%',
zIndex: 1,
pointerEvents: 'none',
display: 'flex',
},
Expand Down Expand Up @@ -99,7 +98,13 @@ const RenderedVerticalGuides = observer(({ model }: { model: LGV }) => {
</>
)
})
const Gridlines = observer(function ({ model }: { model: LGV }) {
const Gridlines = observer(function ({
model,
offset = 0,
}: {
model: LGV
offset?: number
}) {
const { classes } = useStyles()
// find the block that needs pinning to the left side for context
const offsetLeft = model.staticBlocks.offsetPx - model.offsetPx
Expand All @@ -114,7 +119,7 @@ const Gridlines = observer(function ({ model }: { model: LGV }) {
<div
className={classes.verticalGuidesContainer}
style={{
left: offsetLeft,
left: offsetLeft - offset,
width: model.staticBlocks.totalWidthPx,
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'

// locals
import { LinearGenomeViewModel, SPACING } from '..'
import { HEADER_BAR_HEIGHT, LinearGenomeViewModel, SPACING } from '..'
import OverviewScalebar from './OverviewScalebar'
import ZoomControls from './ZoomControls'
import SearchBox from './SearchBox'
Expand All @@ -19,6 +19,7 @@ type LGV = LinearGenomeViewModel
const useStyles = makeStyles()(theme => ({
headerBar: {
display: 'flex',
height: HEADER_BAR_HEIGHT,
},
headerForm: {
flexWrap: 'nowrap',
Expand Down
Loading
Loading