158 lines
4.2 KiB
JavaScript
158 lines
4.2 KiB
JavaScript
'use strict'
|
|
const t = require('typical')
|
|
const arrayify = require('array-back')
|
|
const Column = require('./column')
|
|
const wrap = require('wordwrapjs')
|
|
const Cell = require('./cell')
|
|
const ansi = require('./ansi')
|
|
|
|
const _maxWidth = new WeakMap()
|
|
|
|
/**
|
|
* @module columns
|
|
*/
|
|
|
|
class Columns {
|
|
constructor (columns) {
|
|
this.list = []
|
|
arrayify(columns).forEach(this.add.bind(this))
|
|
}
|
|
|
|
/**
|
|
* sum of all generatedWidth fields
|
|
* @return {number}
|
|
*/
|
|
totalWidth () {
|
|
return this.list.length
|
|
? this.list.map(col => col.generatedWidth).reduce((a, b) => a + b)
|
|
: 0
|
|
}
|
|
|
|
totalFixedWidth () {
|
|
return this.getFixed()
|
|
.map(col => col.generatedWidth)
|
|
.reduce((a, b) => a + b, 0)
|
|
}
|
|
|
|
get (columnName) {
|
|
return this.list.find(column => column.name === columnName)
|
|
}
|
|
|
|
getResizable () {
|
|
return this.list.filter(column => column.isResizable())
|
|
}
|
|
|
|
getFixed () {
|
|
return this.list.filter(column => column.isFixed())
|
|
}
|
|
|
|
add (column) {
|
|
const col = column instanceof Column ? column : new Column(column)
|
|
this.list.push(col)
|
|
return col
|
|
}
|
|
|
|
set maxWidth (val) {
|
|
_maxWidth.set(this, val)
|
|
}
|
|
|
|
/**
|
|
* sets `generatedWidth` for each column
|
|
* @chainable
|
|
*/
|
|
autoSize () {
|
|
const maxWidth = _maxWidth.get(this)
|
|
|
|
/* size */
|
|
this.list.forEach(column => {
|
|
column.generateWidth()
|
|
column.generateMinWidth()
|
|
})
|
|
|
|
/* adjust if user set a min or maxWidth */
|
|
this.list.forEach(column => {
|
|
if (t.isDefined(column.maxWidth) && column.generatedWidth > column.maxWidth) {
|
|
column.generatedWidth = column.maxWidth
|
|
}
|
|
|
|
if (t.isDefined(column.minWidth) && column.generatedWidth < column.minWidth) {
|
|
column.generatedWidth = column.minWidth
|
|
}
|
|
})
|
|
|
|
const width = {
|
|
total: this.totalWidth(),
|
|
view: maxWidth,
|
|
diff: this.totalWidth() - maxWidth,
|
|
totalFixed: this.totalFixedWidth(),
|
|
totalResizable: Math.max(maxWidth - this.totalFixedWidth(), 0)
|
|
}
|
|
|
|
/* adjust if short of space */
|
|
if (width.diff > 0) {
|
|
/* share the available space between resizeable columns */
|
|
let resizableColumns = this.getResizable()
|
|
resizableColumns.forEach(column => {
|
|
column.generatedWidth = Math.floor(width.totalResizable / resizableColumns.length)
|
|
})
|
|
|
|
/* at this point, the generatedWidth should never end up bigger than the contentWidth */
|
|
const grownColumns = this.list.filter(column => column.generatedWidth > column.contentWidth)
|
|
const shrunkenColumns = this.list.filter(column => column.generatedWidth < column.contentWidth)
|
|
let salvagedSpace = 0
|
|
grownColumns.forEach(column => {
|
|
const currentGeneratedWidth = column.generatedWidth
|
|
column.generateWidth()
|
|
salvagedSpace += currentGeneratedWidth - column.generatedWidth
|
|
})
|
|
shrunkenColumns.forEach(column => {
|
|
column.generatedWidth += Math.floor(salvagedSpace / shrunkenColumns.length)
|
|
})
|
|
|
|
/* if, after autosizing, we still don't fit within maxWidth then give up */
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Factory method returning all distinct columns from input
|
|
* @param {object[]} - input recordset
|
|
* @return {module:columns}
|
|
*/
|
|
static getColumns (rows) {
|
|
var columns = new Columns()
|
|
arrayify(rows).forEach(row => {
|
|
for (let columnName in row) {
|
|
let column = columns.get(columnName)
|
|
if (!column) {
|
|
column = columns.add({ name: columnName, contentWidth: 0, minContentWidth: 0 })
|
|
}
|
|
let cell = new Cell(row[columnName], column)
|
|
let cellValue = cell.value
|
|
if (ansi.has(cellValue)) {
|
|
cellValue = ansi.remove(cellValue)
|
|
}
|
|
|
|
if (cellValue.length > column.contentWidth) column.contentWidth = cellValue.length
|
|
|
|
let longestWord = getLongestWord(cellValue)
|
|
if (longestWord > column.minContentWidth) {
|
|
column.minContentWidth = longestWord
|
|
}
|
|
if (!column.contentWrappable) column.contentWrappable = wrap.isWrappable(cellValue)
|
|
}
|
|
})
|
|
return columns
|
|
}
|
|
}
|
|
|
|
function getLongestWord (line) {
|
|
const words = wrap.getChunks(line)
|
|
return words.reduce((max, word) => {
|
|
return Math.max(word.length, max)
|
|
}, 0)
|
|
}
|
|
|
|
module.exports = Columns
|