UNPKG

@discoveryjs/discovery

Version:

Frontend framework for rapid data (JSON) analysis, shareable serverless reports and dashboards

236 lines (191 loc) 6.84 kB
import { isArray, isSet } from '../core/utils/is-type.js'; import { hasOwn } from '../core/utils/object-utils.js'; import usage from './table.usage.js'; function configFromName(name, query) { return { header: name, view: 'table-cell', data: query }; } function resolveColConfig(name, config, dataQuery) { if (typeof config === 'string') { config = { content: config }; } return hasOwn(config, 'content') || hasOwn(config, 'data') ? { header: name, view: 'table-cell', ...config } : { ...configFromName(name, dataQuery), ...config }; } export default function(host) { const isNotObject = host.queryFn('is not object'); host.textView.define('table', async function(node, config, data, context) { let { rows, cols, limit = 25, valueCol = false } = config; let moreCount = 0; if ('rows' in config === false) { rows = data; } if (isSet(rows)) { rows = [...rows]; } if (!isArray(rows)) { rows = rows ? [rows] : []; } if (limit !== false && rows.length > limit) { moreCount = rows.length - limit; rows = rows.slice(0, limit); } const headerCells = []; if (Array.isArray(cols)) { cols = cols.map((def, idx) => typeof def === 'string' ? configFromName(def, host.pathToQuery([def])) : { header: 'col' + idx, view: 'table-cell', ...def } ); } else { const colNames = new Set(); const colsMap = cols && typeof cols === 'object' ? cols : {}; cols = []; for (const value of rows) { if (isNotObject(value)) { valueCol = true; } else { for (const key of Object.keys(value)) { colNames.add(key); } } } for (const key of Object.keys(colsMap)) { if (colsMap[key]) { colNames.add(key); } else { colNames.delete(key); } } for (const name of colNames) { cols.push( hasOwn(colsMap, name) ? resolveColConfig(name, colsMap[name], host.pathToQuery([name])) : configFromName(name, host.pathToQuery([name])) ); } } if (valueCol) { cols.unshift({ header: '[value]', view: 'table-cell', details: false }); } cols = cols.filter(col => !hasOwn(col, 'colWhen') || host.queryBool(col.colWhen, data, context) ); if (!cols.length) { return node.appendText('\n<empty table>\n'); } const renderedCells = Array.from(rows, () => []); const colWidths = []; for (const col of cols) { const headerText = String(col.header).trim(); const headerWidth = headerText.length; let colWidth = headerWidth; for (let i = 0; i < rows.length; i++) { const cellTree = await this.render(null, col, rows[i], context); const cellWidth = host.textView.textWidth(cellTree); colWidth = Math.max(colWidth, cellWidth); renderedCells[i].push({ width: cellWidth, align: col.align, content: cellTree }); } colWidths.push(colWidth); headerCells.push({ width: headerWidth, content: await this.render(null, 'text', headerText) }); } drawLine(colWidths, '┌', '┬', '┐'); drawRow(colWidths, headerCells, true); drawLine(colWidths, '├', '┼', '┤'); for (const row of renderedCells) { drawRow(colWidths, row); } if (moreCount > 0) { const tableWidth = colWidths.reduce((w, cw) => w + cw + 3, 0) + 1; const text = ` ${moreCount} more rows… `; const pad = Math.max(1, tableWidth - text.length); node.appendLine().appendText(`${'~'.repeat(Math.floor(1))}${text}${'~'.repeat(Math.ceil(pad - 1))}`); } else { drawLine(colWidths, '└', '┴', '┘'); } function drawLine(colWidths, s, m, e) { let line = s + '─'.repeat((colWidths[0] || 0) + 2); for (let i = 1; i < colWidths.length; i++) { line += m + '─'.repeat(colWidths[i] + 2); } node.appendLine().appendText(line + e); } function drawRow(maxFieldLen, cells) { const line = node.appendLine(); for (let i = 0; i < cells.length; i++) { const { width, align, content } = cells[i]; const pad = maxFieldLen[i] - width + 1; if (pad > 0 && align !== 'right') { line.appendText('│ '); } else { line.appendText('│' + ' '.repeat(pad)); } line.append(content); if (pad > 0 && align === 'right') { line.appendText(' '); } else { line.appendText(' '.repeat(pad)); } } line.appendText('│'); } }, { type: 'block', usage }); host.textView.define('table-cell', function(node, config, data, context) { let { content, contentWhen = true } = config; const isDataObject = !content && data !== null && (Array.isArray(data) ? data.length > 0 : typeof data === 'object') && data instanceof RegExp === false; if (!host.queryBool(contentWhen, data, context)) { return; } if (content) { return host.textView.render(node, content, data, context); } node.appendText(defaultCellRender(data, isDataObject)); }); } function defaultCellRender(data, isDataObject) { if (Array.isArray(data) || ArrayBuffer.isView(data)) { return data.length || ''; } if (isDataObject) { for (let k in data) { if (hasOwn(data, k)) { return '{…}'; } } return '{}'; } if (data === undefined) { return ''; } return String(data); }