diff --git a/frontend/package.json b/frontend/package.json index c3d75ce037f..0f40cadb2ee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -192,7 +192,6 @@ "react-dnd": "^11.1.3", "react-dnd-html5-backend": "^11.1.3", "react-dom": "^17.0.1", - "react-draggable": "4.x", "react-helmet-async": "^2.0.5", "react-i18next": "^11.12.0", "react-linkify": "^0.2.2", @@ -204,7 +203,6 @@ "react-router-hash-link": "^2.0.0", "react-svg": "^16.2.0", "react-tagsinput": "3.20.x", - "react-transition-group": "2.3.x", "react-virtualized": "9.x", "redux": "4.0.1", "redux-thunk": "2.4.0", @@ -247,7 +245,6 @@ "@types/react-jsonschema-form": "^1.3.8", "@types/react-router": "^5.1.20", "@types/react-router-dom": "5.3.x", - "@types/react-transition-group": "2.x", "@types/react-virtualized": "9.x", "@types/semver": "^6.0.0", "@types/showdown": "1.9.4", diff --git a/frontend/packages/console-app/src/components/tabs/Tabs.tsx b/frontend/packages/console-app/src/components/tabs/Tabs.tsx deleted file mode 100644 index 3d6fa33b989..00000000000 --- a/frontend/packages/console-app/src/components/tabs/Tabs.tsx +++ /dev/null @@ -1,479 +0,0 @@ -import * as React from 'react'; -import { - Button, - TabContent, - TabProps, - TabsContextProvider, - getOUIAProps, - OUIAProps, - getDefaultOUIAId, - canUseDOM, - GenerateId, - PickOptional, - getUniqueId, - isElementInView, - formatBreakpointMods, -} from '@patternfly/react-core'; -import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon'; -import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; -import { css } from '@patternfly/react-styles'; -import buttonStyles from '@patternfly/react-styles/css/components/Button/button'; -import styles from '@patternfly/react-styles/css/components/Tabs/tabs'; - -export enum TabsComponent { - div = 'div', - nav = 'nav', -} - -export interface TabsProps - extends Omit, 'onSelect'>, - OUIAProps { - /** Content rendered inside the tabs component. Must be React.ReactElement[] */ - children: React.ReactNode; - /** Additional classes added to the tabs */ - className?: string; - /** Tabs background color variant */ - variant?: 'default' | 'secondary'; - /** The index of the active tab */ - activeKey?: number | string; - /** The index of the default active tab. Set this for uncontrolled Tabs */ - defaultActiveKey?: number | string; - /** Callback to handle tab selection */ - onSelect?: (event: React.MouseEvent, eventKey: number | string) => void; - /** Uniquely identifies the tabs */ - id?: string; - /** Enables the filled tab list layout */ - isFilled?: boolean; - /** Enables secondary tab styling */ - isSecondary?: boolean; - /** Enables box styling to the tab component */ - isBox?: boolean; - /** Enables vertical tab styling */ - isVertical?: boolean; - /** Aria-label for the left scroll button */ - leftScrollAriaLabel?: string; - /** Aria-label for the right scroll button */ - rightScrollAriaLabel?: string; - /** Determines what tag is used around the tabs. Use "nav" to define the tabs inside a navigation region */ - component?: 'div' | 'nav'; - /** Provides an accessible label for the tabs. Labels should be unique for each set of tabs that are present on a page. When component is set to nav, this prop should be defined to differentiate the tabs from other navigation regions on the page. */ - 'aria-label'?: string; - /** Waits until the first "enter" transition to mount tab children (add them to the DOM) */ - mountOnEnter?: boolean; - /** Unmounts tab children (removes them from the DOM) when they are no longer visible */ - unmountOnExit?: boolean; - /** Flag indicates that the tabs should use page insets. */ - usePageInsets?: boolean; - /** Insets at various breakpoints. */ - inset?: { - default?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl'; - sm?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl'; - md?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl'; - lg?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl'; - xl?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl'; - '2xl'?: 'insetNone' | 'insetSm' | 'insetMd' | 'insetLg' | 'insetXl' | 'inset2xl'; - }; - /** Enable expandable vertical tabs at various breakpoints. (isVertical should be set to true for this to work) */ - expandable?: { - default?: 'expandable' | 'nonExpandable'; - sm?: 'expandable' | 'nonExpandable'; - md?: 'expandable' | 'nonExpandable'; - lg?: 'expandable' | 'nonExpandable'; - xl?: 'expandable' | 'nonExpandable'; - '2xl'?: 'expandable' | 'nonExpandable'; - }; - /** Flag to indicate if the vertical tabs are expanded */ - isExpanded?: boolean; - /** Flag indicating the default expanded state for uncontrolled expand/collapse of */ - defaultIsExpanded?: boolean; - /** Text that appears in the expandable toggle */ - toggleText?: string; - /** Aria-label for the left expandable toggle */ - toggleAriaLabel?: string; - /** Callback function to toggle the expandable tabs. */ - onToggle?: (isExpanded: boolean) => void; -} - -const variantStyle = { - default: '', - secondary: styles.modifiers.secondary, -}; - -interface TabsState { - showScrollButtons: boolean; - disableLeftScrollButton: boolean; - disableRightScrollButton: boolean; - shownKeys: (string | number)[]; - uncontrolledActiveKey: number | string; - uncontrolledIsExpandedLocal: boolean; - ouiaStateId: string; -} - -export class Tabs extends React.Component { - static displayName = 'Tabs'; - - tabList = React.createRef(); - - constructor(props: TabsProps) { - super(props); - this.state = { - showScrollButtons: false, - disableLeftScrollButton: true, - disableRightScrollButton: true, - shownKeys: - this.props.defaultActiveKey !== undefined - ? [this.props.defaultActiveKey] - : [this.props.activeKey], // only for mountOnEnter case - uncontrolledActiveKey: this.props.defaultActiveKey, - uncontrolledIsExpandedLocal: this.props.defaultIsExpanded, - ouiaStateId: getDefaultOUIAId(Tabs.displayName), - }; - - if (this.props.isVertical && this.props.expandable !== undefined) { - if (!this.props.toggleAriaLabel && !this.props.toggleText) { - // eslint-disable-next-line no-console - console.error( - 'Tabs:', - 'toggleAriaLabel or the toggleText prop is required to make the toggle button accessible', - ); - } - } - } - - scrollTimeout: NodeJS.Timeout = null; - - static defaultProps: PickOptional = { - activeKey: 0, - onSelect: () => undefined as any, - isFilled: false, - isSecondary: false, - isVertical: false, - isBox: false, - leftScrollAriaLabel: 'Scroll left', - rightScrollAriaLabel: 'Scroll right', - component: TabsComponent.div, - mountOnEnter: false, - unmountOnExit: false, - ouiaSafe: true, - variant: 'default', - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onToggle: (isExpanded): void => undefined, - }; - - handleTabClick( - event: React.MouseEvent, - eventKey: number | string, - tabContentRef: React.RefObject, - ) { - const { shownKeys } = this.state; - const { onSelect, defaultActiveKey } = this.props; - // if defaultActiveKey Tabs are uncontrolled, set new active key internally - if (defaultActiveKey !== undefined) { - this.setState({ - uncontrolledActiveKey: eventKey, - }); - } else { - onSelect(event, eventKey); - } - - // process any tab content sections outside of the component - if (tabContentRef) { - React.Children.toArray(this.props.children) - .map((child) => child as React.ReactElement) - .filter( - (child) => child.props && child.props.tabContentRef && child.props.tabContentRef.current, - ) - .forEach((child) => (child.props.tabContentRef.current.hidden = true)); - // most recently selected tabContent - if (tabContentRef.current) { - tabContentRef.current.hidden = false; - } - } - if (this.props.mountOnEnter) { - this.setState({ - shownKeys: shownKeys.concat(eventKey), - }); - } - } - - handleScrollButtons = () => { - // add debounce to the scroll event - clearTimeout(this.scrollTimeout); - this.scrollTimeout = setTimeout(() => { - const container = this.tabList.current; - let disableLeftScrollButton = true; - let disableRightScrollButton = true; - let showScrollButtons = false; - - if (container && !this.props.isVertical) { - // get first element and check if it is in view - const overflowOnLeft = !isElementInView( - container, - container.firstChild as HTMLElement, - false, - ); - - // get last element and check if it is in view - const overflowOnRight = !isElementInView( - container, - container.lastChild as HTMLElement, - false, - ); - - showScrollButtons = overflowOnLeft || overflowOnRight; - - disableLeftScrollButton = !overflowOnLeft; - disableRightScrollButton = !overflowOnRight; - } - this.setState({ - showScrollButtons, - disableLeftScrollButton, - disableRightScrollButton, - }); - }, 100); - }; - - scrollLeft = () => { - // find first Element that is fully in view on the left, then scroll to the element before it - if (this.tabList.current) { - const container = this.tabList.current; - const childrenArr = Array.from(container.children); - let firstElementInView: any; - let lastElementOutOfView: any; - let i; - for (i = 0; i < childrenArr.length && !firstElementInView; i++) { - if (isElementInView(container, childrenArr[i] as HTMLElement, false)) { - firstElementInView = childrenArr[i]; - lastElementOutOfView = childrenArr[i - 1]; - } - } - if (lastElementOutOfView) { - container.scrollLeft -= lastElementOutOfView.scrollWidth; - } - } - }; - - scrollRight = () => { - // find last Element that is fully in view on the right, then scroll to the element after it - if (this.tabList.current) { - const container = this.tabList.current as any; - const childrenArr = Array.from(container.children); - let lastElementInView: any; - let firstElementOutOfView: any; - for (let i = childrenArr.length - 1; i >= 0 && !lastElementInView; i--) { - if (isElementInView(container, childrenArr[i] as HTMLElement, false)) { - lastElementInView = childrenArr[i]; - firstElementOutOfView = childrenArr[i + 1]; - } - } - if (firstElementOutOfView) { - container.scrollLeft += firstElementOutOfView.scrollWidth; - } - } - }; - - componentDidMount() { - if (!this.props.isVertical) { - if (canUseDOM) { - window.addEventListener('resize', this.handleScrollButtons, false); - } - // call the handle resize function to check if scroll buttons should be shown - this.handleScrollButtons(); - } - } - - componentWillUnmount() { - if (!this.props.isVertical) { - if (canUseDOM) { - window.removeEventListener('resize', this.handleScrollButtons, false); - } - } - clearTimeout(this.scrollTimeout); - } - - componentDidUpdate(prevProps: TabsProps) { - const { activeKey, mountOnEnter } = this.props; - const { shownKeys } = this.state; - if (prevProps.activeKey !== activeKey && mountOnEnter && shownKeys.indexOf(activeKey) < 0) { - // eslint-disable-next-line - this.setState({ - shownKeys: shownKeys.concat(activeKey), - }); - } - } - - render() { - const { - className, - children, - activeKey, - defaultActiveKey, - id, - isFilled, - isSecondary, - isVertical, - isBox, - leftScrollAriaLabel, - rightScrollAriaLabel, - 'aria-label': ariaLabel, - component, - ouiaId, - ouiaSafe, - mountOnEnter, - unmountOnExit, - usePageInsets, - inset, - variant, - expandable, - isExpanded, - defaultIsExpanded, - toggleText, - toggleAriaLabel, - onToggle, - ...props - } = this.props; - const { - showScrollButtons, - disableLeftScrollButton, - disableRightScrollButton, - shownKeys, - uncontrolledActiveKey, - uncontrolledIsExpandedLocal, - } = this.state; - const filteredChildren = (React.Children.toArray(children) as React.ReactElement[]) - .filter(Boolean) - .filter((child) => !child.props.isHidden); - - const uniqueId = id || getUniqueId(); - const Component: any = component === TabsComponent.nav ? 'nav' : 'div'; - const localActiveKey = defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey; - - const isExpandedLocal = - defaultIsExpanded !== undefined ? uncontrolledIsExpandedLocal : isExpanded; - /* Uncontrolled expandable tabs */ - const toggleTabs = (newValue: boolean) => { - if (isExpanded === undefined) { - this.setState({ uncontrolledIsExpandedLocal: newValue }); - } else { - onToggle(newValue); - } - }; - - return ( - this.handleTabClick(...args), - }} - > - - {expandable && isVertical && ( - - {(randomId) => ( -
-
-
-
- )} -
- )} - -
    -
    - ReactNode -
    -
- - - - ReactNode - - } - id="string" - key="0" - /> - -`; diff --git a/frontend/packages/console-app/src/components/tabs/__tests__/Tabs.test.tsx b/frontend/packages/console-app/src/components/tabs/__tests__/Tabs.test.tsx deleted file mode 100644 index cc6e5bba4d9..00000000000 --- a/frontend/packages/console-app/src/components/tabs/__tests__/Tabs.test.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { Tab, Tabs, TabTitleIcon, TabTitleText } from '@patternfly/react-core'; -import { render } from 'enzyme'; - -test('should render simple tabs', () => { - const view = render( - - {'Tab item 1'}}> - Tab 1 section - - {'Tab item 2'}}> - Tab 2 section - - {'Tab item 3'}}> - Tab 3 section - - - - 4 - {' '} - Users{' '} - - } - > - Tab 4 section - - , - ); - expect(view).toMatchSnapshot(); -}); - -test('should render uncontrolled tabs', () => { - const view = render( - - {'Tab item 1'}}> - Tab 1 section - - {'Tab item 2'}}> - Tab 2 section - - {'Tab item 3'}}> - Tab 3 section - - - - 4 - {' '} - Users{' '} - - } - > - Tab 4 section - - , - ); - expect(view).toMatchSnapshot(); -}); - -test('should render vertical tabs', () => { - const view = render( - - {'Tab item 1'}}> - Tab 1 section - - {'Tab item 2'}}> - Tab 2 section - - {'Tab item 3'}}> - Tab 3 section - - - - 4 - {' '} - Users{' '} - - } - > - Tab 4 section - - , - ); - expect(view).toMatchSnapshot(); -}); - -test('should render expandable vertical tabs', () => { - const view = render( - - {'Tab item 1'}}> - Tab 1 section - - {'Tab item 2'}}> - Tab 2 section - - {'Tab item 3'}}> - Tab 3 section - - - - 4 - {' '} - Users{' '} - - } - > - Tab 4 section - - , - ); - expect(view).toMatchSnapshot(); -}); diff --git a/frontend/packages/console-app/src/components/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap b/frontend/packages/console-app/src/components/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap deleted file mode 100644 index 32b2c1b4180..00000000000 --- a/frontend/packages/console-app/src/components/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +++ /dev/null @@ -1,1959 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render accessible tabs 1`] = ` -Array [ - , -
- Tab 1 section -
, - , - , -] -`; - -exports[`should render box tabs 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , - , -] -`; - -exports[`should render box tabs of light variant 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , -] -`; - -exports[`should render expandable vertical tabs 1`] = ` -Array [ -
-
-
- -
-
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , - , -] -`; - -exports[`should render filled tabs 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , -] -`; - -exports[`should render secondary tabs 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
-
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
- - - -
, - , - , -] -`; - -exports[`should render simple tabs 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , - , -] -`; - -exports[`should render tabs with eventKey Strings 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, - , - , - , -] -`; - -exports[`should render tabs with separate content 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
-
- Tab 1 section -
- - -
, -] -`; - -exports[`should render uncontrolled tabs 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , - , -] -`; - -exports[`should render vertical tabs 1`] = ` -Array [ -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
, -
- Tab 1 section -
, - , - , - , -] -`; diff --git a/frontend/packages/console-app/src/components/tabs/index.ts b/frontend/packages/console-app/src/components/tabs/index.ts deleted file mode 100644 index 856dbbb347c..00000000000 --- a/frontend/packages/console-app/src/components/tabs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Tabs'; diff --git a/frontend/packages/console-shared/src/components/drawer/DraggableCoreIFrameFix.scss b/frontend/packages/console-shared/src/components/drawer/DraggableCoreIFrameFix.scss deleted file mode 100644 index 26043d63d0c..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/DraggableCoreIFrameFix.scss +++ /dev/null @@ -1,5 +0,0 @@ -.ocs-draggable-core-iframe-fix { - & iframe { - pointer-events: none !important; - } -} diff --git a/frontend/packages/console-shared/src/components/drawer/DraggableCoreIFrameFix.tsx b/frontend/packages/console-shared/src/components/drawer/DraggableCoreIFrameFix.tsx deleted file mode 100644 index e2458eef70f..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/DraggableCoreIFrameFix.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from 'react'; -import { DraggableCore, DraggableEvent, DraggableData } from 'react-draggable'; - -import './DraggableCoreIFrameFix.scss'; - -const DraggableCoreIFrameFix: React.FC> = ({ - onStart, - onStop, - ...other -}) => { - const onStartFn = - // rule is inconsistent with typescript return type - // eslint-disable-next-line consistent-return - (e: DraggableEvent, data: DraggableData): false | void => { - document.body.classList.add('ocs-draggable-core-iframe-fix'); - if (onStart) { - return onStart(e, data); - } - }; - - const onStopFn = - // rule is inconsistent with typescript return type - // eslint-disable-next-line consistent-return - (e: DraggableEvent, data: DraggableData): false | void => { - document.body.classList.remove('ocs-draggable-core-iframe-fix'); - if (onStop) { - return onStop(e, data); - } - }; - - return ; -}; - -export default DraggableCoreIFrameFix; diff --git a/frontend/packages/console-shared/src/components/drawer/Drawer.scss b/frontend/packages/console-shared/src/components/drawer/Drawer.scss deleted file mode 100644 index b3fee6d4837..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/Drawer.scss +++ /dev/null @@ -1,53 +0,0 @@ -.ocs-drawer { - position: relative; - overflow: hidden; - flex-grow: 0; - flex-shrink: 0; - transition: opacity 175ms ease-out, transform 225ms ease-out; - box-shadow: var(--pf-t--global--box-shadow--sm--top); - display: flex; - flex-direction: column; - z-index: var(--pf-t--global--z-index--sm); - - &__drag-handle { - width: 100%; - height: 6px; - cursor: ns-resize; - position: absolute; - background-color: transparent; - &:hover { - background-color: var(--pf-t--global--background--color--secondary--hover); - } - } - - &__header { - background-color: var(--pf-t--global--background--color--secondary--default); - display: flex; - align-items: center; - flex-shrink: 0; - } - - &__body { - position: relative; - flex-grow: 1; - overflow: auto; - height: 100%; - } - - &-appear { - opacity: 0; - transform: translatey(10%); - } - &-appear-active { - opacity: 1; - transform: translatey(0); - } - &-exit { - opacity: 1; - transform: translatey(0); - } - &-exit-active { - opacity: 0; - transform: translatey(10%); - } -} diff --git a/frontend/packages/console-shared/src/components/drawer/Drawer.tsx b/frontend/packages/console-shared/src/components/drawer/Drawer.tsx deleted file mode 100644 index 3a508c3a4aa..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/Drawer.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import * as React from 'react'; -import { DraggableEvent } from 'react-draggable'; -import { CSSTransition } from 'react-transition-group'; -import DraggableCoreIFrameFix from './DraggableCoreIFrameFix'; -import './Drawer.scss'; - -type DrawerProps = { - /** - * Controlled height of the drawer. - * Should be set when used as controlled component with onChange callback. - */ - height?: number; - /** - * Default Value: 300 - * Uncontrolled default height of the drawer. - */ - defaultHeight?: number; - /** - * Toggles controlled open state. - */ - open?: boolean; - /** - * Default Value: true - * Uncontrolled open state of the drawer on first render. - */ - defaultOpen?: boolean; - /** - * Maximum height drawer can be resized to. - */ - maxHeight?: number | string; - /** - * Set whether the drawer is resizable or not. - */ - resizable?: boolean; - /** - * Content for the Header of drawer - */ - header?: React.ReactNode; - /** - * This callback is invoked while resizing the drawer. - * @param open boolean: false when the drawer reached minimum height (minimized state) - * @param height number: Height of the drawer while resizing - */ - onChange?: (open: boolean, height: number) => void; -}; - -const useSize = (): [number, (element: T) => void] => { - const [height, setHeight] = React.useState(0); - - const callback = React.useCallback((element: T): void => { - if (element) { - const bb = element.getBoundingClientRect(); - setHeight(bb.height); - } - }, []); - return [height, callback]; -}; - -// get the pageX value from a mouse or touch event -const getPageY = (e: DraggableEvent): number => - (e as MouseEvent).pageY ?? (e as TouchEvent).touches?.[0]?.pageY; - -const Drawer: React.FC = ({ - children, - defaultHeight = 300, - height, - maxHeight = '100%', - open, - defaultOpen = true, - resizable = false, - header, - onChange, -}) => { - const drawerRef = React.useRef(); - const [heightState, setHeightState] = React.useState(defaultHeight); - const [openState, setOpenState] = React.useState(defaultOpen); - const lastObservedHeightRef = React.useRef(); - const startRef = React.useRef(); - const [minHeight, headerRef] = useSize(); - const minimumHeight = minHeight ?? 0; - - // merge controlled and uncontrolled states - const currentOpen = open ?? openState; - const currentHeight = height ?? heightState; - - const setHeight = (drawerHeight: number, forceOpen?: boolean) => { - const newHeight = Math.max(drawerHeight, minimumHeight); - const newOpen = forceOpen ?? newHeight > minimumHeight; - setHeightState(newHeight); - setOpenState(newOpen); - if (onChange) { - onChange(newOpen, newHeight); - } - }; - - const handleDrag = (e: DraggableEvent) => { - setHeight(startRef.current - getPageY(e)); - }; - - const handleResizeStart = (e: DraggableEvent) => { - e.preventDefault(); - lastObservedHeightRef.current = currentHeight; - // always start with actual drawer height - const drawerHeight = drawerRef.current?.offsetHeight || currentHeight; - startRef.current = drawerHeight + getPageY(e); - if (drawerHeight !== currentHeight) { - setHeight(drawerHeight); - } - }; - - const handleResizeStop = () => { - if (currentHeight <= minimumHeight) { - setHeight(lastObservedHeightRef.current, false); - } - }; - - const draggable = resizable && ( - -
- - ); - return ( - -
- {draggable} -
- {header} -
-
{children}
-
-
- ); -}; - -export default Drawer; diff --git a/frontend/packages/console-shared/src/components/drawer/__tests__/DraggableCoreIFrameFix.spec.tsx b/frontend/packages/console-shared/src/components/drawer/__tests__/DraggableCoreIFrameFix.spec.tsx deleted file mode 100644 index e892d881d36..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/__tests__/DraggableCoreIFrameFix.spec.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { shallow } from 'enzyme'; -import { DraggableCore, DraggableEvent, DraggableData } from 'react-draggable'; -import DraggableCoreIFrameFix from '../DraggableCoreIFrameFix'; - -describe('DraggableCoreIFrameFix', () => { - it('should execute handlers and apply fix class', () => { - const onStart = jest.fn(); - const onStop = jest.fn(); - const event = {} as DraggableEvent; - const data = {} as DraggableData; - const wrapper = shallow(); - - wrapper.find(DraggableCore).props().onStart(event, data); - expect(document.body.className).toBe('ocs-draggable-core-iframe-fix'); - - wrapper.find(DraggableCore).props().onStop(event, data); - expect(document.body.className).toBe(''); - - expect(onStart).toHaveBeenCalledWith(event, data); - expect(onStop).toHaveBeenCalledWith(event, data); - }); -}); diff --git a/frontend/packages/console-shared/src/components/drawer/__tests__/Drawer.spec.tsx b/frontend/packages/console-shared/src/components/drawer/__tests__/Drawer.spec.tsx deleted file mode 100644 index 43e8440accd..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/__tests__/Drawer.spec.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { shallow } from 'enzyme'; -import { DraggableData } from 'react-draggable'; -import DraggableCoreIFrameFix from '../DraggableCoreIFrameFix'; -import Drawer from '../Drawer'; - -describe('DrawerComponent', () => { - it('should exist', () => { - const wrapper = shallow(); - expect(wrapper.isEmptyRender()).toBe(false); - }); - - it('should have default values', () => { - const wrapper = shallow( - -

- , - ); - expect(wrapper.find(DraggableCoreIFrameFix).exists()).toBe(false); - const style = wrapper.find('.ocs-drawer').prop('style'); - expect(style.height).toBe(300); - expect(wrapper.find('#dummy-content')).toHaveLength(1); - }); - - it('should be resizable and height 300', () => { - const wrapper = shallow(); - expect(wrapper.find(DraggableCoreIFrameFix).exists()).toBe(true); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(300); - }); - - it('should support initially closed', () => { - let wrapper = shallow(); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(0); - wrapper = shallow(); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(0); - }); - - it('should support initial height', () => { - let wrapper = shallow(); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(100); - wrapper = shallow(); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(100); - }); - - it('should have maximumHeight', () => { - const height = `calc(100vh - 10%)`; - const wrapper = shallow(); - expect(wrapper.find('.ocs-drawer').prop('style').maxHeight).toBe(height); - const nextHeight = 950; - wrapper.setProps({ maxHeight: nextHeight }); - expect(wrapper.find('.ocs-drawer').prop('style').maxHeight).toBe(nextHeight); - }); - - it('should have header', () => { - const wrapper = shallow(); - expect(wrapper.find('.ocs-drawer__header').children()).toHaveLength(0); - wrapper.setProps({ header:

This is header

}); - expect(wrapper.find('.ocs-drawer__header').children()).toHaveLength(1); - expect(wrapper.find('.ocs-drawer__header').children().html()).toBe('

This is header

'); - }); - - it('should render children', () => { - const content = 'This is drawer content'; - const wrapper = shallow( - -

{content}

-
, - ); - expect(wrapper.find('#dummy-content').exists()).toBe(true); - }); - - it('should be set to minimum height when open is set to false and height if open is set to true', () => { - const wrapper = shallow(); - const style = wrapper.find('.ocs-drawer').prop('style'); - expect(style.height).toBe(0); - expect(style.minHeight).toBe(0); - expect(style.maxHeight).toBe('100%'); - wrapper.setProps({ open: true, defaultHeight: 500 }); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(500); - }); - - it('should handle resizing', () => { - const preventDefault = jest.fn(); - const data = {} as DraggableData; - const onChange = jest.fn(); - const wrapper = shallow(); - wrapper - .find(DraggableCoreIFrameFix) - .props() - .onStart({ pageY: 500, preventDefault } as any, data); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(100); - wrapper - .find(DraggableCoreIFrameFix) - .props() - .onDrag({ pageY: 550 } as any, data); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(50); - expect(onChange).toHaveBeenLastCalledWith(true, 50); - onChange.mockClear(); - wrapper - .find(DraggableCoreIFrameFix) - .props() - .onDrag({ pageY: 700 } as any, data); - expect(wrapper.find('.ocs-drawer').prop('style').height).toBe(0); - expect(onChange).toHaveBeenLastCalledWith(false, 0); - onChange.mockClear(); - wrapper - .find(DraggableCoreIFrameFix) - .props() - .onStop({} as any, data); - expect(onChange).toHaveBeenLastCalledWith(false, 100); - }); -}); diff --git a/frontend/packages/console-shared/src/components/drawer/index.ts b/frontend/packages/console-shared/src/components/drawer/index.ts deleted file mode 100644 index 26a0c394d92..00000000000 --- a/frontend/packages/console-shared/src/components/drawer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Drawer } from './Drawer'; diff --git a/frontend/packages/console-shared/src/components/index.ts b/frontend/packages/console-shared/src/components/index.ts index c7155f396b1..5bc2670f5b7 100644 --- a/frontend/packages/console-shared/src/components/index.ts +++ b/frontend/packages/console-shared/src/components/index.ts @@ -10,7 +10,6 @@ export * from './status'; export * from './pod'; export * from './popper'; export * from './shortcuts'; -export * from './drawer'; export * from './health-checks'; export * from './virtualized-grid'; export * from './alerts'; diff --git a/frontend/packages/dev-console/integration-tests/support/pageObjects/webterminal-po.ts b/frontend/packages/dev-console/integration-tests/support/pageObjects/webterminal-po.ts index 8641ab4cc0f..e8b4bb10708 100644 --- a/frontend/packages/dev-console/integration-tests/support/pageObjects/webterminal-po.ts +++ b/frontend/packages/dev-console/integration-tests/support/pageObjects/webterminal-po.ts @@ -1,13 +1,13 @@ export const webTerminalPO = { webTerminalIcon: '[data-tour-id="tour-cloud-shell-button"]', - addTerminalIcon: '[data-test="add-terminal-icon"]', - closeTerminalIcon: '[data-test="close-terminal-icon"]', + addTerminalIcon: '[data-test="multi-tab-terminal"] [aria-label="Add new tab"]', + closeTerminalIcon: '[aria-label="Close terminal tab"]', tabsList: '[data-test="multi-tab-terminal"] ul', openCommandLine: 'button[data-tour-id="tour-cloud-shell-button"]', terminalWindow: 'canvas.xterm-cursor-layer', terminalWindowWithEnabledMouseEvent: 'div.xterm-screen>canvas.xterm-cursor-layer', terminalOpenInNewTabBtn: "a[href='/terminal']", - terminalCloseWindowBtn: "button[data-test='close-terminal-icon']", + terminalCloseWindowBtn: "button[aria-label='Close terminal'], [aria-label='Close terminal tab']", terminalInnactivityMessageArea: 'div.co-cloudshell-exec__error-msg', createProjectMenu: { createProjectDropdownMenu: '[data-test-id="namespace-bar-dropdown"] [type="button"]', diff --git a/frontend/packages/integration-tests-cypress/views/nav.ts b/frontend/packages/integration-tests-cypress/views/nav.ts index 62b966df9b0..3ffc52f0af3 100644 --- a/frontend/packages/integration-tests-cypress/views/nav.ts +++ b/frontend/packages/integration-tests-cypress/views/nav.ts @@ -15,7 +15,7 @@ export const nav = { return; } } - cy.byLegacyTestID('perspective-switcher-toggle').scrollIntoView().contains(text); + cy.byLegacyTestID('perspective-switcher-toggle').contains(text); }); }, changePerspectiveTo: (newPerspective: string) => { diff --git a/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature b/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature index 678bc4b5251..a2c0a3eb01f 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature +++ b/frontend/packages/webterminal-plugin/integration-tests/features/web-terminal/web-terminal-adminuser.feature @@ -4,8 +4,8 @@ Feature: Web Terminal for Admin user Background: - Given user has logged in as admin user - And user is at administrator perspective + Given user has logged in + # And user is at administrator perspective # And user has created or selected namespace "aut-terminal" @@ -21,7 +21,7 @@ Feature: Web Terminal for Admin user Examples: | number_of_terminals | closed_terminal | open_terminals | | 3 | 2nd | 3 | - + @smoke @odc-6745 Scenario: Create new project with timeout and use Web Terminal: WT-02-TC02 @@ -44,4 +44,3 @@ Feature: Web Terminal for Admin user And user will see the terminal instance for namespace "openshift-terminal" And user ID obtained by API should match with user id in yaml editor for "openshift-terminal" namespace And user has closed existing terminal workspace - diff --git a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts index cd207d60ccb..7fc87856995 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts +++ b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/pages/web-terminal/initTerminal-page.ts @@ -31,7 +31,7 @@ export const initTerminalPage = { if ($body.find('[data-test="loading-box-body"]').length === 0) { cy.log('loading did not go through'); cy.wait(10000); - cy.get(webTerminalPO.terminalCloseWindowBtn).click(); + cy.get(webTerminalPO.closeTerminalIcon).click(); cy.reload(); app.waitForDocumentLoad(); perspective.switchTo(switchPerspective.Administrator); diff --git a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/web-terminal/web-terminal-adminuser.ts b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/web-terminal/web-terminal-adminuser.ts index 032d96843e9..ac5095656c0 100644 --- a/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/web-terminal/web-terminal-adminuser.ts +++ b/frontend/packages/webterminal-plugin/integration-tests/support/step-definitions/web-terminal/web-terminal-adminuser.ts @@ -1,6 +1,5 @@ import { Given, When, Then, And } from 'cypress-cucumber-preprocessor/steps'; import { guidedTour } from '@console/cypress-integration-tests/views/guided-tour'; -import { nav } from '@console/cypress-integration-tests/views/nav'; import { switchPerspective, devWorkspaceStatuses, @@ -20,10 +19,8 @@ import { operatorsPage } from '@console/dev-console/integration-tests/support/pa import { searchResource } from '@console/dev-console/integration-tests/support/pages/search-resources/search-page'; import { webTerminalPage } from '../pages/web-terminal/webTerminal-page'; -Given('user has logged in as admin user', () => { +Given('user has logged in', () => { cy.login(); - perspective.switchTo(switchPerspective.Administrator); - nav.sidenav.switcher.shouldHaveText(switchPerspective.Administrator); guidedTour.close(); }); @@ -76,7 +73,7 @@ When('user closed web terminal window', () => { Then('user is able see {int} web terminal tabs', (n: number) => { cy.get(webTerminalPO.tabsList).then(($el) => { - expect($el.prop('children').length).toEqual(n + 1); + expect($el.prop('children').length).toEqual(n); }); }); diff --git a/frontend/packages/webterminal-plugin/locales/en/webterminal-plugin.json b/frontend/packages/webterminal-plugin/locales/en/webterminal-plugin.json index 9bd9efcee9a..9abd342cfd4 100644 --- a/frontend/packages/webterminal-plugin/locales/en/webterminal-plugin.json +++ b/frontend/packages/webterminal-plugin/locales/en/webterminal-plugin.json @@ -20,9 +20,9 @@ "OpenShift command line": "OpenShift command line", "Command line terminal": "Command line terminal", "Failed to connect to your OpenShift command line terminal": "Failed to connect to your OpenShift command line terminal", - "Terminal {{number}}": "Terminal {{number}}", - "Close terminal tab": "Close terminal tab", "Add new tab": "Add new tab", + "Close terminal tab": "Close terminal tab", + "Terminal {{number}}": "Terminal {{number}}", "Project": "Project", "This Project will be used to initialize your command line terminal": "This Project will be used to initialize your command line terminal", "Initialize terminal": "Initialize terminal", diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShell.tsx b/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShell.tsx index 4703a49861e..05e5e204a59 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShell.tsx +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShell.tsx @@ -6,7 +6,6 @@ import { FLAG_DEVWORKSPACE } from '../../const'; import { toggleCloudShellExpanded } from '../../redux/actions/cloud-shell-actions'; import { isCloudShellExpanded } from '../../redux/reducers/cloud-shell-selectors'; import CloudShellDrawer from './CloudShellDrawer'; -import MultiTabTerminal from './MultiTabbedTerminal'; type StateProps = { open: boolean; @@ -18,15 +17,15 @@ type DispatchProps = { type CloudShellProps = WithFlagsProps & StateProps & DispatchProps; -const CloudShell: React.FC = ({ flags, open, onClose }) => { +const CloudShell: React.FC = ({ flags, open, onClose, children }) => { if (!flags[FLAG_DEVWORKSPACE]) { - return null; + return <>{children}; } - return open ? ( - - + return ( + + {children} - ) : null; + ); }; const stateToProps = (state: RootState): StateProps => ({ diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.scss b/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.scss index f69c854334c..bce11c808bd 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.scss +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.scss @@ -1,14 +1,30 @@ +@use '../../../../../public/style/vars'; + .co-cloud-shell-drawer { - &__heading { - padding-left: var(--pf-t--global--spacer--md); + &__header { + background-color: var(--pf-t--global--background--color--secondary--default); } &__body { - display: flex; - flex-direction: column; - overflow: hidden; - height: 100%; + // since we removed the gap and padding from the drawer header, the height + // is exactly the height of the buttons + --co-cloud-shell-header-height: #{vars.$co-button-height}; + + &[hidden] { + // fixes issues with scrolling on e2e + display: none !important; + } + + &-collapsed .pf-v6-c-drawer__panel-main { + overflow-y: hidden; + } + &.pf-v6-c-drawer__panel { + gap: 0; + } & .pf-v6-c-tabs { flex-shrink: 0; } + & .pf-v6-c-drawer__panel-main { + padding: 0; + } } } diff --git a/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.tsx b/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.tsx index 786ebdeb306..de37acc946a 100644 --- a/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.tsx +++ b/frontend/packages/webterminal-plugin/src/components/cloud-shell/CloudShellDrawer.tsx @@ -1,16 +1,30 @@ import * as React from 'react'; -import { Tooltip, Flex, FlexItem, Button } from '@patternfly/react-core'; -import { ExternalLinkAltIcon } from '@patternfly/react-icons/dist/esm/icons/external-link-alt-icon'; +import { + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelContent, + Flex, + FlexItem, + Tooltip, +} from '@patternfly/react-core'; +import { css } from '@patternfly/react-styles'; +import { c_drawer_m_inline_m_panel_bottom__splitter_Height as pfSplitterHeight } from '@patternfly/react-tokens/dist/esm/c_drawer_m_inline_m_panel_bottom__splitter_Height'; import { useTranslation } from 'react-i18next'; -import CloseButton from '@console/shared/src/components/close-button'; -import Drawer from '@console/shared/src/components/drawer/Drawer'; +import { ExternalLinkButton } from '@console/shared/src/components/links/ExternalLinkButton'; import { useTelemetry } from '@console/shared/src/hooks/useTelemetry'; -import MinimizeRestoreButton from './MinimizeRestoreButton'; +import { MinimizeRestoreButton } from '@console/webterminal-plugin/src/components/cloud-shell/MinimizeRestoreButton'; +import { MultiTabbedTerminal } from '@console/webterminal-plugin/src/components/cloud-shell/MultiTabbedTerminal'; import './CloudShellDrawer.scss'; type CloudShellDrawerProps = { + open?: boolean; onClose: () => void; + TerminalBody?: React.FC<{ onClose: () => void }>; }; const getMastheadHeight = (): number => { @@ -20,9 +34,17 @@ const getMastheadHeight = (): number => { return height; }; -const CloudShellDrawer: React.FC = ({ children, onClose }) => { +const HEADER_HEIGHT = `calc(${pfSplitterHeight.var} + var(--co-cloud-shell-header-height))`; + +const CloudShellDrawer: React.FC = ({ + open = true, + onClose, + TerminalBody = MultiTabbedTerminal, + children, +}) => { const [expanded, setExpanded] = React.useState(true); - const { t } = useTranslation(); + const [height, setHeight] = React.useState(385); + const { t } = useTranslation('webterminal-plugin'); const fireTelemetryEvent = useTelemetry(); const onMRButtonClick = (expandedState: boolean) => { @@ -31,51 +53,64 @@ const CloudShellDrawer: React.FC = ({ children, onClose } minimized: expandedState, }); }; - const handleChange = (openState: boolean) => { - setExpanded(openState); - }; - const header = ( - - - {t('webterminal-plugin~OpenShift command line terminal')} - - - -
- } + onClick={() => setActiveTabKey(terminalNumber)} + onMouseDown={(event) => { + // middle click to close + if (event.button === 1) { + event.preventDefault(); + if (typeof removeTabFunction === 'function') { + removeTabFunction(event, terminalNumber); + } + } + }} + title={t('Terminal {{number}}', { number: terminalNumber })} > - ))} - {terminalTabs.length < MAX_TERMINAL_TABS && ( - -