Private/UI/Write-PSMBMenuHeader.ps1
|
function Get-PSMBHeaderModel { <# .SYNOPSIS Builds the interactive header content model from module metadata. #> [CmdletBinding()] [OutputType([PSCustomObject])] param() if (-not $script:PSMBHeaderModelCache) { $moduleVersion = '0.1.0' try { $manifestPath = Join-Path -Path $PSScriptRoot -ChildPath '../../PSModuleBrowser.psd1' if (Test-Path -Path $manifestPath -PathType Leaf) { $manifest = Import-PowerShellDataFile -Path $manifestPath if ($manifest.ModuleVersion) { $moduleVersion = [string]$manifest.ModuleVersion } } } catch { } $script:PSMBHeaderModelCache = [PSCustomObject]@{ Title = 'PSModuleBrowser' Subtitle = 'Browse, Search & Install PowerShell Modules' VersionText = ('v{0}' -f $moduleVersion) RepositoryText = 'github.com/PSModuleBrowser/PSModuleBrowser' } } return $script:PSMBHeaderModelCache } function Get-PSMBHeroLines { <# .SYNOPSIS Returns ASCII art hero lines for the header. .PARAMETER Premium When set, returns Unicode block art for PS 7+ terminals. #> [CmdletBinding()] [OutputType([string[]])] param( [Parameter()] [switch]$Premium ) if ($Premium) { return @( ' ____ ____ __ __ ____ ' '| _ \/ ___|| \/ || __ ) ' '| |_) \___ \| |\/| || _ \ ' '| __/ ___) | | | || |_) |' '|_| |____/|_| |_||____/ ' ) } else { return @( ' ____ ____ __ __ ____ ' '| _ \/ ___|| \/ || __ )' '| |_) \___ \| |\/| || _ \' '| __/ ___) | | | || |_) |' '|_| |____/|_| |_||____/' ) } } function Write-PSMBMenuHeader { <# .SYNOPSIS Displays the PSModuleBrowser interactive console banner. .DESCRIPTION Renders a premium box-drawn header with gradient title on PS 7+, or a plain ASCII border header in hosts without arrow-key support. #> [CmdletBinding()] param() $headerModel = Get-PSMBHeaderModel if (Test-PSMBArrowKeySupport) { Write-PSMBMenuHeaderPremium -HeaderModel $headerModel } else { Write-PSMBMenuHeaderClassic -HeaderModel $headerModel } } function Write-PSMBMenuHeaderPremium { <# .SYNOPSIS Premium box-drawn header with gradient colors for PS 7+. #> [CmdletBinding()] param( [Parameter(Mandatory)] [pscustomobject]$HeaderModel ) $palette = Get-PSMBColorPalette $esc = [char]27 $reset = "${esc}[0m" $innerWidth = Get-PSMBConsoleInnerWidth $fitText = { param([string]$Text) if ($null -eq $Text) { return '' } if ($Text.Length -le $innerWidth) { return $Text } if ($innerWidth -le 3) { return $Text.Substring(0, [Math]::Max(0, $innerWidth)) } return $Text.Substring(0, $innerWidth - 3) + '...' } # Amber/Gold → Rose/Pink gradient $amberRGB = @(168, 213, 162) $roseRGB = @(94, 234, 212) $dimRGB = @(120, 113, 108) $tealRGB = @(94, 234, 212) # Top border $topLine = Get-PSMBGradientLine -Character ([char]0x2500) -Length $innerWidth -StartRGB $amberRGB -EndRGB $roseRGB Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x256D), $topLine, ([char]0x256E)) -NoNewline Write-Host $palette.Reset # Empty line $emptyInner = ' ' * $innerWidth Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x2502), $emptyInner, ([char]0x2502)) -NoNewline Write-Host $palette.Reset # ASCII art title (centered with gradient) $heroArtLines = Get-PSMBHeroLines -Premium $heroArtWidth = ($heroArtLines | Measure-Object -Property Length -Maximum).Maximum foreach ($artLine in $heroArtLines) { $artNormalized = $artLine.PadRight($heroArtWidth) $artClipped = & $fitText $artNormalized $artPad = $innerWidth - $artClipped.Length $artLeft = [Math]::Floor($artPad / 2) $artRight = $artPad - $artLeft $gradientArt = Get-PSMBGradientString -Text $artClipped -StartRGB $amberRGB -EndRGB $roseRGB -Prefix "${esc}[1m" Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.Surface, ([char]0x2502), (' ' * $artLeft), $gradientArt, (' ' * $artRight), $palette.Surface, ([char]0x2502)) -NoNewline Write-Host $palette.Reset } # Brand line $brandText = & $fitText $HeaderModel.Title $brandPad = $innerWidth - $brandText.Length $brandLeft = [Math]::Floor($brandPad / 2) $brandRight = $brandPad - $brandLeft $gradientBrand = Get-PSMBGradientString -Text $brandText -StartRGB $amberRGB -EndRGB $roseRGB -Prefix "${esc}[1m" Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.Surface, ([char]0x2502), (' ' * $brandLeft), $gradientBrand, (' ' * $brandRight), $palette.Surface, ([char]0x2502)) -NoNewline Write-Host $palette.Reset # Subtitle (centered, gradient dim to teal, italic) $subtitleText = & $fitText $HeaderModel.Subtitle $subPad = $innerWidth - $subtitleText.Length $subLeft = [Math]::Floor($subPad / 2) $subRight = $subPad - $subLeft $gradientSub = Get-PSMBGradientString -Text $subtitleText -StartRGB $dimRGB -EndRGB $tealRGB -Prefix "${esc}[3m" Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.Surface, ([char]0x2502), (' ' * $subLeft), $gradientSub, (' ' * $subRight), $palette.Surface, ([char]0x2502)) -NoNewline Write-Host $palette.Reset # Version + repo line $metaText = & $fitText ('{0} | {1}' -f $HeaderModel.VersionText, $HeaderModel.RepositoryText) $metaPad = $innerWidth - $metaText.Length $metaLeft = [Math]::Floor($metaPad / 2) $metaRight = $metaPad - $metaLeft Write-Host (' {0}{1}{2}{3}{4}{5}{6}{7}' -f $palette.Surface, ([char]0x2502), (' ' * $metaLeft), $palette.Dim, $metaText, (' ' * $metaRight), $palette.Surface, ([char]0x2502)) -NoNewline Write-Host $palette.Reset # Empty line Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x2502), $emptyInner, ([char]0x2502)) -NoNewline Write-Host $palette.Reset # Separator (continues the box — used before menu content) $sepLine = Get-PSMBGradientLine -Character ([char]0x2500) -Length $innerWidth -StartRGB $amberRGB -EndRGB $roseRGB Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x251C), $sepLine, ([char]0x2524)) -NoNewline Write-Host $palette.Reset } function Write-PSMBMenuHeaderClassic { <# .SYNOPSIS Classic ASCII header for non-interactive hosts. #> [CmdletBinding()] param( [Parameter(Mandatory)] [pscustomobject]$HeaderModel ) $maxWidth = [Math]::Min((Get-PSMBConsoleInnerWidth), 100) if ($maxWidth -lt 40) { $maxWidth = 40 } $fitText = { param([string]$Text) if ($null -eq $Text) { return '' } if ($Text.Length -le $maxWidth) { return $Text } if ($maxWidth -le 3) { return $Text.Substring(0, [Math]::Max(0, $maxWidth)) } return $Text.Substring(0, $maxWidth - 3) + '...' } $centerText = { param([string]$Text) $clipped = & $fitText $Text if ($clipped.Length -ge $maxWidth) { return $clipped } $left = [Math]::Floor(($maxWidth - $clipped.Length) / 2) $right = $maxWidth - $clipped.Length - $left return (' ' * $left) + $clipped + (' ' * $right) } $heroArtLines = Get-PSMBHeroLines $heroArtWidth = ($heroArtLines | Measure-Object -Property Length -Maximum).Maximum $lines = [System.Collections.ArrayList]::new() foreach ($heroLine in $heroArtLines) { $null = $lines.Add((& $centerText $heroLine.PadRight($heroArtWidth))) } $null = $lines.Add((& $centerText $HeaderModel.Title)) $null = $lines.Add((& $centerText $HeaderModel.Subtitle)) $null = $lines.Add('') $null = $lines.Add((& $centerText ('{0} | {1}' -f $HeaderModel.VersionText, $HeaderModel.RepositoryText))) $border = '=' * ($maxWidth + 2) $shadowBorder = '-' * ($maxWidth + 2) Write-Host '' Write-Host (' {0}' -f $shadowBorder) -ForegroundColor DarkGray Write-Host (' {0}' -f $border) -ForegroundColor Yellow foreach ($line in $lines) { Write-Host (' |{0}|' -f $line) -ForegroundColor Yellow } Write-Host (' {0}' -f $border) -ForegroundColor Yellow Write-Host (' {0}' -f $shadowBorder) -ForegroundColor DarkGray Write-Host '' } |