@grafana/ui
Version:
Grafana Components Library
1 lines • 64.6 kB
Source Map (JSON)
{"version":3,"file":"utils.mjs","sources":["../../../../../src/components/Table/TableNG/utils.ts"],"sourcesContent":["import { Property } from 'csstype';\nimport memoize from 'micro-memoize';\nimport WKT from 'ol/format/WKT';\nimport Geometry from 'ol/geom/Geometry';\nimport { CSSProperties } from 'react';\nimport { SortColumn } from 'react-data-grid';\nimport tinycolor from 'tinycolor2';\nimport { Count, varPreLine } from 'uwrap';\n\nimport {\n FieldType,\n Field,\n formattedValueToString,\n GrafanaTheme2,\n DisplayValue,\n LinkModel,\n DisplayValueAlignmentFactors,\n DataFrame,\n DisplayProcessor,\n isDataFrame,\n FieldSparkline,\n DecimalCount,\n} from '@grafana/data';\nimport {\n BarGaugeDisplayMode,\n FieldTextAlignment,\n TableCellBackgroundDisplayMode,\n TableCellDisplayMode,\n TableCellHeight,\n} from '@grafana/schema';\n\nimport { getTextColorForAlphaBackground } from '../../../utils/colors';\nimport { TableCellInspectorMode } from '../TableCellInspector';\nimport { TableCellOptions } from '../types';\n\nimport { inferPills } from './Cells/PillCell';\nimport { AutoCellRenderer, getAutoRendererDisplayMode, getCellRenderer } from './Cells/renderers';\nimport { COLUMN, TABLE } from './constants';\nimport {\n TableRow,\n ColumnTypes,\n FrameToRowsConverter,\n Comparator,\n TypographyCtx,\n MeasureCellHeight,\n MeasureCellHeightEntry,\n} from './types';\n\n/* ---------------------------- Cell calculations --------------------------- */\nexport type CellNumLinesCalculator = (text: string, cellWidth: number) => number;\n\n/**\n * @internal\n * Returns the default row height based on the theme and cell height setting.\n */\nexport function getDefaultRowHeight(\n theme: GrafanaTheme2,\n fields?: Field[],\n cellHeight?: TableCellHeight\n): NonNullable<CSSProperties['height']> {\n if (fields?.some((field) => field.config?.custom?.cellOptions?.dynamicHeight)) {\n return 'auto';\n }\n\n switch (cellHeight) {\n case TableCellHeight.Sm:\n return 36;\n case TableCellHeight.Md:\n return 42;\n case TableCellHeight.Lg:\n return TABLE.MAX_CELL_HEIGHT;\n }\n\n return TABLE.CELL_PADDING * 2 + theme.typography.fontSize * theme.typography.body.lineHeight;\n}\n\n/**\n * @internal\n * Returns true if cell inspection (hover to see full content) is enabled for the field.\n */\nexport function isCellInspectEnabled(field: Field): boolean {\n return field.config?.custom?.inspect ?? false;\n}\n\n/**\n * @internal\n * Returns true if text wrapping should be applied to the cell.\n */\nexport function shouldTextWrap(field: Field): boolean {\n return Boolean(field.config.custom?.wrapText);\n}\n\n/**\n * @internal wrap a cell height measurer to clamp its output to the maxHeight defined in the field, if any.\n */\nfunction clampByMaxHeight(measurer: MeasureCellHeight, maxHeight = Infinity): MeasureCellHeight {\n return (value, width, field, rowIdx, lineHeight) => {\n const rawHeight = measurer(value, width, field, rowIdx, lineHeight);\n return Math.min(rawHeight, maxHeight);\n };\n}\n\n/**\n * @internal creates a typography context based on a font size and family. used to measure text\n * and estimate size of text in cells.\n */\nexport function createTypographyContext(fontSize: number, fontFamily: string, letterSpacing = 0.15): TypographyCtx {\n const font = `${fontSize}px ${fontFamily}`;\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d')!;\n\n ctx.letterSpacing = `${letterSpacing}px`;\n ctx.font = font;\n // 1/6 of the characters in this string are capitalized. Since the avgCharWidth is used for estimation, it's\n // better that the estimation over-estimates the width than if it underestimates it, so we're a little on the\n // aggressive side here and could even go more aggressive if we get complaints in the future.\n const txt =\n \"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s. 1234567890 ALL CAPS TO HELP WITH MEASUREMENT.\";\n const txtWidth = ctx.measureText(txt).width;\n const avgCharWidth = txtWidth / txt.length + letterSpacing;\n const { count } = varPreLine(ctx);\n\n return {\n ctx,\n fontFamily,\n letterSpacing,\n avgCharWidth,\n estimateHeight: getTextHeightEstimator(avgCharWidth),\n measureHeight: getTextHeightMeasurerFromUwrapCount(count),\n };\n}\n\n/**\n * @internal wraps the uwrap count function to ensure that it is given a string.\n */\nexport function getTextHeightMeasurerFromUwrapCount(count: Count): MeasureCellHeight {\n return (value, width, _field, _rowIdx, lineHeight) => {\n if (value == null) {\n return lineHeight;\n }\n\n const lines = count(String(value), width);\n return lines * lineHeight;\n };\n}\n\n/**\n * @internal returns a measurer which guesstimates a number of lines in a text cell based on the typography context's avgCharWidth.\n */\nexport function getTextHeightEstimator(avgCharWidth: number): MeasureCellHeight {\n return (value, width, _field, _rowIdx, lineHeight) => {\n if (!value) {\n return -1;\n }\n\n // we don't have string breaking enabled in the table,\n // so an unbroken string is by definition a single line.\n const strValue = String(value);\n if (!spaceRegex.test(strValue)) {\n return -1;\n }\n\n const charsPerLine = width / avgCharWidth;\n const lines = Math.ceil(strValue.length / charsPerLine);\n return lines * lineHeight;\n };\n}\n\n/**\n * @internal\n */\nexport function getDataLinksHeightMeasurer(): MeasureCellHeight {\n const linksCountCache: Record<string, number> = {};\n\n // when we render links, we need to filter out the invalid links. since the call to `getLinks` is expensive,\n // we'll cache the result and reuse it for every row in the table. this cache is cleared when line counts are\n // rebuilt anytime from the `useRowHeight` hook, and that includes adding and removing data links.\n return (_value, _width, field, _rowIdx, lineHeight) => {\n const cacheKey = getDisplayName(field);\n if (linksCountCache[cacheKey] === undefined) {\n let count = 0;\n for (const l of field.config?.links ?? []) {\n if (l.onClick || l.url) {\n count += 1;\n }\n }\n linksCountCache[cacheKey] = count;\n }\n\n return linksCountCache[cacheKey] * lineHeight;\n };\n}\n\nconst PILLS_FONT_SIZE = 12;\nconst PILLS_SPACING = 12; // 6px horizontal padding on each side\nconst PILLS_GAP = 4; // gap between pills\n\nexport function getPillCellHeightMeasurer(measureWidth: (value: string) => number): MeasureCellHeight {\n const widthCache: Record<string, number> = {};\n\n return (value, width, _field, _rowIdx, lineHeight) => {\n if (value == null) {\n return 0;\n }\n\n const pillValues = inferPills(String(value));\n if (pillValues.length === 0) {\n return 0;\n }\n\n let lines = 0;\n let currentLineUse = width;\n\n for (const pillValue of pillValues) {\n const strPill = String(pillValue);\n let rawWidth = widthCache[strPill];\n if (rawWidth === undefined) {\n rawWidth = measureWidth(strPill);\n widthCache[strPill] = rawWidth;\n }\n const pillWidth = rawWidth + PILLS_SPACING;\n\n if (currentLineUse + pillWidth + PILLS_GAP > width) {\n lines++;\n currentLineUse = pillWidth;\n } else {\n currentLineUse += pillWidth + PILLS_GAP;\n }\n }\n\n // default line height happens to be the height of a pill, but maybe we need a custom\n // const here to make sure this doesn't get out of sync with the actual pill height.\n return lines * lineHeight + (lines - 1) * PILLS_GAP;\n };\n}\n\n/**\n * @internal return a text measurer for every field which has wrapHeaderText enabled.\n */\nexport function buildHeaderHeightMeasurers(\n fields: Field[],\n typographyCtx: TypographyCtx\n): MeasureCellHeightEntry[] | undefined {\n const wrappedColIdxs = fields.reduce((acc: number[], field, idx) => {\n if (field.config?.custom?.wrapHeaderText) {\n acc.push(idx);\n }\n return acc;\n }, []);\n\n if (wrappedColIdxs.length === 0) {\n return undefined;\n }\n\n // don't bother with estimating the line counts for the headers, because it's punishing\n // when we get it wrong and there won't be that many compared to how many rows a table might contain.\n return [{ measure: typographyCtx.measureHeight, fieldIdxs: wrappedColIdxs }];\n}\n\nconst spaceRegex = /[\\s-]/;\n\n/**\n * @internal return a text height measurer for every field which has wrapHeaderText enabled. we do this once as we're rendering\n * the table, and then getRowHeight uses the output of this to caluclate the height of each row.\n */\nexport function buildCellHeightMeasurers(\n fields: Field[],\n typographyCtx: TypographyCtx,\n maxHeight?: number\n): MeasureCellHeightEntry[] | undefined {\n const result: Record<string, MeasureCellHeightEntry> = {};\n let wrappedFields = 0;\n\n const measurerFactory: Record<\n TableCellDisplayMode.Auto | TableCellDisplayMode.DataLinks | TableCellDisplayMode.Pill,\n () => [MeasureCellHeight, MeasureCellHeight?]\n > = {\n // for string fields, we estimate the length of a line using `avgCharWidth` to limit expensive calls `count`.\n [TableCellDisplayMode.Auto]: () => [typographyCtx.measureHeight, typographyCtx.estimateHeight],\n [TableCellDisplayMode.DataLinks]: () => [getDataLinksHeightMeasurer(), undefined],\n // pills use a different font size, so they require their own typography context.\n [TableCellDisplayMode.Pill]: () => {\n const pillTypographyCtx = createTypographyContext(\n PILLS_FONT_SIZE,\n typographyCtx.fontFamily,\n typographyCtx.letterSpacing\n );\n return [\n getPillCellHeightMeasurer((value) => pillTypographyCtx.ctx.measureText(value).width),\n getPillCellHeightMeasurer((value) => value.length * pillTypographyCtx.avgCharWidth),\n ];\n },\n } as const;\n\n const setupMeasurerForIdx = (measurerFactoryKey: keyof typeof measurerFactory, fieldIdx: number) => {\n if (!result[measurerFactoryKey]) {\n const [measure, estimate] = measurerFactory[measurerFactoryKey]();\n result[measurerFactoryKey] = {\n measure: clampByMaxHeight(measure, maxHeight),\n estimate: estimate != null ? clampByMaxHeight(estimate, maxHeight) : undefined,\n fieldIdxs: [],\n };\n }\n result[measurerFactoryKey].fieldIdxs.push(fieldIdx);\n };\n\n for (let fieldIdx = 0; fieldIdx < fields.length; fieldIdx++) {\n const field = fields[fieldIdx];\n if (shouldTextWrap(field)) {\n wrappedFields++;\n\n const cellType = getCellOptions(field).type;\n if (cellType === TableCellDisplayMode.DataLinks) {\n setupMeasurerForIdx(TableCellDisplayMode.DataLinks, fieldIdx);\n } else if (cellType === TableCellDisplayMode.Pill) {\n setupMeasurerForIdx(TableCellDisplayMode.Pill, fieldIdx);\n } else if (\n field.type === FieldType.string &&\n getCellRenderer(field, getCellOptions(field)) === AutoCellRenderer\n ) {\n setupMeasurerForIdx(TableCellDisplayMode.Auto, fieldIdx);\n } else {\n // no measurer was configured for this cell type\n wrappedFields--;\n }\n }\n }\n\n if (wrappedFields === 0) {\n return undefined;\n }\n\n return Object.values(result);\n}\n\n// in some cases, the estimator might return a value that is less than 1, but when calculated by the measurer, it actually\n// realizes that it's a multi-line cell. to avoid this, we want to give a little buffer away from 1 before we fully trust\n// the estimator to have told us that a cell is single-line.\nexport const SINGLE_LINE_ESTIMATE_THRESHOLD = 18.5;\n\n/**\n * @internal\n * loop through the fields and their values, determine which cell is going to determine the height of the row based\n * on its content and width, and return the height in pixels of that row, with vertial padding applied.\n */\nexport function getRowHeight(\n fields: Field[],\n rowIdx: number,\n columnWidths: number[],\n defaultHeight: number,\n measurers?: MeasureCellHeightEntry[],\n lineHeight = TABLE.LINE_HEIGHT,\n verticalPadding = TABLE.CELL_PADDING * 2\n): number {\n if (!measurers?.length) {\n return defaultHeight;\n }\n\n let maxHeight = -1;\n let maxValue = '';\n let maxWidth = 0;\n let maxField: Field | undefined;\n let preciseMeasurer: MeasureCellHeight | undefined;\n\n for (const { estimate, measure, fieldIdxs } of measurers) {\n // for some of the cell height measurers, getting the precise height is expensive. those entries set\n // both \"estimate\" and \"measure\" functions. if the cell we find to be the max was estimated, we will\n // get the \"true\" value right before calculating the row height by keeping a reference to the measure fn.\n const measurer = (estimate ?? measure) satisfies MeasureCellHeight;\n const isEstimating = estimate !== undefined;\n\n for (const fieldIdx of fieldIdxs) {\n const field = fields[fieldIdx];\n // special case: for the header, provide `-1` as the row index.\n const cellValueRaw = rowIdx === -1 ? getDisplayName(field) : field.values[rowIdx];\n if (cellValueRaw != null) {\n const colWidth = columnWidths[fieldIdx];\n const estimatedHeight = measurer(cellValueRaw, colWidth, field, rowIdx, lineHeight);\n if (estimatedHeight > maxHeight) {\n maxHeight = estimatedHeight;\n maxValue = cellValueRaw;\n maxWidth = colWidth;\n maxField = field;\n preciseMeasurer = isEstimating ? measure : undefined;\n }\n }\n }\n }\n\n // if the value is -1 or the estimate for the max cell was less than the SINGLE_LINE_ESTIMATE_THRESHOLD, we trust\n // that the estimator correctly identified that no text wrapping is needed for this row, skipping the preciseMeasurer.\n if (maxField === undefined || maxHeight < SINGLE_LINE_ESTIMATE_THRESHOLD) {\n return defaultHeight;\n }\n\n // if we finished this row height loop with an estimate, we need to call\n // the `preciseMeasurer` method to get the exact line count.\n if (preciseMeasurer !== undefined) {\n maxHeight = preciseMeasurer(maxValue, maxWidth, maxField, rowIdx, lineHeight);\n }\n\n // adjust for vertical padding, and clamp to a minimum default height\n return Math.max(maxHeight + verticalPadding, defaultHeight);\n}\n\n/**\n * @internal\n * Returns true if text overflow handling should be applied to the cell.\n */\nexport function shouldTextOverflow(field: Field): boolean {\n const cellOptions = getCellOptions(field);\n const eligibleCellType =\n // Tech debt: Technically image cells are of type string, which is misleading (kinda?)\n // so we need to ensurefield.type === FieldType.string we don't apply overflow hover states for type image\n (field.type === FieldType.string && cellOptions.type !== TableCellDisplayMode.Image) ||\n // regardless of the underlying cell type, data links cells have text overflow.\n cellOptions.type === TableCellDisplayMode.DataLinks;\n\n return eligibleCellType && !shouldTextWrap(field) && !isCellInspectEnabled(field);\n}\n\n// we only want to infer justifyContent and textAlign for these cellTypes\nconst TEXT_CELL_TYPES = new Set<TableCellDisplayMode>([\n TableCellDisplayMode.Auto,\n TableCellDisplayMode.ColorText,\n TableCellDisplayMode.ColorBackground,\n]);\n\nexport type TextAlign = 'left' | 'right' | 'center';\n\n/**\n * @internal\n * Returns the text-align value for inline-displayed cells for a field based on its type and configuration.\n */\nexport function getAlignment(field: Field): TextAlign {\n const align: FieldTextAlignment | undefined = field.config.custom?.align;\n\n if (!align || align === 'auto') {\n if (TEXT_CELL_TYPES.has(getCellOptions(field).type) && field.type === FieldType.number) {\n return 'right';\n }\n return 'left';\n }\n\n return align;\n}\n\n/**\n * @internal\n * Returns the justify-content value for flex-displayed cells for a field based on its type and configuration.\n */\nexport function getJustifyContent(textAlign: TextAlign): Property.JustifyContent {\n return textAlign === 'center' ? 'center' : textAlign === 'right' ? 'flex-end' : 'flex-start';\n}\n\nconst DEFAULT_CELL_OPTIONS = { type: TableCellDisplayMode.Auto } as const;\n\n/**\n * @internal\n * Returns the cell options for a field, migrating from legacy displayMode if necessary.\n * TODO: remove live migration in favor of doing it in dashboard or panel migrator\n */\nexport function getCellOptions(field: Field): TableCellOptions {\n if (field.config.custom?.displayMode) {\n return migrateTableDisplayModeToCellOptions(field.config.custom?.displayMode);\n }\n\n return field.config.custom?.cellOptions ?? DEFAULT_CELL_OPTIONS;\n}\n\n/**\n * @internal\n * Getting gauge or sparkline values to align is very tricky without looking at all values and passing them through display processor.\n * For very large tables that could pretty expensive. So this is kind of a compromise. We look at the first 1000 rows and cache the longest value.\n * If we have a cached value we just check if the current value is longer and update the alignmentFactor. This can obviously still lead to\n * unaligned gauges but it should a lot less common.\n **/\nexport function getAlignmentFactor(\n field: Field,\n displayValue: DisplayValue,\n rowIndex: number\n): DisplayValueAlignmentFactors {\n let alignmentFactor = field.state?.alignmentFactors;\n\n if (alignmentFactor) {\n // check if current alignmentFactor is still the longest\n if (formattedValueToString(alignmentFactor).length < formattedValueToString(displayValue).length) {\n alignmentFactor = { ...displayValue };\n field.state!.alignmentFactors = alignmentFactor;\n }\n return alignmentFactor;\n } else {\n // look at the next 1000 rows\n alignmentFactor = { ...displayValue };\n const maxIndex = Math.min(field.values.length, rowIndex + 1000);\n\n for (let i = rowIndex + 1; i < maxIndex; i++) {\n const nextDisplayValue = field.display?.(field.values[i]) ?? field.values[i];\n if (formattedValueToString(alignmentFactor).length > formattedValueToString(nextDisplayValue).length) {\n alignmentFactor.text = displayValue.text;\n }\n }\n\n if (field.state) {\n field.state.alignmentFactors = alignmentFactor;\n } else {\n field.state = { alignmentFactors: alignmentFactor };\n }\n\n return alignmentFactor;\n }\n}\n\n/* ------------------------- Cell color calculation ------------------------- */\nconst CELL_COLOR_DARKENING_MULTIPLIER = 10;\nconst CELL_GRADIENT_HUE_ROTATION_DEGREES = 5;\n\n/**\n * @internal\n * Returns the text and background colors for a table cell based on its options and display value.\n */\nexport function getCellColorInlineStylesFactory(theme: GrafanaTheme2) {\n const bgCellTextColor = memoize((color: string) => getTextColorForAlphaBackground(color, theme.isDark), {\n maxSize: 1000,\n });\n const darkeningFactor = theme.isDark ? 1 : -0.7; // How much to darken elements depends upon if we're in dark mode\n const gradientBg = memoize(\n (color: string) =>\n tinycolor(color)\n .darken(CELL_COLOR_DARKENING_MULTIPLIER * darkeningFactor)\n .spin(CELL_GRADIENT_HUE_ROTATION_DEGREES)\n .toRgbString(),\n { maxSize: 1000 }\n );\n const isTransparent = memoize(\n (color: string) => {\n // if hex, do the simple thing.\n if (color[0] === '#') {\n return color.length === 9 && color.endsWith('00');\n }\n // if not hex, just use tinycolor to avoid extra logic.\n return tinycolor(color).getAlpha() === 0;\n },\n { maxSize: 1000 }\n );\n\n return (cellOptions: TableCellOptions, displayValue: DisplayValue, hasApplyToRow: boolean): CSSProperties => {\n const result: CSSProperties = {};\n const displayValueColor = displayValue.color;\n\n if (!displayValueColor) {\n return result;\n }\n\n if (cellOptions.type === TableCellDisplayMode.ColorText) {\n result.color = displayValueColor;\n } else if (cellOptions.type === TableCellDisplayMode.ColorBackground) {\n // return without setting anything if the bg is transparent. this allows\n // the cell to inherit the row bg color if `applyToRow` is set.\n if (hasApplyToRow && isTransparent(displayValueColor)) {\n return result;\n }\n\n const mode = cellOptions.mode ?? TableCellBackgroundDisplayMode.Gradient;\n result.color = bgCellTextColor(displayValueColor);\n result.background =\n mode === TableCellBackgroundDisplayMode.Gradient\n ? `linear-gradient(120deg, ${gradientBg(displayValueColor)}, ${displayValueColor})`\n : displayValueColor;\n }\n\n return result;\n };\n}\n\n/**\n * @internal\n * Extracts numeric pixel value from theme spacing\n */\nexport const extractPixelValue = (spacing: string | number): number => {\n return typeof spacing === 'number' ? spacing : parseFloat(spacing) || 0;\n};\n\n/* ------------------------------- Data links ------------------------------- */\n/**\n * @internal\n */\nexport const getCellLinks = (field: Field, rowIdx: number) => {\n let links: Array<LinkModel<unknown>> | undefined;\n if (field.getLinks) {\n links = field.getLinks({\n valueRowIndex: rowIdx,\n });\n }\n\n if (!links) {\n return;\n }\n\n for (let i = 0; i < links?.length; i++) {\n if (links[i].onClick) {\n const origOnClick = links[i].onClick;\n\n links[i].onClick = (event: MouseEvent) => {\n // Allow opening in new tab\n if (!(event.ctrlKey || event.metaKey || event.shiftKey)) {\n event.preventDefault();\n origOnClick!(event, {\n field,\n rowIndex: rowIdx,\n });\n }\n };\n }\n }\n\n return links.filter((link) => link.href || link.onClick != null);\n};\n\n/* ----------------------------- Data grid sorting ---------------------------- */\n/**\n * @internal\n */\nexport function applySort(\n rows: TableRow[],\n fields: Field[],\n sortColumns: SortColumn[],\n columnTypes: ColumnTypes = getColumnTypes(fields),\n hasNestedFrames: boolean = getIsNestedTable(fields)\n): TableRow[] {\n if (sortColumns.length === 0) {\n return rows;\n }\n\n const sortNanos = sortColumns.map(\n (c) => fields.find((f) => f.type === FieldType.time && getDisplayName(f) === c.columnKey)?.nanos\n );\n\n const compareRows = (a: TableRow, b: TableRow): number => {\n let result = 0;\n\n for (let i = 0; i < sortColumns.length; i++) {\n const { columnKey, direction } = sortColumns[i];\n const compare = getComparator(columnTypes[columnKey]);\n const sortDir = direction === 'ASC' ? 1 : -1;\n\n result = sortDir * compare(a[columnKey], b[columnKey]);\n\n if (result === 0) {\n const nanos = sortNanos[i];\n\n if (nanos !== undefined) {\n result = sortDir * (nanos[a.__index] - nanos[b.__index]);\n }\n }\n\n if (result !== 0) {\n break;\n }\n }\n\n return result;\n };\n\n // Handle nested tables\n if (hasNestedFrames) {\n return processNestedTableRows(rows, (parents) => [...parents].sort(compareRows));\n }\n\n // Regular sort for tables without nesting\n return [...rows].sort(compareRows);\n}\n\n/* ----------------------------- Data grid mapping ---------------------------- */\n/**\n * @internal\n */\nexport const frameToRecords = (frame: DataFrame): TableRow[] => {\n const fnBody = `\n const rows = Array(frame.length);\n const values = frame.fields.map(f => f.values);\n let rowCount = 0;\n for (let i = 0; i < frame.length; i++) {\n rows[rowCount] = {\n __depth: 0,\n __index: i,\n ${frame.fields.map((field, fieldIdx) => `${JSON.stringify(getDisplayName(field))}: values[${fieldIdx}][i]`).join(',')}\n };\n rowCount += 1;\n if (rows[rowCount-1]['__nestedFrames']){\n const childFrame = rows[rowCount-1]['__nestedFrames'];\n rows[rowCount] = {__depth: 1, __index: i, data: childFrame[0]}\n rowCount += 1;\n }\n }\n return rows;\n `;\n\n // Creates a function that converts a DataFrame into an array of TableRows\n // Uses new Function() for performance as it's faster than creating rows using loops\n const convert = new Function('frame', fnBody) as FrameToRowsConverter;\n return convert(frame);\n};\n\n/* ----------------------------- Data grid comparator ---------------------------- */\n// The numeric: true option is used to sort numbers as strings correctly. It recognizes numeric sequences\n// within strings and sorts numerically instead of lexicographically.\nconst compare = new Intl.Collator('en', { sensitivity: 'base', numeric: true }).compare;\nconst strCompare: Comparator = (a, b) => compare(String(a ?? ''), String(b ?? ''));\nconst numCompare: Comparator = (a, b) => {\n if (a === b) {\n return 0;\n }\n if (a == null) {\n return -1;\n }\n if (b == null) {\n return 1;\n }\n return Number(a) - Number(b);\n};\nconst frameCompare: Comparator = (a, b) => {\n // @ts-ignore The compared vals are DataFrameWithValue. the value is the rendered stat (first, last, etc.)\n return (a?.value ?? 0) - (b?.value ?? 0);\n};\n\n/**\n * @internal\n */\nexport function getComparator(sortColumnType: FieldType): Comparator {\n switch (sortColumnType) {\n // Handle sorting for frame type fields (sparklines)\n case FieldType.frame:\n return frameCompare;\n case FieldType.time:\n case FieldType.number:\n case FieldType.boolean:\n return numCompare;\n case FieldType.string:\n case FieldType.enum:\n default:\n return strCompare;\n }\n}\n\ntype TableCellGaugeDisplayModes =\n | TableCellDisplayMode.BasicGauge\n | TableCellDisplayMode.GradientGauge\n | TableCellDisplayMode.LcdGauge;\nconst TABLE_CELL_GAUGE_DISPLAY_MODES_TO_DISPLAY_MODES: Record<TableCellGaugeDisplayModes, BarGaugeDisplayMode> = {\n [TableCellDisplayMode.BasicGauge]: BarGaugeDisplayMode.Basic,\n [TableCellDisplayMode.GradientGauge]: BarGaugeDisplayMode.Gradient,\n [TableCellDisplayMode.LcdGauge]: BarGaugeDisplayMode.Lcd,\n};\n\ntype TableCellColorBackgroundDisplayModes =\n | TableCellDisplayMode.ColorBackground\n | TableCellDisplayMode.ColorBackgroundSolid;\nconst TABLE_CELL_COLOR_BACKGROUND_DISPLAY_MODES_TO_DISPLAY_MODES: Record<\n TableCellColorBackgroundDisplayModes,\n TableCellBackgroundDisplayMode\n> = {\n [TableCellDisplayMode.ColorBackground]: TableCellBackgroundDisplayMode.Gradient,\n [TableCellDisplayMode.ColorBackgroundSolid]: TableCellBackgroundDisplayMode.Basic,\n};\n\n/* ---------------------------- Miscellaneous ---------------------------- */\n/**\n * Migrates table cell display mode to new object format.\n *\n * @param displayMode The display mode of the cell\n * @returns TableCellOptions object in the correct format\n * relative to the old display mode.\n */\nexport function migrateTableDisplayModeToCellOptions(displayMode: TableCellDisplayMode): TableCellOptions {\n switch (displayMode) {\n // In the case of the gauge we move to a different option\n case TableCellDisplayMode.BasicGauge:\n case TableCellDisplayMode.GradientGauge:\n case TableCellDisplayMode.LcdGauge:\n return {\n type: TableCellDisplayMode.Gauge,\n mode: TABLE_CELL_GAUGE_DISPLAY_MODES_TO_DISPLAY_MODES[displayMode],\n };\n // Also true in the case of the color background\n case TableCellDisplayMode.ColorBackground:\n case TableCellDisplayMode.ColorBackgroundSolid:\n return {\n type: TableCellDisplayMode.ColorBackground,\n mode: TABLE_CELL_COLOR_BACKGROUND_DISPLAY_MODES_TO_DISPLAY_MODES[displayMode],\n };\n // catching a nonsense case: `displayMode`: 'custom' should pre-date the CustomCell.\n // if it doesn't, we need to just nope out and return an auto cell.\n case TableCellDisplayMode.Custom:\n return {\n type: TableCellDisplayMode.Auto,\n };\n default:\n return {\n type: displayMode,\n };\n }\n}\n\n/**\n * @internal\n * Returns true if the DataFrame contains nested frames\n */\nexport const getIsNestedTable = (fields: Field[]): boolean =>\n fields.some(({ type }) => type === FieldType.nestedFrames);\n\n/**\n * @internal\n * Processes nested table rows\n */\nexport const processNestedTableRows = (\n rows: TableRow[],\n processParents: (parents: TableRow[]) => TableRow[]\n): TableRow[] => {\n // Separate parent and child rows\n // Array for parentRows: enables sorting and maintains order for iteration\n // Map for childRows: provides O(1) lookup by parent index when reconstructing the result\n const parentRows: TableRow[] = [];\n const childRows: Map<number, TableRow> = new Map();\n\n for (const row of rows) {\n if (row.__depth === 0) {\n parentRows.push(row);\n } else {\n childRows.set(row.__index, row);\n }\n }\n\n // Process parent rows (filter or sort)\n const processedParents = processParents(parentRows);\n\n // Reconstruct the result\n const result: TableRow[] = [];\n processedParents.forEach((row) => {\n result.push(row);\n const childRow = childRows.get(row.__index);\n if (childRow) {\n result.push(childRow);\n }\n });\n\n return result;\n};\n\n/**\n * @internal\n * Calculate the footer height based on the maximum reducer count\n */\nexport const calculateFooterHeight = (fields: Field[]): number => {\n let maxReducerCount = 0;\n for (const field of fields) {\n maxReducerCount = Math.max(maxReducerCount, field.config.custom?.footer?.reducers?.length ?? 0);\n }\n\n // Base height (+ padding) + height per reducer\n return maxReducerCount > 0 ? maxReducerCount * TABLE.LINE_HEIGHT + TABLE.CELL_PADDING * 2 : 0;\n};\n\n/**\n * @internal\n * returns the display name of a field\n * returns the display name of a field.\n * We intentionally do not want to use @grafana/data's getFieldDisplayName here,\n * instead we have a call to cacheFieldDisplayNames up in TablePanel to handle this\n * before we begin.\n */\nexport const getDisplayName = (field: Field): string => {\n return field.state?.displayName ?? field.name;\n};\n\n/**\n * @internal given a field name or display name, returns a predicate function that checks if a field matches that name.\n */\nexport const predicateByName = (name: string) => (f: Field) => f.name === name || getDisplayName(f) === name;\n\n/**\n * @internal\n * returns only fields that are not nested tables and not explicitly hidden\n */\nexport function getVisibleFields(fields: Field[]): Field[] {\n return fields.filter((field) => field.type !== FieldType.nestedFrames && field.config.custom?.hideFrom?.viz !== true);\n}\n\n/**\n * @internal\n * returns a map of column types by display name\n */\nexport function getColumnTypes(fields: Field[]): ColumnTypes {\n return fields.reduce<ColumnTypes>((acc, field) => {\n switch (field.type) {\n case FieldType.nestedFrames:\n return { ...acc, ...getColumnTypes(field.values[0]?.[0]?.fields ?? []) };\n default:\n return { ...acc, [getDisplayName(field)]: field.type };\n }\n }, {});\n}\n\n/**\n * @internal\n * calculates the width of each field, with the following logic:\n * 1. manual sizing minWidth is hard-coded to 50px, we set this in RDG since it enforces the hard limit correctly\n * 2. if minWidth is configured in fieldConfig (or defaults to 150), it serves as the bottom of the auto-size clamp\n */\nexport function computeColWidths(fields: Field[], availWidth: number) {\n let autoCount = 0;\n let definedWidth = 0;\n\n return (\n fields\n // first pass to add up how many fields have pre-defined widths and what that width totals to.\n .map((field) => {\n const width: number = field.config.custom?.width ?? 0;\n\n if (width === 0) {\n autoCount++;\n } else {\n definedWidth += width;\n }\n\n return width;\n })\n // second pass once `autoCount` and `definedWidth` are known.\n .map(\n (width, i) =>\n width ||\n Math.max(fields[i].config.custom?.minWidth ?? COLUMN.DEFAULT_WIDTH, (availWidth - definedWidth) / autoCount)\n )\n );\n}\n\n/**\n * @internal\n * if applyToRow is true in any field, return a function that gets the row background color\n */\nexport function getApplyToRowBgFn(\n fields: Field[],\n getCellColorInlineStyles: ReturnType<typeof getCellColorInlineStylesFactory>\n): ((rowIndex: number) => CSSProperties) | void {\n for (const field of fields) {\n const cellOptions = getCellOptions(field);\n const fieldDisplay = field.display;\n if (\n fieldDisplay !== undefined &&\n cellOptions.type === TableCellDisplayMode.ColorBackground &&\n cellOptions.applyToRow === true\n ) {\n return (rowIndex: number) => getCellColorInlineStyles(cellOptions, fieldDisplay(field.values[rowIndex]), true);\n }\n }\n}\n\n/** @internal */\nexport function canFieldBeColorized(\n cellType: TableCellDisplayMode,\n applyToRowBgFn?: (rowIndex: number) => CSSProperties\n) {\n return (\n cellType === TableCellDisplayMode.ColorBackground ||\n cellType === TableCellDisplayMode.ColorText ||\n Boolean(applyToRowBgFn)\n );\n}\n\nexport const displayJsonValue: (field: Field) => DisplayProcessor = (field: Field, decimals?: DecimalCount) => {\n const origDisplay = field.display!;\n return (value: unknown): DisplayValue => {\n const displayValue = origDisplay(value, decimals);\n\n let jsonText: string;\n if (!Array.isArray(value) && !isPlainObject(value)) {\n const formattedValue = formattedValueToString(displayValue);\n try {\n const parsed = JSON.parse(formattedValue);\n jsonText = JSON.stringify(parsed, null, ' ');\n } catch {\n jsonText = formattedValue; // Keep original if not valid JSON\n }\n } else {\n jsonText = JSON.stringify(value, null, ' ');\n }\n\n return { ...displayValue, text: jsonText };\n };\n};\n\nexport function prepareSparklineValue(value: unknown, field: Field): FieldSparkline | undefined {\n if (Array.isArray(value)) {\n return {\n y: {\n name: `${field.name}-sparkline`,\n type: FieldType.number,\n values: value,\n config: {},\n },\n };\n }\n\n if (isDataFrame(value)) {\n const timeField = value.fields.find((x) => x.type === FieldType.time);\n const numberField = value.fields.find((x) => x.type === FieldType.number);\n\n if (timeField && numberField) {\n return { x: timeField, y: numberField };\n }\n }\n\n return;\n}\n\nfunction isPlainObject(value: unknown): value is object {\n return typeof value === 'object' && value != null && !Array.isArray(value);\n}\n\nexport function buildInspectValue(value: unknown, field: Field): [string, TableCellInspectorMode] {\n const cellOptions = getCellOptions(field);\n\n let inspectValue: string;\n let mode = TableCellInspectorMode.text;\n\n if (field.type === FieldType.geo && value instanceof Geometry) {\n inspectValue = new WKT().writeGeometry(value, {\n featureProjection: 'EPSG:3857',\n dataProjection: 'EPSG:4326',\n });\n mode = TableCellInspectorMode.code;\n } else if (\n cellOptions.type === TableCellDisplayMode.Sparkline ||\n getAutoRendererDisplayMode(field) === TableCellDisplayMode.Sparkline\n ) {\n // rather than JSON.stringify this, manually format it to make the coordinate tuples more legible to the user.\n const fieldSparkline = prepareSparklineValue(value, field);\n inspectValue = '[';\n if (fieldSparkline != null) {\n // if an x value exists, render as a tuple [x,y], otherwise just y\n const buildValString: (idx: number) => string =\n fieldSparkline.x != null\n ? (idx) => `[${fieldSparkline.x!.values[idx] ?? 'null'}, ${fieldSparkline.y.values[idx] ?? 'null'}]`\n : (idx) => `${fieldSparkline.y.values[idx] ?? 'null'}`;\n for (let i = 0; i < fieldSparkline.y.values.length; i++) {\n inspectValue += `\\n ${buildValString(i)}${i === fieldSparkline.y.values.length - 1 ? '\\n' : ','}`;\n }\n }\n inspectValue += ']';\n mode = TableCellInspectorMode.code;\n } else if (cellOptions.type === TableCellDisplayMode.JSONView || Array.isArray(value) || isPlainObject(value)) {\n let toStringify = value;\n if (typeof value === 'string') {\n try {\n toStringify = JSON.parse(value);\n } catch {\n // do nothing, toStringify will stay as the raw string\n }\n }\n inspectValue = JSON.stringify(toStringify, null, ' ');\n mode = TableCellInspectorMode.code;\n } else {\n inspectValue = String(value ?? '');\n }\n\n return [inspectValue, mode];\n}\n\nexport function getSummaryCellTextAlign(textAlign: TextAlign, cellType: TableCellDisplayMode): TextAlign {\n // gauge is weird. left-aligned gauge has the viz on the left and its numbers on the right, and vice-versa.\n // if you center-aligned your gauge... ok.\n if (cellType === TableCellDisplayMode.Gauge) {\n return (\n {\n left: 'right',\n right: 'left',\n center: 'center',\n } as const\n )[textAlign];\n }\n\n return textAlign;\n}\n\n// we keep this set to avoid spamming the heck out of the console, since it's quite likely that if we fail to parse\n// a value once, it'll happen again and again for many rows in a table, and spamming the console is slow.\nlet warnedAboutStyleJsonSet = new Set<string>();\nexport function parseStyleJson(rawValue: unknown): CSSProperties | void {\n // confirms existence of value and serves as a type guard\n if (typeof rawValue === 'string') {\n try {\n const parsedJsonValue = JSON.parse(rawValue);\n if (parsedJsonValue != null && typeof parsedJsonValue === 'object' && !Array.isArray(parsedJsonValue)) {\n return parsedJsonValue;\n }\n } catch (e) {\n if (!warnedAboutStyleJsonSet.has(rawValue)) {\n console.error(`encountered invalid cell style JSON: ${rawValue}`, e);\n warnedAboutStyleJsonSet.add(rawValue);\n }\n }\n }\n}\n\n// Safari 26 introduced rendering bugs which require us to disable several features of the table.\nexport const IS_SAFARI_26 = (() => {\n if (navigator == null) {\n return false;\n }\n const userAgent = navigator.userAgent;\n const safariVersionMatch = userAgent.match(/Version\\/(\\d+)\\./);\n return safariVersionMatch && parseInt(safariVersionMatch[1], 10) === 26;\n})();\n"],"names":["compare"],"mappings":";;;;;;;;;;;;;;AAuDO,SAAS,mBAAA,CACd,KAAA,EACA,MAAA,EACA,UAAA,EACsC;AACtC,EAAA,IAAI,MAAA,IAAA,IAAA,GAAA,KAAA,CAAA,GAAA,MAAA,CAAQ,IAAA,CAAK,CAAC,KAAA,KAAO;AA5D3B,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AA4D8B,IAAA,OAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,CAAA,EAAA,GAAA,KAAA,CAAM,MAAA,KAAN,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAc,MAAA,KAAd,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAsB,gBAAtB,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAmC,aAAA;AAAA,EAAA,CAAA,CAAA,EAAgB;AAC7E,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,QAAQ,UAAA;AAAY,IAClB,KAAK,eAAA,CAAgB,EAAA;AACnB,MAAA,OAAO,EAAA;AAAA,IACT,KAAK,eAAA,CAAgB,EAAA;AACnB,MAAA,OAAO,EAAA;AAAA,IACT,KAAK,eAAA,CAAgB,EAAA;AACnB,MAAA,OAAO,KAAA,CAAM,eAAA;AAAA;AAGjB,EAAA,OAAO,KAAA,CAAM,eAAe,CAAA,GAAI,KAAA,CAAM,WAAW,QAAA,GAAW,KAAA,CAAM,WAAW,IAAA,CAAK,UAAA;AACpF;AAMO,SAAS,qBAAqB,KAAA,EAAuB;AAhF5D,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAiFE,EAAA,OAAA,CAAO,uBAAM,MAAA,KAAN,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAc,MAAA,KAAd,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAsB,YAAtB,IAAA,GAAA,EAAA,GAAiC,KAAA;AAC1C;AAMO,SAAS,eAAe,KAAA,EAAuB;AAxFtD,EAAA,IAAA,EAAA;AAyFE,EAAA,OAAO,OAAA,CAAA,CAAQ,EAAA,GAAA,KAAA,CAAM,MAAA,CAAO,MAAA,KAAb,mBAAqB,QAAQ,CAAA;AAC9C;AAKA,SAAS,gBAAA,CAAiB,QAAA,EAA6B,SAAA,GAAY,QAAA,EAA6B;AAC9F,EAAA,OAAO,CAAC,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,QAAQ,UAAA,KAAe;AAClD,IAAA,MAAM,YAAY,QAAA,CAAS,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO,QAAQ,UAAU,CAAA;AAClE,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,SAAA,EAAW,SAAS,CAAA;AAAA,EACtC,CAAA;AACF;AAMO,SAAS,uBAAA,CAAwB,QAAA,EAAkB,UAAA,EAAoB,aAAA,GAAgB,IAAA,EAAqB;AACjH,EAAA,MAAM,IAAA,GAAO,CAAA,EAAG,QAAQ,CAAA,GAAA,EAAM,UAAU,CAAA,CAAA;AACxC,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,QAAQ,CAAA;AAC9C,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,IAAI,CAAA;AAElC,EAAA,GAAA,CAAI,aAAA,GAAgB,GAAG,aAAa,CAAA,EAAA,CAAA;AACpC,EAAA,GAAA,CAAI,IAAA,GAAO,IAAA;AAIX,EAAA,MAAM,GAAA,GACJ,wMAAA;AACF,EAAA,MAAM,QAAA,GAAW,GAAA,CAAI,WAAA,CAAY,GAAG,CAAA,CAAE,KAAA;AACtC,EAAA,MAAM,YAAA,GAAe,QAAA,GAAW,GAAA,CAAI,MAAA,GAAS,aAAA;AAC7C,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,UAAA,CAAW,GAAG,CAAA;AAEhC,EAAA,OAAO;AAAA,IACL,GAAA;AAAA,IACA,UAAA;AAAA,IACA,aAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA,EAAgB,uBAAuB,YAAY,CAAA;AAAA,IACnD,aAAA,EAAe,oCAAoC,KAAK;AAAA,GAC1D;AACF;AAKO,SAAS,oCAAoC,KAAA,EAAiC;AACnF,EAAA,OAAO,CAAC,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,SAAS,UAAA,KAAe;AACpD,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,OAAO,UAAA;AAAA,IACT;AAEA,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,MAAA,CAAO,KAAK,GAAG,KAAK,CAAA;AACxC,IAAA,OAAO,KAAA,GAAQ,UAAA;AAAA,EACjB,CAAA;AACF;AAKO,SAAS,uBAAuB,YAAA,EAAyC;AAC9E,EAAA,OAAO,CAAC,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,SAAS,UAAA,KAAe;AACpD,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,OAAO,CAAA,CAAA;AAAA,IACT;AAIA,IAAA,MAAM,QAAA,GAAW,OAAO,KAAK,CAAA;AAC7B,IAAA,IAAI,CAAC,UAAA,CAAW,IAAA,CAAK,QAAQ,CAAA,EAAG;AAC9B,MAAA,OAAO,CAAA,CAAA;AAAA,IACT;AAEA,IAAA,MAAM,eAAe,KAAA,GAAQ,YAAA;AAC7B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,SAAS,YAAY,CAAA;AACtD,IAAA,OAAO,KAAA,GAAQ,UAAA;AAAA,EACjB,CAAA;AACF;AAKO,SAAS,0BAAA,GAAgD;AAC9D,EAAA,MAAM,kBAA0C,EAAC;AAKjD,EAAA,OAAO,CAAC,MAAA,EAAQ,MAAA,EAAQ,KAAA,EAAO,SAAS,UAAA,KAAe;AAjLzD,IAAA,IAAA,EAAA,EAAA,EAAA;AAkLI,IAAA,MAAM,QAAA,GAAW,eAAe,KAAK,CAAA;AACrC,IAAA,IAAI,eAAA,CAAgB,QAAQ,CAAA,KAAM,KAAA,CAAA,EAAW;AAC3C,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,KAAA,MAAW,MAAK,EAAA,GAAA,CAAA,EAAA,GAAA,KAAA,CAAM,MAAA,KAAN,mBAAc,KAAA,KAAd,IAAA,GAAA,EAAA,GAAuB,EAAC,EAAG;AACzC,QAAA,IAAI,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,GAAA,EAAK;AACtB,UAAA,KAAA,IAAS,CAAA;AAAA,QACX;AAAA,MACF;AACA,MAAA,eAAA,CAAgB,QAAQ,CAAA,GAAI,KAAA;AAAA,IAC9B;AAEA,IAAA,OAAO,eAAA,CAAgB,QAAQ,CAAA,GAAI,UAAA;AAAA,EACrC,CAAA;AACF;AAEA,MAAM,eAAA,GAAkB,EAAA;AACxB,MAAM,aAAA,GAAgB,EAAA;AACtB,MAAM,SAAA,GAAY,CAAA;AAEX,SAAS,0BAA0B,YAAA,EAA4D;AACpG,EAAA,MAAM,aAAqC,EAAC;AAE5C,EAAA,OAAO,CAAC,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,SAAS,UAAA,KAAe;AACpD,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,OAAO,CAAA;AAAA,IACT;AAEA,IAAA,MAAM,UAAA,GAAa,UAAA,CAAW,MAAA,CAAO,KAAK,CAAC,CAAA;AAC3C,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAO,CAAA;AAAA,IACT;AAEA,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,IAAI,cAAA,GAAiB,KAAA;AAErB,IAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,MAAA,MAAM,OAAA,GAAU,OAAO,SAAS,CAAA;AAChC,MAAA,IAAI,QAAA,GAAW,WAAW,OAAO,CAAA;AACjC,MAAA,IAAI,aAAa,KAAA,CAAA,EAAW;AAC1B,QAAA,QAAA,GAAW,aAAa,OAAO,CAAA;AAC/B,QAAA,UAAA,CAAW,OAAO,CAAA,GAAI,QAAA;AAAA,MACxB;AACA,MAAA,MAAM,YAAY,QAAA,GAAW,aAAA;AAE7B,MAAA,IAAI,cAAA,GAAiB,SAAA,GAAY,SAAA,GAAY,KAAA,EAAO;AAClD,QAAA,KAAA,EAAA;AACA,QAAA,cAAA,GAAiB,SAAA;AAAA,MACnB,CAAA,MAAO;AACL,QAAA,cAAA,IAAkB,SAAA,GAAY,SAAA;AAAA,MAChC;AAAA,IACF;AAIA,IAAA,OAAO,KAAA,GAAQ,UAAA,GAAA,CAAc,KAAA,GAAQ,CAAA,IAAK,SAAA;AAAA,EAC5C,CAAA;AACF;AAKO,SAAS,0BAAA,CACd,QACA,aAAA,EACsC;AACtC,EAAA,MAAM,iBAAiB,MAAA,CAAO,MAAA,CAAO,CAAC,GAAA,EAAe,OAAO,GAAA,KAAQ;AAnPtE,IAAA,IAAA,EAAA,EAAA,EAAA;AAoPI,IAAA,IAAA,CAAI,EAAA,GAAA,CAAA,EAAA,GAAA,KAAA,CAAM,MAAA,KAAN,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAc,MAAA,KAAd,mBAAsB,cAAA,EAAgB;AACxC,MAAA,GAAA,CAAI,KAAK,GAAG,CAAA;AAAA,IACd;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,IAAI,cAAA,CAAe,WAAW,CAAA,EAAG;AAC/B,IAAA,OAAO,KAAA,CAAA;AAAA,EACT;AAIA,EAAA,OAAO,CAAC,EAAE,OAAA,EAAS,cAAc,aAAA,EAAe,SAAA,EAAW,gBAAgB,CAAA;AAC7E;AAEA,MAAM,UAAA,GAAa,OAAA;AAMZ,SAAS,wBAAA,CACd,MAAA,EACA,aAAA,EACA,SAAA,EACsC;AACtC,EAAA,MAAM,SAAiD,EAAC;AACxD,EAAA,IAAI,aAAA,GAAgB,CAAA;AAEpB,EAAA,MAAM,eAAA,GAGF;AAAA;AAAA,IAEF,CAAC,qBAAqB,IAAI,GAAG,MAAM,CAAC,aAAA,CAAc,aAAA,EAAe,aAAA,CAAc,cAAc,CAAA;AAAA,IAC7F,CAAC,qBAAqB,SAAS,GAAG,MAAM,CAAC,0BAAA,IAA8B,KAAA,CAAS,CAAA;AAAA;AAAA,IAEhF,CAAC,oBAAA,CAAqB,IAAI,GAAG,MAAM;AACjC,MAAA,MAAM,iBAAA,GAAoB,uBAAA;AAAA,QACxB,eAAA;AAAA,QACA,aAAA,CAAc,UAAA;AAAA,QACd,aAAA,CAAc;AAAA,OAChB;AACA,MAAA,OAAO;AAAA,QACL,yBAAA,CAA0B,CAAC,KAAA,KAAU,iBAAA,CAAkB,IAAI,WAAA,CAAY,KAAK,EAAE,KAAK,CAAA;AAAA,QACnF,0BAA0B,CAAC,KAAA,KAAU,KAAA,CAAM,MAAA,GAAS,kBAAkB,YAAY;AAAA,OACpF;AAAA,IACF;AAAA,GACF;AAEA,EAAA,MAAM,mBAAA,GAAsB,CAAC,kBAAA,EAAkD,QAAA,KAAqB;AAClG,IAAA,IAAI,CAAC,MAAA,CAAO,kBAAkB,CAAA,EAAG;AAC/B,MAAA,MAAM,CAAC,OAAA,EAAS,QAAQ,CAAA,GAAI,eAAA,CAAgB,kBAAkB,CAAA,EAAE;AAChE,MAAA,MAAA,CAAO,kBAAkB,CAAA,GAAI;AAAA,QAC3B,OAAA,EAAS,gBAAA,CAAiB,OAAA,EAAS,SAAS,CAAA;AAAA,QAC5C,UAAU,QAAA,IAAY,IAAA,GAAO,gBAAA,CAAiB,QAAA,EAAU,SAAS,CAAA,GAAI,KAAA,CAAA;AAAA,QACrE,WAAW;AAAC,OACd;AAAA,IACF;AACA,IAAA,MAAA,CAAO,kBAAkB,CAAA,CAAE,SAAA,CAAU,IAAA,CAAK,QAAQ,CAAA;AAAA,EACpD,CAAA;AAEA,EAAA,KAAA,IAAS,QAAA,GAAW,CAAA,EAAG,QAAA,GAAW,MAAA,CAAO,QAAQ,QAAA,EAAA,EAAY;AAC3D,IAAA,MAAM,KAAA,GAAQ,OAAO,QAAQ,CAAA;AAC7B,IAAA,IAAI,cAAA,CAAe,KAAK,CAAA,EAAG;AACzB,MAAA,aAAA,EAAA;AAEA,MAAA,MAAM,QAAA,GAAW,cAAA,CAAe,KAAK,CAAA,CAAE,IAAA;AACvC,MAAA,IAAI,QAAA,KAAa,qBAAqB,SAAA,EAAW;AAC/C,QAAA,mBAAA,CAAoB,oBAAA,CAAqB,WAAW,QAAQ,CAAA;AAAA,MAC9D,CAAA,MAAA,IAAW,QAAA,KAAa,oBAAA,CAAqB,IAAA,EAAM;AACjD,QAAA,mBAAA,CAAoB,oBAAA,CAAqB,MAAM,QAAQ,CAAA;AAAA,MACzD,CAAA,MAAA,IACE,KAAA,CAAM,IAAA,KAAS,SAAA,CAAU,MAAA,IACzB,eAAA,CAAgB,KAAA,EAAO,cAAA,CAAe,KAAK,CAAC,CAAA,KAAM,gBAAA,EAClD;AACA,QAAA,mBAAA,CAAoB,oBAAA,CAAqB,MAAM,QAAQ,CAAA;AAAA,MACzD,CAAA,MAAO;AAEL,QAAA,aAAA,EAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,kBAAkB,CAAA,EAAG;AACvB,IAAA,OAAO,KAAA,CAAA;AAAA,EACT;AAEA,EAAA,OAAO,MAAA,CAAO,OAAO,MAAM,CAAA;AAC7B;AAKO,MAAM,8BAAA,GAAiC;AAOvC,SAAS,YAAA,CACd,MAAA,EACA,MAAA,EACA,YAAA,EACA,aAAA,EACA,SAAA,EACA,UAAA,GAAa,KAAA,CAAM,WAAA,EACnB,eAAA,GAAkB,KAAA,CAAM,YAAA,GAAe,CAAA,EAC/B;AACR,EAAA,IAAI,EAAC,uCAAW,MAAA,CAAA,EAAQ;AACtB,IAAA,OAAO,aAAA;AAAA,EACT;AAEA,EAAA,IAAI,SAAA,GAAY,CAAA,CAAA;AAChB,EAAA,IAAI,QAAA,GAAW,EAAA;AACf,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,IAAI,QAAA;AACJ,EAAA,IAAI,eAAA;AAEJ,EAAA,KAAA,MAAW,EAAE,QAAA,EAAU,OAAA,EAAS,SAAA,MAAe,SAAA,EAAW;AAIxD,IAAA,MAAM,WAAY,QAAA,IAAA,IAAA,GAAA,QAAA,GAAY,OAAA;AAC9B,IAAA,MAAM,eAAe,QAAA,KAAa,KAAA,CAAA;AAElC,IAAA,KAAA,MAAW,YAAY,SAAA,EAAW;AAChC,MAAA,MAAM,KAAA,GAAQ,OAAO,QAAQ,CAAA;AAE7B,MAAA,MAAM,YAAA,GAAe,WAAW,CAAA,CAAA,GAAK,cAAA,CAAe,KAAK,CAAA,GAAI,KAAA,CAAM,OAAO,MAAM,CAAA;AAChF,MAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,QAAA,MAAM,QAAA,GAAW,aAAa,QAAQ,CAAA;AACtC,QAAA,MAAM,kBAAkB,QAAA,CAAS,YAAA,EAAc,QAAA,EAAU,KAAA,EAAO,QAAQ,UAAU,CAAA;AAClF,QAAA,IAAI,kBAAkB,SAAA,EAAW;AAC/B,UAAA,SAAA,GAAY,eAAA;AACZ,UAAA,QAAA,GAAW,YAAA;AACX,UAAA,QAAA,GAAW,QAAA;AACX,UAAA,QAAA,GAAW,KAAA;AACX,UAAA,eAAA,GAAkB,eAAe,OAAA,GAAU,KAAA,CAAA;AAAA,QAC7C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAIA,EAAA,IAAI,QAAA,KAAa,KAAA,CAAA,IAAa,SAAA,GAAY,8BAAA,EAAgC;AACxE,IAAA,OAAO,aAAA;AAAA,EACT;AAIA,EAAA,IAAI,oBAAoB,KAAA,CAAA,EAAW;AACjC,IAAA,SAAA,GAAY,eAAA,CAAgB,QAAA,EAAU,QAAA,EAAU,QAAA,EAAU,QAAQ,UAAU,CAAA;AAAA,EAC9E;AAGA,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,SAAA,GAAY,eAAA,EAAiB,aAAa,CAAA;AAC5D;AAMO,SAAS,mBAAmB,KAAA,EAAuB;AACxD,EAAA,MAAM,WAAA,GAAc,eAAe,KAAK,CAAA;AACxC,EAAA,MAAM,gBAAA;AAAA;AAAA;AAAA,IAGH,MAAM,IAAA,KAAS,SAAA,CAAU,MAAA,IAAU,WAAA,CAAY,SAAS,oBAAA,CAAqB,KAAA;AAAA,IAE9E,WAAA,CAAY,SAAS,oBAAA,CAAqB;AAAA,GAAA;AAE5C,EAAA,OAAO,oBAAoB,CAAC,cAAA,CAAe,KAAK,CAAA,IAAK,CAAC,qBAAqB,KAAK,CAAA;AAClF;AAGA,MAAM,eAAA,uBAAsB,GAAA,CAA0B;AAAA,EACpD,oBAAA,CAAqB,IAAA;AAAA,EACrB,oBAAA,CAAqB,SAAA;AAAA,EACrB,oBAAA,CAAqB;AACvB,CAAC,CAAA;AAQM,SAAS,aAAa,KAAA,EAAyB;AAlbtD,EAAA,IAAA,EAAA;AAmbE,EAAA,MAAM,KAAA,GAAA,CAAwC,EAAA,GAAA,KAAA,CAAM,MAAA,CAAO,MAAA,KAAb,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAqB,KAAA;AAEnE,EAAA,IAAI,CAAC,KAAA,IAAS,KAAA,KAAU,MAAA,EAAQ;AAC9B,IAAA,IAAI,eAAA,CAAgB,GAAA,CAAI,cAAA,CAAe,KAAK,CAAA,CAAE,IAAI,CAAA,IAAK,KAAA,CAAM,IAAA,KAAS,SAAA,CAAU,MAAA,EAAQ;AACtF,MAAA,OAAO,OAAA;AAAA,IACT;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,KAAA;AACT;AAMO,SAAS,kBAAkB,SAAA,EAA+C;AAC/E,EAAA,OAAO,SAAA,KAAc,QAAA,GAAW,QAAA,GAAW,SAAA,KAAc,UAAU,UAAA,GAAa,YAAA;AAClF;AAEA,MAAM,oBAAA,GAAuB,EAAE,IAAA,EAAM,oBAAA,CAAqB,IAAA,EAAK;AAOxD,SAAS,eAAe,KAAA,EAAgC;AA9c/D,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AA+cE,EAAA,IAAA,CAAI,EAAA,GAAA,KAAA,CAAM,MAAA,CAAO,MAAA,KAAb,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAqB,WAAA,EAAa;AACpC,IAAA,OAAO,oCAAA,CAAA,CAAqC,EAAA,GAAA,KAAA,CAAM,MAAA,CAAO,MAAA,KAAb,mBAAqB,WAAW,CAAA;AAAA,EAC9E;AAEA,EAAA,OAAA,CAAO,EAAA,GAAA,CAAA,EAAA,GAAA,KAAA,CAAM,MAAA,CAAO,MAAA,KAAb,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAqB,gBAArB,IAAA,GAAA,EAAA,GAAoC,oBAAA;AAC7C;AASO,SAAS,kBAAA,CACd,KAAA,EACA,YAAA,EACA,QAAA,EAC8B;AAjehC,EAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAkeE,EAAA,IAAI,eAAA,GAAA,CAAkB,EAAA,GAAA,KAAA,CAAM,KAAA,KAAN,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAa,gBAAA;AAEnC,EAAA,IAAI,eAAA,EAAiB;AAEnB,IAAA,IAAI,uBAAuB,eAAe,CAAA,CAAE,SAAS,sBAAA,CAAuB,YAAY,EAAE,MAAA,EAAQ;AAChG,MAAA,eAAA,GAAkB,EAAE,GAAG,YAAA,EAAa;AACpC,MAAA,KAAA,CAAM,MAAO,gBAAA,GAAmB,eAAA;AAAA,IAClC;AACA,IAAA,OAAO,eAAA;AAAA,EACT,CAAA,MAAO;AAEL,IAAA,eAAA,GAAkB,EAAE,GAAG,YAAA,EAAa;AACpC,IAAA,MAAM,WAAW,IAAA,CAAK,GAAA,CAAI,MAAM,MAAA,CAAO,MAAA,EAAQ,WAAW,GAAI,CAAA;AAE9D,IAAA,KAAA,IAAS,CAAA,GAAI,QAAA,GAAW,CAAA,EAAG,CAAA,GAAI,UAAU,CAAA,EAAA,EAAK;AAC5C,MAAA,MAAM,gBAAA,GAAA,CAAmB,EAAA,GAAA,CAAA,EAAA,GAAA,KAAA,CAAM,OAAA,KAAN,IAAA,GAAA,KAAA,CAAA,GAAA,EAAA,CAAA,IAAA,CAAA,KAAA,EAAgB,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,CAAA,KAA9B,IAAA,GAAA,EAAA,GAAoC,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA;AAC3E,MAAA,IAAI,uBAAuB,eAAe,CAAA,CAAE,SAAS,sBAAA,CAAuB,gBAAgB,EAAE,MAAA,EAAQ;AACpG,QAAA,eAAA,CAAgB,OAAO,YAAA,CAAa,IAAA;AAAA,MACtC;AAAA,IACF;AAEA,IAAA,IAAI,MAAM,KAAA,EAAO;AACf,MAAA,KAAA,CAAM,MAAM,gBAAA,GAAmB,eAAA;AAAA,IACjC,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,KAAA,GAAQ,EAAE,gBAAA,EAAkB,eAAA,EAAgB;AAAA,IACpD;AAEA,IAAA,OAAO,eAAA;AAAA,EACT;AACF;AAGA,MAAM,+BAAA,GAAkC,EAAA;AACxC,MAAM,kCAAA,GAAqC,CAAA;AAMpC,SAAS,gCAAgC,KAAA,EAAsB;AACpE,EAAA,MAAM,eAAA,GAAkB,QAAQ,CAAC,KAAA,KAAkB,+BAA+B,KAAA,EAA