+
@@ -88,7 +59,11 @@ const ViewHeader = observer(function ({
-
+
)
})
diff --git a/packages/app-core/src/ui/App/ViewHeaderButtons.tsx b/packages/app-core/src/ui/App/ViewHeaderButtons.tsx
new file mode 100644
index 0000000000..062d561461
--- /dev/null
+++ b/packages/app-core/src/ui/App/ViewHeaderButtons.tsx
@@ -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 (
+ <>
+
{
+ view.setFloating(!view.floating)
+ }}
+ >
+
+
+
+ {view.minimized ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ >
+ )
+})
+
+export default ViewHeaderButtons
diff --git a/packages/app-core/src/ui/App/ViewTitle.tsx b/packages/app-core/src/ui/App/ViewTitle.tsx
new file mode 100644
index 0000000000..8bc7ad6eaf
--- /dev/null
+++ b/packages/app-core/src/ui/App/ViewTitle.tsx
@@ -0,0 +1,42 @@
+import React from 'react'
+import { makeStyles } from 'tss-react/mui'
+import { observer } from 'mobx-react'
+import { IBaseViewModel } from '@jbrowse/core/pluggableElementTypes/models'
+
+// icons
+import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'
+
+// locals
+import ViewContainerTitle from './ViewContainerTitle'
+import { getSession } from '@jbrowse/core/util'
+
+const useStyles = makeStyles()(theme => ({
+ icon: {
+ color: theme.palette.secondary.contrastText,
+ },
+ grow: {
+ flexGrow: 1,
+ },
+ viewHeader: {
+ display: 'flex',
+ },
+ viewTitle: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+}))
+
+const ViewTitle = observer(function ({ view }: { view: IBaseViewModel }) {
+ const { classes } = useStyles()
+ const session = getSession(view)
+ return (
+
+ {session.focusedViewId === view.id ? (
+
+ ) : null}
+
+
+ )
+})
+
+export default ViewTitle
diff --git a/packages/app-core/src/ui/App/test.css b/packages/app-core/src/ui/App/test.css
new file mode 100644
index 0000000000..e2c80854c3
--- /dev/null
+++ b/packages/app-core/src/ui/App/test.css
@@ -0,0 +1,65 @@
+.react-resizable {
+ position: relative;
+}
+.react-resizable-handle {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ background-repeat: no-repeat;
+ background-origin: content-box;
+ box-sizing: border-box;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+');
+ background-position: bottom right;
+ padding: 0 3px 3px 0;
+}
+.react-resizable-handle-sw {
+ bottom: 0;
+ left: 0;
+ cursor: sw-resize;
+ transform: rotate(90deg);
+}
+.react-resizable-handle-se {
+ bottom: 0;
+ right: 0;
+ cursor: se-resize;
+}
+.react-resizable-handle-nw {
+ top: 0;
+ left: 0;
+ cursor: nw-resize;
+ transform: rotate(180deg);
+}
+.react-resizable-handle-ne {
+ top: 0;
+ right: 0;
+ cursor: ne-resize;
+ transform: rotate(270deg);
+}
+.react-resizable-handle-w,
+.react-resizable-handle-e {
+ top: 50%;
+ margin-top: -10px;
+ cursor: ew-resize;
+}
+.react-resizable-handle-w {
+ left: 0;
+ transform: rotate(135deg);
+}
+.react-resizable-handle-e {
+ right: 0;
+ transform: rotate(315deg);
+}
+.react-resizable-handle-n,
+.react-resizable-handle-s {
+ left: 50%;
+ margin-left: -10px;
+ cursor: ns-resize;
+}
+.react-resizable-handle-n {
+ top: 0;
+ transform: rotate(225deg);
+}
+.react-resizable-handle-s {
+ bottom: 0;
+ transform: rotate(45deg);
+}
diff --git a/packages/core/package.json b/packages/core/package.json
index ef29e52800..558f8a1d29 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -54,7 +54,9 @@
"load-script": "^2.0.0",
"material-ui-popup-state": "^5.0.0",
"rbush": "^3.0.1",
+ "react-draggable": "^4.4.5",
"react-error-boundary": "^4.0.3",
+ "react-resizable": "^3.0.5",
"serialize-error": "^8.0.0",
"source-map-js": "^1.0.2",
"svg-path-generator": "^1.1.0"
diff --git a/packages/core/pluggableElementTypes/models/BaseViewModel.ts b/packages/core/pluggableElementTypes/models/BaseViewModel.ts
index 13732c0d70..a60fef740f 100644
--- a/packages/core/pluggableElementTypes/models/BaseViewModel.ts
+++ b/packages/core/pluggableElementTypes/models/BaseViewModel.ts
@@ -26,7 +26,12 @@ const BaseViewModel = types
/**
* #property
*/
- minimized: false,
+ minimized: types.optional(types.boolean, false),
+
+ /**
+ * #property
+ */
+ floating: types.optional(types.boolean, false),
})
.volatile(() => ({
width: 800,
@@ -40,6 +45,12 @@ const BaseViewModel = types
},
}))
.actions(self => ({
+ /**
+ * #action
+ */
+ setFloating(b: boolean) {
+ self.floating = b
+ },
/**
* #action
*/
@@ -49,11 +60,11 @@ const BaseViewModel = types
/**
* #action
- * width is an important attribute of the view model, when it becomes set, it
- * often indicates when the app can start drawing to it. certain views like
- * lgv are strict about this because if it tries to draw before it knows the
- * width it should draw to, it may start fetching data for regions it doesn't
- * need to
+ * width is an important attribute of the view model, when it becomes set,
+ * it often indicates when the app can start drawing to it. certain views
+ * like lgv are strict about this because if it tries to draw before it
+ * knows the width it should draw to, it may start fetching data for
+ * regions it doesn't need to
*
* setWidth is updated by a ResizeObserver generally, the views often need
* to know how wide they are to properly draw genomic regions
diff --git a/packages/core/ui/DraggableDialog.tsx b/packages/core/ui/DraggableDialog.tsx
new file mode 100644
index 0000000000..a24ae3b6ff
--- /dev/null
+++ b/packages/core/ui/DraggableDialog.tsx
@@ -0,0 +1,42 @@
+import React, { useRef } from 'react'
+import { Portal } from '@mui/material'
+import { observer } from 'mobx-react'
+
+import {
+ useClientPoint,
+ useFloating,
+ useInteractions,
+} from '@floating-ui/react'
+import Draggable from 'react-draggable'
+
+const DraggableDialog = observer(function DraggableDialog({
+ children,
+ zIndex = 100,
+}: {
+ zIndex?: number
+ children: React.ReactNode
+}) {
+ const ref = useRef
(null)
+ const { refs, floatingStyles, context } = useFloating({
+ placement: 'bottom-start',
+ })
+ const clientPoint = useClientPoint(context, { x: 100, y: 100 })
+ const { getFloatingProps } = useInteractions([clientPoint])
+ return (
+
+
+
+
+
+ )
+})
+
+export default DraggableDialog
diff --git a/packages/core/util/types/index.ts b/packages/core/util/types/index.ts
index 11b1471a9d..c4db874ccc 100644
--- a/packages/core/util/types/index.ts
+++ b/packages/core/util/types/index.ts
@@ -31,6 +31,7 @@ export * from './util'
export interface AbstractViewContainer
extends IStateTreeNode> {
views: AbstractViewModel[]
+ floating?: boolean
removeView(view: AbstractViewModel): void
addView(
typeName: string,
@@ -279,11 +280,13 @@ export interface AbstractViewModel {
id: string
type: string
width: number
+ floating: boolean
minimized: boolean
setWidth(width: number): void
setMinimized(flag: boolean): void
displayName: string | undefined
setDisplayName: (arg: string) => void
+ setFloating: (arg: boolean) => void
menuItems: () => MenuItem[]
}
export function isViewModel(thing: unknown): thing is AbstractViewModel {
diff --git a/yarn.lock b/yarn.lock
index 51dbb2ea47..62f61caa52 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5099,6 +5099,13 @@
dependencies:
"@types/react" "*"
+"@types/react-resizable@^3.0.7":
+ version "3.0.8"
+ resolved "https://registry.yarnpkg.com/@types/react-resizable/-/react-resizable-3.0.8.tgz#b27001b4d262c82cc076272df4b8ef91d9487918"
+ integrity sha512-Pcvt2eGA7KNXldt1hkhVhAgZ8hK41m0mp89mFgQi7LAAEZiaLgm4fHJ5zbJZ/4m2LVaAyYrrRRv1LHDcrGQanA==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-transition-group@^4.4.10":
version "4.4.11"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5"
@@ -13758,7 +13765,7 @@ promzard@^1.0.0:
dependencies:
read "^3.0.1"
-prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
+prop-types@15.x, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -13955,7 +13962,7 @@ react-docgen@^7.0.0:
loose-envify "^1.1.0"
scheduler "^0.23.2"
-react-draggable@^4.4.5:
+react-draggable@^4.0.3, react-draggable@^4.4.5:
version "4.4.6"
resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.6.tgz#63343ee945770881ca1256a5b6fa5c9f5983fe1e"
integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==
@@ -14023,6 +14030,14 @@ react-refresh@^0.14.0:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9"
integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==
+react-resizable@^3.0.5:
+ version "3.0.5"
+ resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-3.0.5.tgz#362721f2efbd094976f1780ae13f1ad7739786c1"
+ integrity sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w==
+ dependencies:
+ prop-types "15.x"
+ react-draggable "^4.0.3"
+
react-transition-group@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"