mirror of
https://github.com/ChrisTitusTech/winutil.git
synced 2026-08-11 02:21:16 +10:00
Turn the Install category filters into toggle chips (#4926)
* Turn the Install category filters into toggle chips - Replace the filter buttons with toggles that show which ones are active - Add ctrl click to select more than one category - Keep the search box and the category filter independent of each other - Expand matching categories while a filter is active * Fix the filter interactions the category chips missed - Pass the selected categories to the lazy rendering batches, the old call used a parameter that no longer exists and threw while typing - Keep the category filter when switching tabs, the chips stayed checked while the filter itself was dropped - Put a category back to collapsed once the filter that expanded it is gone * Document the Install category filters - Add a guide section for the chips, ctrl click and clearing the filter - Note that search and the category chips apply together * Correct the category filter guide wording - Clearing by clicking the chip only applies when it is the only one selected - Only categories with matches expand, and only auto expanded ones re-collapse
This commit is contained in:
@@ -42,6 +42,12 @@ Use the Applications tab to install, upgrade, uninstall, and review supported ap
|
||||
|
||||

|
||||
</TabItem>
|
||||
<TabItem label="Category Filters">
|
||||
* Click a category chip at the top of the tab to show only that category. The chip stays highlighted while its filter is active.
|
||||
* Hold `Ctrl` and click to add more categories to the filter, or to remove one again.
|
||||
* Click `All`, or click the highlighted category again while it is the only one selected, to clear the filter.
|
||||
* Categories with matching results open while a filter is active. Ones that filtering opened for you go back to collapsed when you clear it, ones you opened yourself stay open.
|
||||
</TabItem>
|
||||
<TabItem label="Selected Apps Counter">
|
||||
* The `Selected Apps` counter in the sidebar shows how many applications are currently selected.
|
||||
* Use it to keep track of your selection as you browse categories.
|
||||
@@ -57,7 +63,7 @@ Use the Applications tab to install, upgrade, uninstall, and review supported ap
|
||||
</Tabs>
|
||||
|
||||
:::tip
|
||||
If you have trouble finding an application, press `Ctrl + F` and search for its name. The list filters as you type.
|
||||
If you have trouble finding an application, press `Ctrl + F` and search for its name. The list filters as you type. The search and the category chips work together, so you can search inside the categories you picked.
|
||||
:::
|
||||
|
||||
:::note
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
function Find-AppsByNameOrDescription {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Searches through the Apps on the Install Tab and hides all entries that do not match the string
|
||||
Filters the Install tab entries by search text and by category
|
||||
|
||||
.DESCRIPTION
|
||||
Filters application entries by name or description using literal string matching.
|
||||
Respects collapsed category state and handles null $sync gracefully.
|
||||
Search text and categories are independent filters that both have to pass. An entry is
|
||||
shown when its name or description matches the search text, and when its category is in
|
||||
the selected set. An empty search matches everything, and an empty category set matches
|
||||
every category.
|
||||
|
||||
While either filter is active the matching categories are expanded, since a collapsed
|
||||
category would otherwise hide the very results that were asked for. With no filter at
|
||||
all the collapsed state the user set is restored.
|
||||
|
||||
.PARAMETER SearchString
|
||||
The string to be searched for. Wildcards are treated as literal characters.
|
||||
The string to search for. Wildcards are treated as literal characters.
|
||||
|
||||
.PARAMETER Category
|
||||
When provided, only applications in this exact category are shown.
|
||||
.PARAMETER Categories
|
||||
The categories to show. An empty or missing array shows all of them.
|
||||
|
||||
.NOTES
|
||||
- Uses module-scope $sync (no parameter needed; inherits from caller's scope)
|
||||
- Performs literal matching (no wildcard expansion)
|
||||
- Safely handles missing hashtable keys and null UI elements
|
||||
- Protected by try/catch to prevent UI thread crashes
|
||||
#>
|
||||
@@ -24,7 +29,7 @@ function Find-AppsByNameOrDescription {
|
||||
[string]$SearchString = "",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Category = ""
|
||||
[string[]]$Categories = @()
|
||||
)
|
||||
|
||||
# Validate that $sync exists and has required structure
|
||||
@@ -43,21 +48,34 @@ function Find-AppsByNameOrDescription {
|
||||
return
|
||||
}
|
||||
|
||||
# Categories that filtering expanded on the user's behalf, so clearing the filter can undo it
|
||||
if ($null -eq $sync.AppCategoryAutoExpanded) {
|
||||
$sync.AppCategoryAutoExpanded = @{}
|
||||
}
|
||||
|
||||
try {
|
||||
# Reset the visibility if the search string is empty or the search is cleared
|
||||
if ([string]::IsNullOrWhiteSpace($SearchString) -and [string]::IsNullOrWhiteSpace($Category)) {
|
||||
$activeCategories = @($Categories | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
||||
$hasSearch = -not [string]::IsNullOrWhiteSpace($SearchString)
|
||||
$hasCategories = $activeCategories.Count -gt 0
|
||||
|
||||
# Nothing is filtered, so put every entry back and leave the collapsed categories collapsed
|
||||
if (-not $hasSearch -and -not $hasCategories) {
|
||||
$sync.ItemsControl.Items | ForEach-Object {
|
||||
# Each item is a StackPanel container
|
||||
$_.Visibility = [Windows.Visibility]::Visible
|
||||
|
||||
if ($_.Children.Count -ge 2) {
|
||||
$categoryLabel = $_.Children[0]
|
||||
$wrapPanel = $_.Children[1]
|
||||
|
||||
# Keep category label visible
|
||||
$categoryLabel.Visibility = [Windows.Visibility]::Visible
|
||||
|
||||
# Respect the collapsed state of categories (indicated by + prefix)
|
||||
# A category that filtering expanded goes back to how the user left it
|
||||
$categoryName = $categoryLabel.Content -replace '^[+-] ', ''
|
||||
if ($sync.AppCategoryAutoExpanded.ContainsKey($categoryName)) {
|
||||
$categoryLabel.Content = $categoryLabel.Content -replace "^- ", "+ "
|
||||
$sync.AppCategoryAutoExpanded.Remove($categoryName)
|
||||
}
|
||||
|
||||
if ($categoryLabel.Content -like "+*") {
|
||||
$wrapPanel.Visibility = [Windows.Visibility]::Collapsed
|
||||
}
|
||||
@@ -65,7 +83,6 @@ function Find-AppsByNameOrDescription {
|
||||
$wrapPanel.Visibility = [Windows.Visibility]::Visible
|
||||
}
|
||||
|
||||
# Show all apps within the category
|
||||
$wrapPanel.Children | ForEach-Object {
|
||||
$_.Visibility = [Windows.Visibility]::Visible
|
||||
}
|
||||
@@ -77,7 +94,6 @@ function Find-AppsByNameOrDescription {
|
||||
# Escape wildcard characters for literal matching
|
||||
$escapedSearchString = [System.Management.Automation.WildcardPattern]::Escape($SearchString)
|
||||
|
||||
# Perform search
|
||||
$sync.ItemsControl.Items | ForEach-Object {
|
||||
# Each item is a StackPanel container with Children[0] = label, Children[1] = WrapPanel
|
||||
if ($_.Children.Count -ge 2) {
|
||||
@@ -85,12 +101,9 @@ function Find-AppsByNameOrDescription {
|
||||
$wrapPanel = $_.Children[1]
|
||||
$categoryHasMatch = $false
|
||||
|
||||
# Keep category label visible
|
||||
$categoryLabel.Visibility = [Windows.Visibility]::Visible
|
||||
|
||||
# Search through apps in this category
|
||||
foreach ($appControl in $wrapPanel.Children) {
|
||||
# Safely retrieve app entry from hashtable
|
||||
$appTag = $appControl.Tag
|
||||
$appEntry = $null
|
||||
|
||||
@@ -98,14 +111,13 @@ function Find-AppsByNameOrDescription {
|
||||
$appEntry = $sync.configs.applicationsHashtable[$appTag]
|
||||
}
|
||||
|
||||
# Check if app matches search criteria
|
||||
if ($null -ne $appEntry) {
|
||||
$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*"
|
||||
$categoryMatch = -not $hasCategories -or $activeCategories -contains $appEntry.Category
|
||||
$textMatch = -not $hasSearch -or
|
||||
$appEntry.Content -like "*$escapedSearchString*" -or
|
||||
$appEntry.Description -like "*$escapedSearchString*"
|
||||
|
||||
if ($categoryMatch -or $contentMatch -or $descriptionMatch) {
|
||||
# Show the App and mark that this category has a match
|
||||
if ($categoryMatch -and $textMatch) {
|
||||
$appControl.Visibility = [Windows.Visibility]::Visible
|
||||
$categoryHasMatch = $true
|
||||
}
|
||||
@@ -119,17 +131,17 @@ function Find-AppsByNameOrDescription {
|
||||
}
|
||||
}
|
||||
|
||||
# If category has matches, show the WrapPanel and update the category label to expanded state
|
||||
if ($categoryHasMatch) {
|
||||
$wrapPanel.Visibility = [Windows.Visibility]::Visible
|
||||
$_.Visibility = [Windows.Visibility]::Visible
|
||||
# Update category label to show expanded state (-)
|
||||
# Expand it, otherwise the matches stay hidden behind a collapsed header.
|
||||
# Remember that it was collapsed so clearing the filter can put it back.
|
||||
if ($categoryLabel.Content -like "+*") {
|
||||
$categoryLabel.Content = $categoryLabel.Content -replace "^\+ ", "- "
|
||||
$sync.AppCategoryAutoExpanded[($categoryLabel.Content -replace '^- ', '')] = $true
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Hide the entire category container if no matches
|
||||
$_.Visibility = [Windows.Visibility]::Collapsed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,11 @@ function Initialize-InstallCategoryAppList {
|
||||
# The WrapPanel is the second child
|
||||
$wrapPanel = $categoryContainer.Children[1]
|
||||
|
||||
# An explicit click wins over anything filtering expanded automatically
|
||||
if ($sync.AppCategoryAutoExpanded) {
|
||||
$sync.AppCategoryAutoExpanded.Remove(($categoryToggle.Content -replace '^[+-] ', ''))
|
||||
}
|
||||
|
||||
# Toggle visibility
|
||||
if ($wrapPanel.Visibility -eq [Windows.Visibility]::Visible) {
|
||||
$wrapPanel.Visibility = [Windows.Visibility]::Collapsed
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
function Invoke-WinUtilAppCategoryChip {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Handles a click on an Install tab category chip
|
||||
|
||||
.DESCRIPTION
|
||||
The chip carries its category in Tag, so every chip shares this handler. Holding ctrl
|
||||
adds the category to the current selection instead of replacing it.
|
||||
|
||||
.PARAMETER Chip
|
||||
The chip that was clicked
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
$Chip
|
||||
)
|
||||
|
||||
$ctrlDown = [bool]([System.Windows.Input.Keyboard]::Modifiers -band [System.Windows.Input.ModifierKeys]::Control)
|
||||
Set-WinUtilAppCategoryFilter -Category $Chip.Tag -Additive:$ctrlDown
|
||||
}
|
||||
@@ -1,17 +1,50 @@
|
||||
function Set-WinUtilAppCategoryFilter {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Applies an exact application category filter from an Install tab search chip.
|
||||
Applies the Install tab category filter and syncs the chip states to it
|
||||
|
||||
.DESCRIPTION
|
||||
The selection lives in $sync.SelectedAppCategories. An empty selection means every
|
||||
category is shown, which is what the All chip represents. The category filter and the
|
||||
search box are independent: this only touches categories, and the current search text
|
||||
is reapplied on top.
|
||||
|
||||
.PARAMETER Category
|
||||
The application category to show. An empty value clears the filter.
|
||||
The category to act on. An empty value clears the filter back to All.
|
||||
|
||||
.PARAMETER Additive
|
||||
Toggles this category in or out of the current selection instead of replacing it.
|
||||
Bound to ctrl click.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[string]$Category = ""
|
||||
[string]$Category = "",
|
||||
|
||||
[Parameter(Mandatory = $false)]
|
||||
[switch]$Additive
|
||||
)
|
||||
|
||||
$sync.SearchBar.Tag = $Category
|
||||
$sync.SearchBar.Text = $Category
|
||||
Find-AppsByNameOrDescription -SearchString $Category -Category $Category
|
||||
if ($null -eq $sync.SelectedAppCategories) {
|
||||
$sync.SelectedAppCategories = [System.Collections.Generic.List[string]]::new()
|
||||
}
|
||||
$selected = $sync.SelectedAppCategories
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Category)) {
|
||||
$selected.Clear()
|
||||
} elseif ($Additive) {
|
||||
if ($selected.Contains($Category)) {
|
||||
[void]$selected.Remove($Category)
|
||||
} else {
|
||||
$selected.Add($Category)
|
||||
}
|
||||
} elseif ($selected.Count -eq 1 -and $selected.Contains($Category)) {
|
||||
# Clicking the only active category again clears the filter
|
||||
$selected.Clear()
|
||||
} else {
|
||||
$selected.Clear()
|
||||
$selected.Add($Category)
|
||||
}
|
||||
|
||||
Update-WinUtilAppCategoryChip
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Categories $selected.ToArray()
|
||||
}
|
||||
|
||||
@@ -8,8 +8,14 @@ function Invoke-WinUtilInstallAppRenderBatch {
|
||||
$sync.$appKey = Initialize-InstallAppEntry -TargetElement $CategoryBatch.TargetElement -AppKey $appKey
|
||||
}
|
||||
|
||||
if ($sync.currentTab -eq "Install" -and $sync.SearchBar -and -not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text)) {
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag
|
||||
# Entries render in batches, so a filter that is already active has to be applied to each new
|
||||
# batch. Categories count as an active filter just like search text does.
|
||||
if ($sync.currentTab -eq "Install" -and $sync.SearchBar) {
|
||||
$selectedCategories = if ($sync.SelectedAppCategories) { $sync.SelectedAppCategories.ToArray() } else { @() }
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($sync.SearchBar.Text) -or $selectedCategories.Count -gt 0) {
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Categories $selectedCategories
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
function Update-WinUtilAppCategoryChip {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Pushes the current category selection onto the Install tab filter chips
|
||||
|
||||
.DESCRIPTION
|
||||
The chips are toggle buttons, so their checked state has to follow the selection
|
||||
rather than whatever the last click did to them. The All chip is checked when no
|
||||
category is selected.
|
||||
#>
|
||||
$selected = $sync.SelectedAppCategories
|
||||
if ($null -eq $selected) { return }
|
||||
|
||||
foreach ($chip in $sync.AppCategoryChips) {
|
||||
$control = $sync[$chip.Name]
|
||||
if ($null -eq $control) { continue }
|
||||
$control.IsChecked = if ($chip.Category) { $selected.Contains($chip.Category) } else { $selected.Count -eq 0 }
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,9 @@ function Invoke-WPFTab {
|
||||
|
||||
# Always reset the filter for the current tab
|
||||
if ($sync.currentTab -eq "Install") {
|
||||
# Reset Install tab filter
|
||||
Find-AppsByNameOrDescription -SearchString ""
|
||||
# Reset the search text, but keep the categories the chips are still showing as selected
|
||||
$selectedCategories = if ($sync.SelectedAppCategories) { $sync.SelectedAppCategories.ToArray() } else { @() }
|
||||
Find-AppsByNameOrDescription -SearchString "" -Categories $selectedCategories
|
||||
} elseif ($sync.currentTab -eq "Tweaks") {
|
||||
# Reset Tweaks tab filter
|
||||
Find-TweaksByNameOrDescription -SearchString ""
|
||||
|
||||
@@ -21,7 +21,9 @@ 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 'Find-AppsByNameOrDescription -SearchString \$sync\.SearchBar\.Text -Categories \$selectedCategories'
|
||||
# A batch has to be filtered when either filter is on, not only when there is search text
|
||||
$renderScript | Should -Match '\$selectedCategories\.Count -gt 0'
|
||||
$renderScript | Should -Match '\$sync\.InstallAppEntriesRendered = \$true'
|
||||
}
|
||||
|
||||
|
||||
@@ -379,12 +379,86 @@ Describe "Find-AppsByNameOrDescription" {
|
||||
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($utilityItem, $powerToysItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -SearchString "Utilities" -Category "Utilities"
|
||||
Find-AppsByNameOrDescription -Categories @("Utilities")
|
||||
|
||||
$utilityItem.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
$powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
$category.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
}
|
||||
|
||||
It "shows apps from every selected category when several chips are active" {
|
||||
$utilityItem = New-WinUtilAppSearchItem -Tag "WPFInstallLiteral"
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$browserItem = New-WinUtilAppSearchItem -Tag "WPFInstallBrowser"
|
||||
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($utilityItem, $powerToysItem, $browserItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -Categories @("Utilities", "Microsoft Tools")
|
||||
|
||||
$utilityItem.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
$powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
$browserItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
}
|
||||
|
||||
It "applies the search text and the category filter together" {
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$literalItem = New-WinUtilAppSearchItem -Tag "WPFInstallLiteral"
|
||||
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($powerToysItem, $literalItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -SearchString "PowerToys" -Categories @("Microsoft Tools")
|
||||
|
||||
$powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
$literalItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
}
|
||||
|
||||
It "hides a category when the search text matches nothing inside the selected categories" {
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($powerToysItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -SearchString "Firefox" -Categories @("Microsoft Tools")
|
||||
|
||||
$powerToysItem.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
$category.Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
}
|
||||
|
||||
It "expands a collapsed category that has matches for the selected filter" {
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$category = New-WinUtilAppCategory -Label "+ Tools" -Items @($powerToysItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -Categories @("Microsoft Tools")
|
||||
|
||||
$category.Children[0].Content | Should -Be "- Tools"
|
||||
$category.Children[1].Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
}
|
||||
|
||||
It "re-collapses a category it expanded once the filter is cleared" {
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$category = New-WinUtilAppCategory -Label "+ Tools" -Items @($powerToysItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -Categories @("Microsoft Tools")
|
||||
$category.Children[0].Content | Should -Be "- Tools"
|
||||
|
||||
Find-AppsByNameOrDescription -SearchString ""
|
||||
|
||||
$category.Children[0].Content | Should -Be "+ Tools"
|
||||
$category.Children[1].Visibility | Should -Be ([Windows.Visibility]::Collapsed)
|
||||
}
|
||||
|
||||
It "leaves a category the user had expanded alone when the filter is cleared" {
|
||||
$powerToysItem = New-WinUtilAppSearchItem -Tag "WPFInstallPowerToys"
|
||||
$category = New-WinUtilAppCategory -Label "- Tools" -Items @($powerToysItem)
|
||||
New-WinUtilAppSearchContext -Categories @($category)
|
||||
|
||||
Find-AppsByNameOrDescription -Categories @("Microsoft Tools")
|
||||
Find-AppsByNameOrDescription -SearchString ""
|
||||
|
||||
$category.Children[0].Content | Should -Be "- Tools"
|
||||
$category.Children[1].Visibility | Should -Be ([Windows.Visibility]::Visible)
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Find-TweaksByNameOrDescription" {
|
||||
|
||||
@@ -182,7 +182,8 @@ Describe "XAML document" {
|
||||
|
||||
It "wires the Document search chip to an existing Document category" {
|
||||
$mainScript = Get-Content -Path $script:mainScriptPath -Raw
|
||||
$mainScript | Should -Match '\$sync\["WPFSearchChipDocument"\]\.Add_Click\(\{ Set-WinUtilAppCategoryFilter -Category "Document" \}\)'
|
||||
$mainScript | Should -Match '@\{ Name = "WPFSearchChipDocument";\s+Category = "Document" \}'
|
||||
$mainScript | Should -Match '\$sync\["WPFSearchChipDocument"\]\.Add_Click\(\{ Invoke-WinUtilAppCategoryChip -Chip \$this \}\)'
|
||||
|
||||
$applications = Get-WinUtilConfigObject -Name "applications"
|
||||
$categories = @($applications.PSObject.Properties | ForEach-Object { $_.Value.category } | Sort-Object -Unique)
|
||||
@@ -468,7 +469,10 @@ Describe "XAML and sync wiring" {
|
||||
"Win11ISOProcessRunning",
|
||||
"Win11ISOWorkDir",
|
||||
"Win11ISOContentsDir",
|
||||
"Win11ISOUSBDisks"
|
||||
"Win11ISOUSBDisks",
|
||||
"AppCategoryChips",
|
||||
"SelectedAppCategories",
|
||||
"AppCategoryAutoExpanded"
|
||||
)
|
||||
$allowedNames = @($xamlNames + $generatedNames + $dynamicStateNames) | Sort-Object -Unique
|
||||
$bracketReferences = @(
|
||||
|
||||
+32
-22
@@ -329,7 +329,7 @@ $searchBarTimer.add_Tick({
|
||||
$searchBarTimer.Stop()
|
||||
switch ($sync.currentTab) {
|
||||
"Install" {
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Category $sync.SearchBar.Tag
|
||||
Find-AppsByNameOrDescription -SearchString $sync.SearchBar.Text -Categories $sync.SelectedAppCategories.ToArray()
|
||||
}
|
||||
"Tweaks" {
|
||||
Find-TweaksByNameOrDescription -SearchString $sync.SearchBar.Text
|
||||
@@ -340,10 +340,6 @@ $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"
|
||||
@@ -352,29 +348,43 @@ $sync["SearchBar"].Add_TextChanged({
|
||||
$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["WPFSearchChipDocument"].Add_Click({ Set-WinUtilAppCategoryFilter -Category "Document" })
|
||||
$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" })
|
||||
# Category filter chips. The chip carries its category in Tag, so one handler covers all of them.
|
||||
$sync.AppCategoryChips = @(
|
||||
@{ Name = "WPFSearchChipAll"; Category = "" }
|
||||
@{ Name = "WPFSearchChipBrowsers"; Category = "Browsers" }
|
||||
@{ Name = "WPFSearchChipCommunications"; Category = "Communications" }
|
||||
@{ Name = "WPFSearchChipDevelopment"; Category = "Development" }
|
||||
@{ Name = "WPFSearchChipDocument"; Category = "Document" }
|
||||
@{ Name = "WPFSearchChipGames"; Category = "Games" }
|
||||
@{ Name = "WPFSearchChipMicrosoftTools"; Category = "Microsoft Tools" }
|
||||
@{ Name = "WPFSearchChipMultimediaTools"; Category = "Multimedia Tools" }
|
||||
@{ Name = "WPFSearchChipProTools"; Category = "Pro Tools" }
|
||||
@{ Name = "WPFSearchChipSelfhostedTools"; Category = "Selfhosted Tools" }
|
||||
@{ Name = "WPFSearchChipUtilities"; Category = "Utilities" }
|
||||
)
|
||||
$sync.SelectedAppCategories = [System.Collections.Generic.List[string]]::new()
|
||||
|
||||
foreach ($appCategoryChip in $sync.AppCategoryChips) {
|
||||
$sync[$appCategoryChip.Name].Tag = $appCategoryChip.Category
|
||||
}
|
||||
|
||||
$sync["WPFSearchChipAll"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipBrowsers"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipCommunications"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipDevelopment"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipDocument"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipGames"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipMicrosoftTools"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipMultimediaTools"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipProTools"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipSelfhostedTools"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
$sync["WPFSearchChipUtilities"].Add_Click({ Invoke-WinUtilAppCategoryChip -Chip $this })
|
||||
|
||||
$sync["Form"].Add_Loaded({
|
||||
param($e)
|
||||
|
||||
+55
-16
@@ -997,6 +997,44 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!-- Category filter chips. A toggle rather than a button, so the active filter is visible
|
||||
on the chip itself instead of only in the results below. -->
|
||||
<Style x:Key="FilterChipToggleStyle" TargetType="ToggleButton">
|
||||
<Setter Property="Margin" Value="2"/>
|
||||
<Setter Property="Padding" Value="12,4,12,4"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontSize" Value="{DynamicResource ButtonFontSize}"/>
|
||||
<Setter Property="FontFamily" Value="{DynamicResource ButtonFontFamily}"/>
|
||||
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundColor}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundColor}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Name="ChipBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{DynamicResource BorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="{DynamicResource ButtonCornerRadius}"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
TextBlock.Foreground="{TemplateBinding Foreground}"
|
||||
TextBlock.FontSize="{TemplateBinding FontSize}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="ChipBorder" Property="Background" Value="{DynamicResource ButtonBackgroundMouseoverColor}"/>
|
||||
</Trigger>
|
||||
<!-- Only colours change on check. Anything affecting text width, bold for
|
||||
instance, would resize the chip and shift every chip after it. -->
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter TargetName="ChipBorder" Property="Background" Value="{DynamicResource ButtonBackgroundSelectedColor}"/>
|
||||
<Setter TargetName="ChipBorder" Property="BorderBrush" Value="{DynamicResource LabelboxForegroundColor}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
<Grid Background="{DynamicResource MainBackgroundColor}" ShowGridLines="False" Name="WPFMainGrid" Width="Auto" Height="Auto" HorizontalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -1313,26 +1351,27 @@
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Quick Category Search Chips -->
|
||||
<!-- Category filters. Click one to filter, ctrl click to combine several. -->
|
||||
<WrapPanel Grid.Row="0" Orientation="Horizontal" Margin="5,5,5,5" Name="WPFSearchChips">
|
||||
<TextBlock Text="Filters"
|
||||
FontSize="{DynamicResource HeaderFontSize}"
|
||||
FontFamily="{DynamicResource HeaderFontFamily}"
|
||||
<TextBlock Text=""
|
||||
FontFamily="Segoe MDL2 Assets"
|
||||
FontSize="{DynamicResource IconFontSize}"
|
||||
Foreground="{DynamicResource LabelboxForegroundColor}"
|
||||
Background="Transparent"
|
||||
VerticalAlignment="Center"
|
||||
Margin="15,0,8,0"/>
|
||||
<Button Name="WPFSearchChipAll" Content="All" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipBrowsers" Content="Browsers" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipCommunications" Content="Communications" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipDevelopment" Content="Development" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipDocument" Content="Document" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipGames" Content="Games" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipMicrosoftTools" Content="Microsoft Tools" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipMultimediaTools" Content="Multimedia Tools" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipProTools" Content="Pro Tools" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipSelfhostedTools" Content="Selfhosted Tools" Style="{StaticResource FilterChipStyle}"/>
|
||||
<Button Name="WPFSearchChipUtilities" Content="Utilities" Style="{StaticResource FilterChipStyle}"/>
|
||||
Margin="10,0,10,0"
|
||||
ToolTip="Filter by category. Ctrl click to select more than one."/>
|
||||
<ToggleButton Name="WPFSearchChipAll" Content="All" Style="{StaticResource FilterChipToggleStyle}" IsChecked="True"/>
|
||||
<ToggleButton Name="WPFSearchChipBrowsers" Content="Browsers" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipCommunications" Content="Communications" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipDevelopment" Content="Development" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipDocument" Content="Document" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipGames" Content="Games" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipMicrosoftTools" Content="Microsoft Tools" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipMultimediaTools" Content="Multimedia Tools" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipProTools" Content="Pro Tools" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipSelfhostedTools" Content="Selfhosted Tools" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
<ToggleButton Name="WPFSearchChipUtilities" Content="Utilities" Style="{StaticResource FilterChipToggleStyle}"/>
|
||||
</WrapPanel>
|
||||
|
||||
<Grid Grid.Row="1" Margin="{DynamicResource TabContentMargin}">
|
||||
|
||||
Reference in New Issue
Block a user