diff --git a/src/fireedge/src/client/components/List/ListVirtualized.js b/src/fireedge/src/client/components/List/ListVirtualized.js index 51f4b0a9d9..d294cb660c 100644 --- a/src/fireedge/src/client/components/List/ListVirtualized.js +++ b/src/fireedge/src/client/components/List/ListVirtualized.js @@ -2,65 +2,79 @@ import * as React from 'react' import PropTypes from 'prop-types' import { useVirtual } from 'react-virtual' -import { Box } from '@material-ui/core' +import { debounce, Box, LinearProgress } from '@material-ui/core' -const ListVirtualized = ({ list = [] }) => { - const parentRef = React.useRef() +import { useNearScreen } from 'client/hooks' - const rowVirtualizer = useVirtual({ - size: list.length, - parentRef, - overscan: 20, - estimateSize: React.useCallback(() => 35, []), - keyExtractor: index => list[index]?.ID +const ListVirtualized = ({ + canFetchMore, + containerProps, + data, + isLoading, + fetchMore, + children +}) => { + // OBSERVER + const loaderRef = React.useRef() + const { isNearScreen } = useNearScreen({ + distance: '100px', + externalRef: isLoading ? null : loaderRef, + once: false }) + // VIRTUALIZER + const parentRef = React.useRef() + const rowVirtualizer = useVirtual({ + size: data.length, + parentRef, + overscan: 20, + estimateSize: React.useCallback(() => 40, []), + keyExtractor: index => data[index]?.id + }) + + const debounceHandleNextPage = React.useCallback(debounce(fetchMore, 200), []) + + React.useEffect(() => { + if (isNearScreen && !canFetchMore) debounceHandleNextPage() + }, [isNearScreen, canFetchMore, debounceHandleNextPage]) + return ( - -
+ -
- {rowVirtualizer.virtualItems.map(virtualRow => { - console.log(virtualRow) - return ( -
- Row {virtualRow.index} -
- ) - })} -
-
+ {children(rowVirtualizer.virtualItems)} +
+ + {!canFetchMore && ( + + )} ) } ListVirtualized.propTypes = { - list: PropTypes.arrayOf(PropTypes.any) + canFetchMore: PropTypes.bool, + containerProps: PropTypes.object, + data: PropTypes.arrayOf(PropTypes.any), + isLoading: PropTypes.bool, + fetchMore: PropTypes.func, + children: PropTypes.func } ListVirtualized.defaultProps = { - list: [] + canFetchMore: false, + containerProps: undefined, + data: [], + isLoading: false, + fetchMore: () => undefined, + children: () => undefined } export default ListVirtualized diff --git a/src/fireedge/src/client/components/Table/Filters/GlobalFilter.js b/src/fireedge/src/client/components/Table/Filters/GlobalFilter.js index 6031d0ccb6..d14bdcb2a4 100644 --- a/src/fireedge/src/client/components/Table/Filters/GlobalFilter.js +++ b/src/fireedge/src/client/components/Table/Filters/GlobalFilter.js @@ -8,20 +8,19 @@ const useStyles = makeStyles(theme => ({ search: { position: 'relative', borderRadius: theme.shape.borderRadius, - backgroundColor: fade(theme.palette.common.white, 0.15), + backgroundColor: fade(theme.palette.primary.dark, 0.15), '&:hover': { - backgroundColor: fade(theme.palette.common.white, 0.25) + backgroundColor: fade(theme.palette.primary.dark, 0.25) }, - marginRight: theme.spacing(2), - marginLeft: 0, + margin: theme.spacing(1, 0), width: '100%', [theme.breakpoints.up('sm')]: { - marginLeft: theme.spacing(3), + marginLeft: theme.spacing(1), width: 'auto' } }, searchIcon: { - width: theme.spacing(7), + padding: theme.spacing(0, 2), height: '100%', position: 'absolute', pointerEvents: 'none', @@ -33,12 +32,10 @@ const useStyles = makeStyles(theme => ({ color: 'inherit' }, inputInput: { - padding: theme.spacing(1, 1, 1, 7), - transition: theme.transitions.create('width'), - width: '100%', - [theme.breakpoints.up('md')]: { - width: 200 - } + padding: theme.spacing(1, 1, 1, 0), + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)}px)`, + width: '100%' } })) @@ -59,6 +56,22 @@ const GlobalFilter = props => { // This may not be a problem for server side pagination when // only the current page is downloaded. + /* + + + + + */ + return (
diff --git a/src/fireedge/src/client/components/Tables/VmTable/columns.js b/src/fireedge/src/client/components/Tables/VmTable/columns.js index 1bb2c03f2a..f1c0dcc8a1 100644 --- a/src/fireedge/src/client/components/Tables/VmTable/columns.js +++ b/src/fireedge/src/client/components/Tables/VmTable/columns.js @@ -33,7 +33,6 @@ export default [ }, { Header: 'Name', accessor: 'NAME' }, - { Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` }, { Header: 'State', id: 'STATE', @@ -47,6 +46,7 @@ export default [ filter: (rows, id, filterValue) => rows.filter(row => row.values[id]?.name === filterValue) }, + { Header: 'Owner/Group', accessor: row => `${row.UNAME}/${row.GNAME}` }, { Header: 'Ips', accessor: row => VirtualMachineModel.getIps(row), diff --git a/src/fireedge/src/client/components/Tables/VmTable/header.js b/src/fireedge/src/client/components/Tables/VmTable/header.js new file mode 100644 index 0000000000..a730de7a1a --- /dev/null +++ b/src/fireedge/src/client/components/Tables/VmTable/header.js @@ -0,0 +1,57 @@ +import * as React from 'react' +import PropTypes from 'prop-types' + +import { makeStyles, Box } from '@material-ui/core' + +const useStyles = makeStyles(theme => ({ + root: { + textTransform: 'uppercase', + fontSize: '0.9em', + fontWeight: 700, + lineHeight: '1rem', + letterSpacing: '0.05em', + + overflowWrap: 'break-word', + textAlign: 'start', + padding: '1em', + + color: '#4A5568', + backgroundColor: '#e6e8f7', + borderBlock: '0.5px solid #EDF2F7' + } +})) + +const Header = ({ useTableProps }) => { + const classes = useStyles() + + /** @type {import('react-table').UseTableInstanceProps} */ + const { headerGroups } = useTableProps + + const renderHeaderColumn = React.useCallback(column => ( + + {column.render('Header')} + + ), []) + + const renderHeaderGroup = React.useCallback(headerGroup => ( + + {headerGroup.headers.map(renderHeaderColumn)} + + ), []) + + return ( + + {headerGroups.map(renderHeaderGroup)} + + ) +} + +Header.propTypes = { + useTableProps: PropTypes.object +} + +Header.defaultProps = { + useTableProps: {} +} + +export default Header diff --git a/src/fireedge/src/client/components/Tables/VmTable/index.js b/src/fireedge/src/client/components/Tables/VmTable/index.js index dcf1216cc6..7543ae894f 100644 --- a/src/fireedge/src/client/components/Tables/VmTable/index.js +++ b/src/fireedge/src/client/components/Tables/VmTable/index.js @@ -2,155 +2,79 @@ /* eslint-disable react/jsx-key */ import * as React from 'react' -import { Paper, debounce, LinearProgress, CircularProgress } from '@material-ui/core' -import { useVirtual } from 'react-virtual' +import { Box, CircularProgress } from '@material-ui/core' import { useTable, useGlobalFilter, - useSortBy, useRowSelect, - useFilters, - usePagination, useFlexLayout } from 'react-table' -import { useNearScreen } from 'client/hooks' -import { EnhancedTable, DefaultFilter } from 'client/components/Table' - +import { ListVirtualized } from 'client/components/List' +import Toolbar from 'client/components/Tables/VmTable/toolbar' +import Header from 'client/components/Tables/VmTable/header' +import Row from 'client/components/Tables/VmTable/row' import Columns from 'client/components/Tables/VmTable/columns' -import { console } from 'window-or-global' -const VmTable = ({ data, isLoading, finish, getNextData }) => { - const parentRef = React.useRef() - - // <----------- USE TABLE -----------> +const VmTable = ({ data, isLoading, canFetchMore, fetchMore }) => { const columns = React.useMemo(() => Columns, []) const defaultColumn = React.useMemo(() => ({ - Filter: DefaultFilter + // Filter: DefaultFilter, + Cell: React.memo(({ value }) => value ?? '--') }), []) - const { - getTableProps, - getTableBodyProps, - headerGroups, - rows, - totalColumnsWidth, - prepareRow - } = useTable( - { - columns, - data, - defaultColumn - }, + const useTableProps = useTable( + { columns, data, defaultColumn }, useRowSelect, - useFlexLayout + useFlexLayout, + useGlobalFilter ) - // <----------- FINISH USE TABLE -----------> - // <----------- VIRTUALIZER -----------> - const rowVirtualizer = useVirtual({ - size: rows.length, - parentRef, - overscan: 10, - estimateSize: React.useCallback(() => 50, []), - keyExtractor: index => rows[index]?.id - }) - // <----------- FINISH VIRTUALIZER -----------> - - // <----------- OBSERVER -----------> - const loaderRef = React.useRef() - const { isNearScreen } = useNearScreen({ - distance: '100px', - externalRef: isLoading ? null : loaderRef, - once: false - }) - - const debounceHandleNextPage = React.useCallback(debounce(getNextData, 200), []) - - React.useEffect(() => { - if (isNearScreen && !finish) debounceHandleNextPage() - }, [isNearScreen, finish, debounceHandleNextPage]) - // <----------- FINISH OBSERVER -----------> - - const RenderRow = React.useCallback(({ row, virtualRow }) => ( -
- {row.cells.map(cell => ( -
- {cell.render('Cell')} -
- ))} -
- ), [prepareRow, rows]) + const { getTableProps, getTableBodyProps, rows } = useTableProps return ( - -
+ +
+ + -
- {headerGroups.map(headerGroup => ( -
- {headerGroup.headers.map(column => ( -
- {column.render('Header')} -
- ))} -
- ))} -
+ {virtualItems => virtualItems?.map(virtualRow => ( + + )) + } +
-
-
- {rowVirtualizer.virtualItems?.map(virtualRow => { - const row = rows[virtualRow.index] - prepareRow(row) - - return - })} -
- - {!finish && ( - - )} -
- -

- Total loaded: {rows.length} - {isLoading && } -

+
+ Total loaded: {useTableProps.rows.length} + {isLoading && }
- - + ) } diff --git a/src/fireedge/src/client/components/Tables/VmTable/row.js b/src/fireedge/src/client/components/Tables/VmTable/row.js new file mode 100644 index 0000000000..614663cd04 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/VmTable/row.js @@ -0,0 +1,72 @@ +import * as React from 'react' +import PropTypes from 'prop-types' + +import { makeStyles, Box } from '@material-ui/core' +import clsx from 'clsx' + +const useStyles = makeStyles(theme => ({ + root: { + // <-- it's needed to virtualize --> + position: 'absolute', + top: 0, + left: 0, + width: '100%', + + fontSize: '1em', + fontWeight: theme.typography.fontWeightMedium, + lineHeight: '1rem', + + overflowWrap: 'break-word', + textAlign: 'start', + padding: '1em', + alignItems: 'center', + + boxShadow: '0 0 0 0.5px #e6e8f7' + }, + virtual: ({ size, start }) => ({ + height: size, + transform: `translateY(${start}px)` + }) +})) + +const Row = ({ virtualRow, useTableProps }) => { + /** @type {import('react-virtual').VirtualItem} */ + const { index, measureRef, size, start } = virtualRow + + const classes = useStyles({ size, start }) + + /** @type {import('react-table').UseTableInstanceProps} */ + const { rows, prepareRow } = useTableProps + + /** @type {import('react-table').UseTableRowProps} */ + const row = rows[index] + + prepareRow(row) + + const renderCell = React.useCallback(cell => ( + + {cell.render('Cell')} + + ), []) + + return ( + + {row?.cells?.map(renderCell)} + + ) +} + +Row.propTypes = { + virtualRow: PropTypes.object, + useTableProps: PropTypes.object +} + +Row.defaultProps = { + virtualRow: {}, + useTableProps: {} +} + +export default Row diff --git a/src/fireedge/src/client/components/Tables/VmTable/toolbar.js b/src/fireedge/src/client/components/Tables/VmTable/toolbar.js new file mode 100644 index 0000000000..5ee666afb9 --- /dev/null +++ b/src/fireedge/src/client/components/Tables/VmTable/toolbar.js @@ -0,0 +1,77 @@ +import * as React from 'react' +import PropTypes from 'prop-types' + +import { makeStyles, Toolbar as MToolbar, Button } from '@material-ui/core' +import { Filter as FilterIcon } from 'iconoir-react' + +import GlobalFilter from 'client/components/Table/Filters/GlobalFilter' + +const useToolbarStyles = makeStyles(theme => ({ + root: { + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(1) + }, + filterWrapper: { + display: 'flex', + alignItems: 'center', + gap: '1em' + }, + filterButton: { + ...theme.typography.body1, + fontWeight: theme.typography.fontWeightBold, + textTransform: 'none' + }, + filters: { + ...theme.typography.body1, + color: theme.palette.grey[700] + } +})) + +const Toolbar = ({ useTableProps }) => { + const classes = useToolbarStyles() + + /** @type {import('react-table').UseGlobalFiltersInstanceProps} */ + const { preGlobalFilteredRows, setGlobalFilter, state } = useTableProps + + // const { selectedRowIds, globalFilter } = state + // const numSelected = Object.keys(selectedRowIds).length + + return ( + +
+ + + No filters selected + +
+ + {/* numSelected > 0 && ( + + {numSelected} selected + + ) */} + + {/* */} +
+ ) +} + +Toolbar.propTypes = { + useTableProps: PropTypes.object +} + +Toolbar.defaultProps = { + useTableProps: {} +} + +export default Toolbar diff --git a/src/fireedge/src/client/containers/VirtualMachines/index.js b/src/fireedge/src/client/containers/VirtualMachines/index.js index 17974bd4f3..96ce976640 100644 --- a/src/fireedge/src/client/containers/VirtualMachines/index.js +++ b/src/fireedge/src/client/containers/VirtualMachines/index.js @@ -1,14 +1,12 @@ import React, { useEffect, useState } from 'react' -import { Container } from '@material-ui/core' - import { useAuth } from 'client/features/Auth' import { useVm, useVmApi } from 'client/features/One' import { useFetch } from 'client/hooks' import { VmTable } from 'client/components/Tables' -const INITIAL_ELEMENT = -1 +const INITIAL_ELEMENT = 0 const NUMBER_OF_INTERVAL = -100 function VirtualMachines () { @@ -22,9 +20,7 @@ function VirtualMachines () { useEffect(() => { fetchRequest({ start, end }) }, [filterPool]) - const handleGetMoreData = () => { - console.log('FETCH MORE') - + const fetchMore = () => { setPage(prevState => { const newStart = prevState.start + NUMBER_OF_INTERVAL const newEnd = prevState.end - NUMBER_OF_INTERVAL @@ -36,17 +32,14 @@ function VirtualMachines () { } const finish = data?.length < NUMBER_OF_INTERVAL - // console.log({ start, end, loading, finish, vms }) return ( - - - + ) }