// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 import React, { MutableRefObject, ReactElement, ReactNode, useEffect, useRef, useState } from 'react'; import { Button, Grid, Paper, StyleRulesCallback, Tab, Tabs, Tooltip, withStyles, WithStyles } from "@material-ui/core"; import { GridProps } from '@material-ui/core/Grid'; import { isArray } from 'lodash'; import { DefaultView } from 'components/default-view/default-view'; import { InfoIcon } from 'components/icon/icon'; import { ReactNodeArray } from 'prop-types'; import classNames from 'classnames'; type CssRules = | 'gridContainerRoot' | 'exclusiveGridContainerRoot' | 'gridItemRoot' | 'paperRoot' | 'button' | 'buttonIcon' | 'content' | 'exclusiveContentPaper' | 'tabs'; const styles: StyleRulesCallback = theme => ({ gridContainerRoot: { marginTop: '10px', }, exclusiveGridContainerRoot: { marginTop: 0, }, gridItemRoot: { paddingTop: '0 !important', }, paperRoot: { height: '100%', display: 'flex', flexDirection: 'column', }, button: { padding: '2px 5px', marginRight: '5px', }, buttonIcon: { boxShadow: 'none', padding: '2px 0px 2px 5px', fontSize: '1rem' }, content: { overflow: 'auto', maxWidth: 'initial', }, exclusiveContentPaper: { boxShadow: 'none', }, tabs: { flexGrow: 1, flexShrink: 1, maxWidth: 'initial', }, }); interface MPVHideablePanelDataProps { name: string; visible: boolean; maximized: boolean; illuminated: boolean; children: ReactNode; panelRef?: MutableRefObject; paperClassName?: string; } interface MPVHideablePanelActionProps { doHidePanel: () => void; doMaximizePanel: () => void; doUnMaximizePanel: () => void; } type MPVHideablePanelProps = MPVHideablePanelDataProps & MPVHideablePanelActionProps; const MPVHideablePanel = ({ doHidePanel, doMaximizePanel, doUnMaximizePanel, name, visible, maximized, illuminated, paperClassName, ...props }: MPVHideablePanelProps) => visible ? <> {React.cloneElement((props.children as ReactElement), { doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName: name, panelMaximized: maximized, panelIlluminated: illuminated, panelRef: props.panelRef, paperClassName, })} : null; interface MPVPanelDataProps { panelName?: string; panelMaximized?: boolean; panelIlluminated?: boolean; panelRef?: MutableRefObject; forwardProps?: boolean; maxHeight?: string; minHeight?: string; paperClassName?: string; } interface MPVPanelActionProps { doHidePanel?: () => void; doMaximizePanel?: () => void; doUnMaximizePanel?: () => void; } // Props received by panel implementors export type MPVPanelProps = MPVPanelDataProps & MPVPanelActionProps; type MPVPanelContentProps = { children: ReactElement } & MPVPanelProps & GridProps; // Grid item compatible component for layout and MPV props passing export const MPVPanelContent = ({ doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, panelIlluminated, panelRef, forwardProps, maxHeight, minHeight, paperClassName, ...props }: MPVPanelContentProps) => { useEffect(() => { if (panelRef && panelRef.current) { panelRef.current.scrollIntoView({ alignToTop: true }); } }, [panelRef]); const maxH = panelMaximized ? '100%' : maxHeight; return {/* Element to scroll to when the panel is selected */} {forwardProps ? React.cloneElement(props.children, { doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, paperClassName }) : React.cloneElement(props.children, { paperClassName })} ; } export interface MPVPanelState { name: string; visible?: boolean; } interface MPVContainerDataProps { panelStates?: MPVPanelState[]; mutuallyExclusive?: boolean; } type MPVContainerProps = MPVContainerDataProps & GridProps; // Grid container compatible component that also handles panel toggling. const MPVContainerComponent = ({ children, panelStates, classes, ...props }: MPVContainerProps & WithStyles) => { if (children === undefined || children === null || Object.keys(children).length === 0) { children = []; } else if (!isArray(children)) { children = [children]; } const initialVisibility = (children as ReactNodeArray).map((_, idx) => !panelStates || // if panelStates wasn't passed, default to all visible panels (panelStates[idx] && (panelStates[idx].visible || panelStates[idx].visible === undefined))); const [panelVisibility, setPanelVisibility] = useState(initialVisibility); const [previousPanelVisibility, setPreviousPanelVisibility] = useState(initialVisibility); const [highlightedPanel, setHighlightedPanel] = useState(-1); const currentSelectedPanel = panelVisibility.findIndex(Boolean); const [selectedPanel, setSelectedPanel] = useState(-1); const panelRef = useRef(null); let panels: JSX.Element[] = []; let buttons: JSX.Element[] = []; let tabs: JSX.Element[] = []; let buttonBar: JSX.Element = <>; if (isArray(children)) { const showFn = (idx: number) => () => { setPreviousPanelVisibility(initialVisibility); if (props.mutuallyExclusive) { // Hide all other panels setPanelVisibility([ ...(new Array(idx).fill(false)), true, ...(new Array(panelVisibility.length-(idx+1)).fill(false)), ]); } else { setPanelVisibility([ ...panelVisibility.slice(0, idx), true, ...panelVisibility.slice(idx + 1) ]); } setSelectedPanel(idx); }; const hideFn = (idx: number) => () => { setPreviousPanelVisibility(initialVisibility); setPanelVisibility([ ...panelVisibility.slice(0, idx), false, ...panelVisibility.slice(idx+1) ]) }; const maximizeFn = (idx: number) => () => { setPreviousPanelVisibility(panelVisibility); // Maximize X == hide all but X setPanelVisibility([ ...panelVisibility.slice(0, idx).map(() => false), true, ...panelVisibility.slice(idx+1).map(() => false), ]); }; const unMaximizeFn = (idx: number) => () => { setPanelVisibility(previousPanelVisibility); setSelectedPanel(idx); } for (let idx = 0; idx < children.length; idx++) { const panelName = panelStates === undefined ? `Panel ${idx + 1}` : (panelStates[idx] && panelStates[idx].name) || `Panel ${idx + 1}`; const btnVariant = panelVisibility[idx] ? "contained" : "outlined"; const btnTooltip = panelVisibility[idx] ? `` : `Open ${panelName} panel`; const panelIsMaximized = panelVisibility[idx] && panelVisibility.filter(e => e).length === 1; buttons = [ ...buttons, ]; tabs = [ ...tabs, <>{panelName} ]; const aPanel = null}> {children[idx]} ; panels = [...panels, aPanel]; }; buttonBar = props.mutuallyExclusive ? showFn(val)()} data-cy={"mpv-tabs"}> {tabs.map((tgl, idx) => )} : {buttons.map((tgl, idx) => {tgl})} ; }; const content = setSelectedPanel(-1)}> {panelVisibility.includes(true) ? panels : } ; if (props.mutuallyExclusive) { return {buttonBar} {content} ; } else { return {buttonBar} {content} ; } }; export const MPVContainer = withStyles(styles)(MPVContainerComponent);