1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

F #5422: Improve the split pane component (#2048)

This commit is contained in:
Sergio Betanzos 2022-05-18 10:25:12 +02:00 committed by GitHub
parent a40167e9b4
commit 440ed864a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1023 additions and 477 deletions

View File

@ -92,6 +92,7 @@
"socket.io": "4.4.1",
"socket.io-client": "4.4.1",
"speakeasy": "2.0.0",
"split-grid": "1.0.11",
"sprintf-js": "1.1.2",
"style-loader": "3.2.1",
"terser-webpack-plugin": "5.1.4",
@ -11544,6 +11545,11 @@
"node": ">= 0.10.0"
}
},
"node_modules/split-grid": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/split-grid/-/split-grid-1.0.11.tgz",
"integrity": "sha512-ELtFtxc3r5we5GZfe6Fi0BFFxIi2M6BY1YEntBscKRDD3zx4JVHqx2VnTRSQu1BixCYSTH3MTjKd4esI2R7EgQ=="
},
"node_modules/sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
@ -21752,6 +21758,11 @@
"base32.js": "0.0.1"
}
},
"split-grid": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/split-grid/-/split-grid-1.0.11.tgz",
"integrity": "sha512-ELtFtxc3r5we5GZfe6Fi0BFFxIi2M6BY1YEntBscKRDD3zx4JVHqx2VnTRSQu1BixCYSTH3MTjKd4esI2R7EgQ=="
},
"sprintf-js": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",

View File

@ -126,6 +126,7 @@
"socket.io": "4.4.1",
"socket.io-client": "4.4.1",
"speakeasy": "2.0.0",
"split-grid": "1.0.11",
"sprintf-js": "1.1.2",
"style-loader": "3.2.1",
"terser-webpack-plugin": "5.1.4",

View File

@ -13,130 +13,117 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState, createRef, useRef, useEffect } from 'react'
import { useRef, useEffect, useCallback, Children, ReactElement } from 'react'
import PropTypes from 'prop-types'
import { Divider } from '@mui/material'
import makeStyles from '@mui/styles/makeStyles'
import Split, { SplitOptions } from 'split-grid'
import { styled, Divider } from '@mui/material'
const useStyles = makeStyles((theme) => ({
splitPane: {
display: 'flex',
flexDirection: 'column',
height: '100%',
},
topPane: {
flex: 1,
},
separator: {
position: 'relative',
cursor: 'row-resize',
const Gutter = styled(Divider)(({ theme, direction = 'row' }) => ({
position: 'relative',
cursor: `${direction}-resize`,
height: 8,
marginBlock: '1em',
background: `linear-gradient(
to right,
transparent,
${theme.palette.divider},
transparent
)`,
'&:after': {
content: "''",
position: 'absolute',
zIndex: 1,
left: '50%',
width: 8,
height: 8,
marginBlock: '1em',
background: `linear-gradient(
to right,
transparent,
${theme.palette.divider},
transparent
)`,
'&:after': {
content: "''",
position: 'absolute',
zIndex: 1,
left: '50%',
width: 8,
height: 8,
transform: 'rotate(45deg)',
backgroundColor: theme.palette.action.active,
},
transform: 'rotate(45deg)',
backgroundColor: theme.palette.action.active,
},
}))
const SplitPane = ({ children, containerProps }) => {
const classes = useStyles()
const [topHeight, setTopHeight] = useState(null)
/**
* @typedef SplitGridHook
* @property {function():object} getGridProps - Function to get grid props
* @property {ReactElement} GutterComponent - Gutter component
*/
const splitPaneRef = createRef()
const topRef = createRef()
const separatorYPosition = useRef(null)
/**
* Hook to create a split pane with a divider between the two panes.
*
* @param {SplitOptions} options - Options to configure the split pane
* @returns {SplitGridHook} Hook functions to element grid and gutter
*/
const useSplitGrid = (options) => {
const {
columnMinSizes,
rowMinSizes,
columnMaxSizes,
rowMaxSizes,
gridTemplateColumns,
gridTemplateRows,
} = options
const onMouseDown = (event) => {
separatorYPosition.current = event?.touches?.[0]?.clientY ?? event.clientY
}
const onMouseMove = (event) => {
if (!separatorYPosition.current) return
const clientY = event?.touches?.[0]?.clientY || event.clientY
const newTopHeight = topHeight + clientY - separatorYPosition.current
separatorYPosition.current = clientY
if (newTopHeight <= 0) {
return topHeight !== 0 && setTopHeight(0)
}
const splitPaneHeight = splitPaneRef.current?.clientHeight
if (newTopHeight >= splitPaneHeight) {
return topHeight !== splitPaneHeight && setTopHeight(splitPaneHeight)
}
setTopHeight(newTopHeight)
}
const onMouseUp = () => {
separatorYPosition.current = null
}
const split = useRef({})
useEffect(() => {
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
// on mobile device
document.addEventListener('touchmove', onMouseMove)
document.addEventListener('touchend', onMouseUp)
split.current = Split(options)
return () => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
// on mobile device
document.removeEventListener('touchmove', onMouseMove)
document.removeEventListener('touchend', onMouseUp)
}
})
return () => split.current.destroy()
}, [columnMinSizes, rowMinSizes, columnMaxSizes, rowMaxSizes])
useEffect(() => {
if (!topHeight && children[1]) {
setTopHeight(document.body.clientHeight / 2)
topRef.current.style.flex = 'none'
}
topRef.current.style.height = children[1]
? `${topHeight}px`
: `${splitPaneRef.current?.clientHeight}px`
}, [topHeight, children[1]])
return (
<div {...containerProps} className={classes.splitPane} ref={splitPaneRef}>
<div className={classes.topPane} ref={topRef}>
{children[0]}
</div>
{!!children[1] && (
<Divider
className={classes.separator}
onTouchStart={onMouseDown}
onMouseDown={onMouseDown}
/>
)}
{children[1]}
</div>
const getGridProps = useCallback(
() => ({
display: 'grid',
height: 1, // 100%
gridTemplateColumns,
gridTemplateRows,
}),
[gridTemplateColumns, gridTemplateRows]
)
const GutterComponent = useCallback(
({ direction, track }) => {
const handleDragStart = (e) => {
split.current?.handleDragStart(e, direction, track)
}
return (
<Gutter
key={`gutter-${direction}-${track}`}
className="gutter"
direction={direction}
track={track}
onMouseDown={handleDragStart}
onTouchStart={handleDragStart}
/>
)
},
[split.current?.handleDragStart]
)
return { getGridProps, GutterComponent }
}
SplitPane.propTypes = {
children: PropTypes.array.isRequired,
containerProps: PropTypes.object,
/**
* @param {SplitOptions} props - Component props
* @param {Function|ReactElement} [props.children] - Child components
* @returns {ReactElement|function(SplitGridHook):ReactElement} Split pane component
*/
const SplitGrid = ({ children, ...options }) => {
const hook = useSplitGrid(options)
if (!children) return null
if (typeof children === 'function') return children(hook)
return !(Children.count(children) === 0) ? Children.only(children) : null
}
export default SplitPane
SplitGrid.propTypes = {
children: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
options: PropTypes.object,
}
export { Gutter, useSplitGrid }
export default SplitGrid

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { ClustersTable } from 'client/components/Tables'
import ClusterTabs from 'client/components/Tabs/Cluster'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Clusters with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Clusters list and selected row(s)
*/
function Clusters() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<ClustersTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto">
{selectedRows?.length === 1 ? (
<ClusterTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<ClustersTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs cluster={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Cluster.
*
* @param {object} cluster - Cluster to display
* @returns {ReactElement} Cluster details
*/
const InfoTabs = memo(({ cluster }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${cluster.ID} | ${cluster.NAME}`}
</Typography>
<ClusterTabs id={cluster.ID} />
</Stack>
))
InfoTabs.propTypes = { cluster: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Clusters

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { DatastoresTable } from 'client/components/Tables'
import DatastoreTabs from 'client/components/Tabs/Datastore'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Datastores with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Datastores list and selected row(s)
*/
function Datastores() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<DatastoresTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto">
{selectedRows?.length === 1 ? (
<DatastoreTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<DatastoresTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs datastore={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Datastore.
*
* @param {object} datastore - Datastore to display
* @returns {ReactElement} Datastore details
*/
const InfoTabs = memo(({ datastore }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${datastore.ID} | ${datastore.NAME}`}
</Typography>
<DatastoreTabs id={datastore.ID} />
</Stack>
))
InfoTabs.propTypes = { datastore: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Datastores

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { GroupsTable } from 'client/components/Tables'
import GroupTabs from 'client/components/Tabs/Group'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Groups with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Groups list and selected row(s)
*/
function Groups() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<GroupsTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<GroupTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<GroupsTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs group={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Group.
*
* @param {object} group - Group to display
* @returns {ReactElement} Group details
*/
const InfoTabs = memo(({ group }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${group.ID} | ${group.NAME}`}
</Typography>
<GroupTabs id={group.ID} />
</Stack>
))
InfoTabs.propTypes = { group: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Groups

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { HostsTable } from 'client/components/Tables'
import HostTabs from 'client/components/Tabs/Host'
@ -23,42 +24,84 @@ import HostActions from 'client/components/Tables/Hosts/actions'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Hosts with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Hosts list and selected row(s)
*/
function Hosts() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = HostActions()
return (
<SplitPane>
<HostsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto">
{selectedRows?.length === 1 ? (
<HostTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<HostsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs host={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Host.
*
* @param {object} host - Host to display
* @returns {ReactElement} Host details
*/
const InfoTabs = memo(({ host }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${host.ID} | ${host.NAME}`}
</Typography>
<HostTabs id={host.ID} />
</Stack>
))
InfoTabs.propTypes = { host: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Hosts

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { ImagesTable } from 'client/components/Tables'
import ImageTabs from 'client/components/Tabs/Image'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Images with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Images list and selected row(s)
*/
function Images() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<ImagesTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto">
{selectedRows?.length === 1 ? (
<ImageTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<ImagesTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs image={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of an Image.
*
* @param {object} image - Image to display
* @returns {ReactElement} Image details
*/
const InfoTabs = memo(({ image }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${image.ID} | ${image.NAME}`}
</Typography>
<ImageTabs id={image.ID} />
</Stack>
))
InfoTabs.propTypes = { image: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Images

View File

@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { useState, JSXElementConstructor } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { MarketplaceAppsTable } from 'client/components/Tables'
import MarketplaceAppActions from 'client/components/Tables/MarketplaceApps/actions'
@ -23,46 +25,83 @@ import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a Marketplace Apps list.
* Displays a list of Marketplace Apps with a split pane between the list and selected row(s).
*
* @returns {JSXElementConstructor} List of Marketplace Apps
* @returns {ReactElement} Marketplace Apps list and selected row(s)
*/
function MarketplaceApps() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = MarketplaceAppActions()
return (
<SplitPane>
<MarketplaceAppsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto">
{selectedRows?.length === 1 ? (
<MarketplaceAppsTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<MarketplaceAppsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs app={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Marketplace App.
*
* @param {object} app - Marketplace App to display
* @returns {ReactElement} Marketplace App details
*/
const InfoTabs = memo(({ app }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${app.ID} | ${app.NAME}`}
</Typography>
<MarketplaceAppsTabs id={app.ID} />
</Stack>
))
InfoTabs.propTypes = { app: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default MarketplaceApps

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { MarketplacesTable } from 'client/components/Tables'
import MarketplaceTabs from 'client/components/Tabs/Marketplace'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Marketplaces with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Marketplaces list and selected row(s)
*/
function Marketplaces() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<MarketplacesTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<MarketplaceTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<MarketplacesTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs market={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Marketplace.
*
* @param {object} market - Marketplace to display
* @returns {ReactElement} Marketplace details
*/
const InfoTabs = memo(({ market }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${market.ID} | ${market.NAME}`}
</Typography>
<MarketplaceTabs id={market.ID} />
</Stack>
))
InfoTabs.propTypes = { market: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Marketplaces

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { UsersTable } from 'client/components/Tables'
import UserTabs from 'client/components/Tabs/User'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Users with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Users list and selected row(s)
*/
function Users() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<UsersTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<UserTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<UsersTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs user={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of an User.
*
* @param {object} user - User to display
* @returns {ReactElement} User details
*/
const InfoTabs = memo(({ user }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${user.ID} | ${user.NAME}`}
</Typography>
<UserTabs id={user.ID} />
</Stack>
))
InfoTabs.propTypes = { user: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Users

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { VNetworkTemplatesTable } from 'client/components/Tables'
import VNetworkTemplateTabs from 'client/components/Tabs/VNetworkTemplate'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of VNet Templates with a split pane between the list and selected row(s).
*
* @returns {ReactElement} VNet Templates list and selected row(s)
*/
function VNetworkTemplates() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<VNetworkTemplatesTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<VNetworkTemplateTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<VNetworkTemplatesTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs vnTemplate={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a VNet Template.
*
* @param {object} vnTemplate - VNet Template to display
* @returns {ReactElement} VNet Template details
*/
const InfoTabs = memo(({ vnTemplate }) => (
<Stack overflow="auto" data-cy="detail">
<Typography color="text.primary" noWrap mb={1}>
{`#${vnTemplate.ID} | ${vnTemplate.NAME}`}
</Typography>
<VNetworkTemplateTabs id={vnTemplate.ID} />
</Stack>
))
InfoTabs.propTypes = { vnTemplate: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default VNetworkTemplates

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Typography, Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { VmsTable } from 'client/components/Tables'
import VmActions from 'client/components/Tables/Vms/actions'
@ -23,46 +24,84 @@ import VmTabs from 'client/components/Tabs/Vm'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of VMs with a split pane between the list and selected row(s).
*
* @returns {ReactElement} VMs list and selected row(s)
*/
function VirtualMachines() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = VmActions()
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
return (
<SplitPane>
<VmsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<VmsTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{hasSelectedRows && (
<>
<Typography color="text.primary" noWrap mb={1}>
{`#${selectedRows[0]?.original.ID} | ${selectedRows[0]?.original.NAME}`}
</Typography>
<VmTabs id={selectedRows[0]?.original.ID} />
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs vm={selectedRows[0]?.original} />
)}
</>
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="outlined"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a VM.
*
* @param {object} vm - VM to display
* @returns {ReactElement} VM details
*/
const InfoTabs = memo(({ vm }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${vm.ID} | ${vm.NAME}`}
</Typography>
<VmTabs id={vm.ID} />
</Stack>
))
InfoTabs.propTypes = { vm: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default VirtualMachines

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { VNetworksTable } from 'client/components/Tables'
import VNetworkTabs from 'client/components/Tabs/VNetwork'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Virtual Networks with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Virtual Networks list and selected row(s)
*/
function VirtualNetworks() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<VNetworksTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<VNetworkTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<VNetworksTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs vnet={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Virtual Network.
*
* @param {object} vnet - Virtual Network to display
* @returns {ReactElement} Virtual Network details
*/
const InfoTabs = memo(({ vnet }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${vnet.ID} | ${vnet.NAME}`}
</Typography>
<VNetworkTabs id={vnet.ID} />
</Stack>
))
InfoTabs.propTypes = { vnet: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default VirtualNetworks

View File

@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Typography, Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { VmTemplatesTable } from 'client/components/Tables'
import VmTemplateActions from 'client/components/Tables/VmTemplates/actions'
@ -23,46 +24,84 @@ import VmTemplateTabs from 'client/components/Tabs/VmTemplate'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of VM Templates with a split pane between the list and selected row(s).
*
* @returns {ReactElement} VM Templates list and selected row(s)
*/
function VmTemplates() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
const actions = VmTemplateActions()
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
return (
<SplitPane>
<VmTemplatesTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{selectedRows?.length > 0 && (
<Stack overflow="auto">
{selectedRows?.length === 1 ? (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<VmTemplatesTable
onSelectedRowsChange={onSelectedRowsChange}
globalActions={actions}
/>
{hasSelectedRows && (
<>
<Typography color="text.primary" noWrap mb={1}>
{`#${selectedRows[0]?.original.ID} | ${selectedRows[0]?.original.NAME}`}
</Typography>
<VmTemplateTabs id={selectedRows[0]?.original.ID} />
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs vmTemplate={selectedRows[0]?.original} />
)}
</>
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a VM Template.
*
* @param {object} vmTemplate - VM Template to display
* @returns {ReactElement} VM Template details
*/
const InfoTabs = memo(({ vmTemplate }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${vmTemplate.ID} | ${vmTemplate.NAME}`}
</Typography>
<VmTemplateTabs id={vmTemplate.ID} />
</Stack>
))
InfoTabs.propTypes = { vmTemplate: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default VmTemplates

View File

@ -13,47 +13,90 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
/* eslint-disable jsdoc/require-jsdoc */
import { useState } from 'react'
import { Stack, Chip } from '@mui/material'
import { ReactElement, useState, memo } from 'react'
import PropTypes from 'prop-types'
import { Typography, Box, Stack, Chip } from '@mui/material'
import { Row } from 'react-table'
import { ZonesTable } from 'client/components/Tables'
import ZoneTabs from 'client/components/Tabs/Zone'
import SplitPane from 'client/components/SplitPane'
import MultipleTags from 'client/components/MultipleTags'
/**
* Displays a list of Zones with a split pane between the list and selected row(s).
*
* @returns {ReactElement} Zones list and selected row(s)
*/
function Zones() {
const [selectedRows, onSelectedRowsChange] = useState(() => [])
return (
<SplitPane>
<ZonesTable onSelectedRowsChange={onSelectedRowsChange} />
const hasSelectedRows = selectedRows?.length > 0
const moreThanOneSelected = selectedRows?.length > 1
const gridTemplateRows = hasSelectedRows ? '1fr auto 1fr' : '1fr'
{selectedRows?.length > 0 && (
<Stack overflow="auto" data-cy={'detail'}>
{selectedRows?.length === 1 ? (
<ZoneTabs id={selectedRows[0]?.original.ID} />
) : (
<Stack direction="row" flexWrap="wrap" gap={1} alignItems="center">
<MultipleTags
limitTags={10}
tags={selectedRows?.map(
({ original, id, toggleRowSelected }) => (
<Chip
key={id}
variant="text"
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
)
)}
/>
</Stack>
return (
<SplitPane gridTemplateRows={gridTemplateRows}>
{({ getGridProps, GutterComponent }) => (
<Box {...getGridProps()}>
<ZonesTable onSelectedRowsChange={onSelectedRowsChange} />
{hasSelectedRows && (
<>
<GutterComponent direction="row" track={1} />
{moreThanOneSelected ? (
<GroupedTags tags={selectedRows} />
) : (
<InfoTabs zone={selectedRows[0]?.original} />
)}
</>
)}
</Stack>
</Box>
)}
</SplitPane>
)
}
/**
* Displays details of a Zone.
*
* @param {object} zone - Zone to display
* @returns {ReactElement} Zone details
*/
const InfoTabs = memo(({ zone }) => (
<Stack overflow="auto">
<Typography color="text.primary" noWrap mb={1}>
{`#${zone.ID} | ${zone.NAME}`}
</Typography>
<ZoneTabs id={zone.ID} />
</Stack>
))
InfoTabs.propTypes = { zone: PropTypes.object.isRequired }
InfoTabs.displayName = 'InfoTabs'
/**
* Displays a list of tags that represent the selected rows.
*
* @param {Row[]} tags - Row(s) to display as tags
* @returns {ReactElement} List of tags
*/
const GroupedTags = memo(({ tags = [] }) => (
<Stack direction="row" flexWrap="wrap" gap={1}>
<MultipleTags
limitTags={10}
tags={tags?.map(({ original, id, toggleRowSelected }) => (
<Chip
key={id}
label={original?.NAME ?? id}
onDelete={() => toggleRowSelected(false)}
/>
))}
/>
</Stack>
))
GroupedTags.propTypes = { tags: PropTypes.array }
GroupedTags.displayName = 'GroupedTags'
export default Zones