Skip to content

Commit 4ee443f

Browse files
committed
Floating view panel
Back with main
1 parent c5ba32e commit 4ee443f

File tree

19 files changed

+3717
-2755
lines changed

19 files changed

+3717
-2755
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"@types/rbush": "^3.0.0",
8383
"@types/react": "^18.0.26",
8484
"@types/react-dom": "^18.0.0",
85+
"@types/react-resizable": "^3.0.7",
8586
"@types/react-virtualized-auto-sizer": "^1.0.0",
8687
"@types/set-value": "^4.0.1",
8788
"@types/string-template": "^1.0.2",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { useEffect, useState } from 'react'
2+
import { ResizableBox } from 'react-resizable'
3+
import { observer } from 'mobx-react'
4+
import { AbstractViewModel, SessionWithDrawerWidgets } from '@jbrowse/core/util'
5+
import { SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel'
6+
import { MenuItem as JBMenuItem } from '@jbrowse/core/ui/Menu'
7+
import DraggableDialog from '@jbrowse/core/ui/DraggableDialog'
8+
9+
// locals
10+
import StaticViewPanel from './StaticViewPanel'
11+
import './test.css'
12+
import useMeasure from '@jbrowse/core/util/useMeasure'
13+
14+
type AppSession = SessionWithDrawerWidgets & {
15+
savedSessionNames: string[]
16+
menus: {
17+
label: string
18+
menuItems: JBMenuItem[]
19+
}[]
20+
snackbarMessages: SnackbarMessage[]
21+
renameCurrentSession: (arg: string) => void
22+
popSnackbarMessage: () => unknown
23+
}
24+
25+
const FloatingViewPanel = observer(function ({
26+
view,
27+
session,
28+
}: {
29+
view: AbstractViewModel
30+
session: AppSession
31+
}) {
32+
const zIndex = session.focusedViewId === view.id ? 101 : 100
33+
const [ref, { height }] = useMeasure()
34+
const [mode, setMode] = useState<'se' | 'e'>('se')
35+
const [size, setSize] = useState<{ width: number; height: number }>()
36+
const h = size !== undefined ? size.height : undefined
37+
38+
useEffect(() => {
39+
if (h !== undefined && height !== undefined && height - h > 50) {
40+
setMode('e')
41+
}
42+
}, [h, height])
43+
44+
return (
45+
<DraggableDialog zIndex={zIndex}>
46+
<ResizableBox
47+
className="box"
48+
height={(height || 100) + 20}
49+
resizeHandles={[mode]}
50+
onResize={(_event, { size }) => {
51+
setSize(size)
52+
}}
53+
width={1000}
54+
>
55+
<div ref={ref}>
56+
<StaticViewPanel view={view} session={session} />
57+
</div>
58+
</ResizableBox>
59+
</DraggableDialog>
60+
)
61+
})
62+
63+
export default FloatingViewPanel
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import React, { Suspense } from 'react'
2+
import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary'
3+
import { observer } from 'mobx-react'
4+
5+
// locals
6+
import {
7+
getEnv,
8+
AbstractViewModel,
9+
SessionWithDrawerWidgets,
10+
} from '@jbrowse/core/util'
11+
import { SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel'
12+
13+
// ui elements
14+
import ErrorMessage from '@jbrowse/core/ui/ErrorMessage'
15+
import LoadingEllipses from '@jbrowse/core/ui/LoadingEllipses'
16+
import { MenuItem as JBMenuItem } from '@jbrowse/core/ui/Menu'
17+
18+
// locals
19+
import ViewContainer from './ViewContainer'
20+
21+
type AppSession = SessionWithDrawerWidgets & {
22+
savedSessionNames: string[]
23+
menus: { label: string; menuItems: JBMenuItem[] }[]
24+
snackbarMessages: SnackbarMessage[]
25+
renameCurrentSession: (arg: string) => void
26+
popSnackbarMessage: () => unknown
27+
}
28+
29+
const StaticViewPanel = observer(function StaticViewPanel2({
30+
view,
31+
session,
32+
}: {
33+
view: AbstractViewModel
34+
session: AppSession
35+
}) {
36+
const { pluginManager } = getEnv(session)
37+
const viewType = pluginManager.getViewType(view.type)
38+
if (!viewType) {
39+
throw new Error(`unknown view type ${view.type}`)
40+
}
41+
const { ReactComponent } = viewType
42+
return (
43+
<ViewContainer
44+
view={view}
45+
onClose={() => {
46+
session.removeView(view)
47+
}}
48+
onMinimize={() => {
49+
view.setMinimized(!view.minimized)
50+
}}
51+
>
52+
{!view.minimized ? (
53+
<ErrorBoundary
54+
FallbackComponent={({ error }) => <ErrorMessage error={error} />}
55+
>
56+
<Suspense fallback={<LoadingEllipses variant="h6" />}>
57+
<ReactComponent model={view} session={session} />
58+
</Suspense>
59+
</ErrorBoundary>
60+
) : null}
61+
</ViewContainer>
62+
)
63+
})
64+
65+
export default StaticViewPanel
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React, { useEffect, useRef } from 'react'
2+
import { observer } from 'mobx-react'
3+
4+
const StaticViewWrapper = observer(function ({
5+
children,
6+
}: {
7+
children: React.ReactNode
8+
}) {
9+
const scrollRef = useRef<HTMLDivElement>(null)
10+
11+
// scroll the view into view when first mounted. note: this effect will run
12+
// only once, because of the empty array second param
13+
useEffect(() => {
14+
scrollRef.current?.scrollIntoView({ block: 'center' })
15+
}, [])
16+
return <div>{children}</div>
17+
})
18+
19+
export default StaticViewWrapper

packages/app-core/src/ui/App/ViewContainer.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util'
88

99
// locals
1010
import ViewHeader from './ViewHeader'
11+
import StaticViewWrapper from './StaticViewWrapper'
1112

1213
const useStyles = makeStyles()(theme => ({
1314
viewContainer: {
@@ -23,7 +24,7 @@ const useStyles = makeStyles()(theme => ({
2324
},
2425
}))
2526

26-
const ViewContainer = observer(function ({
27+
const ViewContainer = observer(function ViewContainer2({
2728
view,
2829
onClose,
2930
onMinimize,
@@ -38,6 +39,7 @@ const ViewContainer = observer(function ({
3839
const ref = useWidthSetter(view, theme.spacing(1))
3940
const { classes, cx } = useStyles()
4041
const session = getSession(view) as SessionWithFocusedViewAndDrawerWidgets
42+
const { focusedViewId } = session
4143

4244
useEffect(() => {
4345
function handleSelectView(e: Event) {
@@ -60,12 +62,18 @@ const ViewContainer = observer(function ({
6062
elevation={12}
6163
className={cx(
6264
classes.viewContainer,
63-
session.focusedViewId === view.id
64-
? classes.focusedView
65-
: classes.unfocusedView,
65+
focusedViewId === view.id ? classes.focusedView : classes.unfocusedView,
6666
)}
6767
>
68-
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
68+
{view.isFloating ? (
69+
<div style={{ cursor: 'all-scroll' }}>
70+
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
71+
</div>
72+
) : (
73+
<StaticViewWrapper>
74+
<ViewHeader view={view} onClose={onClose} onMinimize={onMinimize} />
75+
</StaticViewWrapper>
76+
)}
6977
<Paper>{children}</Paper>
7078
</Paper>
7179
)

packages/app-core/src/ui/App/ViewContainerTitle.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const useStyles = makeStyles()(theme => ({
2525
backgroundColor: theme.palette.secondary.light,
2626
},
2727
}))
28+
2829
const ViewContainerTitle = observer(function ({
2930
view,
3031
}: {

packages/app-core/src/ui/App/ViewHeader.tsx

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import React, { useEffect, useRef } from 'react'
2-
import { IconButton } from '@mui/material'
32
import { makeStyles } from 'tss-react/mui'
43
import { observer } from 'mobx-react'
54
import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
5+
import { getSession } from '@jbrowse/core/util'
66

77
// icons
8-
import CloseIcon from '@mui/icons-material/Close'
9-
import MinimizeIcon from '@mui/icons-material/Minimize'
10-
import AddIcon from '@mui/icons-material/Add'
118
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'
129

1310
// locals
1411
import ViewMenu from './ViewMenu'
1512
import ViewContainerTitle from './ViewContainerTitle'
16-
import { getSession } from '@jbrowse/core/util'
13+
import ViewHeaderButtons from './ViewHeaderButtons'
1714

1815
const useStyles = makeStyles()(theme => ({
1916
icon: {
@@ -31,32 +28,6 @@ const useStyles = makeStyles()(theme => ({
3128
},
3229
}))
3330

34-
const ViewButtons = observer(function ({
35-
view,
36-
onClose,
37-
onMinimize,
38-
}: {
39-
view: IBaseViewModel
40-
onClose: () => void
41-
onMinimize: () => void
42-
}) {
43-
const { classes } = useStyles()
44-
return (
45-
<>
46-
<IconButton data-testid="minimize_view" onClick={onMinimize}>
47-
{view.minimized ? (
48-
<AddIcon className={classes.icon} fontSize="small" />
49-
) : (
50-
<MinimizeIcon className={classes.icon} fontSize="small" />
51-
)}
52-
</IconButton>
53-
<IconButton data-testid="close_view" onClick={onClose}>
54-
<CloseIcon className={classes.icon} fontSize="small" />
55-
</IconButton>
56-
</>
57-
)
58-
})
59-
6031
const ViewHeader = observer(function ({
6132
view,
6233
onClose,
@@ -66,7 +37,7 @@ const ViewHeader = observer(function ({
6637
onClose: () => void
6738
onMinimize: () => void
6839
}) {
69-
const { classes } = useStyles()
40+
const { classes, cx } = useStyles()
7041
const scrollRef = useRef<HTMLDivElement>(null)
7142
const session = getSession(view)
7243

@@ -78,7 +49,7 @@ const ViewHeader = observer(function ({
7849
}
7950
}, [])
8051
return (
81-
<div ref={scrollRef} className={classes.viewHeader}>
52+
<div ref={scrollRef} className={cx('viewHeader', classes.viewHeader)}>
8253
<ViewMenu model={view} IconProps={{ className: classes.icon }} />
8354
<div className={classes.grow} />
8455
<div className={classes.viewTitle}>
@@ -88,7 +59,11 @@ const ViewHeader = observer(function ({
8859
<ViewContainerTitle view={view} />
8960
</div>
9061
<div className={classes.grow} />
91-
<ViewButtons onClose={onClose} onMinimize={onMinimize} view={view} />
62+
<ViewHeaderButtons
63+
onClose={onClose}
64+
onMinimize={onMinimize}
65+
view={view}
66+
/>
9267
</div>
9368
)
9469
})
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react'
2+
import { IconButton } from '@mui/material'
3+
import { makeStyles } from 'tss-react/mui'
4+
import { observer } from 'mobx-react'
5+
import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
6+
7+
// icons
8+
import CloseIcon from '@mui/icons-material/Close'
9+
import MinimizeIcon from '@mui/icons-material/Minimize'
10+
import AddIcon from '@mui/icons-material/Add'
11+
import OpenInNew from '@mui/icons-material/OpenInNew'
12+
13+
const useStyles = makeStyles()(theme => ({
14+
icon: {
15+
color: theme.palette.secondary.contrastText,
16+
},
17+
}))
18+
19+
const ViewHeaderButtons = observer(function ({
20+
view,
21+
onClose,
22+
onMinimize,
23+
}: {
24+
view: IBaseViewModel
25+
onClose: () => void
26+
onMinimize: () => void
27+
}) {
28+
const { classes } = useStyles()
29+
return (
30+
<>
31+
<IconButton
32+
data-testid="open_in_new"
33+
onClick={() => {
34+
view.setIsFloating(!view.isFloating)
35+
}}
36+
>
37+
<OpenInNew className={classes.icon} fontSize="small" />
38+
</IconButton>
39+
<IconButton data-testid="minimize_view" onClick={onMinimize}>
40+
{view.minimized ? (
41+
<AddIcon className={classes.icon} fontSize="small" />
42+
) : (
43+
<MinimizeIcon className={classes.icon} fontSize="small" />
44+
)}
45+
</IconButton>
46+
<IconButton data-testid="close_view" onClick={onClose}>
47+
<CloseIcon className={classes.icon} fontSize="small" />
48+
</IconButton>
49+
</>
50+
)
51+
})
52+
53+
export default ViewHeaderButtons

packages/app-core/src/ui/App/ViewPanel.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { Suspense } from 'react'
2-
import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary'
2+
import { ErrorBoundary } from 'react-error-boundary'
33
import { observer } from 'mobx-react'
44

55
// locals
@@ -20,7 +20,10 @@ import ViewContainer from './ViewContainer'
2020

2121
type AppSession = SessionWithDrawerWidgets & {
2222
savedSessionNames: string[]
23-
menus: { label: string; menuItems: JBMenuItem[] }[]
23+
menus: {
24+
label: string
25+
menuItems: JBMenuItem[]
26+
}[]
2427
snackbarMessages: SnackbarMessage[]
2528
renameCurrentSession: (arg: string) => void
2629
popSnackbarMessage: () => unknown

0 commit comments

Comments
 (0)