public/Invoke-Octa.ps1
|
function Wait-OctaKeypress { <# Shared "press any key to return to the menu" step after running a category/tool - factored out so every action site (categories, tools, Quick Clean, Undo, Help) uses the exact same prompt instead of repeating it. #> param([Parameter(Mandatory)]$Strings) Write-Host '' Write-Host -NoNewline $Strings.pressKeyToContinue [Console]::ReadKey($true) | Out-Null } function Invoke-Octa { <# .SYNOPSIS Interactive entry point: language menu (first run) -> banner + main menu -> dispatch, looping back to the menu after each action so the whole session (every category, the built-in tools, undo) happens from this one running terminal - the user never needs to re-invoke bin/octa.ps1 with a different flag. FR-018, FR-022, FR-023. 009/010: categories and built-in tools are grouped (main menu -> group picker -> leaf picker) instead of one flat 32-item list, following real user feedback that a flat list got overwhelming as more categories/tools were added. Running a leaf item returns to the group screen it was picked from, not all the way back to the top-level main menu. 010: every screen (a menu, or a running action's own output) is drawn through Show-OctaFrame, anchored to the same starting row captured once at the top of this function - real user feedback (with a screenshot of the redesigned menu confirming the cursor-position redraw fix does work on their terminal) asked for the whole session to feel like one fixed, updating screen instead of each action's output piling up in the terminal's ordinary scrollback history. ponytail: direct single-keypress shortcuts are guaranteed unique for items 1-9 within any one screen (main menu, a group picker, or a leaf picker never has more than 9 items at once); items 10+ are still always reachable via Up/Down + Enter, just without a guaranteed single-digit direct key. #> [CmdletBinding()] param() $strings = Get-OctaStrings # -SkipRiskScan: this menu never displays HighestRiskLevel (only DisplayName/Description), # so the ~20s it took to live-scan all 20 categories just to compute a badge nothing read # was pure waste - see Get-OctaCategory's own comment for how this was found. $categories = @(Get-OctaCategory -SkipRiskScan) $anchorRow = [Console]::CursorTop $categoryGroups = @( [pscustomobject]@{ Key = '1'; Label = $strings.groupPrivacyAi; Description = $strings.groupPrivacyAiDesc; Ids = @('telemetry', 'background-activity', 'copilot', 'recall', 'ai-features') } [pscustomobject]@{ Key = '2'; Label = $strings.groupBloatware; Description = $strings.groupBloatwareDesc; Ids = @('appx', 'oem-suites', 'onedrive', 'widgets') } [pscustomobject]@{ Key = '3'; Label = $strings.groupPerformance; Description = $strings.groupPerformanceDesc; Ids = @('performance', 'power', 'gpu', 'services') } [pscustomobject]@{ Key = '4'; Label = $strings.groupDesktop; Description = $strings.groupDesktopDesc; Ids = @('explorer-start', 'desktop', 'preferences', 'user-experience', 'gaming') } [pscustomobject]@{ Key = '5'; Label = $strings.groupAdvanced; Description = $strings.groupAdvancedDesc; Ids = @('windows-features', 'registry-orphans') } ) $toolCatalog = @( [pscustomobject]@{ Id = 'disk-cleanup'; Label = $strings.menuDiskCleanup; Description = $strings.descDiskCleanup; Action = { Invoke-OctaDiskCleanup | Out-Null } } [pscustomobject]@{ Id = 'disk-usage'; Label = $strings.menuDiskUsage; Description = $strings.descDiskUsage; Action = { Show-OctaDiskUsage | Out-Null } } [pscustomobject]@{ Id = 'disk-health'; Label = $strings.menuDiskHealth; Description = $strings.descDiskHealth; Action = { Show-OctaDiskHealth | Out-Null } } [pscustomobject]@{ Id = 'uninstaller'; Label = $strings.menuUninstaller; Description = $strings.descUninstaller; Action = { Invoke-OctaUninstaller | Out-Null } } [pscustomobject]@{ Id = 'startup-manager'; Label = $strings.menuStartupManager; Description = $strings.descStartupManager; Action = { Invoke-OctaStartupManager | Out-Null } } [pscustomobject]@{ Id = 'tasks'; Label = $strings.menuTasks; Description = $strings.descTasks; Action = { Invoke-OctaTaskBrowser | Out-Null } } [pscustomobject]@{ Id = 'scheduler'; Label = $strings.menuScheduler; Description = $strings.descScheduler; Action = { Invoke-OctaScheduler | Out-Null } } [pscustomobject]@{ Id = 'software-updater'; Label = $strings.menuSoftwareUpdater; Description = $strings.descSoftwareUpdater; Action = { Invoke-OctaSoftwareUpdater | Out-Null } } [pscustomobject]@{ Id = 'dashboard'; Label = $strings.menuDashboard; Description = $strings.descDashboard; Action = { Show-OctaDashboard | Out-Null } } [pscustomobject]@{ Id = 'history'; Label = $strings.menuHistory; Description = $strings.descHistory; Action = { Show-OctaHistory | Out-Null } } [pscustomobject]@{ Id = 'network-cleanup'; Label = $strings.menuNetworkCleanup; Description = $strings.descNetworkCleanup; Action = { Invoke-OctaNetworkCleanup | Out-Null } } [pscustomobject]@{ Id = 'malware-scan'; Label = $strings.menuMalwareScan; Description = $strings.descMalwareScan; Action = { Invoke-OctaMalwareScan | Format-List } } ) $toolGroups = @( [pscustomobject]@{ Key = '1'; Label = $strings.groupDiskStorage; Description = $strings.groupDiskStorageDesc; ToolIds = @('disk-cleanup', 'disk-usage', 'disk-health', 'uninstaller') } [pscustomobject]@{ Key = '2'; Label = $strings.groupSystemMgmt; Description = $strings.groupSystemMgmtDesc; ToolIds = @('startup-manager', 'tasks', 'scheduler', 'software-updater') } [pscustomobject]@{ Key = '3'; Label = $strings.groupInfoHistory; Description = $strings.groupInfoHistoryDesc; ToolIds = @('dashboard', 'history') } [pscustomobject]@{ Key = '4'; Label = $strings.groupNetworkSecurity; Description = $strings.groupNetworkSecurityDesc; ToolIds = @('network-cleanup', 'malware-scan') } ) while ($true) { $mainItems = @( [pscustomobject]@{ Key = '1'; Label = $strings.menuCategories; Description = $strings.menuCategoriesDesc } [pscustomobject]@{ Key = '2'; Label = $strings.menuTools; Description = $strings.menuToolsDesc } [pscustomobject]@{ Key = 'q'; Label = $strings.menuQuickClean; Description = $strings.menuQuickCleanDesc } [pscustomobject]@{ Key = 'u'; Label = $strings.menuUndo; Description = $strings.menuUndoDesc } [pscustomobject]@{ Key = 'l'; Label = $strings.menuLanguage; Description = $strings.menuLanguageDesc } [pscustomobject]@{ Key = 'h'; Label = $strings.menuHelp; Description = $strings.menuHelpDesc } [pscustomobject]@{ Key = 'x'; Label = $strings.menuExit; Description = $strings.menuExitDesc } ) $choice = Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaMenu -Items $mainItems -Header $strings.mainMenuHeader -NavHint $strings.menuNavHint } switch ($choice) { '1' { while ($true) { $groupItems = @($categoryGroups | ForEach-Object { [pscustomobject]@{ Key = $_.Key; Label = $_.Label; Description = $_.Description } }) $groupItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $groupChoice = Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaMenu -Items $groupItems -Header $strings.categoryGroupHeader -NavHint $strings.menuNavHint } if ($groupChoice -eq 'b') { break } $group = $categoryGroups | Where-Object { $_.Key -eq $groupChoice } while ($true) { $leafCategories = @($categories | Where-Object { $_.Id -in $group.Ids }) $leafItems = @() for ($i = 0; $i -lt $leafCategories.Count; $i++) { $leafItems += [pscustomobject]@{ Key = ($i + 1).ToString(); Label = $leafCategories[$i].DisplayName; Description = $leafCategories[$i].Description } } $leafItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $leafChoice = Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaMenu -Items $leafItems -Header $group.Label -NavHint $strings.menuNavHint } if ($leafChoice -eq 'b') { break } $index = [int]$leafChoice - 1 if ($index -ge 0 -and $index -lt $leafCategories.Count) { Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $leafCategories[$index].DisplayName) Invoke-OctaCategory -CategoryId $leafCategories[$index].Id -Apply | Format-List Wait-OctaKeypress -Strings $strings } | Out-Null } } } } '2' { while ($true) { $groupItems = @($toolGroups | ForEach-Object { [pscustomobject]@{ Key = $_.Key; Label = $_.Label; Description = $_.Description } }) $groupItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $groupChoice = Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaMenu -Items $groupItems -Header $strings.toolGroupHeader -NavHint $strings.menuNavHint } if ($groupChoice -eq 'b') { break } $group = $toolGroups | Where-Object { $_.Key -eq $groupChoice } while ($true) { $leafTools = @($toolCatalog | Where-Object { $_.Id -in $group.ToolIds }) $leafItems = @() for ($i = 0; $i -lt $leafTools.Count; $i++) { $leafItems += [pscustomobject]@{ Key = ($i + 1).ToString(); Label = $leafTools[$i].Label; Description = $leafTools[$i].Description } } $leafItems += [pscustomobject]@{ Key = 'b'; Label = $strings.menuBack; Description = '' } $leafChoice = Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaMenu -Items $leafItems -Header $group.Label -NavHint $strings.menuNavHint } if ($leafChoice -eq 'b') { break } $index = [int]$leafChoice - 1 if ($index -ge 0 -and $index -lt $leafTools.Count) { Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $leafTools[$index].Label) & $leafTools[$index].Action Wait-OctaKeypress -Strings $strings } | Out-Null } } } } 'q' { Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $strings.menuQuickClean) Invoke-OctaQuickClean -Apply | Format-List Wait-OctaKeypress -Strings $strings } | Out-Null } 'u' { Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaBanner Write-Host ($strings.runningHeader -f $strings.menuUndo) Undo-OctaRun -RunId 'latest' | Format-List Wait-OctaKeypress -Strings $strings } | Out-Null } 'l' { # Was not wrapped in Show-OctaFrame at all before - its own banner+prompt just # printed wherever the cursor happened to be after the previous menu, instead of # replacing it in place, which is exactly the "everything stacks up" symptom a # real user reported. $culture = Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaLanguageMenu } Set-OctaLanguage -Culture $culture $strings = Get-OctaStrings } 'h' { Show-OctaFrame -AnchorRow $anchorRow -Draw { Show-OctaBanner Write-Host 'https://github.com/jpiribe/octa (see README for full reference)' Wait-OctaKeypress -Strings $strings } | Out-Null } 'x' { return } } } } |