UNPKG

@kinvolk/headlamp-plugin

Version:

The needed infrastructure for building Headlamp plugins.

301 lines (300 loc) 13.4 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; /* * Copyright 2025 The Kubernetes Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; import { useTheme } from '@mui/material/styles'; import MuiTable from '@mui/material/Table'; import TableHead from '@mui/material/TableHead'; import { alpha, styled } from '@mui/system'; import { MRT_BottomToolbar, MRT_TableBodyCell, MRT_TableHeadCell, MRT_TopToolbar, useMaterialReactTable, useMRT_Rows, } from 'material-react-table'; import { MRT_Localization_DE } from 'material-react-table/locales/de'; import { MRT_Localization_EN } from 'material-react-table/locales/en'; import { MRT_Localization_ES } from 'material-react-table/locales/es'; import { MRT_Localization_FR } from 'material-react-table/locales/fr'; import { MRT_Localization_IT } from 'material-react-table/locales/it'; import { MRT_Localization_JA } from 'material-react-table/locales/ja'; import { MRT_Localization_KO } from 'material-react-table/locales/ko'; import { MRT_Localization_PT } from 'material-react-table/locales/pt'; import { MRT_Localization_ZH_HANS } from 'material-react-table/locales/zh-Hans'; import { MRT_Localization_ZH_HANT } from 'material-react-table/locales/zh-Hant'; import { memo, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { getTablesRowsPerPage } from '../../../helpers/tablesRowsPerPage'; import { useURLState } from '../../../lib/util'; import { useSettings } from '../../App/Settings/hook'; import Empty from '../EmptyContent'; import Loader from '../Loader'; // Use a zero-indexed "useURLState" hook, so pages are shown in the URL as 1-indexed // but internally are 0-indexed. function usePageURLState(key, prefix, initialPage) { const [page, setPage] = useURLState(key, { defaultValue: initialPage + 1, prefix }); const [zeroIndexPage, setZeroIndexPage] = useState(page - 1); useEffect(() => { setZeroIndexPage((zeroIndexPage) => { if (page - 1 !== zeroIndexPage) { return page - 1; } return zeroIndexPage; }); }, [page]); useEffect(() => { setPage(zeroIndexPage + 1); }, [zeroIndexPage]); return [zeroIndexPage, setZeroIndexPage]; } const tableLocalizationMap = { de: MRT_Localization_DE, en: MRT_Localization_EN, es: MRT_Localization_ES, fr: MRT_Localization_FR, it: MRT_Localization_IT, ja: MRT_Localization_JA, pt: MRT_Localization_PT, ko: MRT_Localization_KO, zh: MRT_Localization_ZH_HANS, 'zh-TW': MRT_Localization_ZH_HANT, }; const StyledHeadRow = styled('tr')(({ theme }) => ({ display: 'contents', background: theme.palette.background.muted, })); const StyledRow = styled('tr')(({ theme }) => ({ display: 'contents', '&[data-selected=true]': { background: alpha(theme.palette.primary.main, 0.2), }, })); const StyledBody = styled('tbody')({ display: 'contents' }); /** * Table component based on the Material React Table * * @see https://www.material-react-table.com/docs */ export default function Table({ emptyMessage, reflectInURL, initialPage = 1, rowsPerPage, filterFunction, errorMessage, loading, ...tableProps }) { const shouldReflectInURL = reflectInURL !== undefined && reflectInURL !== false; const prefix = reflectInURL === true ? '' : reflectInURL || ''; const [page, setPage] = usePageURLState(shouldReflectInURL ? 'p' : '', prefix, initialPage); const storeRowsPerPageOptions = useSettings('tableRowsPerPageOptions'); const rowsPerPageOptions = rowsPerPage || storeRowsPerPageOptions; const defaultRowsPerPage = useMemo(() => getTablesRowsPerPage(rowsPerPageOptions[0]), []); const [pageSize, setPageSize] = useURLState(shouldReflectInURL ? 'perPage' : '', { defaultValue: defaultRowsPerPage, prefix, }); const { t, i18n } = useTranslation(); const theme = useTheme(); // Provide defaults for the columns const tableColumns = useMemo(() => tableProps.columns.map((column, i) => ({ ...column, id: column.id ?? String(i), header: column.header || '', })), [tableProps.columns]); const tableData = useMemo(() => { if (!filterFunction) return tableProps.data ?? []; return (tableProps.data ?? []).filter(it => filterFunction(it)); }, [tableProps.data, filterFunction]); const paginationSelectProps = import.meta.env.UNDER_TEST ? { inputProps: { SelectDisplayProps: { 'aria-controls': 'test-id', }, }, } : undefined; const columnOrder = useMemo(() => { const ids = tableProps.columns.map((it, i) => it.id ?? String(i)); if (tableProps.enableRowActions) { ids.push('mrt-row-actions'); } if (tableProps.enableRowSelection) { ids.unshift('mrt-row-select'); } return ids; }, [tableProps.columns, tableProps.enableRowActions, tableProps.enableRowSelection]); const table = useMaterialReactTable({ ...tableProps, columns: tableColumns ?? [], data: tableData, enablePagination: tableData.length > rowsPerPageOptions[0], enableDensityToggle: tableProps.enableDensityToggle ?? false, enableFullScreenToggle: tableProps.enableFullScreenToggle ?? false, enableColumnActions: false, localization: tableLocalizationMap[i18n.language], autoResetAll: false, onPaginationChange: (updater) => { if (!tableProps.data?.length) return; const pagination = updater({ pageIndex: Number(page) - 1, pageSize: Number(pageSize) }); setPage(pagination.pageIndex + 1); setPageSize(pagination.pageSize); }, renderToolbarInternalActions: props => { const isSomeRowsSelected = tableProps.enableRowSelection && props.table.getSelectedRowModel().rows.length !== 0; if (isSomeRowsSelected) { const renderRowSelectionToolbar = tableProps.renderRowSelectionToolbar; if (renderRowSelectionToolbar !== undefined) { return renderRowSelectionToolbar(props); } } return null; }, initialState: { density: 'compact', ...(tableProps.initialState ?? {}), }, state: { ...(tableProps.state ?? {}), columnOrder: columnOrder, pagination: { pageIndex: page - 1, pageSize: pageSize, }, }, positionActionsColumn: 'last', layoutMode: 'grid', // Need to provide our own empty message // because default one breaks with our custom layout renderEmptyRowsFallback: () => (_jsx(Box, { height: 60, children: _jsx(Box, { position: "absolute", left: 0, right: 0, textAlign: "center", children: _jsx(Empty, { children: t('No results found') }) }) })), muiSearchTextFieldProps: { id: 'table-search-field', }, muiPaginationProps: { rowsPerPageOptions: rowsPerPageOptions, showFirstButton: false, showLastButton: false, SelectProps: paginationSelectProps, }, muiTableBodyCellProps: { sx: { // By default in compact mode text doesn't wrap // so we need to override that whiteSpace: 'normal', width: 'unset', minWidth: 'unset', }, }, muiTopToolbarProps: { sx: { height: '3.5rem', backgroundColor: undefined, }, }, muiBottomToolbarProps: { sx: { backgroundColor: undefined, boxShadow: undefined, }, }, muiTableHeadCellProps: { sx: { width: 'unset', minWidth: 'unset', '.MuiTableSortLabel-icon': { margin: 0, width: '14px', height: '14px', marginTop: '-2px', }, ',MuiTableSortLabel-root': { width: 'auto', }, }, }, muiSelectCheckboxProps: { size: 'small', sx: { padding: 0 }, }, muiSelectAllCheckboxProps: { size: 'small', sx: { padding: 0 }, }, }); const gridTemplateColumns = useMemo(() => { let preGridTemplateColumns = tableProps.columns .filter((it, i) => { const id = it.id ?? String(i); const isHidden = table.getState().columnVisibility?.[id] === false || tableProps.state?.columnVisibility?.[id] === false; return !isHidden; }) .map(it => { if (typeof it.gridTemplate === 'number') { return `${it.gridTemplate}fr`; } return it.gridTemplate ?? '1fr'; }) .join(' '); if (tableProps.enableRowActions) { preGridTemplateColumns = `${preGridTemplateColumns} 0.05fr`; } if (tableProps.enableRowSelection) { preGridTemplateColumns = `44px ${preGridTemplateColumns}`; } return preGridTemplateColumns; }, [ tableProps.columns, table.getState()?.columnVisibility, tableProps.state?.columnVisibility, tableProps.enableRowActions, tableProps.enableRowSelection, ]); const rows = useMRT_Rows(table); if (!!errorMessage) { return _jsx(Empty, { color: "error", children: errorMessage }); } if (loading) { return _jsx(Loader, { title: t('Loading table data') }); } if (!tableProps.data?.length && !loading) { return (_jsx(Paper, { variant: "outlined", children: _jsx(Empty, { children: emptyMessage || t('No data to be shown.') }) })); } const headerGroups = table.getHeaderGroups(); return (_jsxs(_Fragment, { children: [_jsx(MRT_TopToolbar, { table: table }), _jsxs(MuiTable, { sx: { display: 'grid', border: '1px solid', borderColor: theme.palette.tables.head.borderColor, borderRadius: 1, borderBottom: 'none', overflowX: 'auto', width: '100%', gridTemplateColumns, }, children: [_jsx(TableHead, { sx: { display: 'contents' }, children: _jsx(StyledHeadRow, { children: headerGroups[0].headers.map(header => (_jsx(MemoHeadCell, { header: header, table: table, isFiltered: header.column.getIsFiltered(), sorting: header.column.getIsSorted(), showColumnFilters: table.getState().showColumnFilters, selected: table.getSelectedRowModel().flatRows.length }, header.id))) }) }), _jsx(StyledBody, { children: rows.map(row => (_jsx(Row, { cells: row.getVisibleCells(), table: table, isSelected: row.getIsSelected() }, row.id))) })] }), _jsx(MRT_BottomToolbar, { table: table })] })); } const MemoHeadCell = memo(({ header, table, }) => { return (_jsx(MRT_TableHeadCell, { header: header, staticColumnIndex: -1, table: table, sx: theme => ({ borderColor: theme.palette.divider }) }, header.id)); }, (a, b) => a.header.column.id === b.header.column.id && a.sorting === b.sorting && a.isFiltered === b.isFiltered && a.showColumnFilters === b.showColumnFilters && (a.header.column.id === 'mrt-row-select' ? a.selected === b.selected : true)); const Row = memo(({ cells, table, isSelected, }) => (_jsx(StyledRow, { "data-selected": isSelected, children: cells.map(cell => (_jsx(MemoCell, { cell: cell, table: table, isRowSelected: cell.row.getIsSelected(), canSelect: cell.row.getCanSelect() }, cell.id))) }))); const MemoCell = memo(({ cell, table, }) => { const column = cell.column.columnDef; return (_jsx(MRT_TableBodyCell, { staticRowIndex: -1, cell: cell, table: table, rowRef: { current: null }, sx: theme => ({ whiteSpace: 'normal', width: 'unset', minWidth: 'unset', wordBreak: column.gridTemplate === 'min-content' ? 'normal' : 'break-word', borderColor: theme.palette.divider, ...column.muiTableBodyCellProps?.sx, }) })); }, (a, b) => a.cell.getValue() === b.cell.getValue() && (a.cell.column.id === 'mrt-row-select' && b.cell.column.id === 'mrt-row-select' ? a.canSelect === b.canSelect && a.isRowSelected === b.isRowSelected : true));