Scripts/Example.ps1

Remove-Module -Name CliScrollMenu -ErrorAction SilentlyContinue; Import-Module -Name CliScrollMenu -Force -Verbose:$true

$global:Props = @{
    SelectedItem = $null
}
$global:Display = @{
    Selected = "No item selected"
}

function Write-Log {
    <#
    .SYNOPSIS
    Writes a message to the log file with a timestamp.
    .DESCRIPTION
    Writes a message to the log file with a timestamp. This is useful for recording important events or user actions for later review.
    .PARAMETER Message
    The message to write to the log file.
    .EXAMPLE
    Write-Log "User selected an item."
    Writes "User selected an item." to the log file with a timestamp.
    #>

    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$Message
    )

    process {
        $logFile = "cliScrollMenu.log"
        $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        "$timestamp - $Message" | Add-Content -Path $logFile
    }
}
Function Get-ConsoleWidth
{
  <#
    .SYNOPSIS
    Returns the width of the current PowerShell Console
    .DESCRIPTION
    Returns the width of the current PowerShell Console
    .EXAMPLE
    $width = Get-ConsoleWidth
    Stores the Console Width into the $width variable.
  #>

  $width = (Get-Host).UI.RawUI.WindowSize.Width
  if ($width -le 0) {
    $width = 80  # Default fallback width
  }
  return $width
}

Function Get-ConsoleHeight
{
    <#
        .SYNOPSIS
        Returns the height of the current PowerShell Console
        .DESCRIPTION
        Returns the height of the current PowerShell Console
        .EXAMPLE
        $height = Get-ConsoleHeight
        Stores the Console Height into the $height variable.
    #>

    $height = (Get-Host).UI.RawUI.WindowSize.Height
    if ($height -le 0) {
        $height = 25  # Default fallback height
    }
    return $height
}

Function Write-Both {
    <#
    .SYNOPSIS
    Writes a message to both the console and the log file.
    .DESCRIPTION
    Writes a message to both the console and the log file. This is useful for ensuring important messages are visible to the user and also recorded in the log for later review.
    .PARAMETER Message
    The message to write to the console and log file.
    .EXAMPLE
    Write-Both "This is a test message."
    Writes "This is a test message." to both the console and the log file.
    #>

    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]$Message
    )

    process {
        Write-Log $Message
        Write-Output $Message
    }
}

Function Invoke-FileBrowser {
    <#
    .SYNOPSIS
    A simple file browser implemented using Show-ListMenu to navigate through directories and select files.
    .DESCRIPTION
    This function allows the user to browse through the file system starting from a specified path. It
    displays the contents of the current directory as a menu, allowing the user to navigate and select a file or folder.
    .PARAMETER Path
    The initial path to start browsing from. This can be any valid directory path.
    .EXAMPLE
    Invoke-FileBrowser -Path "C:\Users\Example\Documents"
    Starts the file browser at the "C:\Users\Example\Documents" directory, allowing
    the user to navigate and select files or folders within that directory.
    #>

    param(
        [string]$Path
    )

    $files = @(Get-ChildItem -Path $Path -ErrorAction SilentlyContinue)
    if ($files.Count -eq 0) {
                Write-Host "Downloads folder is empty" -ForegroundColor Yellow
                return
    }
    $currentPath = $Path
    $lastResult = ""
    while ($true) {
        $parentPath = Split-Path -Path $currentPath -Parent
        $items = [array]@()
        #add all $files as items too
        foreach ($file in $files) {
            $items += [pscustomobject]@{Name=""; DisplayName=$file.Name; Value=$file }
        }
        $items += @(
            [pscustomobject]@{Name=""; DisplayName=". (Select Current Folder)"; Value=$currentPath ; DisableMultiSelect=$true},
            [pscustomobject]@{Name=""; DisplayName=".. (Go Up to Parent Folder)"; Value=$parentPath ; DisableMultiSelect=$true},
            [pscustomobject]@{Name=""; DisplayName="Exit Browser"; Value="Exit Browser" ; DisableMultiSelect=$true}
        )
        $selected = Show-ListMenu -MenuItems $items -MenuName $currentPath -Force -Multi -LastResult $lastResult
        if ($selected -is [array]) {
            $lastResult = "Multiple selection is not supported in this file browser."
            continue
        }
        elseif ($selected -is [System.IO.DirectoryInfo] -or $selected -eq $parentPath) {
            $currentPath = if ($selected -is [System.IO.DirectoryInfo]) { $selected.FullName } else { $parentPath }
            $files = @(Get-ChildItem -Path $currentPath -ErrorAction SilentlyContinue)
            if ($items.Count -eq 0) {
                $lastResult = "This folder is empty"
                continue
            }
        }
        elseif ($selected -eq "Exit Browser") {
            break
        }
        else {
            if ($selected -is [string]) {
                $selected = Get-Item -Path $currentPath
            }
            Write-Output $selected
            break
        }
    }
}

Function Update-SelectedItem {
    <#
    .SYNOPSIS
    Updates the selected item in the properties and display objects.
    .DESCRIPTION
    This function updates the selected item in the properties and display objects, ensuring that the UI reflects the currently selected item.
    .PARAMETER Props
    The properties object containing the selected item.
    .PARAMETER Display
    The display object containing the selected item information.
    .PARAMETER SelectedItem
    The currently selected item.
    #>

    param(
        [PSCustomObject]$SelectedItem
    )

    if ($SelectedItem) {
        $global:Props.SelectedItem = $SelectedItem
        $global:Display.Selected = $SelectedItem.FullName
    }
    else {
        $global:Display.Selected = "No item selected"
    }
}

function Add-BigDisplayItem {
    <#
    .SYNOPSIS
    Adds a big display item to the menu, which is shown in the footer area.
    .DESCRIPTION
    This function adds a big display item to the menu, which is shown in the footer area. It can be used to display important information or status updates to the user.
    .PARAMETER Name
    The name of the display item, used for reference in the display object.
    .PARAMETER DisplayName
    The text to display for this item in the footer.
    .EXAMPLE
    Add-BigDisplayItem -Name "Status" -DisplayName "Current Status: OK"
    Adds a big display item named "Status" with the text "Current Status: OK" to the menu footer.
    #>

    param(
        [string]$Name = "Default",
        [string]$DisplayName = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
    )

    $global:Display.$Name = $DisplayName
}

$width = Get-ConsoleWidth
$height = Get-ConsoleHeight
$heading = "kinda bad file browser"
$subHeading = "K.B.F.B"
$menuFillChar = "#"

Set-MenuOption -HeadingColor DarkCyan -MenuNameColor DarkGray -SubHeadingColor Green -FooterTextColor DarkGray
Set-MenuOption -Heading $heading -SubHeading $subHeading -MenuFillChar $menuFillChar -MenuFillColor DarkYellow
Set-MenuOption -HeadingColor DarkCyan -MenuNameColor DarkGray -SubHeadingColor Green -FooterTextColor DarkGray

Set-MenuOption -MaxWidth $width -MaxHeight $height

$newMenu = @{
    Name = "Main"
    DisplayName = "* * * M a i n M e n u * * *"
}

$subMenu = @{
    Name = "SubMenu"
    DisplayName = "* * * S u b M e n u * * *"
}

$subSubMenu = @{
    Name = "SubSubMenu"
    DisplayName = "* * * S u b S u b M e n u * * *"
}

$goToSubItem = @{
    Name = "GoToSub"
    DisplayName = "Go to Sub Menu (demo of submenu navigation)"
    Action = {  Show-SubMenu -MenuName SubMenu}
}

$goToSubSubItem = @{
    Name = "GoToSubSub"
    DisplayName = "Go down another level to Sub Sub Menu (demo of submenu navigation)"
    Action = {  Show-SubMenu -MenuName SubSubMenu}
}

$multiSelectItem = @{
    Name = "MultiSelectDemo"
    DisplayName = "Multi-Select List Menu (demo of using Show-ListMenu with -Multi)"
    Action = {
        $items = @(
            [pscustomobject]@{Name=""; DisplayName="Option 1"; Value=1; DisableMultiSelect=$false},
            [pscustomobject]@{Name=""; DisplayName="Option 2"; Value=2; DisableMultiSelect=$false},
            [pscustomobject]@{Name=""; DisplayName="Option 3"; Value=3; DisableMultiSelect=$false},
            [pscustomobject]@{Name=""; DisplayName="Cancel"; Value=$null; DisableMultiSelect=$true}
        )
        $selected = Show-ListMenu -MenuItems $items -MenuName "Multi-Select Demo" -Multi
        if ($selected -is [array]) {
            Write-Both "You selected: $($selected -join ', ')"
        }
        elseif ($selected) {
            Write-Both "You selected: $selected"
        }
        else {
            Write-Both "No items selected."
        }
    }
}

$browseFolderItem = @{
    Name = "SelectItem"
    DisplayName = "Select Item (demo of using Show-ListMenu to create a file browser)"
    Action = { 
        
        $originalPath = $env:USERPROFILE
        $selectedItem = Invoke-FileBrowser -Path $originalPath
        if ($selectedItem) {
            Write-Output $selectedItem
            Update-SelectedItem -SelectedItem $selectedItem
            Write-Log "Selected: $($selectedItem.FullName)"
        }
        else {
            Write-Host "No item selected" -ForegroundColor Yellow
        }
    }
}

$copySelectedItem = @{
    Name = "CopySelected"
    DisplayName = "Copy Selected Item (demo of using props to pass data)"
    Action = {  
        if ($global:Props.SelectedItem) {
            $path = if ($global:Props.SelectedItem.DirectoryName) { $global:Props.SelectedItem.DirectoryName } else { (Get-Item $global:Props.SelectedItem).Parent }
            $selected = Invoke-FileBrowser -Path $path
             if ($selected) {
                if ($selected.PSIsContainer) {
                    $selected = Join-Path -Path $selected.FullName -ChildPath $global:Props.SelectedItem.Name
                } else {
                    $answer = read-host "Overwrite $($selected.FullName) (yes/no)? "
                    if ($answer -ne 'yes') {
                        Write-Both "Copy cancelled by user."
                        return
                    }
                }
                Write-Both "Copying item: $($global:Props.SelectedItem.FullName) to $(if ($selected.FullName) { $selected.FullName } else { $selected })."
                if ($selected.FullName) { Update-SelectedItem -Props $global:Props -Display $global:Display -SelectedItem $selected }
             }
             else {
                Write-Both "No destination selected to copy the item to."
             }
        }
        else {
            Write-Both "No item selected to copy"
        }
    }
}

$moveSelectedItem = @{
    Name = "MoveSelected"
    DisplayName = "Move Selected Item (demo of using props to pass data)"
    Action = {  
        if ($global:Props.SelectedItem) {
            $path = if ($global:Props.SelectedItem.DirectoryName) { $global:Props.SelectedItem.DirectoryName } else { (Get-Item $global:Props.SelectedItem).Parent }
            $selected = Invoke-FileBrowser -Path $path
             if ($selected) {
                if ($selected.PSIsContainer) {
                    $selected = Join-Path -Path $selected.FullName -ChildPath $global:Props.SelectedItem.Name
                } else {
                    $answer = read-host "Overwrite $($selected.FullName) (yes/no)? "
                    if ($answer -ne 'yes') {
                        Write-Both "Move cancelled by user."
                        return
                    }
                }
                Write-Both "Moving item: $($global:Props.SelectedItem.FullName) to $(if ($selected.FullName) { $selected.FullName } else { $selected })."
                if ($selected.FullName) { Update-SelectedItem -Props $global:Props -Display $global:Display -SelectedItem $selected }
             }
             else {
                Write-Both "No destination selected to move the item to."
             }
        }
        else {
            Write-Both "No item selected to move"
        }
    }
}

$deleteSelectedItem = @{
    Name = "DeleteSelected"
    DisplayName = "Delete Selected Item (demo of using props to pass data)"
    Action = {  
        if ($global:Props.SelectedItem) {
            Write-Both "Deleting item: $($global:Props.SelectedItem.FullName)."
        }
        else {
            Write-Both "No item selected to delete"
        }
    }
}

$writeHostItem = @{
    Name = "WriteHost"
    DisplayName = "Send Remote Command"
    Action = {  
        $text = Read-Host "Send?"
        Write-Both $text
    }
}

# Create a new menu Item
$menuItem = New-MenuItem @goToSubItem -DisableConfirm

# Create a new menu (first menu will become the main menu)
$mainMenu = New-Menu @newMenu

# Add menu item to the menu named 'Main'
$menuItem | Add-MenuItem -Menu Main
$mainMenu | New-MenuItem @browseFolderItem -DisableConfirm
$mainMenu | New-MenuItem @copySelectedItem -DisableConfirm
$mainMenu | New-MenuItem @moveSelectedItem -DisableConfirm
$mainMenu | New-MenuItem @deleteSelectedItem -DisableConfirm
$mainMenu | New-MenuItem @multiSelectItem -DisableConfirm
Add-BuiltInMenuItem -MenuName Main -Item 'PSVersion'
Add-BuiltInMenuItem -MenuName Main -Item 'Timestamp'
Add-BuiltInMenuItem -MenuName Main -Item 'Exit'

# Create a new menu (sub-menu)
$subMenu = New-Menu @subMenu | New-MenuItem @writeHostItem -DisableConfirm
# Add menu item to the menu named 'SubMenu'
$subMenu | New-MenuItem @goToSubSubItem -DisableConfirm
Add-BuiltInMenuItem -MenuName SubMenu -Item 'GoToParent'
Add-BuiltInMenuItem -MenuName SubMenu -Item 'GoToMainMenu'
Add-BuiltInMenuItem -MenuName SubMenu -Item 'Exit'

# Create a new menu (sub-sub-menu)
$subSubMenu = New-Menu @subSubMenu | New-MenuItem @writeHostItem -DisableConfirm
# Add menu item to the menu named 'SubSubMenu'
Add-BuiltInMenuItem -MenuName SubSubMenu -Item 'GoToParent'
Add-BuiltInMenuItem -MenuName SubSubMenu -Item 'GoToMainMenu'
Add-BuiltInMenuItem -MenuName SubSubMenu -Item 'Exit'

#Add-BigDisplayItem -Name "FooterInfo" -DisplayName "This is a big display item in the footer. It can be used to show important information or status updates to the user."
#Add-BigDisplayItem
Clear-Host
Show-SubMenu -MenuName Main