diff --git a/package.json b/package.json index bf3f20b8..76fab6bc 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-flot": "^1.3.0", "react-query": "^3.39.2", "react-redux": "^7.2.6", + "react-resizable": "^3.0.4", "react-responsive": "^9.0.0-beta.6", "react-router-dom": "^6.2.1", "react-table": "7.7.0", @@ -102,6 +103,7 @@ "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.16.7", "@types/react-redux": "^7.1.24", + "@types/react-resizable": "^3.0.3", "babel-loader": "^8.2.3", "babel-plugin-syntax-dynamic-import": "^6.18.0", "babel-plugin-transform-class-properties": "^6.24.1", diff --git a/src/components/LabelBrowser/components/QueryBar.js b/src/components/LabelBrowser/components/QueryBar.js index 0ded88ee..b76de58a 100644 --- a/src/components/LabelBrowser/components/QueryBar.js +++ b/src/components/LabelBrowser/components/QueryBar.js @@ -1,5 +1,5 @@ /**React */ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useRef, useMemo } from "react"; import { useLocation } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; /**npm */ @@ -73,14 +73,17 @@ export const QueryBar = (props) => { queryHistory, start, stop, + isSplit } = useSelector((store) => store); const panelQuery = useSelector((store) => store[name]); - const isTabletOrMobile = useMediaQuery({ query: "(max-width: 914px)" }); + const isTabletOrMobile = useMediaQuery({ query: "(max-width: 864px)" }); const [queryInput, setQueryInput] = useState(data.expr); const [queryValid, setQueryValid] = useState(false); const [queryValue, setQueryValue] = useState(queryInit(data.expr)); const [open, setOpen] = useState(false); - + const wrapperRef = useRef(null) + const labelsButtonRef = useRef(null) + const buttonsContainerRef = useRef(null) useEffect(() => {}); const saveUrl = localUrl(); const expr = useMemo(() => { @@ -203,6 +206,15 @@ export const QueryBar = (props) => { function onClose() { showQuerySettings(); } + const getMaxWidth = () => { + const labelButtonWidth = !isNaN(labelsButtonRef?.current?.clientWidth) ? labelsButtonRef?.current?.clientWidth : 0; + const buttonsContainerWidth = !isNaN(buttonsContainerRef?.current?.clientWidth) ? buttonsContainerRef?.current?.clientWidth : 0; + if (isSplit || isTabletOrMobile) { + return 0; + } else { + return ( labelButtonWidth + buttonsContainerWidth + 5) + } + } return ( !isEmbed && (
{ `} > - +
{ /> - - - - - + {!isSplit && } +
+ +
+ {!isSplit && ( +
+ - + +
+ )}
- {!isTabletOrMobile && } - {isTabletOrMobile && ( + {!isTabletOrMobile && !isSplit && ( + + )} + {(isTabletOrMobile || isSplit) && ( props.theme.textColor}; + color: ${(props) => props.theme.textColor}; background: ${(props) => props.theme.buttonDefault}; - border:1px solid ${(props)=>props.theme.buttonBorder}; - height:28px; + border: 1px solid ${(props) => props.theme.buttonBorder}; + height: 28px; span { margin-left: 5px; } @@ -26,10 +26,10 @@ export const HistoryButtonStyled = styled(BtnSmall)` export const ShowLabelsBtn = styled(BtnSmall)` background: ${({ theme }) => theme.buttonDefault}; - border:1px solid ${(props)=>props.theme.buttonBorder}; + border: 1px solid ${(props) => props.theme.buttonBorder}; text-overflow: ellipsis; transition: 0.25s all; - padding-left:6px; + padding-left: 6px; justify-content: flex-start; color: ${({ theme }) => theme.textColor}; height: 28px; @@ -46,7 +46,7 @@ export const ShowLabelsBtn = styled(BtnSmall)` export const QueryBarContainer = styled.div` display: flex; padding: 6px; - margin-top:5px; + margin-top: 5px; margin-left: 0px; background: ${({ theme }) => theme.widgetContainer}; flex-wrap: wrap; @@ -54,21 +54,21 @@ export const QueryBarContainer = styled.div` `; export const ShowLogsBtn = styled(BtnSmall)` background: ${(props) => props.theme.primaryDark}; - border:1px solid ${(props)=>props.theme.buttonBorder}; + border: 1px solid ${(props) => props.theme.buttonBorder}; color: ${(props) => props.theme.buttonText}; margin-left: 5px; transition: 0.25s all; justify-content: center; padding: 3px 12px; - height:28px; + height: 28px; &:hover { background: ${(props) => props.theme.primaryLight}; } &:disabled { background: ${(props) => props.theme.buttonDefault}; - border:1px solid ${(props)=>props.theme.buttonBorder}; + border: 1px solid ${(props) => props.theme.buttonBorder}; cursor: not-allowed; - color: ${props => props.theme.textColor}; + color: ${(props) => props.theme.textColor}; } @media screen and (max-width: 864px) { display: ${(props) => (props.isMobile ? "flex" : "none")}; @@ -80,35 +80,33 @@ export const ShowLogsBtn = styled(BtnSmall)` export const ShowSettingsBtn = styled(BtnSmall)` background: none; margin-left: 5px; - color: ${(props)=> props.theme.textColor}; + color: ${(props) => props.theme.textColor}; background: ${(props) => props.theme.buttonDefault}; - border:1px solid ${(props)=>props.theme.buttonBorder}; - height:28px; + border: 1px solid ${(props) => props.theme.buttonBorder}; + height: 28px; span { margin-left: 5px; } - @media screen and (max-width: 864px) { - display: ${(props) => (props.isMobile ? "flex" : "none")}; - } + display: ${(props) => (props.isMobile || props.isSplit ? "flex" : "none")}; `; export const MobileTopQueryMenu = styled.div` - display: none; + display: ${(props) => + props.isSplit || props.dataSourceType === "flux" ? "flex" : "none"}; + @media screen and (max-width: 864px) { display: flex; - justify-content: space-between + justify-content: space-between; } `; - - export const InputGroup = styled.div` display: flex; - flex:1; - align-items:center; - justify-content:space-between; + flex: 1; + align-items: center; + justify-content: space-between; margin-bottom: 20px; - margin-right:10px; + margin-right: 10px; `; export const InlineGroup = styled.div` display: flex; @@ -120,7 +118,6 @@ export const SettingCont = styled.div` flex: 1; flex-direction: column; - background: ${({ theme }) => theme.widgetContainer}; `; @@ -129,9 +126,9 @@ export const SettingsInputContainer = styled.div` display: flex; flex-direction: column; flex: 1; - .options-input{ - margin:10px; - display:flex; + .options-input { + margin: 10px; + display: flex; } `; diff --git a/src/components/Panel/Panel.js b/src/components/Panel/Panel.js index d45c0063..830d038d 100644 --- a/src/components/Panel/Panel.js +++ b/src/components/Panel/Panel.js @@ -11,7 +11,7 @@ const PanelCont = styled.div` display: flex; flex-direction: column; flex: 1; - width: ${(props) => (props.isSplit ? "50%" : "100%")}; + width: 100%; `; // Panel should have injected data export default function Panel(props) { diff --git a/src/plugins/ResizableBox/ResiableBox.tsx b/src/plugins/ResizableBox/ResiableBox.tsx new file mode 100644 index 00000000..008e4b4c --- /dev/null +++ b/src/plugins/ResizableBox/ResiableBox.tsx @@ -0,0 +1,111 @@ +import { ResizableBox as ReactResizableBox } from "react-resizable"; +import { css, cx } from "@emotion/css"; +import { useSelector } from "react-redux"; + +interface ResizableBoxProps { + height: number; + minHeight: number; + maxHeight: number; + width: number; + minWidth: number; + maxWidth: number; + children: any; + handle: any; + axis: Axis; + resizeHandles?: Array + onResize: any; + className: string; +} +type Axis = 'both' | 'x' | 'y' | undefined +type ResizeHandleAxis = 's' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'; + +const getStyles = (theme: string): any => { + return { + "react-resizable": css` + position: relative; + `, + "react-resizable-handle": css` + position: absolute; + width: 20px; + height: 20px; + background-repeat: no-repeat; + background-origin: content-box; + box-sizing: border-box; + filter: ${theme !== 'dark' ? 'none' : 'invert(100%)'}; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+"); + background-position: bottom right; + padding: 0 3px 3px 0; + `, + "react-resizable-handle-sw": css` + bottom: 0; + left: 0; + cursor: sw-resize; + transform: rotate(90deg); + `, + "react-resizable-handle-se": css` + bottom: 0; + right: 0; + cursor: se-resize; + `, + "react-resizable-handle-nw": css` + top: 0; + left: 0; + cursor: nw-resize; + transform: rotate(180deg); + `, + "react-resizable-handle-ne": css` + top: 0; + right: 0; + cursor: ne-resize; + transform: rotate(270deg); + `, + "react-resizable-handle-w": css` + top: 50%; + margin-top: -10px; + cursor: ew-resize; + left: 0; + transform: rotate(135deg); + `, + "react-resizable-handle-e": css` + top: 50%; + margin-top: -10px; + cursor: ew-resize; + right: 0; + transform: rotate(315deg); + `, + "react-resizable-handle-n": css` + left: 50%; + margin-left: -10px; + cursor: ns-resize; + top: 0; + transform: rotate(225deg); + `, + "react-resizable-handle-s": css` + left: 50%; + margin-left: -10px; + cursor: ns-resize; + bottom: 0; + transform: rotate(45deg); + ` +}}; +export function ResizableBox(props: ResizableBoxProps) { + + const storeTheme = useSelector(({ theme }: any) => theme); + const { height, width, children, minWidth, maxWidth, minHeight, maxHeight, resizeHandles, onResize, axis, className } = props; + const styles = getStyles(storeTheme); + const handleFn = (axis: ResizeHandleAxis, ref: any) => { + return ; + }; + + return ( + + {children} + + ); +} diff --git a/src/plugins/queryeditor/index.js b/src/plugins/queryeditor/index.js index 1fbb00ce..f37470f0 100644 --- a/src/plugins/queryeditor/index.js +++ b/src/plugins/queryeditor/index.js @@ -1,6 +1,6 @@ import styled from "@emotion/styled"; import { css } from "@emotion/css"; -import React, { useCallback, useState, useMemo, useEffect } from "react"; +import React, { useCallback, useState, useMemo, useEffect, useRef } from "react"; import { createEditor, Text } from "slate"; import { Slate, Editable, withReact } from "slate-react"; @@ -11,9 +11,11 @@ import "prismjs/components/prism-sql"; import { themes } from "../../theme/themes"; import { ThemeProvider } from "@emotion/react"; import { useSelector } from "react-redux"; - +import { ResizableBox } from "../ResizableBox/ResiableBox"; +import { useMediaQuery } from "react-responsive"; const CustomEditor = styled(Editable)` flex: 1; + height: 100%; background: ${(props) => props.theme.inputBg}; border: 1px solid ${(props) => props.theme.buttonBorder}; color: ${(props) => props.theme.textColor}; @@ -21,11 +23,16 @@ const CustomEditor = styled(Editable)` font-size: 1em; font-family: monospace; margin: 0px 5px; + margin-bottom: 20px; border-radius: 3px; line-height: 1.5; line-break: anywhere; + overflow-y: scroll; +`; +const Resizable = css` + margin-bottom: 10px; + width: 100%; `; - const QueryBar = styled.div` display: flex; align-items: center; @@ -106,12 +113,28 @@ export function getTokenLength(token) { return token.content.reduce((l, t) => l + getTokenLength(t), 0); } -export default function QueryEditor({ onQueryChange, value, onKeyDown, defaultValue }) { +export default function QueryEditor({ + onQueryChange, + value, + onKeyDown, + defaultValue, + isSplit, + wrapperRef +}) { const theme = useSelector((store) => store.theme); const renderLeaf = useCallback((props) => , []); - + const [height, setHeight] = useState(0); + const [width, setWidth] = useState(0); const editor = useMemo(() => withHistory(withReact(createEditor())), []); + const ref = useRef(null); + + useEffect(()=> { + setHeight(30) + },[setHeight]) + useEffect(()=> { + setWidth(wrapperRef) + },[width, setWidth, isSplit, wrapperRef]) // Keep track of state for the value of the editor. const [language] = useState("sql"); @@ -152,26 +175,46 @@ export default function QueryEditor({ onQueryChange, value, onKeyDown, defaultVa setEditorValue(value); editor.children = value; }, [value, setEditorValue]); - + const onResize = (e, {size}) => { + console.log(size) + setHeight(size.height) + }; return ( - + {/* */} + + + */} - + {" "} + + + diff --git a/src/views/Main.js b/src/views/Main.js index fc6f7863..d265c902 100644 --- a/src/views/Main.js +++ b/src/views/Main.js @@ -10,8 +10,8 @@ import StatusBar from "../components/StatusBar"; import QueryHistory from "../plugins/queryhistory"; import { useMediaQuery } from "react-responsive"; import MainTabs from "./MainTabs.js"; -import { useMemo } from "react"; - +import { useMemo, useState, useEffect, useRef } from "react"; +import { ResizableBox } from "../plugins/ResizableBox/ResiableBox"; export const MainContainer = styled.div` position: absolute; display: flex; @@ -23,7 +23,7 @@ export const MainContainer = styled.div` background-color: ${(props) => props.theme.mainBgColor} !important; &::-webkit-scrollbar-corner { background: transparent; - } + } &::-webkit-scrollbar-thumb { border-radius: 5px; background: ${(props) => props.theme.scrollbarThumb} !important; @@ -62,13 +62,118 @@ export function MobileView({ theme, isEmbed, settingsDialogOpen }) { * @returns Desktop View */ export function DesktopView({ theme, isEmbed, isSplit, settingsDialogOpen }) { + const [height, setHeight] = useState(0); + const [widthTotal, setWidthTotal] = useState(0); + const [widthLeft, setWidthLeft] = useState(0); + const [widthRight, setWidthRight] = useState(0); + const [widthLeftPercent, setWidthLeftPercent] = useState(0); + const [widthRightPercent, setWidthRightercent] = useState(0); + const [minWidth, setMinWidth] = useState(0); + const [maxWidth, setMaxWidth] = useState(0); + const refTotal = useRef(null); + useEffect(() => { + const widthTotal = refTotal.current.clientWidth + setHeight(refTotal.current.clientHeight); + setWidthTotal(refTotal.current.clientWidth); + setWidthLeft(widthTotal / (isSplit ? 2 : 1)); + if (isSplit) { + setWidthRight(widthTotal / 2); + } + const realMinWidth = !isSplit ? widthTotal : widthTotal / 4 > 370 ? widthTotal / 4 : 370; + setMinWidth(realMinWidth); + const realMaxWidth = !isSplit ? widthTotal : widthTotal - realMinWidth + setMaxWidth(realMaxWidth); + }, [ + setWidthLeft, + setWidthRight, + setWidthTotal, + setHeight, + setMinWidth, + setMaxWidth, + minWidth, + isSplit, + ]); + useEffect(() => { + const widthTotal = refTotal.current.clientWidth + setWidthLeftPercent(widthLeft / widthTotal); + if (isSplit) { + setWidthRightercent(widthRight / widthTotal); + } + }, [widthLeft, widthRight]); + useEffect(() => { + const onWindowResize = () => { + const widthTotal = refTotal.current.clientWidth + setWidthTotal(widthTotal); + setWidthLeft(widthTotal * widthLeftPercent); + if (isSplit) { + setWidthRight(widthTotal * widthRightPercent); + } + }; + window.addEventListener("resize", onWindowResize); + return () => { + window.removeEventListener("resize", onWindowResize); + }; + }, [ + widthTotal, + widthLeft, + widthRight, + widthLeftPercent, + widthRightPercent, + isSplit, + ]); + const onSplitResize = (event, { element, size, handle }) => { + if (handle === "e") { + setWidthRight(widthTotal - size.width); + setWidthLeft(size.width); + } else { + setWidthLeft(widthTotal - size.width); + setWidthRight(size.width); + } + setWidthLeftPercent(widthLeft / widthTotal); + setWidthRightercent(widthRight / widthTotal); + }; + return ( {!isEmbed && } -
- - {isSplit && } +
+ + + + {isSplit && ( + + + + )}