Examples/Invoke-FileExplorerDemo.ps1
|
Import-Module "$PSScriptRoot/../Elm.psd1" -Force # --------------------------------------------------------------------------- # File explorer demo # Navigate the filesystem with arrow keys, Enter to open directories. # Demonstrates: real PS integration, two-pane layout, scrollable list, # script-scoped item cache (keeps Items out of the model to avoid # expensive JSON roundtrips in Copy-ElmModel on every keypress) # --------------------------------------------------------------------------- # Cache: path -> items array. Never serialized as part of the model. $script:explorerCache = @{} function Get-CachedItems { param([string]$Path) if (-not $script:explorerCache.ContainsKey($Path)) { $parentPath = [System.IO.Path]::GetDirectoryName($Path) $dotDot = if ($null -ne $parentPath -and (Test-Path -LiteralPath $parentPath)) { @([PSCustomObject]@{ Name = '..' FullName = $parentPath PSIsContainer = $true Length = 0L LastWriteTime = '' }) } else { @() } $raw = @(Get-ChildItem -LiteralPath $Path -ErrorAction SilentlyContinue | Sort-Object -Property @{Expression = { $_.PSIsContainer }; Descending = $true }, Name) $entries = @($raw | ForEach-Object { [PSCustomObject]@{ Name = [string]$_.Name FullName = [string]$_.FullName PSIsContainer = [bool]$_.PSIsContainer Length = if ($_.PSIsContainer) { 0L } else { [long]$_.Length } LastWriteTime = $_.LastWriteTime.ToString('yyyy-MM-dd HH:mm') } }) $script:explorerCache[$Path] = @($dotDot) + $entries } $script:explorerCache[$Path] } function Format-ItemSize { param([long]$Bytes) if ($Bytes -ge 1MB) { return '{0:N1} MB' -f ($Bytes / 1MB) } if ($Bytes -ge 1KB) { return '{0:N1} KB' -f ($Bytes / 1KB) } return "$Bytes B" } $visibleCount = 14 $init = { $startPath = (Get-Location).Path $null = Get-CachedItems -Path $startPath [PSCustomObject]@{ Model = [PSCustomObject]@{ Path = $startPath; Cursor = 0; Offset = 0 } Cmd = $null } } $update = { param($msg, $model) $items = @(Get-CachedItems -Path $model.Path) $itemCount = $items.Count switch ($msg.Key) { 'UpArrow' { if ($itemCount -eq 0) { return [PSCustomObject]@{ Model = $model; Cmd = $null } } $newCursor = if ($model.Cursor -gt 0) { $model.Cursor - 1 } else { $model.Cursor } $newOffset = if ($newCursor -lt $model.Offset) { $newCursor } else { $model.Offset } return [PSCustomObject]@{ Model = [PSCustomObject]@{ Path = $model.Path; Cursor = $newCursor; Offset = $newOffset } Cmd = $null } } 'DownArrow' { if ($itemCount -eq 0) { return [PSCustomObject]@{ Model = $model; Cmd = $null } } $newCursor = if ($model.Cursor -lt $itemCount - 1) { $model.Cursor + 1 } else { $model.Cursor } $newOffset = if ($newCursor -ge $model.Offset + $visibleCount) { $newCursor - $visibleCount + 1 } else { $model.Offset } return [PSCustomObject]@{ Model = [PSCustomObject]@{ Path = $model.Path; Cursor = $newCursor; Offset = $newOffset } Cmd = $null } } 'Enter' { if ($itemCount -eq 0) { return [PSCustomObject]@{ Model = $model; Cmd = $null } } $selected = $items[$model.Cursor] if ($selected.PSIsContainer) { $newPath = $selected.FullName $null = Get-CachedItems -Path $newPath return [PSCustomObject]@{ Model = [PSCustomObject]@{ Path = $newPath; Cursor = 0; Offset = 0 } Cmd = $null } } return [PSCustomObject]@{ Model = $model; Cmd = $null } } 'Q' { return [PSCustomObject]@{ Model = $model Cmd = [PSCustomObject]@{ Type = 'Quit' } } } } [PSCustomObject]@{ Model = $model; Cmd = $null } } $view = { param($model) $titleStyle = New-ElmStyle -Foreground 'BrightCyan' -Bold $pathStyle = New-ElmStyle -Foreground 'BrightBlack' $selectedStyle = New-ElmStyle -Foreground 'BrightYellow' -Bold $dirStyle = New-ElmStyle -Foreground 'BrightBlue' $fileStyle = New-ElmStyle -Foreground 'White' $labelStyle = New-ElmStyle -Foreground 'BrightBlack' $valueStyle = New-ElmStyle -Foreground 'BrightWhite' $hintStyle = New-ElmStyle -Foreground 'BrightBlack' $leftStyle = New-ElmStyle -Border 'Normal' -Width 34 -Padding @(0, 1) $rightStyle = New-ElmStyle -Border 'Normal' -Width 'Fill' -Padding @(0, 1) $items = @(Get-CachedItems -Path $model.Path) $itemCount = $items.Count # Left pane: scrollable file list $listNodes = @() if ($itemCount -eq 0) { $listNodes += New-ElmText -Content '(empty)' -Style $labelStyle } else { $end = [Math]::Min($model.Offset + $visibleCount - 1, $itemCount - 1) for ($i = $model.Offset; $i -le $end; $i++) { $item = $items[$i] $isDir = $item.PSIsContainer $rawName = $item.Name $name = if ($rawName.Length -gt 28) { $rawName.Substring(0, 25) + '...' } else { $rawName } $prefix = if ($isDir) { '[>] ' } else { ' ' } $baseStyle = if ($isDir) { $dirStyle } else { $fileStyle } $style = if ($i -eq $model.Cursor) { $selectedStyle } else { $baseStyle } $listNodes += New-ElmText -Content "$prefix$name" -Style $style } } $leftPane = New-ElmBox -Style $leftStyle -Children $listNodes # Right pane: selected item details $rightNodes = @() if ($itemCount -gt 0) { $sel = $items[$model.Cursor] $rightNodes += New-ElmText -Content 'Name' -Style $labelStyle $rightNodes += New-ElmText -Content $sel.Name -Style $valueStyle $rightNodes += New-ElmText -Content '' $rightNodes += New-ElmText -Content 'Type' -Style $labelStyle $rightNodes += New-ElmText -Content $(if ($sel.PSIsContainer) { 'Directory' } else { 'File' }) -Style $valueStyle if (-not $sel.PSIsContainer) { $rightNodes += New-ElmText -Content '' $rightNodes += New-ElmText -Content 'Size' -Style $labelStyle $rightNodes += New-ElmText -Content (Format-ItemSize -Bytes $sel.Length) -Style $valueStyle } if ($sel.LastWriteTime -ne '') { $rightNodes += New-ElmText -Content '' $rightNodes += New-ElmText -Content 'Modified' -Style $labelStyle $rightNodes += New-ElmText -Content $sel.LastWriteTime -Style $valueStyle } } else { $rightNodes += New-ElmText -Content '(no selection)' -Style $labelStyle } $rightPane = New-ElmBox -Style $rightStyle -Children $rightNodes $displayPath = $model.Path if ($displayPath.Length -gt 60) { $displayPath = '...' + $displayPath.Substring($displayPath.Length - 57) } $scrollInfo = if ($itemCount -gt 0) { " ($($model.Cursor + 1)/$itemCount)" } else { '' } New-ElmBox -Children @( New-ElmText -Content 'File Explorer' -Style $titleStyle New-ElmText -Content "$displayPath$scrollInfo" -Style $pathStyle New-ElmRow -Children @($leftPane, $rightPane) New-ElmText -Content '[Up/Down] navigate [Enter] open dir / .. [Q] quit' -Style $hintStyle ) } Start-ElmProgram -InitFn $init -UpdateFn $update -ViewFn $view |