diff --git a/packages/web/src/App.js b/packages/web/src/App.js index f8383bf80..0af43db1d 100644 --- a/packages/web/src/App.js +++ b/packages/web/src/App.js @@ -7,6 +7,7 @@ import { OpenedTabsProvider, SavedSqlFilesProvider, OpenedConnectionsProvider, + LeftPanelWidthProvider, } from './utility/globalState'; import { SocketProvider } from './utility/SocketProvider'; import ConnectionsPinger from './utility/ConnectionsPinger'; @@ -19,9 +20,11 @@ function App() { - - - + + + + + diff --git a/packages/web/src/Screen.js b/packages/web/src/Screen.js index 8dbf1ed85..33f17598d 100644 --- a/packages/web/src/Screen.js +++ b/packages/web/src/Screen.js @@ -6,15 +6,16 @@ import styled from 'styled-components'; import TabsPanel from './TabsPanel'; import TabContent from './TabContent'; import WidgetIconPanel from './widgets/WidgetIconPanel'; -import { useCurrentWidget } from './utility/globalState'; +import { useCurrentWidget, useLeftPanelWidth, useSetLeftPanelWidth } from './utility/globalState'; import WidgetContainer from './widgets/WidgetContainer'; import ToolBar from './widgets/Toolbar'; import StatusBar from './widgets/StatusBar'; +import { useSplitterDrag, HorizontalSplitHandle } from './widgets/Splitter'; const BodyDiv = styled.div` position: fixed; top: ${theme.tabsPanel.height + theme.toolBar.height}px; - left: ${props => theme.widgetMenu.iconSize + props.leftPanelWidth}px; + left: ${(props) => props.contentLeft}px; bottom: ${theme.statusBar.height}px; right: 0; background-color: ${theme.mainArea.background}; @@ -43,7 +44,6 @@ const LeftPanel = styled.div` top: ${theme.toolBar.height}px; left: ${theme.widgetMenu.iconSize}px; bottom: ${theme.statusBar.height}px; - width: ${theme.leftPanel.width}px; background-color: ${theme.leftPanel.background}; display: flex; `; @@ -52,11 +52,11 @@ const TabsPanelContainer = styled.div` display: flex; position: fixed; top: ${theme.toolBar.height}px; - left: ${props => theme.widgetMenu.iconSize + props.leftPanelWidth}px; + left: ${(props) => props.contentLeft}px; height: ${theme.tabsPanel.height}px; right: 0; background-color: ${theme.tabsPanel.background}; - border-top: 1px solid #CCC; + border-top: 1px solid #ccc; `; const StausBarContainer = styled.div` @@ -68,10 +68,21 @@ const StausBarContainer = styled.div` background-color: ${theme.statusBar.background}; `; +const ScreenHorizontalSplitHandle = styled(HorizontalSplitHandle)` + position: absolute; + top: ${theme.toolBar.height}px; + bottom: ${theme.statusBar.height}px; +`; + export default function Screen() { const currentWidget = useCurrentWidget(); - const leftPanelWidth = currentWidget ? theme.leftPanel.width : 0; + const leftPanelWidth = useLeftPanelWidth(); + const setLeftPanelWidth = useSetLeftPanelWidth(); + const contentLeft = currentWidget + ? theme.widgetMenu.iconSize + leftPanelWidth + theme.splitter.thickness + : theme.widgetMenu.iconSize; const toolbarPortalRef = React.useRef(); + const onSplitDown = useSplitterDrag('clientX', (diff) => setLeftPanelWidth((v) => v + diff)); return ( <> @@ -85,10 +96,16 @@ export default function Screen() { )} - + {!!currentWidget && ( + + )} + - + diff --git a/packages/web/src/theme.js b/packages/web/src/theme.js index cab10aa5d..29350d09e 100644 --- a/packages/web/src/theme.js +++ b/packages/web/src/theme.js @@ -1,30 +1,33 @@ export default { widgetMenu: { iconSize: 60, - background: "#222", - iconFontSize: "23pt", - iconFontColor: "#eee", - backgroundHover: "#555", - backgroundSelected: "#4CAF50", + background: '#222', + iconFontSize: '23pt', + iconFontColor: '#eee', + backgroundHover: '#555', + backgroundSelected: '#4CAF50', }, leftPanel: { - width: 300, - background: "#ccc" + // width: 300, + background: '#ccc', }, tabsPanel: { height: 31, - background: "#ddd", - hoverFont: "#338" + background: '#ddd', + hoverFont: '#338', }, statusBar: { height: 20, - background: "#00c" + background: '#00c', }, toolBar: { height: 30, - background: "#eee", + background: '#eee', }, mainArea: { - background: "#eee" - } + background: '#eee', + }, + splitter: { + thickness: 3, + }, }; diff --git a/packages/web/src/utility/globalState.js b/packages/web/src/utility/globalState.js index 39b3dc972..e2fd1136c 100644 --- a/packages/web/src/utility/globalState.js +++ b/packages/web/src/utility/globalState.js @@ -104,3 +104,7 @@ export { SavedSqlFilesProvider, useSavedSqlFiles, useSetSavedSqlFiles }; const [OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnections] = createGlobalState([]); export { OpenedConnectionsProvider, useOpenedConnections, useSetOpenedConnections }; + +const [LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth] = createGlobalState(300); + +export { LeftPanelWidthProvider, useLeftPanelWidth, useSetLeftPanelWidth }; diff --git a/packages/web/src/widgets/Splitter.js b/packages/web/src/widgets/Splitter.js index cf9a39e1a..d3d666e5a 100644 --- a/packages/web/src/widgets/Splitter.js +++ b/packages/web/src/widgets/Splitter.js @@ -2,6 +2,7 @@ import _ from 'lodash'; import React from 'react'; import styled from 'styled-components'; import useDimensions from '../utility/useDimensions'; +import theme from '../theme'; const MainContainer = styled.div` flex: 1; @@ -9,13 +10,20 @@ const MainContainer = styled.div` flex-direction: column; `; -const VerticalSplitHandle = styled.div` +export const VerticalSplitHandle = styled.div` background-color: #ccc; - height: 4px; + height: ${theme.splitter.thickness}px; cursor: row-resize; z-index: 1; `; +export const HorizontalSplitHandle = styled.div` + background-color: #ccc; + width: ${theme.splitter.thickness}px; + cursor: col-resize; + z-index: 1; +`; + const ChildContainer1 = styled.div` // flex: 0 0 50%; // flex-basis: 100px; @@ -33,27 +41,16 @@ const ChildContainer2 = styled.div` position: relative; `; -export function VerticalSplitter({ children }) { - const childrenArray = _.isArray(children) ? children : [children]; - if (childrenArray.length !== 1 && childrenArray.length != 2) { - throw new Error('Splitter must have 1 or 2 children'); - } - const [refNode, dimensions] = useDimensions(); - const [height1, setHeight1] = React.useState(0); - - React.useEffect(() => { - setHeight1(dimensions.height / 2); - }, [dimensions]); - +export function useSplitterDrag(axes, onResize) { const [resizeStart, setResizeStart] = React.useState(null); React.useEffect(() => { if (resizeStart != null) { const handleResizeMove = (e) => { e.preventDefault(); - let diff = e.clientY - resizeStart; - setResizeStart(e.clientY); - setHeight1((v) => v + diff); + let diff = e[axes] - resizeStart; + setResizeStart(e[axes]); + onResize(diff); }; const handleResizeEnd = (e) => { e.preventDefault(); @@ -71,9 +68,26 @@ export function VerticalSplitter({ children }) { }, [resizeStart]); const handleResizeDown = (e) => { - setResizeStart(e.clientY); + setResizeStart(e[axes]); }; + return handleResizeDown; +} + +export function VerticalSplitter({ children }) { + const childrenArray = _.isArray(children) ? children : [children]; + if (childrenArray.length !== 1 && childrenArray.length != 2) { + throw new Error('Splitter must have 1 or 2 children'); + } + const [refNode, dimensions] = useDimensions(); + const [height1, setHeight1] = React.useState(0); + + React.useEffect(() => { + setHeight1(dimensions.height / 2); + }, [dimensions]); + + const handleResizeDown = useSplitterDrag('clientY', (diff) => setHeight1((v) => v + diff)); + const isSplitter = !!childrenArray[1]; return ( diff --git a/packages/web/src/widgets/WidgetStyles.js b/packages/web/src/widgets/WidgetStyles.js index 913bab8d2..5ea454d36 100644 --- a/packages/web/src/widgets/WidgetStyles.js +++ b/packages/web/src/widgets/WidgetStyles.js @@ -1,5 +1,8 @@ +// @ts-nocheck +import React from 'react'; import styled from 'styled-components'; import theme from '../theme'; +import { useLeftPanelWidth } from '../utility/globalState'; export const SearchBoxWrapper = styled.div` display: flex; @@ -15,21 +18,31 @@ export const WidgetsMainContainer = styled.div` user-select: none; `; -export const WidgetsOuterContainer = styled.div` +const StyledWidgetsOuterContainer = styled.div` flex: 1 1 0; overflow: hidden; - width: ${theme.leftPanel.width}px; + width: ${(props) => props.leftPanelWidth}px; position: relative; flex-direction: column; display: flex; `; -export const WidgetsInnerContainer = styled.div` +export function WidgetsOuterContainer({ children }) { + const leftPanelWidth = useLeftPanelWidth(); + return {children}; +} + +export const StyledWidgetsInnerContainer = styled.div` flex: 1 1; overflow: scroll; - width: ${theme.leftPanel.width}px; + width: ${(props) => props.leftPanelWidth}px; `; +export function WidgetsInnerContainer({ children }) { + const leftPanelWidth = useLeftPanelWidth(); + return {children}; +} + export const Input = styled.input` flex: 1; min-width: 90px;