import React, { PureComponent, CSSProperties } from 'react'; import { VizOrientation } from '@grafana/data'; import { calculateGridDimensions } from '../../utils/squares'; interface Props { /** * Optionally precalculate dimensions to support consistent behavior between repeated * values. Two typical patterns are: * 1) Calculate raw values like font size etc and pass them to each vis * 2) find the maximum input values and pass that to the vis */ getAlignmentFactors?: (values: V[], width: number, height: number) => D; /** * Render a single value */ renderValue: (props: VizRepeaterRenderValueProps) => JSX.Element; height: number; width: number; source: any; // If this changes, new values will be requested getValues: () => V[]; renderCounter: number; // force update of values & render orientation: VizOrientation; itemSpacing?: number; /** When orientation is set to auto layout items in a grid */ autoGrid?: boolean; minVizHeight?: number; } export interface VizRepeaterRenderValueProps { value: V; width: number; height: number; orientation: VizOrientation; alignmentFactors: D; /** * Total number of values being shown in repeater */ count: number; } interface DefaultProps { itemSpacing: number; } type PropsWithDefaults = Props & DefaultProps; interface State { values: V[]; } export class VizRepeater extends PureComponent, State> { static defaultProps: DefaultProps = { itemSpacing: 8, }; constructor(props: Props) { super(props); this.state = { values: props.getValues(), }; } componentDidUpdate(prevProps: Props) { const { renderCounter, source } = this.props; if (renderCounter !== prevProps.renderCounter || source !== prevProps.source) { this.setState({ values: this.props.getValues() }); } } getOrientation(): VizOrientation { const { orientation, width, height } = this.props; if (orientation === VizOrientation.Auto) { if (width > height) { return VizOrientation.Vertical; } else { return VizOrientation.Horizontal; } } return orientation; } renderGrid() { const { renderValue, height, width, itemSpacing, getAlignmentFactors, orientation } = this .props as PropsWithDefaults; const { values } = this.state; const grid = calculateGridDimensions(width, height, itemSpacing, values.length); const alignmentFactors = getAlignmentFactors ? getAlignmentFactors(values, grid.width, grid.height) : ({} as D); let xGrid = 0; let yGrid = 0; let items: JSX.Element[] = []; for (let i = 0; i < values.length; i++) { const value = values[i]; const isLastRow = yGrid === grid.yCount - 1; const itemWidth = isLastRow ? grid.widthOnLastRow : grid.width; const itemHeight = grid.height; const xPos = xGrid * itemWidth + itemSpacing * xGrid; const yPos = yGrid * itemHeight + itemSpacing * yGrid; const itemStyles: CSSProperties = { position: 'absolute', left: xPos, top: yPos, width: `${itemWidth}px`, height: `${itemHeight}px`, }; items.push(
{renderValue({ value, width: itemWidth, height: itemHeight, alignmentFactors, orientation, count: values.length, })}
); xGrid++; if (xGrid === grid.xCount) { xGrid = 0; yGrid++; } } return
{items}
; } render() { const { renderValue, height, width, itemSpacing, getAlignmentFactors, autoGrid, orientation, minVizHeight } = this .props as PropsWithDefaults; const { values } = this.state; if (autoGrid && orientation === VizOrientation.Auto) { return this.renderGrid(); } const itemStyles: React.CSSProperties = { display: 'flex', }; const repeaterStyle: React.CSSProperties = { display: 'flex', overflow: minVizHeight ? 'hidden auto' : 'hidden', }; let vizHeight = height; let vizWidth = width; let resolvedOrientation = this.getOrientation(); switch (resolvedOrientation) { case VizOrientation.Horizontal: repeaterStyle.flexDirection = 'column'; repeaterStyle.height = `${height}px`; itemStyles.marginBottom = `${itemSpacing}px`; vizWidth = width; vizHeight = Math.max(height / values.length - itemSpacing + itemSpacing / values.length, minVizHeight ?? 0); break; case VizOrientation.Vertical: repeaterStyle.flexDirection = 'row'; repeaterStyle.justifyContent = 'space-between'; itemStyles.marginRight = `${itemSpacing}px`; vizHeight = height; vizWidth = width / values.length - itemSpacing + itemSpacing / values.length; } itemStyles.width = `${vizWidth}px`; itemStyles.height = `${vizHeight}px`; const alignmentFactors = getAlignmentFactors ? getAlignmentFactors(values, vizWidth, vizHeight) : ({} as D); return (
{values.map((value, index) => { return (
{renderValue({ value, width: vizWidth, height: vizHeight, alignmentFactors, orientation: resolvedOrientation, count: values.length, })}
); })}
); } } /* * Removes any padding on the last item */ function getItemStylesForIndex(itemStyles: CSSProperties, index: number, length: number): CSSProperties { if (index === length - 1) { return { ...itemStyles, marginRight: 0, marginBottom: 0, }; } return itemStyles; }