Table: New cell hover behavior and major refactoring of table cells & style build (#27669)

* Table: Image cell and new hover behavior

* ImageCell: progress

* Table: refactoring cell style generation, tricky stuff

* About to do something

* Getting close

* Need another big change

* Almost everything working

* Filter actions working

* Updated

* Updated

* removed unused prop from interface

* Fixed unit test

* remove unused type
This commit is contained in:
Torkel Ödegaard 2020-09-24 20:09:01 +02:00 committed by GitHub
parent 8018059fc4
commit f06dcfc9ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 285 additions and 361 deletions

View File

@ -18,11 +18,7 @@ const defaultScale: ThresholdsConfig = {
}; };
export const BarGaugeCell: FC<TableCellProps> = props => { export const BarGaugeCell: FC<TableCellProps> = props => {
const { field, column, tableStyles, cell } = props; const { field, column, tableStyles, cell, cellProps } = props;
if (!field.display) {
return null;
}
let { config } = field; let { config } = field;
if (!config.thresholds) { if (!config.thresholds) {
@ -32,7 +28,7 @@ export const BarGaugeCell: FC<TableCellProps> = props => {
}; };
} }
const displayValue = field.display(cell.value); const displayValue = field.display!(cell.value);
let barGaugeMode = BarGaugeDisplayMode.Gradient; let barGaugeMode = BarGaugeDisplayMode.Gradient;
if (field.config.custom && field.config.custom.displayMode === TableCellDisplayMode.LcdGauge) { if (field.config.custom && field.config.custom.displayMode === TableCellDisplayMode.LcdGauge) {
@ -49,7 +45,7 @@ export const BarGaugeCell: FC<TableCellProps> = props => {
} }
return ( return (
<div className={tableStyles.tableCell}> <div {...cellProps} className={tableStyles.cellContainer}>
<BarGauge <BarGauge
width={width} width={width}
height={tableStyles.cellHeightInner} height={tableStyles.cellHeightInner}

View File

@ -1,46 +1,65 @@
import React, { FC } from 'react'; import React, { FC, MouseEventHandler } from 'react';
import { formattedValueToString, LinkModel } from '@grafana/data'; import { DisplayValue, Field, formattedValueToString, LinkModel } from '@grafana/data';
import { TableCellProps } from './types'; import { TableCellDisplayMode, TableCellProps } from './types';
import tinycolor from 'tinycolor2';
import { TableStyles } from './styles';
import { FilterActions } from './FilterActions';
export const DefaultCell: FC<TableCellProps> = props => { export const DefaultCell: FC<TableCellProps> = props => {
const { field, cell, tableStyles, row } = props; const { field, cell, tableStyles, row, cellProps } = props;
let link: LinkModel<any> | undefined;
const displayValue = field.display ? field.display(cell.value) : cell.value; const displayValue = field.display!(cell.value);
const value = formattedValueToString(displayValue);
const cellStyle = getCellStyle(tableStyles, field, displayValue);
const showFilters = field.config.filterable;
let link: LinkModel<any> | undefined;
let onClick: MouseEventHandler<HTMLAnchorElement> | undefined;
if (field.getLinks) { if (field.getLinks) {
link = field.getLinks({ link = field.getLinks({
valueRowIndex: row.index, valueRowIndex: row.index,
})[0]; })[0];
} }
const value = field.display ? formattedValueToString(displayValue) : `${displayValue}`;
if (!link) { if (link && link.onClick) {
return <div className={tableStyles.tableCell}>{value}</div>; onClick = event => {
}
return (
<div className={tableStyles.tableCell}>
<a
href={link.href}
onClick={
link.onClick
? event => {
// Allow opening in new tab // Allow opening in new tab
if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link!.onClick) { if (!(event.ctrlKey || event.metaKey || event.shiftKey) && link!.onClick) {
event.preventDefault(); event.preventDefault();
link!.onClick(event); link!.onClick(event);
} }
};
} }
: undefined
} return (
target={link.target} <div {...cellProps} className={cellStyle}>
title={link.title} {!link && <div className={tableStyles.cellText}>{value}</div>}
className={tableStyles.tableCellLink} {link && (
> <a href={link.href} onClick={onClick} target={link.target} title={link.title} className={tableStyles.cellLink}>
{value} {value}
</a> </a>
)}
{showFilters && cell.value && <FilterActions {...props} />}
</div> </div>
); );
}; };
function getCellStyle(tableStyles: TableStyles, field: Field, displayValue: DisplayValue) {
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorText) {
return tableStyles.buildCellContainerStyle(displayValue.color);
}
if (field.config.custom?.displayMode === TableCellDisplayMode.ColorBackground) {
const themeFactor = tableStyles.theme.isDark ? 1 : -0.7;
const bgColor2 = tinycolor(displayValue.color)
.darken(10 * themeFactor)
.spin(5)
.toRgbString();
return tableStyles.buildCellContainerStyle('white', `linear-gradient(120deg, ${bgColor2}, ${displayValue.color})`);
}
return tableStyles.cellContainer;
}

View File

@ -0,0 +1,31 @@
import React, { FC, useCallback } from 'react';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps } from './types';
import { Icon, Tooltip } from '..';
export const FilterActions: FC<TableCellProps> = ({ cell, field, tableStyles, onCellFilterAdded }) => {
const onFilterFor = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
const onFilterOut = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
return (
<div className={tableStyles.filterWrapper}>
<div className={tableStyles.filterItem}>
<Tooltip content="Filter for value" placement="top">
<Icon name={'search-plus'} onClick={onFilterFor} />
</Tooltip>
</div>
<div className={tableStyles.filterItem}>
<Tooltip content="Filter out value" placement="top">
<Icon name={'search-minus'} onClick={onFilterOut} />
</Tooltip>
</div>
</div>
);
};

View File

@ -1,72 +0,0 @@
import React, { FC, useCallback, useState } from 'react';
import { TableCellProps } from 'react-table';
import { GrafanaTheme } from '@grafana/data';
import { css } from 'emotion';
import { stylesFactory, useTheme } from '../../themes';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableFilterActionCallback } from './types';
import { Icon, Tooltip } from '..';
import { Props, renderCell } from './TableCell';
interface FilterableTableCellProps extends Pick<Props, 'cell' | 'field' | 'tableStyles'> {
onCellFilterAdded: TableFilterActionCallback;
cellProps: TableCellProps;
}
export const FilterableTableCell: FC<FilterableTableCellProps> = ({
cell,
field,
tableStyles,
onCellFilterAdded,
cellProps,
}) => {
const [showFilters, setShowFilter] = useState(false);
const onMouseOver = useCallback((event: React.MouseEvent<HTMLDivElement>) => setShowFilter(true), [setShowFilter]);
const onMouseLeave = useCallback((event: React.MouseEvent<HTMLDivElement>) => setShowFilter(false), [setShowFilter]);
const onFilterFor = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_FOR_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
const onFilterOut = useCallback(
(event: React.MouseEvent<HTMLDivElement>) =>
onCellFilterAdded({ key: field.name, operator: FILTER_OUT_OPERATOR, value: cell.value }),
[cell, field, onCellFilterAdded]
);
const theme = useTheme();
const styles = getFilterableTableCellStyles(theme);
return (
<div {...cellProps} className={tableStyles.tableCellWrapper} onMouseOver={onMouseOver} onMouseLeave={onMouseLeave}>
{renderCell(cell, field, tableStyles)}
{showFilters && cell.value && (
<div className={styles.filterWrapper}>
<div className={styles.filterItem}>
<Tooltip content="Filter for value" placement="top">
<Icon name={'search-plus'} onClick={onFilterFor} />
</Tooltip>
</div>
<div className={styles.filterItem}>
<Tooltip content="Filter out value" placement="top">
<Icon name={'search-minus'} onClick={onFilterOut} />
</Tooltip>
</div>
</div>
)}
</div>
);
};
const getFilterableTableCellStyles = stylesFactory((theme: GrafanaTheme) => ({
filterWrapper: css`
label: filterWrapper;
display: inline-flex;
justify-content: space-around;
cursor: pointer;
`,
filterItem: css`
label: filterItem;
color: ${theme.colors.textSemiWeak};
padding: 0 ${theme.spacing.xxs};
`,
}));

View File

@ -0,0 +1,12 @@
import React, { FC } from 'react';
import { TableCellProps } from './types';
export const ImageCell: FC<TableCellProps> = props => {
const { cell, tableStyles, cellProps } = props;
return (
<div {...cellProps} className={tableStyles.cellContainer}>
<img src={cell.value} className={tableStyles.imageCell} />
</div>
);
};

View File

@ -8,11 +8,7 @@ import { TableCellProps } from './types';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
export const JSONViewCell: FC<TableCellProps> = props => { export const JSONViewCell: FC<TableCellProps> = props => {
const { field, cell, tableStyles } = props; const { cell, tableStyles, cellProps } = props;
if (!field.display) {
return null;
}
const txt = css` const txt = css`
cursor: pointer; cursor: pointer;
@ -21,6 +17,7 @@ export const JSONViewCell: FC<TableCellProps> = props => {
let value = cell.value; let value = cell.value;
let displayValue = value; let displayValue = value;
if (isString(value)) { if (isString(value)) {
try { try {
value = JSON.parse(value); value = JSON.parse(value);
@ -28,11 +25,13 @@ export const JSONViewCell: FC<TableCellProps> = props => {
} else { } else {
displayValue = JSON.stringify(value); displayValue = JSON.stringify(value);
} }
const content = <JSONTooltip value={value} />; const content = <JSONTooltip value={value} />;
return ( return (
<div className={cx(txt, tableStyles.tableCell)}> <div {...cellProps} className={tableStyles.cellContainer}>
<Tooltip placement="auto" content={content} theme="info-alt"> <Tooltip placement="auto" content={content} theme="info-alt">
<div className={tableStyles.overflow}>{displayValue}</div> <div className={cx(tableStyles.cellText, txt)}>{displayValue}</div>
</Tooltip> </Tooltip>
</div> </div>
); );

View File

@ -14,7 +14,7 @@ import {
useTable, useTable,
} from 'react-table'; } from 'react-table';
import { FixedSizeList } from 'react-window'; import { FixedSizeList } from 'react-window';
import { getColumns, getHeaderAlign } from './utils'; import { getColumns } from './utils';
import { useTheme } from '../../themes'; import { useTheme } from '../../themes';
import { import {
TableColumnResizeActionCallback, TableColumnResizeActionCallback,
@ -23,10 +23,10 @@ import {
TableSortByFieldState, TableSortByFieldState,
} from './types'; } from './types';
import { getTableStyles, TableStyles } from './styles'; import { getTableStyles, TableStyles } from './styles';
import { TableCell } from './TableCell';
import { Icon } from '../Icon/Icon'; import { Icon } from '../Icon/Icon';
import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar'; import { CustomScrollbar } from '../CustomScrollbar/CustomScrollbar';
import { Filter } from './Filter'; import { Filter } from './Filter';
import { TableCell } from './TableCell';
const COLUMN_MIN_WIDTH = 150; const COLUMN_MIN_WIDTH = 150;
@ -229,7 +229,7 @@ function renderHeaderCell(column: any, tableStyles: TableStyles, field?: Field)
} }
headerProps.style.position = 'absolute'; headerProps.style.position = 'absolute';
headerProps.style.justifyContent = getHeaderAlign(field); headerProps.style.justifyContent = (column as any).justifyContent;
return ( return (
<div className={tableStyles.headerCell} {...headerProps}> <div className={tableStyles.headerCell} {...headerProps}>

View File

@ -1,11 +1,8 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { Cell } from 'react-table'; import { Cell } from 'react-table';
import { Field } from '@grafana/data'; import { Field } from '@grafana/data';
import { getTextAlign } from './utils';
import { TableFilterActionCallback } from './types'; import { TableFilterActionCallback } from './types';
import { TableStyles } from './styles'; import { TableStyles } from './styles';
import { FilterableTableCell } from './FilterableTableCell';
export interface Props { export interface Props {
cell: Cell; cell: Cell;
@ -15,31 +12,25 @@ export interface Props {
} }
export const TableCell: FC<Props> = ({ cell, field, tableStyles, onCellFilterAdded }) => { export const TableCell: FC<Props> = ({ cell, field, tableStyles, onCellFilterAdded }) => {
const filterable = field.config.filterable;
const cellProps = cell.getCellProps(); const cellProps = cell.getCellProps();
if (!field.display) {
return null;
}
if (cellProps.style) { if (cellProps.style) {
cellProps.style.textAlign = getTextAlign(field); cellProps.style.minWidth = cellProps.style.width;
} cellProps.style.justifyContent = (cell.column as any).justifyContent;
if (filterable && onCellFilterAdded) {
return (
<FilterableTableCell
cell={cell}
field={field}
tableStyles={tableStyles}
onCellFilterAdded={onCellFilterAdded}
cellProps={cellProps}
/>
);
} }
return ( return (
<div {...cellProps} className={tableStyles.tableCellWrapper}> <>
{renderCell(cell, field, tableStyles)} {cell.render('Cell', {
</div> field,
tableStyles,
onCellFilterAdded,
cellProps,
})}
</>
); );
}; };
export const renderCell = (cell: Cell, field: Field, tableStyles: TableStyles) =>
cell.render('Cell', { field, tableStyles });

View File

@ -1,44 +1,59 @@
import { css } from 'emotion'; import { css, cx } from 'emotion';
import { GrafanaTheme } from '@grafana/data'; import { GrafanaTheme } from '@grafana/data';
import { styleMixins, stylesFactory } from '../../themes'; import { styleMixins, stylesFactory } from '../../themes';
import { getScrollbarWidth } from '../../utils'; import { getScrollbarWidth } from '../../utils';
export interface TableStyles { export const getTableStyles = stylesFactory((theme: GrafanaTheme) => {
cellHeight: number;
cellHeightInner: number;
cellPadding: number;
rowHeight: number;
table: string;
thead: string;
headerCell: string;
headerCellLabel: string;
headerFilter: string;
tableCell: string;
tableCellWrapper: string;
tableCellLink: string;
row: string;
theme: GrafanaTheme;
resizeHandle: string;
overflow: string;
}
export const getTableStyles = stylesFactory(
(theme: GrafanaTheme): TableStyles => {
const { palette, colors } = theme; const { palette, colors } = theme;
const headerBg = theme.colors.bg2; const headerBg = theme.colors.bg2;
const borderColor = theme.colors.border1; const borderColor = theme.colors.border1;
const resizerColor = theme.isLight ? palette.blue95 : palette.blue77; const resizerColor = theme.isLight ? palette.blue95 : palette.blue77;
const padding = 6; const cellPadding = 6;
const lineHeight = theme.typography.lineHeight.md; const lineHeight = theme.typography.lineHeight.md;
const bodyFontSize = 14; const bodyFontSize = 14;
const cellHeight = padding * 2 + bodyFontSize * lineHeight; const cellHeight = cellPadding * 2 + bodyFontSize * lineHeight;
const rowHoverBg = styleMixins.hoverColor(theme.colors.bg1, theme); const rowHoverBg = styleMixins.hoverColor(theme.colors.bg1, theme);
const scollbarWidth = getScrollbarWidth(); const scollbarWidth = getScrollbarWidth();
const buildCellContainerStyle = (color?: string, background?: string) => {
return css`
padding: ${cellPadding}px;
width: 100%;
height: 100%;
display: flex;
align-items: center;
border-right: 1px solid ${borderColor};
${color ? `color: ${color};` : ''};
${background ? `background: ${background};` : ''};
&:last-child {
border-right: none;
> div {
padding-right: ${scollbarWidth + cellPadding}px;
}
}
&:hover {
overflow: visible;
width: auto !important;
box-shadow: 0 0 2px ${theme.colors.formFocusOutline};
background: ${background ?? rowHoverBg};
z-index: 1;
.cell-filter-actions  {
display: inline-flex;
}
}
`;
};
return { return {
theme, theme,
cellHeight, cellHeight,
cellPadding: padding, buildCellContainerStyle,
cellPadding,
cellHeightInner: bodyFontSize * lineHeight, cellHeightInner: bodyFontSize * lineHeight,
rowHeight: cellHeight + 2, rowHeight: cellHeight + 2,
table: css` table: css`
@ -56,7 +71,7 @@ export const getTableStyles = stylesFactory(
position: relative; position: relative;
`, `,
headerCell: css` headerCell: css`
padding: ${padding}px; padding: ${cellPadding}px;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
color: ${colors.textBlue}; color: ${colors.textBlue};
@ -75,6 +90,22 @@ export const getTableStyles = stylesFactory(
display: flex; display: flex;
margin-right: ${theme.spacing.xs}; margin-right: ${theme.spacing.xs};
`, `,
cellContainer: buildCellContainerStyle(),
cellText: css`
cursor: text;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
`,
cellLink: css`
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
user-select: text;
white-space: nowrap;
text-decoration: underline;
`,
headerFilter: css` headerFilter: css`
label: headerFilter; label: headerFilter;
cursor: pointer; cursor: pointer;
@ -87,33 +118,8 @@ export const getTableStyles = stylesFactory(
background-color: ${rowHoverBg}; background-color: ${rowHoverBg};
} }
`, `,
tableCellWrapper: css` imageCell: css`
border-right: 1px solid ${borderColor};
display: inline-flex;
align-items: center;
height: 100%; height: 100%;
&:last-child {
border-right: none;
> div {
padding-right: ${scollbarWidth + padding}px;
}
}
`,
tableCellLink: css`
text-decoration: underline;
`,
tableCell: css`
padding: ${padding}px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
flex: 1;
`,
overflow: css`
overflow: hidden;
text-overflow: ellipsis;
`, `,
resizeHandle: css` resizeHandle: css`
label: resizeHandle; label: resizeHandle;
@ -135,6 +141,23 @@ export const getTableStyles = stylesFactory(
opacity: 1; opacity: 1;
} }
`, `,
filterWrapper: cx(
css`
label: filterWrapper;
display: none;
justify-content: flex-end;
flex-grow: 1;
opacity: 0.6;
padding-left: ${theme.spacing.xxs};
`,
'cell-filter-actions'
),
filterItem: css`
label: filterItem;
cursor: pointer;
padding: 0 ${theme.spacing.xxs};
`,
}; };
} });
);
export type TableStyles = ReturnType<typeof getTableStyles>;

View File

@ -1,7 +1,7 @@
import { CellProps } from 'react-table'; import { CellProps } from 'react-table';
import { Field } from '@grafana/data'; import { Field } from '@grafana/data';
import { TableStyles } from './styles'; import { TableStyles } from './styles';
import { FC } from 'react'; import { CSSProperties, FC } from 'react';
export interface TableFieldOptions { export interface TableFieldOptions {
width: number; width: number;
@ -18,6 +18,7 @@ export enum TableCellDisplayMode {
LcdGauge = 'lcd-gauge', LcdGauge = 'lcd-gauge',
JSONView = 'json-view', JSONView = 'json-view',
BasicGauge = 'basic', BasicGauge = 'basic',
Image = 'image',
} }
export type FieldTextAlignment = 'auto' | 'left' | 'right' | 'center'; export type FieldTextAlignment = 'auto' | 'left' | 'right' | 'center';
@ -41,7 +42,9 @@ export interface TableSortByFieldState {
export interface TableCellProps extends CellProps<any> { export interface TableCellProps extends CellProps<any> {
tableStyles: TableStyles; tableStyles: TableStyles;
cellProps: CSSProperties;
field: Field; field: Field;
onCellFilterAdded: TableFilterActionCallback;
} }
export type CellComponent = FC<TableCellProps>; export type CellComponent = FC<TableCellProps>;

View File

@ -66,7 +66,7 @@ describe('Table utils', () => {
it('Should set textAlign to right for number values', () => { it('Should set textAlign to right for number values', () => {
const data = getData(); const data = getData();
const textAlign = getTextAlign(data.fields[1]); const textAlign = getTextAlign(data.fields[1]);
expect(textAlign).toBe('right'); expect(textAlign).toBe('flex-end');
}); });
}); });

View File

@ -1,8 +1,6 @@
import { Column, Row } from 'react-table'; import { Column, Row } from 'react-table';
import memoizeOne from 'memoize-one'; import memoizeOne from 'memoize-one';
import { css, cx } from 'emotion'; import { ContentPosition } from 'csstype';
import tinycolor from 'tinycolor2';
import { ContentPosition, TextAlignProperty } from 'csstype';
import { import {
DataFrame, DataFrame,
Field, Field,
@ -14,13 +12,13 @@ import {
import { DefaultCell } from './DefaultCell'; import { DefaultCell } from './DefaultCell';
import { BarGaugeCell } from './BarGaugeCell'; import { BarGaugeCell } from './BarGaugeCell';
import { TableCellDisplayMode, TableCellProps, TableFieldOptions } from './types'; import { TableCellDisplayMode, TableFieldOptions } from './types';
import { withTableStyles } from './withTableStyles';
import { JSONViewCell } from './JSONViewCell'; import { JSONViewCell } from './JSONViewCell';
import { ImageCell } from './ImageCell';
export function getTextAlign(field?: Field): TextAlignProperty { export function getTextAlign(field?: Field): ContentPosition {
if (!field) { if (!field) {
return 'left'; return 'flex-start';
} }
if (field.config.custom) { if (field.config.custom) {
@ -28,19 +26,19 @@ export function getTextAlign(field?: Field): TextAlignProperty {
switch (custom.align) { switch (custom.align) {
case 'right': case 'right':
return 'right'; return 'flex-end';
case 'left': case 'left':
return 'left'; return 'flex-start';
case 'center': case 'center':
return 'center'; return 'center';
} }
} }
if (field.type === FieldType.number) { if (field.type === FieldType.number) {
return 'right'; return 'flex-end';
} }
return 'left'; return 'flex-start';
} }
export function getColumns(data: DataFrame, availableWidth: number, columnMinWidth: number): Column[] { export function getColumns(data: DataFrame, availableWidth: number, columnMinWidth: number): Column[] {
@ -68,6 +66,7 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
return 'alphanumeric'; return 'alphanumeric';
} }
}; };
const Cell = getCellComponent(fieldTableOptions.displayMode, field); const Cell = getCellComponent(fieldTableOptions.displayMode, field);
columns.push({ columns.push({
Cell, Cell,
@ -80,6 +79,7 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
width: fieldTableOptions.width, width: fieldTableOptions.width,
minWidth: 50, minWidth: 50,
filter: memoizeOne(filterByValue), filter: memoizeOne(filterByValue),
justifyContent: getTextAlign(field),
}); });
} }
@ -97,9 +97,10 @@ export function getColumns(data: DataFrame, availableWidth: number, columnMinWid
function getCellComponent(displayMode: TableCellDisplayMode, field: Field) { function getCellComponent(displayMode: TableCellDisplayMode, field: Field) {
switch (displayMode) { switch (displayMode) {
case TableCellDisplayMode.ColorText: case TableCellDisplayMode.ColorText:
return withTableStyles(DefaultCell, getTextColorStyle);
case TableCellDisplayMode.ColorBackground: case TableCellDisplayMode.ColorBackground:
return withTableStyles(DefaultCell, getBackgroundColorStyle); return DefaultCell;
case TableCellDisplayMode.Image:
return ImageCell;
case TableCellDisplayMode.LcdGauge: case TableCellDisplayMode.LcdGauge:
case TableCellDisplayMode.BasicGauge: case TableCellDisplayMode.BasicGauge:
case TableCellDisplayMode.GradientGauge: case TableCellDisplayMode.GradientGauge:
@ -115,58 +116,6 @@ function getCellComponent(displayMode: TableCellDisplayMode, field: Field) {
return DefaultCell; return DefaultCell;
} }
function getTextColorStyle(props: TableCellProps) {
const { field, cell, tableStyles } = props;
if (!field.display) {
return tableStyles;
}
const displayValue = field.display(cell.value);
if (!displayValue.color) {
return tableStyles;
}
const extendedStyle = css`
color: ${displayValue.color};
`;
return {
...tableStyles,
tableCell: cx(tableStyles.tableCell, extendedStyle),
};
}
function getBackgroundColorStyle(props: TableCellProps) {
const { field, cell, tableStyles } = props;
if (!field.display) {
return tableStyles;
}
const displayValue = field.display(cell.value);
if (!displayValue.color) {
return tableStyles;
}
const themeFactor = tableStyles.theme.isDark ? 1 : -0.7;
const bgColor2 = tinycolor(displayValue.color)
.darken(10 * themeFactor)
.spin(5)
.toRgbString();
const extendedStyle = css`
background: linear-gradient(120deg, ${bgColor2}, ${displayValue.color});
color: white;
height: ${tableStyles.cellHeight}px;
padding: ${tableStyles.cellPadding}px;
`;
return {
...tableStyles,
tableCell: cx(tableStyles.tableCell, extendedStyle),
};
}
export function filterByValue(rows: Row[], id: string, filterValues?: SelectableValue[]) { export function filterByValue(rows: Row[], id: string, filterValues?: SelectableValue[]) {
if (rows.length === 0) { if (rows.length === 0) {
return rows; return rows;
@ -186,20 +135,6 @@ export function filterByValue(rows: Row[], id: string, filterValues?: Selectable
}); });
} }
export function getHeaderAlign(field?: Field): ContentPosition {
const align = getTextAlign(field);
if (align === 'right') {
return 'flex-end';
}
if (align === 'center') {
return align;
}
return 'flex-start';
}
export function calculateUniqueFieldValues(rows: any[], field?: Field) { export function calculateUniqueFieldValues(rows: any[], field?: Field) {
if (!field || rows.length === 0) { if (!field || rows.length === 0) {
return {}; return {};

View File

@ -1,14 +0,0 @@
import { CellComponent, TableCellProps } from './types';
import { TableStyles } from './styles';
export const withTableStyles = (
CellComponent: CellComponent,
getExtendedStyles: (props: TableCellProps) => TableStyles
): CellComponent => {
function WithTableStyles(props: TableCellProps) {
return CellComponent({ ...props, tableStyles: getExtendedStyles(props) });
}
WithTableStyles.displayName = CellComponent.displayName || CellComponent.name;
return WithTableStyles;
};

View File

@ -47,6 +47,7 @@ export const plugin = new PanelPlugin<Options, CustomFieldConfig>(TablePanel)
{ value: TableCellDisplayMode.LcdGauge, label: 'LCD gauge' }, { value: TableCellDisplayMode.LcdGauge, label: 'LCD gauge' },
{ value: TableCellDisplayMode.BasicGauge, label: 'Basic gauge' }, { value: TableCellDisplayMode.BasicGauge, label: 'Basic gauge' },
{ value: TableCellDisplayMode.JSONView, label: 'JSON View' }, { value: TableCellDisplayMode.JSONView, label: 'JSON View' },
{ value: TableCellDisplayMode.Image, label: 'Image' },
], ],
}, },
}) })