modules/visual_components.ps1
|
<#
.SYNOPSIS PSConsoleUI - Visual Components .DESCRIPTION Visual UI components including lists, trees, charts. #> function Show-ConsoleList { <# .SYNOPSIS Displays a formatted list of items. .DESCRIPTION Writes a list with optional numbering or bullets. .PARAMETER Items Array of items to display. .PARAMETER Numbered If specified, displays numbered list (1., 2., 3.) .PARAMETER Bullet Bullet character to use. Default: '•'. Ignored if -Numbered is used. .PARAMETER Color Color for the list items. Default: White #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [array]$Items, [Parameter(Mandatory = $false)] [switch]$Numbered, [Parameter(Mandatory = $false)] [string]$Bullet = '•', [Parameter(Mandatory = $false)] [string]$Color = 'White', [Parameter(Mandatory = $false)] [int]$Width = 40, [Parameter(Mandatory = $false)] [switch]$ReturnLines ) $lines = @() for ($i = 0; $i -lt $Items.Count; $i++) { $prefix = if ($Numbered) { ($i + 1).ToString() + '. ' } else { $Bullet + ' ' } $fullLine = "$prefix$($Items[$i])" if ($Width -gt 0 -and $fullLine.Length -gt $Width) { $fullLine = $fullLine.Substring(0, $Width) } $lines += $fullLine } if ($ReturnLines) { return $lines } foreach ($l in $lines) { Write-Host " $l" -ForegroundColor $Color } } function Write-ConsoleTree { param( [hashtable]$TreeData, [string]$Indent = ' ' ) $Tee = [string][char]0x251C $HBar = [string][char]0x2500 $VBar = [string][char]0x2502 $Corner = [string][char]0x2514 $branch = $Tee + $HBar + $HBar + ' ' $leaf = $Corner + $HBar + $HBar + ' ' $pipe = $VBar + ' ' foreach ($key in $TreeData.Keys) { Write-Host ($Indent + $branch + $key) -ForegroundColor Cyan $val = $TreeData[$key] if ($val -is [hashtable]) { Write-ConsoleTree -TreeData $val -Indent ($Indent + $pipe) } elseif ($val -is [array] -or $val -is [string]) { foreach ($item in $val) { Write-Host ($Indent + $pipe + $leaf + $item) -ForegroundColor Gray } } } } function Write-ConsoleChart { <# .SYNOPSIS Displays a horizontal bar chart. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Label, [Parameter(Mandatory = $true)] [ValidateRange(0, [int]::MaxValue)] [int]$Value, [Parameter(Mandatory = $false)] [ValidateRange(1, [int]::MaxValue)] [int]$Max = 100, [Parameter(Mandatory = $false)] [string]$Color = 'Green', [Parameter(Mandatory = $false)] [int]$Width = 20, [Parameter(Mandatory = $false)] [switch]$ReturnLines ) $chartWidth = $Width if ($Max -gt 0) { $percent = [Math]::Round(($Value / $Max) * 100) } else { $percent = 0 } $filledLen = [Math]::Floor(($Value / $Max) * $chartWidth) $f = [char]0x2588 # Full block $e = [char]0x2591 # Light shade $filledLen = [Math]::Max(0, [Math]::Min($chartWidth, $filledLen)) $emptyLen = $chartWidth - $filledLen $bar = ([string]$f * $filledLen) + ([string]$e * $emptyLen) $fullLine = $Label + ": " + $percent + "% [" + $bar + "]" if ($ReturnLines) { return @($fullLine) } Write-Host (' ' + $Label) -NoNewline -ForegroundColor White Write-Host (': ' + $percent + '% [' + $bar + ']') -ForegroundColor $Color } function Write-ConsoleSparkline { <# .SYNOPSIS Displays an inline mini-chart for data trends. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [array]$Data, [Parameter(Mandatory = $false)] [string]$Color = 'Cyan', [Parameter(Mandatory = $false)] [switch]$ShowMinMax ) # Sparkline characters (8 levels) $chars = @([char]0x2581, [char]0x2582, [char]0x2583, [char]0x2584, [char]0x2585, [char]0x2586, [char]0x2587, [char]0x2588) # Find min and max for normalization $min = ($Data | Measure-Object -Minimum).Minimum $max = ($Data | Measure-Object -Maximum).Maximum $range = $max - $min if ($range -eq 0) { # All values are the same $sparkline = $chars[4] * $Data.Count } else { $sparkline = "" for ($i = 0; $i -lt $Data.Count; $i++) { $value = $Data[$i] # Normalize to 0-7 range $normalized = [Math]::Floor((($value - $min) / $range) * 7) $normalized = [Math]::Max(0, [Math]::Min(7, $normalized)) if ($ShowMinMax) { if ($value -eq $min) { Write-Host $chars[$normalized] -NoNewline -ForegroundColor Red } elseif ($value -eq $max) { Write-Host $chars[$normalized] -NoNewline -ForegroundColor Green } else { Write-Host $chars[$normalized] -NoNewline -ForegroundColor $Color } } else { $sparkline += $chars[$normalized] } } if (-not $ShowMinMax) { Write-Host (' ' + $sparkline) -ForegroundColor $Color } else { Write-Host '' } } } function Write-ConsolePanel { <# .SYNOPSIS Displays content in a bordered panel. #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [string]$Title = '', [Parameter(Mandatory = $true)] [ValidateNotNull()] [scriptblock]$Content, [Parameter(Mandatory = $false)] [ValidateSet('Single', 'Double', 'Rounded', 'Heavy')] [string]$BorderStyle = 'Single', [Parameter(Mandatory = $false)] [string]$Color = 'Cyan', [Parameter(Mandatory = $false)] [ValidateRange(20, 120)] [int]$Width = 60 ) # Define border characters for each style $borders = @{ 'Single' = @{ TopLeft = [char]0x250C; TopRight = [char]0x2510; BottomLeft = [char]0x2514; BottomRight = [char]0x2518 Horizontal = [char]0x2500; Vertical = [char]0x2502 } 'Double' = @{ TopLeft = [char]0x2554; TopRight = [char]0x2557; BottomLeft = [char]0x255A; BottomRight = [char]0x255D Horizontal = [char]0x2550; Vertical = [char]0x2551 } 'Rounded' = @{ TopLeft = [char]0x256D; TopRight = [char]0x256E; BottomLeft = [char]0x2570; BottomRight = [char]0x256F Horizontal = [char]0x2500; Vertical = [char]0x2502 } 'Heavy' = @{ TopLeft = [char]0x250F; TopRight = [char]0x2513; BottomLeft = [char]0x2517; BottomRight = [char]0x251B Horizontal = [char]0x2501; Vertical = [char]0x2503 } } $b = $borders[$BorderStyle] $innerWidth = $Width - 4 # Top border with title Write-Host '' if ($Title) { $titleLen = $Title.Length + 2 $leftLen = [Math]::Floor(($Width - $titleLen - 2) / 2) $rightLen = $Width - $titleLen - $leftLen - 2 Write-Host (' ' + $b.TopLeft + (([string]$b.Horizontal) * $leftLen) + ' ' + $Title + ' ' + (([string]$b.Horizontal) * $rightLen) + $b.TopRight) -ForegroundColor $Color } else { Write-Host (' ' + $b.TopLeft + (([string]$b.Horizontal) * ($Width - 2)) + $b.TopRight) -ForegroundColor $Color } # Get content lines $contentLines = @() if ($Content -is [scriptblock]) { # Execute scriptblock and capture return values (not Write-Host) $contentLines = @(& $Content) } elseif ($Content -is [array]) { $contentLines = $Content } else { $contentLines = @($Content.ToString()) } # Display content with borders if ($contentLines.Count -eq 0) { Write-Host (' ' + $b.Vertical + (' ' * ($Width - 2)) + $b.Vertical) -ForegroundColor $Color } else { foreach ($line in $contentLines) { $trimmed = $line.ToString().TrimEnd() if ($trimmed.Length -gt $innerWidth) { $trimmed = $trimmed.Substring(0, $innerWidth) } $padding = ' ' * ($innerWidth - $trimmed.Length) Write-Host (' ' + $b.Vertical + ' ') -NoNewline -ForegroundColor $Color Write-Host $trimmed -NoNewline Write-Host (' ' + $padding + $b.Vertical) -ForegroundColor $Color } } # Bottom border Write-Host (' ' + $b.BottomLeft + (([string]$b.Horizontal) * ($Width - 2)) + $b.BottomRight) -ForegroundColor $Color Write-Host '' } function Write-ConsoleBox { <# .SYNOPSIS Displays a message in a bordered box. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Message, [Parameter(Mandatory = $false)] [string]$Color = 'Cyan', [Parameter(Mandatory = $false)] [int]$Width = 0, # Auto-size if 0 [Parameter(Mandatory = $false)] [switch]$ReturnLines ) $boxWidth = if ($Width -gt 0) { $Width } else { $maxLen = 0 $Message.Split("`n") | ForEach-Object { if ($_.Trim().Length -gt $maxLen) { $maxLen = $_.Trim().Length } } $maxLen + 4 } $border = '+' + ('-' * ($boxWidth - 2)) + '+' $lines = @($border) $Message.Split("`n") | ForEach-Object { $msg = $_.Trim() $inner = if ($msg.Length -gt ($boxWidth - 4)) { $msg.Substring(0, $boxWidth - 4) } else { $msg + (' ' * ($boxWidth - 4 - $msg.Length)) } $lines += "| $inner |" } $lines += $border if ($ReturnLines) { return $lines } Write-Host '' foreach ($l in $lines) { Write-Host $l -ForegroundColor $Color } Write-Host '' } # No local Export-ModuleMember here - handled by main module |