diff --git a/functions/private/Find-AppsByNameOrDescription.ps1 b/functions/private/Find-AppsByNameOrDescription.ps1 index ec405789..96037237 100644 --- a/functions/private/Find-AppsByNameOrDescription.ps1 +++ b/functions/private/Find-AppsByNameOrDescription.ps1 @@ -10,6 +10,9 @@ function Find-AppsByNameOrDescription { .PARAMETER SearchString The string to be searched for. Wildcards are treated as literal characters. + .PARAMETER Category + When provided, only applications in this exact category are shown. + .NOTES - Uses module-scope $sync (no parameter needed; inherits from caller's scope) - Performs literal matching (no wildcard expansion) @@ -18,7 +21,10 @@ function Find-AppsByNameOrDescription { #> param( [Parameter(Mandatory = $false)] - [string]$SearchString = "" + [string]$SearchString = "", + + [Parameter(Mandatory = $false)] + [string]$Category = "" ) # Validate that $sync exists and has required structure @@ -39,7 +45,7 @@ function Find-AppsByNameOrDescription { try { # Reset the visibility if the search string is empty or the search is cleared - if ([string]::IsNullOrWhiteSpace($SearchString)) { + if ([string]::IsNullOrWhiteSpace($SearchString) -and [string]::IsNullOrWhiteSpace($Category)) { $sync.ItemsControl.Items | ForEach-Object { # Each item is a StackPanel container $_.Visibility = [Windows.Visibility]::Visible @@ -94,10 +100,11 @@ function Find-AppsByNameOrDescription { # Check if app matches search criteria if ($null -ne $appEntry) { - $contentMatch = $appEntry.Content -like "*$escapedSearchString*" - $descriptionMatch = $appEntry.Description -like "*$escapedSearchString*" + $categoryMatch = -not [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Category -eq $Category + $contentMatch = [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Content -like "*$escapedSearchString*" + $descriptionMatch = [string]::IsNullOrWhiteSpace($Category) -and $appEntry.Description -like "*$escapedSearchString*" - if ($contentMatch -or $descriptionMatch) { + if ($categoryMatch -or $contentMatch -or $descriptionMatch) { # Show the App and mark that this category has a match $appControl.Visibility = [Windows.Visibility]::Visible $categoryHasMatch = $true diff --git a/functions/private/Set-WinUtilAppCategoryFilter.ps1 b/functions/private/Set-WinUtilAppCategoryFilter.ps1 new file mode 100644 index 00000000..dbc0e555 --- /dev/null +++ b/functions/private/Set-WinUtilAppCategoryFilter.ps1 @@ -0,0 +1,17 @@ +function Set-WinUtilAppCategoryFilter { + <# + .SYNOPSIS + Applies an exact application category filter from an Install tab search chip. + + .PARAMETER Category + The application category to show. An empty value clears the filter. + #> + param( + [Parameter(Mandatory = $false)] + [string]$Category = "" + ) + + $sync.SearchBar.Tag = $Category + $sync.SearchBar.Text = $Category + Find-AppsByNameOrDescription -SearchString $Category -Category $Category +} diff --git a/functions/private/Start-WinUtilInstallAppRendering.ps1 b/functions/private/Start-WinUtilInstallAppRendering.ps1 index 2eed3844..f3bf9da1 100644 --- a/functions/private/Start-WinUtilInstallAppRendering.ps1 +++ b/functions/private/Start-WinUtilInstallAppRendering.ps1 @@ -9,7 +9,7 @@ function Invoke-WinUtilInstallAppRenderBatch { } if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) { - Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text + Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag } } diff --git a/pester/install-rendering.Tests.ps1 b/pester/install-rendering.Tests.ps1 index 9bb4d366..5c77f6a9 100644 --- a/pester/install-rendering.Tests.ps1 +++ b/pester/install-rendering.Tests.ps1 @@ -21,6 +21,7 @@ Describe "Install app rendering startup contract" { $renderScript | Should -Match 'Dispatcher\.BeginInvoke' $renderScript | Should -Match 'Invoke-WinUtilInstallAppRenderNextBatch' $renderScript | Should -Match 'Initialize-InstallAppEntry' + $renderScript | Should -Match 'Find-AppsByNameOrDescription -SearchString \$sync\.SearchBar\.Text -Category \$sync\.SearchBar\.Tag' $renderScript | Should -Match '\$sync\.InstallAppEntriesRendered = \$true' } @@ -59,7 +60,7 @@ Describe "Install app rendering startup contract" { } function global:Find-AppsByNameOrDescription { - param($SearchString) + param($SearchString, $Category) throw "Search should not run for an empty search box in this test." } diff --git a/pester/search-filter.Tests.ps1 b/pester/search-filter.Tests.ps1 index 8231f5d1..254ce8fd 100644 --- a/pester/search-filter.Tests.ps1 +++ b/pester/search-filter.Tests.ps1 @@ -172,18 +172,27 @@ namespace Windows.Controls WPFInstallBrowser = [pscustomobject]@{ Content = "Firefox" Description = "Fast private browser" + Category = "Browsers" } WPFInstallMedia = [pscustomobject]@{ Content = "VLC" Description = "Media player" + Category = "Multimedia Tools" } WPFInstallLiteral = [pscustomobject]@{ Content = "Tool [abc]" Description = "Literal wildcard sample" + Category = "Utilities" } WPFInstallEditor = [pscustomobject]@{ Content = "Code Editor" Description = "Text editing" + Category = "Development" + } + WPFInstallPowerToys = [pscustomobject]@{ + Content = "PowerToys" + Description = "A collection of system utilities" + Category = "Microsoft Tools" } } } @@ -363,6 +372,19 @@ Describe "Find-AppsByNameOrDescription" { $mediaItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed) $category.Visibility | Should -Be ([Windows.Visibility]::Visible) } + + It "filters category chips by exact application category" { + $utilityItem = New-WinUtilAppSearchItem -Tag "WPFInstallLiteral" + $powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys" + $category = New-WinUtilAppCategory -Label "- Tools" -Items @($utilityItem, $powerToysItem) + New-WinUtilAppSearchContext -Categories @($category) + + Find-AppsByNameOrDescription -SearchString "Utilities" -Category "Utilities" + + $utilityItem.Visibility | Should -Be ([Windows.Visibility]::Visible) + $powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed) + $category.Visibility | Should -Be ([Windows.Visibility]::Visible) + } } Describe "Find-TweaksByNameOrDescription" { diff --git a/pester/xaml.Tests.ps1 b/pester/xaml.Tests.ps1 index 5b07c562..c6801bf9 100644 --- a/pester/xaml.Tests.ps1 +++ b/pester/xaml.Tests.ps1 @@ -137,6 +137,17 @@ Describe "XAML document" { "SearchBar", "SearchBarIcon", "SearchBarClearButton", + "WPFSearchChips", + "WPFSearchChipAll", + "WPFSearchChipBrowsers", + "WPFSearchChipCommunications", + "WPFSearchChipDevelopment", + "WPFSearchChipGames", + "WPFSearchChipMicrosoftTools", + "WPFSearchChipMultimediaTools", + "WPFSearchChipProTools", + "WPFSearchChipSelfhostedTools", + "WPFSearchChipUtilities", "appscategory", "appspanel", "tweakspanel", diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 90ba5a73..121bd9ff 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -341,7 +341,7 @@ $searchBarTimer.add_Tick({ $searchBarTimer.Stop() switch ($sync.currentTab) { "Install" { - Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text + Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag } "Tweaks" { Find-TweaksByNameOrDescription -SearchString $sync.SearchBar.Text @@ -352,6 +352,10 @@ $searchBarTimer.add_Tick({ } }) $sync["SearchBar"].Add_TextChanged({ + if ($sync.SearchBar.Tag -ne $sync.SearchBar.Text) { + $sync.SearchBar.Tag = $null + } + if ($sync.SearchBar.Text -ne "") { $sync.SearchBarClearButton.Visibility = "Visible" $sync.SearchBarIcon.Visibility = "Collapsed" @@ -359,12 +363,30 @@ $sync["SearchBar"].Add_TextChanged({ $sync.SearchBarClearButton.Visibility = "Collapsed" $sync.SearchBarIcon.Visibility = "Visible" } + + # Category chip handlers apply their filter immediately. + if ($sync.SearchBar.Tag -eq $sync.SearchBar.Text) { + return + } + if ($searchBarTimer.IsEnabled) { $searchBarTimer.Stop() } $searchBarTimer.Start() }) +# Quick Category Search Chips +$sync["WPFSearchChipAll"].Add_Click({ Set-WinUtilAppCategoryFilter }) +$sync["WPFSearchChipBrowsers"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Browsers" }) +$sync["WPFSearchChipCommunications"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Communications" }) +$sync["WPFSearchChipDevelopment"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Development" }) +$sync["WPFSearchChipGames"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Games" }) +$sync["WPFSearchChipMicrosoftTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Microsoft Tools" }) +$sync["WPFSearchChipMultimediaTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Multimedia Tools" }) +$sync["WPFSearchChipProTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Pro Tools" }) +$sync["WPFSearchChipSelfhostedTools"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Selfhosted Tools" }) +$sync["WPFSearchChipUtilities"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Utilities" }) + $sync["Form"].Add_Loaded({ param($e) $null = $e diff --git a/xaml/inputXML.xaml b/xaml/inputXML.xaml index 50a2326e..127e8e41 100644 --- a/xaml/inputXML.xaml +++ b/xaml/inputXML.xaml @@ -1275,8 +1275,26 @@ + + + + - + + +