Skip to content

Commit

Permalink
Merge pull request #1278 from kubeshop/razvantopliceanu/refactor/acti…
Browse files Browse the repository at this point in the history
…ons-pane-footer

refactor: actions pane footer
  • Loading branch information
topliceanurazvan authored Feb 3, 2022
2 parents 5cae675 + 4ebabec commit d9cd2f1
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 81 deletions.
3 changes: 2 additions & 1 deletion src/components/atoms/SplitView/SplitView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ const SplitView: FunctionComponent<SplitViewProps> = ({
) {
dispatch(
setPaneConfiguration({
...paneConfiguration,
leftWidth,
navWidth,
editWidth,
Expand Down Expand Up @@ -491,7 +492,7 @@ const SplitView: FunctionComponent<SplitViewProps> = ({
<StyledDivider />
</StyledDividerHitBox>

<Pane id="EditorPane" width={editWidth * viewWidth} hide={false}>
<Pane id="EditorPane" width={editWidth * viewWidth + 5} hide={false}>
{editor}
</Pane>

Expand Down
22 changes: 22 additions & 0 deletions src/components/organisms/ActionsPane/ActionsPane.styled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
Expand Down Expand Up @@ -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;
Expand Down
108 changes: 84 additions & 24 deletions src/components/organisms/ActionsPane/ActionsPane.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -113,6 +117,9 @@ const ActionsPane = (props: {contentHeight: string}) => {
const tabsList = document.getElementsByClassName('ant-tabs-nav-list');
const extraButton = useRef<any>();

const [actionsPaneFooterRef, {height: actionsPaneFooterHeight, width: actionsPaneFooterWidth}] =
useMeasure<HTMLDivElement>();

const getDistanceBetweenTwoComponents = useCallback(() => {
const tabsListEl = tabsList[0].getBoundingClientRect();
const extraButtonEl = extraButton.current.getBoundingClientRect();
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 (
<>
<Row>
Expand Down Expand Up @@ -490,7 +523,34 @@ const ActionsPane = (props: {contentHeight: string}) => {
)}
</S.Tabs>
</S.TabsContainer>
{featureFlags.ActionsPaneFooter && <ActionsPaneFooter />}

{featureFlags.ActionsPaneFooter && (
<S.ActionsPaneFooterContainer ref={actionsPaneFooterRef}>
<ResizableBox
height={resizableBoxHeight}
width={actionsPaneFooterWidth}
axis="y"
resizeHandles={['n']}
minConstraints={[
actionsPaneFooterWidth,
isActionsPaneFooterExpanded
? ACTIONS_PANE_FOOTER_EXPANDED_DEFAULT_HEIGHT
: ACTIONS_PANE_FOOTER_DEFAULT_HEIGHT,
]}
maxConstraints={[actionsPaneFooterWidth, navigatorHeight - 200]}
handle={(h: number, ref: LegacyRef<HTMLSpanElement>) => (
<span className={isActionsPaneFooterExpanded ? 'custom-handle' : ''} ref={ref} />
)}
>
<ActionsPaneFooter
tabs={{
terminal: {title: 'Terminal', content: <>Terminal content</>},
documentation: {title: 'Documentation', content: <>Documentation content</>},
}}
/>
</ResizableBox>
</S.ActionsPaneFooterContainer>
)}
</S.ActionsPaneContainer>

{isApplyModalVisible && (
Expand Down
56 changes: 56 additions & 0 deletions src/components/organisms/ActionsPane/ActionsPaneFooter.styled.tsx
Original file line number Diff line number Diff line change
@@ -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;
`;
91 changes: 39 additions & 52 deletions src/components/organisms/ActionsPane/ActionsPaneFooter.tsx
Original file line number Diff line number Diff line change
@@ -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<IProps> = 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<string>();

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 (
<Container>
<TitleBar>
<TitleBarRightButtons>
<ArrowContainer onClick={toggleIsExpanded}>
{isExpanded ? <ArrowDownOutlined /> : <ArrowUpOutlined />}
</ArrowContainer>
</TitleBarRightButtons>
</TitleBar>
{isExpanded && (
<Pane>
<Divider style={{margin: 0}} />
</Pane>
)}
</Container>
<S.Container>
<S.TitleBar>
<S.TitleBarTabs>
{Object.entries(tabs).map(([key, value]) => (
<S.TitleLabel
className={activeTab === key && isExpanded ? 'selected-tab' : ''}
onClick={() => onClickTabLabel(key)}
>
{value.title}
</S.TitleLabel>
))}
</S.TitleBarTabs>

<S.TitleIcon onClick={toggleIsExpanded}>
{isExpanded ? <DownCircleOutlined /> : <UpCircleOutlined />}
</S.TitleIcon>
</S.TitleBar>

{isExpanded && activeTab && <S.Pane>{tabs[activeTab].content}</S.Pane>}
</S.Container>
);
};

Expand Down
Loading

0 comments on commit d9cd2f1

Please sign in to comment.