Skip to content

Commit

Permalink
Merge b9db077 into d58adb1
Browse files Browse the repository at this point in the history
  • Loading branch information
nam-hle committed Nov 27, 2021
2 parents d58adb1 + b9db077 commit 2dcdfb8
Show file tree
Hide file tree
Showing 46 changed files with 2,069 additions and 544 deletions.
2 changes: 2 additions & 0 deletions .README/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@

{"gitdown": "include", "file": "./api/table/header.md"}

{"gitdown": "include", "file": "./api/table/spanningCells.md"}

{"gitdown": "include", "file": "./api/stream/index.md"}

{"gitdown": "include", "file": "./api/getBorderCharacters.md"}
2 changes: 2 additions & 0 deletions .README/api/table/header.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Type: `object`

Header configuration.

*Deprecated in favor of the new spanning cells API.*

The header configuration inherits the most of the column's, except:
- `content` **{string}**: the header content.
- `width:` calculate based on the content width automatically.
Expand Down
73 changes: 73 additions & 0 deletions .README/api/table/spanningCells.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
##### config.spanningCells

Type: `SpanningCellConfig[]`

Spanning cells configuration.

The configuration should be straightforward: just specify an array of minimal cell configurations including the position of top-left cell
and the number of columns and/or rows will be expanded from it.

The content of overlap cells will be ignored to make the `data` shape be consistent.

By default, the configuration of column that the top-left cell belongs to will be applied to the whole spanning cell, except:
* The `width` will be summed up of all spanning columns.
* The `paddingRight` will be received from the right-most column intentionally.

Advances customized column-like styles can be configurable to each spanning cell to overwrite the default behavior.

```js
const data = [
['Test Coverage Report', '', '', '', '', ''],
['Module', 'Component', 'Test Cases', 'Failures', 'Durations', 'Success Rate'],
['Services', 'User', '50', '30', '3m 7s', '60.0%'],
['', 'Payment', '100', '80', '7m 15s', '80.0%'],
['Subtotal', '', '150', '110', '10m 22s', '73.3%'],
['Controllers', 'User', '24', '18', '1m 30s', '75.0%'],
['', 'Payment', '30', '24', '50s', '80.0%'],
['Subtotal', '', '54', '42', '2m 20s', '77.8%'],
['Total', '', '204', '152', '12m 42s', '74.5%'],
];

const config = {
columns: [
{ alignment: 'center', width: 12 },
{ alignment: 'center', width: 10 },
{ alignment: 'right' },
{ alignment: 'right' },
{ alignment: 'right' },
{ alignment: 'right' }
],
spanningCells: [
{ col: 0, row: 0, colSpan: 6 },
{ col: 0, row: 2, rowSpan: 2, verticalAlignment: 'middle'},
{ col: 0, row: 4, colSpan: 2, alignment: 'right'},
{ col: 0, row: 5, rowSpan: 2, verticalAlignment: 'middle'},
{ col: 0, row: 7, colSpan: 2, alignment: 'right' },
{ col: 0, row: 8, colSpan: 2, alignment: 'right' }
],
};

console.log(table(data, config));
```

```
╔══════════════════════════════════════════════════════════════════════════════╗
║ Test Coverage Report ║
╟──────────────┬────────────┬────────────┬──────────┬───────────┬──────────────╢
║ Module │ Component │ Test Cases │ Failures │ Durations │ Success Rate ║
╟──────────────┼────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ User │ 50 │ 30 │ 3m 7s │ 60.0% ║
║ Services ├────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ Payment │ 100 │ 80 │ 7m 15s │ 80.0% ║
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
║ Subtotal │ 150 │ 110 │ 10m 22s │ 73.3% ║
╟──────────────┬────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ User │ 24 │ 18 │ 1m 30s │ 75.0% ║
║ Controllers ├────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ Payment │ 30 │ 24 │ 50s │ 80.0% ║
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
║ Subtotal │ 54 │ 42 │ 2m 20s │ 77.8% ║
╟───────────────────────────┼────────────┼──────────┼───────────┼──────────────╢
║ Total │ 204 │ 152 │ 12m 42s │ 74.5% ║
╚═══════════════════════════╧════════════╧══════════╧═══════════╧══════════════╝
```
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,8 @@ Type: `object`

Header configuration.

*Deprecated in favor of the new spanning cells API.*

The header configuration inherits the most of the column's, except:
- `content` **{string}**: the header content.
- `width:` calculate based on the content width automatically.
Expand Down Expand Up @@ -589,6 +591,82 @@ console.log(table(data, config));
```


<a name="table-api-table-1-config-spanningcells"></a>
##### config.spanningCells

Type: `SpanningCellConfig[]`

Spanning cells configuration.

The configuration should be straightforward: just specify an array of minimal cell configurations including the position of top-left cell
and the number of columns and/or rows will be expanded from it.

The content of overlap cells will be ignored to make the `data` shape be consistent.

By default, the configuration of column that the top-left cell belongs to will be applied to the whole spanning cell, except:
* The `width` will be summed up of all spanning columns.
* The `paddingRight` will be received from the right-most column intentionally.

Advances customized column-like styles can be configurable to each spanning cell to overwrite the default behavior.

```js
const data = [
['Test Coverage Report', '', '', '', '', ''],
['Module', 'Component', 'Test Cases', 'Failures', 'Durations', 'Success Rate'],
['Services', 'User', '50', '30', '3m 7s', '60.0%'],
['', 'Payment', '100', '80', '7m 15s', '80.0%'],
['Subtotal', '', '150', '110', '10m 22s', '73.3%'],
['Controllers', 'User', '24', '18', '1m 30s', '75.0%'],
['', 'Payment', '30', '24', '50s', '80.0%'],
['Subtotal', '', '54', '42', '2m 20s', '77.8%'],
['Total', '', '204', '152', '12m 42s', '74.5%'],
];

const config = {
columns: [
{ alignment: 'center', width: 12 },
{ alignment: 'center', width: 10 },
{ alignment: 'right' },
{ alignment: 'right' },
{ alignment: 'right' },
{ alignment: 'right' }
],
spanningCells: [
{ col: 0, row: 0, colSpan: 6 },
{ col: 0, row: 2, rowSpan: 2, verticalAlignment: 'middle'},
{ col: 0, row: 4, colSpan: 2, alignment: 'right'},
{ col: 0, row: 5, rowSpan: 2, verticalAlignment: 'middle'},
{ col: 0, row: 7, colSpan: 2, alignment: 'right' },
{ col: 0, row: 8, colSpan: 2, alignment: 'right' }
],
};

console.log(table(data, config));
```

```
╔══════════════════════════════════════════════════════════════════════════════╗
║ Test Coverage Report ║
╟──────────────┬────────────┬────────────┬──────────┬───────────┬──────────────╢
║ Module │ Component │ Test Cases │ Failures │ Durations │ Success Rate ║
╟──────────────┼────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ User │ 50 │ 30 │ 3m 7s │ 60.0% ║
║ Services ├────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ Payment │ 100 │ 80 │ 7m 15s │ 80.0% ║
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
║ Subtotal │ 150 │ 110 │ 10m 22s │ 73.3% ║
╟──────────────┬────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ User │ 24 │ 18 │ 1m 30s │ 75.0% ║
║ Controllers ├────────────┼────────────┼──────────┼───────────┼──────────────╢
║ │ Payment │ 30 │ 24 │ 50s │ 80.0% ║
╟──────────────┴────────────┼────────────┼──────────┼───────────┼──────────────╢
║ Subtotal │ 54 │ 42 │ 2m 20s │ 77.8% ║
╟───────────────────────────┼────────────┼──────────┼───────────┼──────────────╢
║ Total │ 204 │ 152 │ 12m 42s │ 74.5% ║
╚═══════════════════════════╧════════════╧══════════╧═══════════╧══════════════╝
```


<a name="table-api-createstream"></a>
### createStream

Expand Down
66 changes: 66 additions & 0 deletions src/alignSpanningCell.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
alignString,
} from './alignString';
import {
padCellVertically,
} from './mapDataUsingRowHeights';
import {
padString,
} from './padTableData';
import type {
SpanningCellContext,
} from './spanningCellManager';
import {
truncateString,
} from './truncateTableData';
import type {
RangeConfig,
} from './types/internal';
import {
sequence, sumArray,
} from './utils';
import {
wrapCell,
} from './wrapCell';

/**
* Fill content into all cells in range in order to calculate total height
*/
export const wrapRangeContent = (rangeConfig: RangeConfig, rangeWidth: number, context: SpanningCellContext): string[] => {
const {topLeft, paddingRight, paddingLeft, truncate, wrapWord, alignment} = rangeConfig;

const originalContent = context.rows[topLeft.row][topLeft.col];
const contentWidth = rangeWidth - paddingLeft - paddingRight;

return wrapCell(truncateString(originalContent, truncate), contentWidth, wrapWord).map((line) => {
const alignedLine = alignString(line, contentWidth, alignment);

return padString(alignedLine, paddingLeft, paddingRight);
});
};

export const alignVerticalRangeContent = (range: RangeConfig, content: string[], context: SpanningCellContext) => {
const {rows, drawHorizontalLine, rowHeights} = context;
const {topLeft, bottomRight, verticalAlignment} = range;

// They are empty before calculateRowHeights function run
if (rowHeights.length === 0) {
return [];
}

const totalCellHeight = sumArray(rowHeights.slice(topLeft.row, bottomRight.row + 1));
const totalBorderHeight = bottomRight.row - topLeft.row;
const hiddenHorizontalBorderCount = sequence(topLeft.row + 1, bottomRight.row).filter((horizontalBorderIndex) => {
return !drawHorizontalLine(horizontalBorderIndex, rows.length);
}).length;

const availableRangeHeight = totalCellHeight + totalBorderHeight - hiddenHorizontalBorderCount;

return padCellVertically(content, availableRangeHeight, verticalAlignment).map((line) => {
if (line.length === 0) {
return ' '.repeat(content[0].length);
}

return line;
});
};
8 changes: 7 additions & 1 deletion src/alignTableData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import type {
} from './types/internal';

export const alignTableData = (rows: Row[], config: BaseConfig): Row[] => {
return rows.map((row) => {
return rows.map((row, rowIndex) => {
return row.map((cell, cellIndex) => {
const {width, alignment} = config.columns[cellIndex];

const containingRange = config.spanningCellManager?.getContainingRange({col: cellIndex,
row: rowIndex}, {mapped: true});
if (containingRange) {
return cell;
}

return alignString(cell, width, alignment);
});
});
Expand Down
15 changes: 0 additions & 15 deletions src/calculateCellWidths.ts

This file was deleted.

23 changes: 0 additions & 23 deletions src/calculateColumnWidths.ts

This file was deleted.

42 changes: 42 additions & 0 deletions src/calculateMaximumColumnWidths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import stringWidth from 'string-width';
import type {
SpanningCellConfig,
} from './types/api';
import type {
Row,
Cell,
} from './types/internal';
import {
calculateRangeCoordinate, isCellInRange,
} from './utils';

export const calculateMaximumCellWidth = (cell: Cell): number => {
return Math.max(
...cell.split('\n').map(stringWidth),
);
};

/**
* Produces an array of values that describe the largest value length (width) in every column.
*/
export const calculateMaximumColumnWidths = (rows: Row[], spanningCellConfigs: SpanningCellConfig[] = []): number[] => {
const columnWidths = new Array(rows[0].length).fill(0);
const rangeCoordinates = spanningCellConfigs.map(calculateRangeCoordinate);
const isSpanningCell = (rowIndex: number, columnIndex: number): boolean => {
return rangeCoordinates.some((rangeCoordinate) => {
return isCellInRange({col: columnIndex,
row: rowIndex}, rangeCoordinate);
});
};

rows.forEach((row, rowIndex) => {
row.forEach((cell, cellIndex) => {
if (isSpanningCell(rowIndex, cellIndex)) {
return;
}
columnWidths[cellIndex] = Math.max(columnWidths[cellIndex], calculateMaximumCellWidth(cell));
});
});

return columnWidths;
};
9 changes: 9 additions & 0 deletions src/calculateOutputColumnWidths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type {
TableConfig,
} from './types/internal';

export const calculateOutputColumnWidths = (config: TableConfig): number[] => {
return config.columns.map((col) => {
return col.paddingLeft + col.width + col.paddingRight;
});
};

0 comments on commit 2dcdfb8

Please sign in to comment.