diff --git a/src/components/atoms/SplitView/SplitView.tsx b/src/components/atoms/SplitView/SplitView.tsx index d7a15302e4..eb6ea2b116 100644 --- a/src/components/atoms/SplitView/SplitView.tsx +++ b/src/components/atoms/SplitView/SplitView.tsx @@ -269,6 +269,7 @@ const SplitView: FunctionComponent = ({ ) { dispatch( setPaneConfiguration({ + ...paneConfiguration, leftWidth, navWidth, editWidth, @@ -491,7 +492,7 @@ const SplitView: FunctionComponent = ({ - + {editor} diff --git a/src/components/organisms/ActionsPane/ActionsPane.styled.tsx b/src/components/organisms/ActionsPane/ActionsPane.styled.tsx index 794056d3e3..821f11a538 100644 --- a/src/components/organisms/ActionsPane/ActionsPane.styled.tsx +++ b/src/components/organisms/ActionsPane/ActionsPane.styled.tsx @@ -2,6 +2,8 @@ import {Button, Skeleton as RawSkeleton, Tabs as RawTabs} from 'antd'; import styled from 'styled-components'; +import {GlobalScrollbarStyle} from '@utils/scrollbar'; + import {BackgroundColors} from '@styles/Colors'; export const Tabs = styled(RawTabs)` @@ -29,6 +31,26 @@ export const ActionsPaneContainer = styled.div<{$height: number}>` align-items: start; `; +export const ActionsPaneFooterContainer = styled.div` + position: relative; + width: 100%; + + & .react-resizable { + overflow-y: auto; + + ${GlobalScrollbarStyle} + } + + & .custom-handle { + position: absolute; + left: 0; + right: 0; + top: 0px; + height: 3px; + cursor: row-resize; + } +`; + export const TabsContainer = styled.div` flex-grow: 1; flex-basis: 0; diff --git a/src/components/organisms/ActionsPane/ActionsPane.tsx b/src/components/organisms/ActionsPane/ActionsPane.tsx index c3cd16e37d..4bf56f254a 100644 --- a/src/components/organisms/ActionsPane/ActionsPane.tsx +++ b/src/components/organisms/ActionsPane/ActionsPane.tsx @@ -1,13 +1,16 @@ import {ipcRenderer} from 'electron'; -import {useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import {LegacyRef, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'; +import {ResizableBox} from 'react-resizable'; +import {useMeasure} from 'react-use'; import {Button, Row, Tabs, Tooltip} from 'antd'; import {ArrowLeftOutlined, ArrowRightOutlined, BookOutlined, CodeOutlined, ContainerOutlined} from '@ant-design/icons'; import { - ACTIONS_PANE_FOOTER_HEIGHT, + ACTIONS_PANE_FOOTER_DEFAULT_HEIGHT, + ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT, ACTIONS_PANE_TAB_PANE_OFFSET, KUSTOMIZE_HELP_URL, NAVIGATOR_HEIGHT_OFFSET, @@ -29,7 +32,7 @@ import {K8sResource} from '@models/k8sresource'; import {useAppDispatch, useAppSelector} from '@redux/hooks'; import {setAlert} from '@redux/reducers/alert'; import {openResourceDiffModal} from '@redux/reducers/main'; -import {openSaveResourcesToFileFolderModal, setMonacoEditor} from '@redux/reducers/ui'; +import {openSaveResourcesToFileFolderModal, setMonacoEditor, setPaneConfiguration} from '@redux/reducers/ui'; import { isInPreviewModeSelector, knownResourceKindsSelector, @@ -74,27 +77,28 @@ const ActionsPane = (props: {contentHeight: string}) => { const {windowSize} = useContext(AppContext); const windowHeight = windowSize.height; - const selectedResourceId = useAppSelector(state => state.main.selectedResourceId); - const selectedValuesFileId = useAppSelector(state => state.main.selectedValuesFileId); - const helmValuesMap = useAppSelector(state => state.main.helmValuesMap); - const helmChartMap = useAppSelector(state => state.main.helmChartMap); const applyingResource = useAppSelector(state => state.main.isApplyingResource); - const resourceMap = useAppSelector(state => state.main.resourceMap); - const selectedPath = useAppSelector(state => state.main.selectedPath); - const fileMap = useAppSelector(state => state.main.fileMap); - const previewLoader = useAppSelector(state => state.main.previewLoader); - const uiState = useAppSelector(state => state.ui); const currentSelectionHistoryIndex = useAppSelector(state => state.main.currentSelectionHistoryIndex); - const selectionHistory = useAppSelector(state => state.main.selectionHistory); - const previewType = useAppSelector(state => state.main.previewType); - const monacoEditor = useAppSelector(state => state.ui.monacoEditor); - const isClusterDiffVisible = useAppSelector(state => state.ui.isClusterDiffVisible); + const fileMap = useAppSelector(state => state.main.fileMap); + const helmChartMap = useAppSelector(state => state.main.helmChartMap); + const helmValuesMap = useAppSelector(state => state.main.helmValuesMap); const isActionsPaneFooterExpanded = useAppSelector(state => state.ui.isActionsPaneFooterExpanded); + const isClusterDiffVisible = useAppSelector(state => state.ui.isClusterDiffVisible); const isInPreviewMode = useAppSelector(isInPreviewModeSelector); const kubeConfigContext = useAppSelector(kubeConfigContextSelector); const kubeConfigPath = useAppSelector(kubeConfigPathSelector); const {kustomizeCommand} = useAppSelector(settingsSelector); const knownResourceKinds = useAppSelector(knownResourceKindsSelector); + const monacoEditor = useAppSelector(state => state.ui.monacoEditor); + const paneConfiguration = useAppSelector(state => state.ui.paneConfiguration); + const previewLoader = useAppSelector(state => state.main.previewLoader); + const previewType = useAppSelector(state => state.main.previewType); + const resourceMap = useAppSelector(state => state.main.resourceMap); + const selectedPath = useAppSelector(state => state.main.selectedPath); + const selectedResourceId = useAppSelector(state => state.main.selectedResourceId); + const selectedValuesFileId = useAppSelector(state => state.main.selectedValuesFileId); + const selectionHistory = useAppSelector(state => state.main.selectionHistory); + const uiState = useAppSelector(state => state.ui); const navigatorHeight = useMemo( () => windowHeight - NAVIGATOR_HEIGHT_OFFSET - (isInPreviewMode ? 25 : 0), @@ -113,6 +117,9 @@ const ActionsPane = (props: {contentHeight: string}) => { const tabsList = document.getElementsByClassName('ant-tabs-nav-list'); const extraButton = useRef(); + const [actionsPaneFooterRef, {height: actionsPaneFooterHeight, width: actionsPaneFooterWidth}] = + useMeasure(); + const getDistanceBetweenTwoComponents = useCallback(() => { const tabsListEl = tabsList[0].getBoundingClientRect(); const extraButtonEl = extraButton.current.getBoundingClientRect(); @@ -132,16 +139,27 @@ const ActionsPane = (props: {contentHeight: string}) => { } }, [isButtonShrinked, tabsList]); - const editorTabPaneHeight = useMemo(() => { - let defaultHeight = parseInt(contentHeight, 10) - ACTIONS_PANE_TAB_PANE_OFFSET; - if (!featureFlags.ActionsPaneFooter) { - defaultHeight += 20; - } + const resizableBoxHeight = useMemo(() => { if (isActionsPaneFooterExpanded) { - return defaultHeight - ACTIONS_PANE_FOOTER_HEIGHT; + if (actionsPaneFooterHeight >= ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT) { + return actionsPaneFooterHeight; + } + + return paneConfiguration.actionsPaneFooterExpandedHeight || ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT; + } + + if (featureFlags.ActionsPaneFooter) { + return ACTIONS_PANE_FOOTER_DEFAULT_HEIGHT; } + + return -5; + }, [actionsPaneFooterHeight, isActionsPaneFooterExpanded, paneConfiguration.actionsPaneFooterExpandedHeight]); + + const editorTabPaneHeight = useMemo(() => { + let defaultHeight = parseInt(contentHeight, 10) - ACTIONS_PANE_TAB_PANE_OFFSET - resizableBoxHeight; + return defaultHeight; - }, [contentHeight, isActionsPaneFooterExpanded]); + }, [contentHeight, resizableBoxHeight]); const onSaveHandler = () => { if (selectedResource) { @@ -226,6 +244,12 @@ const ActionsPane = (props: {contentHeight: string}) => { [dispatch] ); + const onMouseUp = useCallback(() => { + if (isActionsPaneFooterExpanded && actionsPaneFooterHeight !== paneConfiguration.actionsPaneFooterExpandedHeight) { + dispatch(setPaneConfiguration({...paneConfiguration, actionsPaneFooterExpandedHeight: actionsPaneFooterHeight})); + } + }, [actionsPaneFooterHeight, dispatch, isActionsPaneFooterExpanded, paneConfiguration]); + const isDiffButtonDisabled = useMemo(() => { if (!selectedResource) { return true; @@ -339,6 +363,15 @@ const ActionsPane = (props: {contentHeight: string}) => { } }, [tabsList, uiState.paneConfiguration, windowSize, selectedResource, getDistanceBetweenTwoComponents]); + useEffect(() => { + document.addEventListener('mouseup', onMouseUp); + + return () => { + document.removeEventListener('mouseup', onMouseUp); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionsPaneFooterHeight, paneConfiguration.actionsPaneFooterExpandedHeight]); + return ( <> @@ -490,7 +523,34 @@ const ActionsPane = (props: {contentHeight: string}) => { )} - {featureFlags.ActionsPaneFooter && } + + {featureFlags.ActionsPaneFooter && ( + + ) => ( + + )} + > + Terminal content}, + documentation: {title: 'Documentation', content: <>Documentation content}, + }} + /> + + + )} {isApplyModalVisible && ( diff --git a/src/components/organisms/ActionsPane/ActionsPaneFooter.styled.tsx b/src/components/organisms/ActionsPane/ActionsPaneFooter.styled.tsx new file mode 100644 index 0000000000..c20a8b12a8 --- /dev/null +++ b/src/components/organisms/ActionsPane/ActionsPaneFooter.styled.tsx @@ -0,0 +1,56 @@ +import styled from 'styled-components'; + +import Colors from '@styles/Colors'; + +export const Container = styled.div` + width: 100%; + border-top: 1px solid ${Colors.grey3}; + padding: 10px; + display +`; + +export const Pane = styled.div` + margin-top: 15px; +`; + +export const TitleBar = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export const TitleBarTabs = styled.div` + display: flex; + gap: 20px; + + & .selected-tab::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -4px; + border: 1px solid ${Colors.grey9}; + } +`; + +export const TitleLabel = styled.span` + color: ${Colors.grey9}; + cursor: pointer; + text-transform: uppercase; + font-size: 12px; + position: relative; + + &:hover::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -4px; + border: 1px solid ${Colors.grey9}; + } +`; + +export const TitleIcon = styled.span` + color: ${Colors.grey8}; + cursor: pointer; +`; diff --git a/src/components/organisms/ActionsPane/ActionsPaneFooter.tsx b/src/components/organisms/ActionsPane/ActionsPaneFooter.tsx index da9f2b5370..404a8cc3f2 100644 --- a/src/components/organisms/ActionsPane/ActionsPaneFooter.tsx +++ b/src/components/organisms/ActionsPane/ActionsPaneFooter.tsx @@ -1,68 +1,55 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useState} from 'react'; -import {Divider} from 'antd'; - -import {ArrowDownOutlined, ArrowUpOutlined} from '@ant-design/icons'; - -import styled from 'styled-components'; - -import {ACTIONS_PANE_FOOTER_HEIGHT} from '@constants/constants'; +import {DownCircleOutlined, UpCircleOutlined} from '@ant-design/icons'; import {useAppDispatch, useAppSelector} from '@redux/hooks'; import {toggleExpandActionsPaneFooter} from '@redux/reducers/ui'; -import Colors from '@styles/Colors'; +import * as S from './ActionsPaneFooter.styled'; + +interface IProps { + tabs: {[tabKey: string]: {title: string; content: React.ReactNode}}; +} -const TitleBar = styled.div` - height: 25px; - width: 100%; - background-color: ${Colors.grey900}; - display: flex; - align-items: center; -`; +const ActionsPaneFooter: React.FC = props => { + const {tabs} = props; -const TitleBarRightButtons = styled.div` - margin-left: auto; -`; + const dispatch = useAppDispatch(); + const isExpanded = useAppSelector(state => state.ui.isActionsPaneFooterExpanded); -const ArrowContainer = styled.span` - margin-right: 4px; - padding: 0 8px; - cursor: pointer; -`; + const toggleIsExpanded = useCallback(() => dispatch(toggleExpandActionsPaneFooter()), [dispatch]); -const Container = styled.div` - min-height: 0; - width: 100%; -`; + const [activeTab, setActiveTab] = useState(); -const Pane = styled.div` - height: ${ACTIONS_PANE_FOOTER_HEIGHT}px; - background-color: ${Colors.grey900}; -`; + const onClickTabLabel = (key: string) => { + setActiveTab(key); -const ActionsPaneFooter = () => { - const dispatch = useAppDispatch(); - const isExpanded = useAppSelector(state => state.ui.isActionsPaneFooterExpanded); - const toggleIsExpanded = useCallback(() => { - dispatch(toggleExpandActionsPaneFooter()); - }, [dispatch]); + if (!isExpanded) { + toggleIsExpanded(); + } + }; return ( - - - - - {isExpanded ? : } - - - - {isExpanded && ( - - - - )} - + + + + {Object.entries(tabs).map(([key, value]) => ( + onClickTabLabel(key)} + > + {value.title} + + ))} + + + + {isExpanded ? : } + + + + {isExpanded && activeTab && {tabs[activeTab].content}} + ); }; diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 78dc64c4b4..a513a0fae7 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -15,8 +15,9 @@ export const KUSTOMIZATION_API_GROUP = 'kustomize.config.k8s.io'; export const KUSTOMIZE_HELP_URL = 'https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/'; export const DEFAULT_EDITOR_DEBOUNCE = 500; export const DEFAULT_KUBECONFIG_DEBOUNCE = 1000; -export const ACTIONS_PANE_FOOTER_HEIGHT = 200; -export const ACTIONS_PANE_TAB_PANE_OFFSET = 106; +export const ACTIONS_PANE_TAB_PANE_OFFSET = 95; +export const ACTIONS_PANE_FOOTER_DEFAULT_HEIGHT = 43; +export const ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT = 150; export const TEMPLATES_HEIGHT_OFFSET = 190; export const DEFAULT_PLUGINS = [ { diff --git a/src/models/ui.ts b/src/models/ui.ts index 512c7ab1ef..f7f7e4efb2 100644 --- a/src/models/ui.ts +++ b/src/models/ui.ts @@ -121,4 +121,5 @@ export type PaneConfiguration = { navWidth: number; editWidth: number; rightWidth: number; + actionsPaneFooterExpandedHeight: number; }; diff --git a/src/redux/reducers/ui.ts b/src/redux/reducers/ui.ts index 491178555f..6ad8d4e7d0 100644 --- a/src/redux/reducers/ui.ts +++ b/src/redux/reducers/ui.ts @@ -2,6 +2,8 @@ import {Draft, PayloadAction, createSlice} from '@reduxjs/toolkit'; import path from 'path'; +import {ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT} from '@constants/constants'; + import { HighlightItems, LeftMenuSelectionType, @@ -196,6 +198,7 @@ export const uiSlice = createSlice({ navWidth: 0.3333, editWidth: 0.3333, rightWidth: 0, + actionsPaneFooterExpandedHeight: ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT, }; state.paneConfiguration = defaultPaneConfiguration; electronStore.set('ui.paneConfiguration', defaultPaneConfiguration); diff --git a/src/styles/Colors.ts b/src/styles/Colors.ts index b0a7f67d93..504b463a19 100644 --- a/src/styles/Colors.ts +++ b/src/styles/Colors.ts @@ -5,10 +5,10 @@ enum Colors { grey800 = '#7B8185', grey700 = '#93989C', grey500 = '#C5C8CB', - grey450 = '#DBDBDB', grey400 = '#DBE0DE', grey200 = '#F3F5F4', grey100 = '#F9FAFA', + grey9 = '#DBDBDB', // gray, grey 9 grey8 = '#ACACAC', // gray, gray 8 grey7 = '#7D7D7D', // gray, gray 7 https://www.figma.com/file/3UVW3KVNob7QjgvH62blGU/add-left-and-right-toolbars?node-id=3%3A5926 grey6 = '#5A5A5A', // gray, gray 6 @@ -62,7 +62,7 @@ export enum BackgroundColors { export enum FontColors { lightThemeMainFont = Colors.blackPure, - darkThemeMainFont = Colors.grey450, + darkThemeMainFont = Colors.grey9, elementSelectTitle = Colors.blue6, resourceRowHighlight = Colors.highlightGreen, grey = Colors.grey700,