Private/RenderTable.ps1
|
function Render-InTUITable { <# .SYNOPSIS Renders a table with column headers, auto-calculated widths, markup-aware cells, and box borders. Replaces Format-SpectreTable. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string[]]$Columns, [Parameter(Mandatory)] [array]$Rows, [Parameter()] [string]$BorderColor = 'Blue' ) $palette = Get-InTUIColorPalette $reset = $palette.Reset $innerWidth = Get-InTUIConsoleInnerWidth $borderAnsi = switch ($BorderColor.ToLower()) { 'blue' { $palette.Blue } 'green' { $palette.Green } 'red' { $palette.Red } 'yellow' { $palette.Yellow } 'cyan' { $palette.Cyan } default { $palette.Blue } } $colCount = $Columns.Count # Calculate column widths based on header and data (using display width) $colWidths = [int[]]::new($colCount) for ($c = 0; $c -lt $colCount; $c++) { $colWidths[$c] = Measure-InTUIDisplayWidth -Text (Strip-InTUIMarkup -Text $Columns[$c]) } foreach ($row in $Rows) { for ($c = 0; $c -lt $colCount; $c++) { $cellText = if ($c -lt $row.Count) { [string]$row[$c] } else { '' } $plainLen = Measure-InTUIDisplayWidth -Text (Strip-InTUIMarkup -Text $cellText) if ($plainLen -gt $colWidths[$c]) { $colWidths[$c] = $plainLen } } } # Clamp total width $separators = ($colCount - 1) * 3 # " | " between columns $borders = 4 # "| " + " |" $totalContent = ($colWidths | Measure-Object -Sum).Sum + $separators + $borders $maxWidth = $innerWidth if ($totalContent -gt $maxWidth) { # Proportionally shrink columns $available = $maxWidth - $separators - $borders $currentTotal = ($colWidths | Measure-Object -Sum).Sum for ($c = 0; $c -lt $colCount; $c++) { $colWidths[$c] = [Math]::Max(4, [int]($colWidths[$c] * $available / $currentTotal)) } } $tableWidth = ($colWidths | Measure-Object -Sum).Sum + $separators + $borders # Box chars $topLeft = [char]0x256D $topRight = [char]0x256E $bottomLeft = [char]0x2570 $bottomRight = [char]0x256F $horizontal = [char]0x2500 $vertical = [char]0x2502 # Title $plainTitle = Strip-InTUIMarkup -Text $Title $ansiTitle = ConvertFrom-InTUIMarkup -Text $Title $titleDisplayWidth = Measure-InTUIDisplayWidth -Text $plainTitle $titleLineLen = $tableWidth - 4 - $titleDisplayWidth $leftLine = [Math]::Max(1, [int]([Math]::Floor($titleLineLen / 2))) $rightLine = [Math]::Max(1, $titleLineLen - $leftLine) Write-Host "$borderAnsi$topLeft$([string]::new($horizontal, $leftLine))$reset $ansiTitle $borderAnsi$([string]::new($horizontal, $rightLine))$topRight$reset" # Header row $headerCells = @() for ($c = 0; $c -lt $colCount; $c++) { $plainCol = Strip-InTUIMarkup -Text $Columns[$c] $colDisplayWidth = Measure-InTUIDisplayWidth -Text $plainCol $pad = [Math]::Max(0, $colWidths[$c] - $colDisplayWidth) $headerCells += "$($palette.Bold)$($palette.White)$plainCol$(' ' * $pad)$reset" } $headerLine = $headerCells -join " $($palette.Dim)$vertical$reset " Write-Host "$borderAnsi$vertical$reset $headerLine $borderAnsi$vertical$reset" # Header underline $underlineParts = @() for ($c = 0; $c -lt $colCount; $c++) { $underlineParts += [string]::new($horizontal, $colWidths[$c]) } $underline = $underlineParts -join "$horizontal$([char]0x253C)$horizontal" Write-Host "$borderAnsi$vertical$reset$horizontal$underline$horizontal$borderAnsi$vertical$reset" # Data rows foreach ($row in $Rows) { $cells = @() for ($c = 0; $c -lt $colCount; $c++) { $cellText = if ($c -lt $row.Count) { [string]$row[$c] } else { '' } $plainCell = Strip-InTUIMarkup -Text $cellText $ansiCell = ConvertFrom-InTUIMarkup -Text $cellText $cellDisplayWidth = Measure-InTUIDisplayWidth -Text $plainCell if ($cellDisplayWidth -gt $colWidths[$c]) { # Walk the plain text to find the cut point $cutLen = 0 $cutWidth = 0 $maxCut = $colWidths[$c] - 3 for ($ci = 0; $ci -lt $plainCell.Length; $ci++) { $charWidth = Measure-InTUIDisplayWidth -Text ([string]$plainCell[$ci]) if (($cutWidth + $charWidth) -gt $maxCut) { break } $cutWidth += $charWidth $cutLen++ } $plainCell = $plainCell.Substring(0, [Math]::Max(1, $cutLen)) + '...' $ansiCell = $plainCell $cellDisplayWidth = $cutWidth + 3 } $pad = [Math]::Max(0, $colWidths[$c] - $cellDisplayWidth) $cells += "$ansiCell$reset$(' ' * $pad)" } $rowLine = $cells -join " $($palette.Dim)$vertical$reset " Write-Host "$borderAnsi$vertical$reset $rowLine $borderAnsi$vertical$reset" } # Bottom border Write-Host "$borderAnsi$bottomLeft$([string]::new($horizontal, ($tableWidth - 2)))$bottomRight$reset" } function Show-InTUISortableTable { <# .SYNOPSIS Wraps Render-InTUITable with interactive column sorting. Press S to sort, then select a column. Toggles asc/desc. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [string[]]$Columns, [Parameter(Mandatory)] [array]$Rows, [Parameter()] [string]$BorderColor = 'Blue' ) $sortColIndex = -1 $sortAsc = $true $currentRows = $Rows $currentColumns = $Columns.Clone() while ($true) { Clear-Host Show-InTUIHeader Render-InTUITable -Title $Title -Columns $currentColumns -Rows $currentRows -BorderColor $BorderColor Write-InTUIText "[grey]S: Sort | Any other key: continue[/]" $keyInfo = [Console]::ReadKey($true) if ($keyInfo.KeyChar -eq 's' -or $keyInfo.KeyChar -eq 'S') { $colChoices = @($Columns) + @('Cancel') $colSelection = Show-InTUIMenu -Title "[blue]Sort by column[/]" -Choices $colChoices if ($colSelection -ne 'Cancel') { $newIndex = [Array]::IndexOf($Columns, $colSelection) if ($newIndex -ge 0) { if ($newIndex -eq $sortColIndex) { $sortAsc = -not $sortAsc } else { $sortColIndex = $newIndex $sortAsc = $true } # Sort rows by the selected column (strip markup for comparison) $currentRows = @($Rows | Sort-Object { $val = if ($sortColIndex -lt $_.Count) { [string]$_[$sortColIndex] } else { '' } $plain = Strip-InTUIMarkup -Text $val # Try numeric sort $num = 0 if ([double]::TryParse(($plain -replace '[%,]', ''), [ref]$num)) { $num } else { $plain } } -Descending:(-not $sortAsc)) # Update column headers with sort indicator $currentColumns = $Columns.Clone() $arrow = if ($sortAsc) { [char]0x25B2 } else { [char]0x25BC } $currentColumns[$sortColIndex] = "$($Columns[$sortColIndex]) $arrow" } } } else { break } } } |