Skip to content

Commit

Permalink
Floating view panel
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Aug 19, 2024
1 parent 317644a commit 65ed1eb
Show file tree
Hide file tree
Showing 16 changed files with 380 additions and 68 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"@types/rbush": "^3.0.0",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.0",
"@types/react-resizable": "^3.0.7",
"@types/react-virtualized-auto-sizer": "^1.0.0",
"@types/set-value": "^4.0.1",
"@types/string-template": "^1.0.2",
Expand Down
64 changes: 48 additions & 16 deletions packages/app-core/src/ui/App/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { MenuItem as JBMenuItem } from '@jbrowse/core/ui/Menu'
// locals
import AppToolbar from './AppToolbar'
import ViewLauncher from './ViewLauncher'
import ViewPanel from './ViewPanel'
import StaticViewPanel from './StaticViewPanel'
import FloatingViewPanel from './FloatingViewPanel'
import DialogQueue from './DialogQueue'
import AppFab from './AppFab'

Expand Down Expand Up @@ -66,9 +67,21 @@ const ViewContainer = observer(function (props: Props) {
return (
<div className={classes.viewContainer}>
{views.length > 0 ? (
views.map(view => (
<ViewPanel key={`view-${view.id}`} view={view} session={session} />
))
views.map(view =>
view.floating ? (
<FloatingViewPanel
key={`view-${view.id}`}
view={view}
session={session}
/>
) : (
<StaticViewPanel
key={`view-${view.id}`}
view={view}
session={session}
/>
),
)
) : (
<ViewLauncher {...props} />
)}
Expand All @@ -79,15 +92,30 @@ const ViewContainer = observer(function (props: Props) {
)
})

const App = observer(function (props: Props) {
const { session } = props
const AppContainer = observer(function (props: Props) {
const { classes } = useStyles()
return (
<div className={classes.appContainer}>
<AppBar className={classes.appBar} position="static">
<AppToolbar {...props} />
</AppBar>
<ViewContainer {...props} />
</div>
)
})

const DrawerWrapper = observer(function (
props: Props & { children: React.ReactNode },
) {
const { children } = props
const { classes } = useStyles()
const { session } = props
const { minimized, visibleWidget, drawerWidth, drawerPosition } = session
const drawerVisible = visibleWidget && !minimized
const d = drawerVisible ? `[drawer] ${drawerWidth}px` : undefined

const grid =
drawerPosition === 'right' ? ['[main] 1fr', d] : [d, '[main] 1fr']

return (
<div
className={classes.root}
Expand All @@ -96,21 +124,25 @@ const App = observer(function (props: Props) {
{drawerVisible && drawerPosition === 'left' ? (
<LazyDrawerWidget session={session} />
) : null}
<DialogQueue session={session} />
<div className={classes.appContainer}>
<AppBar className={classes.appBar} position="static">
<AppToolbar {...props} />
</AppBar>
<ViewContainer {...props} />
</div>
<AppFab session={session} />

{children}

{drawerVisible && drawerPosition === 'right' ? (
<LazyDrawerWidget session={session} />
) : null}
</div>
)
})

const App = observer(function (props: Props) {
const { session } = props
return (
<DrawerWrapper {...props}>
<DialogQueue session={session} />
<AppContainer {...props} />
<AppFab session={session} />
<Snackbar session={session} />
</div>
</DrawerWrapper>
)
})

Expand Down
43 changes: 43 additions & 0 deletions packages/app-core/src/ui/App/FloatingViewPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'
import { ResizableBox } from 'react-resizable'
import { observer } from 'mobx-react'
import { AbstractViewModel, SessionWithDrawerWidgets } from '@jbrowse/core/util'
import { SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel'
import { MenuItem as JBMenuItem } from '@jbrowse/core/ui/Menu'
import DraggableDialog from '@jbrowse/core/ui/DraggableDialog'

// locals
import StaticViewPanel from './StaticViewPanel'
import './test.css'

type AppSession = SessionWithDrawerWidgets & {
savedSessionNames: string[]
menus: { label: string; menuItems: JBMenuItem[] }[]
snackbarMessages: SnackbarMessage[]
renameCurrentSession: (arg: string) => void
popSnackbarMessage: () => unknown
}

const FloatingViewPanel = observer(function ({
view,
session,
}: {
view: AbstractViewModel
session: AppSession
}) {
const zIndex = session.focusedViewId === view.id ? 101 : 100
return (
<DraggableDialog zIndex={zIndex}>
<ResizableBox
className="box"
height={200}
resizeHandles={['se']}
width={1000}
>
<StaticViewPanel view={view} session={session} />
</ResizableBox>
</DraggableDialog>
)
})

export default FloatingViewPanel
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ type AppSession = SessionWithDrawerWidgets & {
popSnackbarMessage: () => unknown
}

const ViewPanel = observer(function ({
const StaticViewPanel = observer(function StaticViewPanel2({
view,
session,
}: {
Expand Down Expand Up @@ -57,11 +57,9 @@ const ViewPanel = observer(function ({
<ReactComponent model={view} session={session} />
</Suspense>
</ErrorBoundary>
) : (
false
)}
) : null}
</ViewContainer>
)
})

export default ViewPanel
export default StaticViewPanel
19 changes: 19 additions & 0 deletions packages/app-core/src/ui/App/StaticViewWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { useEffect, useRef } from 'react'
import { observer } from 'mobx-react'

const StaticViewWrapper = observer(function ({
children,
}: {
children: React.ReactNode
}) {
const scrollRef = useRef<HTMLDivElement>(null)

// scroll the view into view when first mounted. note: this effect will run
// only once, because of the empty array second param
useEffect(() => {
scrollRef.current?.scrollIntoView({ block: 'center' })
}, [])
return <div>{children}</div>
})

export default StaticViewWrapper
18 changes: 13 additions & 5 deletions packages/app-core/src/ui/App/ViewContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util'

// locals
import ViewHeader from './ViewHeader'
import StaticViewWrapper from './StaticViewWrapper'

const useStyles = makeStyles()(theme => ({
viewContainer: {
Expand All @@ -23,7 +24,7 @@ const useStyles = makeStyles()(theme => ({
},
}))

const ViewContainer = observer(function ({
const ViewContainer = observer(function ViewContainer2({
view,
onClose,
onMinimize,
Expand All @@ -38,6 +39,7 @@ const ViewContainer = observer(function ({
const ref = useWidthSetter(view, theme.spacing(1))
const { classes, cx } = useStyles()
const session = getSession(view) as SessionWithFocusedViewAndDrawerWidgets
const { focusedViewId } = session

useEffect(() => {
function handleSelectView(e: Event) {
Expand All @@ -60,12 +62,18 @@ const ViewContainer = observer(function ({
elevation={12}
className={cx(
classes.viewContainer,
session.focusedViewId === view.id
? classes.focusedView
: classes.unfocusedView,
focusedViewId === view.id ? classes.focusedView : classes.unfocusedView,
)}
>
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
{view.floating ? (
<div style={{ cursor: 'all-scroll' }}>
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
</div>
) : (
<StaticViewWrapper>
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
</StaticViewWrapper>
)}
<Paper>{children}</Paper>
</Paper>
)
Expand Down
1 change: 1 addition & 0 deletions packages/app-core/src/ui/App/ViewContainerTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const useStyles = makeStyles()(theme => ({
backgroundColor: theme.palette.secondary.light,
},
}))

const ViewContainerTitle = observer(function ({
view,
}: {
Expand Down
43 changes: 9 additions & 34 deletions packages/app-core/src/ui/App/ViewHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import React, { useEffect, useRef } from 'react'
import { IconButton } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
import { getSession } from '@jbrowse/core/util'

// icons
import CloseIcon from '@mui/icons-material/Close'
import MinimizeIcon from '@mui/icons-material/Minimize'
import AddIcon from '@mui/icons-material/Add'
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'

// locals
import ViewMenu from './ViewMenu'
import ViewContainerTitle from './ViewContainerTitle'
import { getSession } from '@jbrowse/core/util'
import ViewHeaderButtons from './ViewHeaderButtons'

const useStyles = makeStyles()(theme => ({
icon: {
Expand All @@ -31,32 +28,6 @@ const useStyles = makeStyles()(theme => ({
},
}))

const ViewButtons = observer(function ({
view,
onClose,
onMinimize,
}: {
view: IBaseViewModel
onClose: () => void
onMinimize: () => void
}) {
const { classes } = useStyles()
return (
<>
<IconButton data-testid="minimize_view" onClick={onMinimize}>
{view.minimized ? (
<AddIcon className={classes.icon} fontSize="small" />
) : (
<MinimizeIcon className={classes.icon} fontSize="small" />
)}
</IconButton>
<IconButton data-testid="close_view" onClick={onClose}>
<CloseIcon className={classes.icon} fontSize="small" />
</IconButton>
</>
)
})

const ViewHeader = observer(function ({
view,
onClose,
Expand All @@ -66,7 +37,7 @@ const ViewHeader = observer(function ({
onClose: () => void
onMinimize: () => void
}) {
const { classes } = useStyles()
const { classes, cx } = useStyles()
const scrollRef = useRef<HTMLDivElement>(null)
const session = getSession(view)

Expand All @@ -78,7 +49,7 @@ const ViewHeader = observer(function ({
}
}, [])
return (
<div ref={scrollRef} className={classes.viewHeader}>
<div ref={scrollRef} className={cx('viewHeader', classes.viewHeader)}>
<ViewMenu model={view} IconProps={{ className: classes.icon }} />
<div className={classes.grow} />
<div className={classes.viewTitle}>
Expand All @@ -88,7 +59,11 @@ const ViewHeader = observer(function ({
<ViewContainerTitle view={view} />
</div>
<div className={classes.grow} />
<ViewButtons onClose={onClose} onMinimize={onMinimize} view={view} />
<ViewHeaderButtons
onClose={onClose}
onMinimize={onMinimize}
view={view}
/>
</div>
)
})
Expand Down
55 changes: 55 additions & 0 deletions packages/app-core/src/ui/App/ViewHeaderButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react'
import { IconButton } from '@mui/material'
import { makeStyles } from 'tss-react/mui'
import { observer } from 'mobx-react'
import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'

// icons
import CloseIcon from '@mui/icons-material/Close'
import MinimizeIcon from '@mui/icons-material/Minimize'
import AddIcon from '@mui/icons-material/Add'

// locals
import OpenInNew from '@mui/icons-material/OpenInNew'

const useStyles = makeStyles()(theme => ({
icon: {
color: theme.palette.secondary.contrastText,
},
}))

const ViewHeaderButtons = observer(function ({
view,
onClose,
onMinimize,
}: {
view: IBaseViewModel
onClose: () => void
onMinimize: () => void
}) {
const { classes } = useStyles()
return (
<>
<IconButton
data-testid="open_in_new"
onClick={() => {
view.setFloating(!view.floating)
}}
>
<OpenInNew className={classes.icon} fontSize="small" />
</IconButton>
<IconButton data-testid="minimize_view" onClick={onMinimize}>
{view.minimized ? (
<AddIcon className={classes.icon} fontSize="small" />
) : (
<MinimizeIcon className={classes.icon} fontSize="small" />
)}
</IconButton>
<IconButton data-testid="close_view" onClick={onClose}>
<CloseIcon className={classes.icon} fontSize="small" />
</IconButton>
</>
)
})

export default ViewHeaderButtons
Loading

0 comments on commit 65ed1eb

Please sign in to comment.