From 6d88e514624d1299ef42123a44fd42ea56bc03fa Mon Sep 17 00:00:00 2001 From: Martin Wiethan <47688561+Marterich@users.noreply.github.com> Date: Sun, 13 Oct 2024 19:25:02 +0200 Subject: [PATCH] LETS GOOO (#12) * Add Selected Apps Label, Reshuffel the nesting of the checkbox and the label to be able to reference the name from the actual checkbox * Add visual selection and allow click on the whole app section * Fix Theme definition to work with theme change * Fix Highlight on if label or icon is clicked * change applications.json to powershell object list and refactor UI Creation logic * Optimization and Add Collapsable Categories * Add Button functionality for install, uninstall, info, install selected, uninstall selected, clear and implement search * Rest application.json to Main * Reset Compile to main * Pretty much revamp_apps but without changes to applications.json * Small fixes --- config/themes.json | 4 + functions/public/Invoke-WPFGetInstalled.ps1 | 14 +- functions/public/Invoke-WPFInstall.ps1 | 6 +- .../public/Invoke-WPFSelectedLabelUpdate.ps1 | 41 ++ functions/public/Invoke-WPFUIApps.ps1 | 450 ++++++++++++++++++ functions/public/Invoke-WPFUIElements.ps1 | 114 +++-- functions/public/Invoke-WPFUnInstall.ps1 | 21 +- scripts/main.ps1 | 102 ++-- 8 files changed, 632 insertions(+), 120 deletions(-) create mode 100644 functions/public/Invoke-WPFSelectedLabelUpdate.ps1 create mode 100644 functions/public/Invoke-WPFUIApps.ps1 diff --git a/config/themes.json b/config/themes.json index 18f55b5649..04a1934b6d 100644 --- a/config/themes.json +++ b/config/themes.json @@ -38,6 +38,8 @@ "ButtonCornerRadius": "2" }, "Light": { + "AppInstallUnselectedColor": "#F0F0F0", + "AppInstallSelectedColor": "#C2C2C2", "ComboBoxForegroundColor": "#232629", "ComboBoxBackgroundColor": "#F7F7F7", "LabelboxForegroundColor": "#232629", @@ -73,6 +75,8 @@ }, "Dark": { + "AppInstallUnselectedColor": "#232629", + "AppInstallSelectedColor": "#4C4C4C", "ComboBoxForegroundColor": "#F7F7F7", "ComboBoxBackgroundColor": "#1E3747", "LabelboxForegroundColor": "#0567ff", diff --git a/functions/public/Invoke-WPFGetInstalled.ps1 b/functions/public/Invoke-WPFGetInstalled.ps1 index c27fd13ce1..fc111a8c16 100644 --- a/functions/public/Invoke-WPFGetInstalled.ps1 +++ b/functions/public/Invoke-WPFGetInstalled.ps1 @@ -20,9 +20,9 @@ function Invoke-WPFGetInstalled { return } $preferChoco = $sync.WPFpreferChocolatey.IsChecked - Invoke-WPFRunspace -ArgumentList $checkbox, $preferChoco -DebugPreference $DebugPreference -ScriptBlock { - param($checkbox, $preferChoco, $DebugPreference) - + Invoke-WPFRunspace -ArgumentList $checkbox, $preferChoco -ParameterList @(,("ShowOnlyCheckedApps",${function:Show-OnlyCheckedApps})) -DebugPreference $DebugPreference -ScriptBlock { + param($checkbox, $preferChoco, $ShowOnlyCheckedApps,$DebugPreference) + Write-Host $ShowOnlyCheckedApps.ToString() $sync.ProcessRunning = $true $sync.form.Dispatcher.Invoke([action]{ Set-WinUtilTaskbaritem -state "Indeterminate" }) @@ -38,13 +38,15 @@ function Invoke-WPFGetInstalled { else{ $Checkboxes = Invoke-WinUtilCurrentSystem -CheckBox $checkbox } - + $sync.form.Dispatcher.invoke({ foreach($checkbox in $Checkboxes) { $sync.$checkbox.ischecked = $True - } + } + }) + $sync.ItemsControl.Dispatcher.Invoke([action]{ + $ShowOnlyCheckedApps.Invoke($sync.SelectedApps, $sync.ItemsControl) }) - Write-Host "Done..." $sync.ProcessRunning = $false $sync.form.Dispatcher.Invoke([action] { Set-WinUtilTaskbaritem -state "None" }) diff --git a/functions/public/Invoke-WPFInstall.ps1 b/functions/public/Invoke-WPFInstall.ps1 index ef03af36da..dd361b1578 100644 --- a/functions/public/Invoke-WPFInstall.ps1 +++ b/functions/public/Invoke-WPFInstall.ps1 @@ -1,4 +1,8 @@ function Invoke-WPFInstall { + param ( + [Parameter(Mandatory=$false)] + [PSObject[]]$PackagesToInstall = $($sync.selectedApps | Foreach-Object { $SortedAppsHashtable.$_ }) + ) <# .SYNOPSIS @@ -12,8 +16,6 @@ function Invoke-WPFInstall { return } - $PackagesToInstall = (Get-WinUtilCheckBoxes)["Install"] - Write-Host $PackagesToInstall if ($PackagesToInstall.Count -eq 0) { $WarningMsg = "Please select the program(s) to install or upgrade" [System.Windows.MessageBox]::Show($WarningMsg, $AppTitle, [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) diff --git a/functions/public/Invoke-WPFSelectedLabelUpdate.ps1 b/functions/public/Invoke-WPFSelectedLabelUpdate.ps1 new file mode 100644 index 0000000000..a955b6738d --- /dev/null +++ b/functions/public/Invoke-WPFSelectedLabelUpdate.ps1 @@ -0,0 +1,41 @@ +function Invoke-WPFSelectedLabelUpdate { + <# + .SYNOPSIS + This is a helper function that is called by the Checked and Unchecked events of the Checkboxes on the install tab. + It Updates the "Selected Apps" Label on the Install Tab to represent the current collection + .PARAMETER type + Eigther: Add | Remove + .PARAMETER checkbox + should contain the current instance of the checkbox that triggered the Event. + Most of the time will be the automatic variable $this + .EXAMPLE + $checkbox.Add_Unchecked({Invoke-WPFSelectedLabelUpdate -type "Remove" -checkbox $this}) + OR + Invoke-WPFSelectedLabelUpdate -type "Add" -checkbox $specificCheckbox + #> + param ( + $type, + $checkbox + ) + $selectedLabel = $sync.WPFSelectedLabel + # Get the actual Name from the Label inside the Checkbox + $appKey = $checkbox.Parent.Parent.Tag + if ($type -eq "Add") { + $sync.selectedApps.Add($appKey) + # The List type needs to be specified again, because otherwise Sort-Object will convert the list to a string if there is only a single entry + [System.Collections.Generic.List[pscustomobject]]$sync.selectedApps = $sync.SelectedApps | Sort-Object + } + elseif ($type -eq "Remove") { + $sync.SelectedApps.Remove($appKey) + } + else{ + Write-Error "Type: $type not implemented" + } + $count = $sync.SelectedApps.Count + $SelectedLabel.Content = "Selected Apps: $count" + if ($count -gt 0) { + $SelectedLabel.ToolTip = $($sync.SelectedApps | Foreach-Object { $SortedAppsHashtable.$_.Content }) -join "`n" + } else { + $SelectedLabel.ToolTip = $Null + } +} \ No newline at end of file diff --git a/functions/public/Invoke-WPFUIApps.ps1 b/functions/public/Invoke-WPFUIApps.ps1 new file mode 100644 index 0000000000..7cde4b0b68 --- /dev/null +++ b/functions/public/Invoke-WPFUIApps.ps1 @@ -0,0 +1,450 @@ +function Toggle-CategoryVisibility { + param( + [Parameter(Mandatory=$true)] + [string]$Category, + [Parameter(Mandatory=$true)] + [System.Windows.Controls.ItemsControl]$ItemsControl + ) + + $appsInCategory = $ItemsControl.Items | Where-Object { + if ($null -ne $_.Tag){ + $SortedAppsHashtable.$($_.Tag).Category -eq $Category + } + } + $isCollapsed = $appsInCategory[0].Visibility -eq [Windows.Visibility]::Visible + foreach ($appEntry in $appsInCategory) { + $appEntry.Visibility = if ($isCollapsed) { + [Windows.Visibility]::Collapsed + } else { + [Windows.Visibility]::Visible + } + } + $categoryPanel = $ItemsControl.Items | Where-Object { $_ -is [System.Windows.Controls.StackPanel] -and $_.Children[1].Text -eq $Category } | Select-Object -First 1 + $categoryPanel.Children[0].Text = if ($isCollapsed) { "[+] " } else { "[-] " } +} + +function Search-AppsByNameOrDescription { + param( + [Parameter(Mandatory=$false)] + [string]$SearchString = "", + [Parameter(Mandatory=$false)] + [System.Windows.Controls.ItemsControl]$ItemsControl = $sync.ItemsControl + ) + $Apps = $ItemsControl.Items + + if ([string]::IsNullOrWhiteSpace($SearchString)) { + $Apps | ForEach-Object { + $_.Visibility = 'Visible' + } + } else { + $Apps | ForEach-Object { + if ($null -ne $_.Tag) { + if ($SortedAppsHashtable.$($_.Tag).Content -like "*$SearchString*") { + $_.Visibility = 'Visible' + } else { + $_.Visibility = 'Collapsed' + } + } + } + } +} +function Show-OnlyCheckedApps { + param ( + [Parameter(Mandatory=$true)] + [String[]]$appKeys, + [Parameter(Mandatory=$true)] + [System.Windows.Controls.ItemsControl]$ItemsControl + ) + Write-Host "Showing only $($appKeys.Count) apps" + $sync.ShowOnlySelected = -not $sync.ShowOnlySelected + if ($sync.ShowOnlySelected) { + $sync.Buttons.ShowSelectedAppsButton.Content = "Show All" + foreach ($item in $ItemsControl.Items) { + if ($appKeys -contains $item.Tag) { + $item.Visibility = [Windows.Visibility]::Visible + } else { + $item.Visibility = [Windows.Visibility]::Collapsed + } + } + } else { + $sync.Buttons.ShowSelectedAppsButton.Content = "Show Selected" + $ItemsControl.Items | ForEach-Object { $_.Visibility = [Windows.Visibility]::Visible } + } +} +function Invoke-WPFUIApps { + [OutputType([void])] + param( + [Parameter(Mandatory, Position = 0)] + [PSCustomObject[]]$Apps, + + [Parameter(Mandatory, Position = 1)] + [string]$TargetGridName, + [Parameter()] + [System.Boolean]$Alphabetical = $false + ) + + function Initialize-StackPanel { + $targetGrid = $window.FindName($TargetGridName) + $borderStyle = $window.FindResource("BorderStyle") + + $null = $targetGrid.Children.Clear() + + $mainBorder = New-Object Windows.Controls.Border + $mainBorder.VerticalAlignment = "Stretch" + $mainBorder.Style = $borderStyle + + $null = $targetGrid.Children.Add($mainBorder) + return $targetGrid + } + + function Initialize-Header { + param($TargetGrid) + $mainBorder = $TargetGrid.Children[0] + $dockPanel = New-Object Windows.Controls.DockPanel + $mainBorder.Child = $dockPanel + + function New-WPFButton { + param ( + [string]$Name, + [string]$Content + ) + $button = New-Object Windows.Controls.Button + $button.Name = $Name + $button.Content = $Content + $button.Margin = New-Object Windows.Thickness(2) + $button.HorizontalAlignment = "Stretch" + return $button + } + + $wrapPanelTop = New-Object Windows.Controls.WrapPanel + $wrapPanelTop.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "MainBackgroundColor") + $wrapPanelTop.HorizontalAlignment = "Left" + $wrapPanelTop.VerticalAlignment = "Top" + $wrapPanelTop.Orientation = "Horizontal" + $wrapPanelTop.Margin = $window.FindResource("TabContentMargin") + + $buttonConfigs = @( + @{Name="WPFInstall"; Content="Install/Upgrade Selected"}, + @{Name="WPFInstallUpgrade"; Content="Upgrade All"}, + @{Name="WPFUninstall"; Content="Uninstall Selected"} + ) + + foreach ($config in $buttonConfigs) { + $button = New-WPFButton -Name $config.Name -Content $config.Content + $null = $wrapPanelTop.Children.Add($button) + $sync[$config.Name] = $button + } + + $selectedLabel = New-Object Windows.Controls.Label + $selectedLabel.Name = "WPFSelectedLabel" + $selectedLabel.Content = "Selected Apps: 0" + $selectedLabel.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSizeHeading") + $selectedLabel.SetResourceReference([Windows.Controls.Control]::MarginProperty, "TabContentMargin") + $selectedLabel.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $selectedLabel.HorizontalAlignment = "Center" + $selectedLabel.VerticalAlignment = "Center" + + $null = $wrapPanelTop.Children.Add($selectedLabel) + $sync.$($selectedLabel.Name) = $selectedLabel + + $showSelectedAppsButton = New-Object Windows.Controls.Button + $showSelectedAppsButton.Name = "ShowSelectedAppsButton" + $showSelectedAppsButton.Content = "Show Selected" + $showSelectedAppsButton.Add_Click({ + if ($sync.SelectedApps.Count -gt 0) { + Show-OnlyCheckedApps -appKeys $sync.SelectedApps -ItemsControl $sync.ItemsControl + } + }) + $sync.Buttons.ShowSelectedAppsButton = $showSelectedAppsButton + $null = $wrapPanelTop.Children.Add($showSelectedAppsButton) + + [Windows.Controls.DockPanel]::SetDock($wrapPanelTop, [Windows.Controls.Dock]::Top) + $null = $dockPanel.Children.Add($wrapPanelTop) + return $dockPanel + } + + function Initialize-AppArea { + param($TargetGrid) + $scrollViewer = New-Object Windows.Controls.ScrollViewer + $scrollViewer.VerticalScrollBarVisibility = 'Auto' + $scrollViewer.HorizontalAlignment = 'Stretch' + $scrollViewer.VerticalAlignment = 'Stretch' + $scrollViewer.CanContentScroll = $true + + $itemsControl = New-Object Windows.Controls.ItemsControl + $itemsControl.HorizontalAlignment = 'Stretch' + $itemsControl.VerticalAlignment = 'Stretch' + + $itemsPanelTemplate = New-Object Windows.Controls.ItemsPanelTemplate + $factory = New-Object Windows.FrameworkElementFactory ([Windows.Controls.VirtualizingStackPanel]) + $itemsPanelTemplate.VisualTree = $factory + $itemsControl.ItemsPanel = $itemsPanelTemplate + + $itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::IsVirtualizingProperty, $true) + $itemsControl.SetValue([Windows.Controls.VirtualizingStackPanel]::VirtualizationModeProperty, [Windows.Controls.VirtualizationMode]::Recycling) + + $scrollViewer.Content = $itemsControl + + [Windows.Controls.DockPanel]::SetDock($scrollViewer, [Windows.Controls.Dock]::Bottom) + $null = $TargetGrid.Children.Add($scrollViewer) + return $itemsControl + } + + function Add-CategoryLabel { + param( + [string]$Category, + $ItemsControl + ) + + $categoryPanel = New-Object Windows.Controls.StackPanel + $categoryPanel.Orientation = [Windows.Controls.Orientation]::Horizontal + + $expanderIcon = New-Object Windows.Controls.TextBlock + $expanderIcon.Text = "[+] " + $expanderIcon.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSizeHeading") + $expanderIcon.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily") + $expanderIcon.VerticalAlignment = "Center" + $null = $categoryPanel.Children.Add($expanderIcon) + + $categoryLabel = New-Object Windows.Controls.TextBlock + $categoryLabel.Text = $Category + $categoryLabel.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSizeHeading") + $categoryLabel.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily") + $categoryLabel.VerticalAlignment = "Center" + $null = $categoryPanel.Children.Add($categoryLabel) + $categoryPanel.Cursor = [System.Windows.Input.Cursors]::Hand + + $categoryPanel.Add_MouseUp({ + Toggle-CategoryVisibility -Category $this.Children[1].Text -ItemsControl $this.Parent + }) + $null = $ItemsControl.Items.Add($categoryPanel) + } + + function New-CategoryAppList { + param( + $TargetGrid, + $Apps, + [System.Boolean]$Alphabetical + ) + $loadingLabel = New-Object Windows.Controls.Label + $loadingLabel.Content = "Loading, please wait..." + $loadingLabel.HorizontalAlignment = "Center" + $loadingLabel.VerticalAlignment = "Center" + $loadingLabel.FontSize = 16 + $loadingLabel.FontWeight = [Windows.FontWeights]::Bold + $loadingLabel.Foreground = [Windows.Media.Brushes]::Gray + + $itemsControl.Items.Clear() + $null = $itemsControl.Items.Add($loadingLabel) + + $itemsControl.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ + $itemsControl.Items.Clear() + + if (-not ($Alphabetical)) { + $categories = $Apps.Values | Select-Object -ExpandProperty category -Unique | Sort-Object + foreach ($category in $categories) { + Add-CategoryLabel -Category $category -ItemsControl $itemsControl + $Apps.Keys | Where-Object { $Apps.$_.Category -eq $category } | ForEach-Object { + New-AppEntry -ItemsControl $itemsControl -AppKey $_ -Hidden $true + } + } + } else { + foreach ($appKey in $Apps.Keys) { + New-AppEntry -ItemsControl $itemsControl -AppKey $appKey -Hidden $false + } + } + }) + } + + function New-AppEntry { + param( + $ItemsControl, + $AppKey, + [bool]$Hidden + ) + $App = $Apps.$AppKey + # Create the outer Border for the application type + $border = New-Object Windows.Controls.Border + $border.BorderBrush = [Windows.Media.Brushes]::Gray + $border.BorderThickness = 1 + $border.CornerRadius = 5 + $border.Padding = New-Object Windows.Thickness(10) + $border.HorizontalAlignment = "Stretch" + $border.VerticalAlignment = "Top" + $border.Margin = New-Object Windows.Thickness(0, 10, 0, 0) + $border.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallUnselectedColor") + $border.Tag = $Appkey + $border.ToolTip = $App.description + $border.Visibility = if ($Hidden) {[Windows.Visibility]::Collapsed} else {[Windows.Visibility]::Visible} + $border.Add_MouseUp({ + $childCheckbox = ($this.Child.Children | Where-Object {$_.Template.TargetType -eq [System.Windows.Controls.Checkbox]})[0] + $childCheckBox.isChecked = -not $childCheckbox.IsChecked + }) + # Create a DockPanel inside the Border + $dockPanel = New-Object Windows.Controls.DockPanel + $dockPanel.LastChildFill = $true + $border.Child = $dockPanel + + # Create the CheckBox, vertically centered + $checkBox = New-Object Windows.Controls.CheckBox + $checkBox.Name = $AppKey + $checkBox.Background = "Transparent" + $checkBox.HorizontalAlignment = "Left" + $checkBox.VerticalAlignment = "Center" + $checkBox.Margin = New-Object Windows.Thickness(5, 0, 10, 0) + $checkbox.Add_Checked({ + Invoke-WPFSelectedLabelUpdate -type "Add" -checkbox $this + $borderElement = $this.Parent.Parent + $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallSelectedColor") + }) + + $checkbox.Add_Unchecked({ + Invoke-WPFSelectedLabelUpdate -type "Remove" -checkbox $this + $borderElement = $this.Parent.Parent + $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallUnselectedColor") + }) + $sync.$($checkBox.Name) = $checkBox + # Create a StackPanel for the image and name + $imageAndNamePanel = New-Object Windows.Controls.StackPanel + $imageAndNamePanel.Orientation = "Horizontal" + $imageAndNamePanel.VerticalAlignment = "Center" + + # Create the Image and set a placeholder + $image = New-Object Windows.Controls.Image + # $image.Name = "wpfapplogo" + $App.Name + $image.Width = 40 + $image.Height = 40 + $image.Margin = New-Object Windows.Thickness(0, 0, 10, 0) + $image.Source = $noimage # Ensure $noimage is defined in your script + + # Clip the image corners + $image.Clip = New-Object Windows.Media.RectangleGeometry + $image.Clip.Rect = New-Object Windows.Rect(0, 0, $image.Width, $image.Height) + $image.Clip.RadiusX = 5 + $image.Clip.RadiusY = 5 + + $imageAndNamePanel.Children.Add($image) | Out-Null + + # Create the TextBlock for the application name + $appName = New-Object Windows.Controls.TextBlock + $appName.Text = $App.Content + $appName.FontSize = 16 + $appName.FontWeight = [Windows.FontWeights]::Bold + $appName.VerticalAlignment = "Center" + $appName.Margin = New-Object Windows.Thickness(5, 0, 0, 0) + $appName.Background = "Transparent" + $imageAndNamePanel.Children.Add($appName) | Out-Null + + # Add the image and name panel to the Checkbox + $checkBox.Content = $imageAndNamePanel + + # Add the checkbox to the DockPanel + [Windows.Controls.DockPanel]::SetDock($checkBox, [Windows.Controls.Dock]::Left) + $dockPanel.Children.Add($checkBox) | Out-Null + + # Create the StackPanel for the buttons and dock it to the right + $buttonPanel = New-Object Windows.Controls.StackPanel + $buttonPanel.Orientation = "Horizontal" + $buttonPanel.HorizontalAlignment = "Right" + $buttonPanel.VerticalAlignment = "Center" + $buttonPanel.Margin = New-Object Windows.Thickness(10, 0, 0, 0) + [Windows.Controls.DockPanel]::SetDock($buttonPanel, [Windows.Controls.Dock]::Right) + + # Create the "Install" button + $installButton = New-Object Windows.Controls.Button + $installButton.Width = 45 + $installButton.Height = 35 + $installButton.Margin = New-Object Windows.Thickness(0, 0, 10, 0) + + $installIcon = New-Object Windows.Controls.TextBlock + $installIcon.Text = [char]0xE118 # Install Icon + $installIcon.FontFamily = "Segoe MDL2 Assets" + $installIcon.FontSize = 20 + $installIcon.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $installIcon.Background = "Transparent" + $installIcon.HorizontalAlignment = "Center" + $installIcon.VerticalAlignment = "Center" + + $installButton.Content = $installIcon + $installButton.ToolTip = "Install or Upgrade the application" + $buttonPanel.Children.Add($installButton) | Out-Null + + # Add Click event for the "Install" button + $installButton.Add_Click({ + $appKey = $this.Parent.Parent.Parent.Tag + $appObject = $SortedAppsHashtable.$appKey + Invoke-WPFInstall -PackagesToInstall $appObject + }) + + # Create the "Uninstall" button + $uninstallButton = New-Object Windows.Controls.Button + $uninstallButton.Width = 45 + $uninstallButton.Height = 35 + + $uninstallIcon = New-Object Windows.Controls.TextBlock + $uninstallIcon.Text = [char]0xE74D # Uninstall Icon + $uninstallIcon.FontFamily = "Segoe MDL2 Assets" + $uninstallIcon.FontSize = 20 + $uninstallIcon.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $uninstallIcon.Background = "Transparent" + $uninstallIcon.HorizontalAlignment = "Center" + $uninstallIcon.VerticalAlignment = "Center" + + $uninstallButton.Content = $uninstallIcon + $buttonPanel.Children.Add($uninstallButton) | Out-Null + + $uninstallButton.ToolTip = "Uninstall the application" + $uninstallButton.Add_Click({ + $appKey = $this.Parent.Parent.Parent.Tag + $appObject = $SortedAppsHashtable.$appKey + Invoke-WPFUnInstall -PackagesToUninstall $appObject + }) + + # Create the "Info" button + $infoButton = New-Object Windows.Controls.Button + $infoButton.Width = 45 + $infoButton.Height = 35 + $infoButton.Margin = New-Object Windows.Thickness(10, 0, 0, 0) + + $infoIcon = New-Object Windows.Controls.TextBlock + $infoIcon.Text = [char]0xE946 # Info Icon + $infoIcon.FontFamily = "Segoe MDL2 Assets" + $infoIcon.FontSize = 20 + $infoIcon.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $infoIcon.Background = "Transparent" + $infoIcon.HorizontalAlignment = "Center" + $infoIcon.VerticalAlignment = "Center" + + $infoButton.Content = $infoIcon + $infoButton.ToolTip = "Open the application's website in your default browser" + $buttonPanel.Children.Add($infoButton) | Out-Null + + $infoButton.Add_Click({ + $appKey = $this.Parent.Parent.Parent.Tag + $appObject = $SortedAppsHashtable.$appKey + Start-Process $appObject.link + }) + + # Add the button panel to the DockPanel + $dockPanel.Children.Add($buttonPanel) | Out-Null + + # Add the border to the main items control in the grid + $itemsControl.Items.Add($border) | Out-Null + } + + + $window = $sync.Form + + switch ($TargetGridName) { + "appspanel" { + $targetGrid = Initialize-StackPanel + $dockPanelContainer = Initialize-Header -TargetGrid $targetGrid + $itemsControl = Initialize-AppArea -TargetGrid $dockPanelContainer + $sync.ItemsControl = $itemsControl + New-CategoryAppList -TargetGrid $itemsControl -Apps $Apps -Alphabetical $Alphabetical + } + default { + Write-Output "$TargetGridName not yet implemented" + } + } +} diff --git a/functions/public/Invoke-WPFUIElements.ps1 b/functions/public/Invoke-WPFUIElements.ps1 index e319d70211..dc0371e08b 100644 --- a/functions/public/Invoke-WPFUIElements.ps1 +++ b/functions/public/Invoke-WPFUIElements.ps1 @@ -27,7 +27,6 @@ function Invoke-WPFUIElements { $window = $sync.form - $theme = $window.Resources $borderstyle = $window.FindResource("BorderStyle") $HoverTextBlockStyle = $window.FindResource("HoverTextBlockStyle") $ColorfulToggleSwitchStyle = $window.FindResource("ColorfulToggleSwitchStyle") @@ -124,7 +123,9 @@ function Invoke-WPFUIElements { if ($configVariable -eq $sync.configs.applications) { # Create a WrapPanel to hold buttons at the top $wrapPanelTop = New-Object Windows.Controls.WrapPanel - $wrapPanelTop.Background = $window.FindResource("MainBackgroundColor") + + $wrapPanelTop.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "MainBackgroundColor") + $wrapPanelTop.HorizontalAlignment = "Left" $wrapPanelTop.VerticalAlignment = "Top" $wrapPanelTop.Orientation = "Horizontal" @@ -155,6 +156,18 @@ function Invoke-WPFUIElements { $wrapPanelTop.Children.Add($uninstallButton) | Out-Null $sync["WPFUninstall"] = $uninstallButton + $selectedLabel = New-Object Windows.Controls.Label + $selectedLabel.Name = "WPFSelectedLabel" + $selectedLabel.Content = "Selected Apps: 0" + $selectedLabel.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSizeHeading") + $selectedLabel.SetResourceReference([Windows.Controls.Control]::MarginProperty, "TabContentMargin") + $selectedLabel.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $selectedLabel.HorizontalAlignment = "Center" + $selectedLabel.VerticalAlignment = "Center" + + $wrapPanelTop.Children.Add($selectedLabel) | Out-null + $sync.$($selectedLabel.Name) = $selectedLabel + # Dock the WrapPanel at the top of the DockPanel [Windows.Controls.DockPanel]::SetDock($wrapPanelTop, [Windows.Controls.Dock]::Top) $dockPanelContainer.Children.Add($wrapPanelTop) | Out-Null @@ -197,8 +210,10 @@ function Invoke-WPFUIElements { $label = New-Object Windows.Controls.Label $label.Content = $category -replace ".*__", "" - $label.FontSize = $theme.FontSizeHeading - $label.FontFamily = $theme.HeaderFontFamily + + $label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSizeHeading") + $label.SetResourceReference([Windows.Controls.Control]::FontFamilyProperty, "HeaderFontFamily") + $itemsControl.Items.Add($label) | Out-Null $sync[$category] = $label @@ -220,6 +235,12 @@ function Invoke-WPFUIElements { $border.VerticalAlignment = "Top" $border.Margin = New-Object Windows.Thickness(0, 10, 0, 0) + $border.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallUnselectedColor") + $border.Add_MouseUp({ + $childCheckbox = ($this.Child.Children | Where-Object {$_.Template.TargetType -eq [System.Windows.Controls.Checkbox]})[0] + $childCheckBox.isChecked = -not $childCheckbox.IsChecked + }) + # Create a DockPanel inside the Border $dockPanel = New-Object Windows.Controls.DockPanel $dockPanel.LastChildFill = $true @@ -228,11 +249,22 @@ function Invoke-WPFUIElements { # Create the CheckBox, vertically centered $checkBox = New-Object Windows.Controls.CheckBox $checkBox.Name = $entryInfo.Name + + $checkBox.Background = "Transparent" $checkBox.HorizontalAlignment = "Left" $checkBox.VerticalAlignment = "Center" $checkBox.Margin = New-Object Windows.Thickness(5, 0, 10, 0) - [Windows.Controls.DockPanel]::SetDock($checkBox, [Windows.Controls.Dock]::Left) - $dockPanel.Children.Add($checkBox) | Out-Null + $checkbox.Add_Checked({ + Invoke-WPFSelectedLabelUpdate -type "Add" -checkbox $this + $borderElement = $this.Parent.Parent + $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallSelectedColor") + }) + + $checkbox.Add_Unchecked({ + Invoke-WPFSelectedLabelUpdate -type "Remove" -checkbox $this + $borderElement = $this.Parent.Parent + $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallUnselectedColor") + }) # Create a StackPanel for the image and name $imageAndNamePanel = New-Object Windows.Controls.StackPanel @@ -262,11 +294,16 @@ function Invoke-WPFUIElements { $appName.FontWeight = [Windows.FontWeights]::Bold $appName.VerticalAlignment = "Center" $appName.Margin = New-Object Windows.Thickness(5, 0, 0, 0) + + $appName.Background = "Transparent" $imageAndNamePanel.Children.Add($appName) | Out-Null - # Add the image and name panel to the dock panel - [Windows.Controls.DockPanel]::SetDock($imageAndNamePanel, [Windows.Controls.Dock]::Left) - $dockPanel.Children.Add($imageAndNamePanel) | Out-Null + # Add the image and name panel to the Checkbox + $checkBox.Content = $imageAndNamePanel + + # Add the checkbox to the DockPanel + [Windows.Controls.DockPanel]::SetDock($checkBox, [Windows.Controls.Dock]::Left) + $dockPanel.Children.Add($checkBox) | Out-Null # Create the StackPanel for the buttons and dock it to the right $buttonPanel = New-Object Windows.Controls.StackPanel @@ -286,7 +323,9 @@ function Invoke-WPFUIElements { $installIcon.Text = [char]0xE118 # Install Icon $installIcon.FontFamily = "Segoe MDL2 Assets" $installIcon.FontSize = 20 - $installIcon.Foreground = $theme.MainForegroundColor + + $installIcon.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $installIcon.Background = "Transparent" $installIcon.HorizontalAlignment = "Center" $installIcon.VerticalAlignment = "Center" @@ -308,7 +347,9 @@ function Invoke-WPFUIElements { $uninstallIcon.Text = [char]0xE74D # Uninstall Icon $uninstallIcon.FontFamily = "Segoe MDL2 Assets" $uninstallIcon.FontSize = 20 - $uninstallIcon.Foreground = $theme.MainForegroundColor + + $uninstallIcon.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $uninstallIcon.Background = "Transparent" $uninstallIcon.HorizontalAlignment = "Center" $uninstallIcon.VerticalAlignment = "Center" @@ -330,7 +371,9 @@ function Invoke-WPFUIElements { $infoIcon.Text = [char]0xE946 # Info Icon $infoIcon.FontFamily = "Segoe MDL2 Assets" $infoIcon.FontSize = 20 - $infoIcon.Foreground = $theme.MainForegroundColor + + $infoIcon.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $infoIcon.Background = "Transparent" $infoIcon.HorizontalAlignment = "Center" $infoIcon.VerticalAlignment = "Center" @@ -388,7 +431,9 @@ function Invoke-WPFUIElements { $label.Content = $entryInfo.Content $label.ToolTip = $entryInfo.Description $label.HorizontalAlignment = "Left" - $label.FontSize = $theme.FontSize + + $label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") + $label.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") $dockPanel.Children.Add($label) | Out-Null $itemsControl.Items.Add($dockPanel) | Out-Null @@ -407,14 +452,18 @@ function Invoke-WPFUIElements { $toggleButton = New-Object Windows.Controls.ToggleButton $toggleButton.Name = $entryInfo.Name $toggleButton.HorizontalAlignment = "Left" - $toggleButton.Height = $theme.TabButtonHeight - $toggleButton.Width = $theme.TabButtonWidth + + $toggleButton.SetResourceReference([Windows.Controls.Control]::HeightProperty, "TabButtonHeight") + $toggleButton.SetResourceReference([Windows.Controls.Control]::WidthProperty, "TabButtonWidth") + $toggleButton.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "ButtonInstallBackgroundColor") $toggleButton.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") $toggleButton.FontWeight = [Windows.FontWeights]::Bold $textBlock = New-Object Windows.Controls.TextBlock - $textBlock.FontSize = $theme.TabButtonFontSize + + $textBlock.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "TabButtonFontSize") + $textBlock.Background = [Windows.Media.Brushes]::Transparent $textBlock.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "ButtonInstallForegroundColor") @@ -443,21 +492,26 @@ function Invoke-WPFUIElements { $label.Content = $entryInfo.Content $label.HorizontalAlignment = "Left" $label.VerticalAlignment = "Center" - $label.FontSize = $theme.ButtonFontSize + + $label.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") + $horizontalStackPanel.Children.Add($label) | Out-Null $comboBox = New-Object Windows.Controls.ComboBox $comboBox.Name = $entryInfo.Name - $comboBox.Height = $theme.ButtonHeight - $comboBox.Width = $theme.ButtonWidth + + $comboBox.SetResourceReference([Windows.Controls.Control]::HeightProperty, "ButtonHeight") + $comboBox.SetResourceReference([Windows.Controls.Control]::WidthProperty, "ButtonWidth") $comboBox.HorizontalAlignment = "Left" $comboBox.VerticalAlignment = "Center" - $comboBox.Margin = $theme.ButtonMargin + $comboBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin") foreach ($comboitem in ($entryInfo.ComboItems -split " ")) { $comboBoxItem = New-Object Windows.Controls.ComboBoxItem $comboBoxItem.Content = $comboitem - $comboBoxItem.FontSize = $theme.ButtonFontSize + + $comboBoxItem.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") + $comboBox.Items.Add($comboBoxItem) | Out-Null } @@ -474,8 +528,10 @@ function Invoke-WPFUIElements { $button.Name = $entryInfo.Name $button.Content = $entryInfo.Content $button.HorizontalAlignment = "Left" - $button.Margin = $theme.ButtonMargin - $button.FontSize = $theme.ButtonFontSize + + $button.SetResourceReference([Windows.Controls.Control]::MarginProperty, "ButtonMargin") + $button.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") + if ($entryInfo.ButtonWidth) { $button.Width = $entryInfo.ButtonWidth } @@ -504,8 +560,10 @@ function Invoke-WPFUIElements { $radioButton.GroupName = $entryInfo.GroupName $radioButton.Content = $entryInfo.Content $radioButton.HorizontalAlignment = "Left" - $radioButton.Margin = $theme.CheckBoxMargin - $radioButton.FontSize = $theme.ButtonFontSize + + $radioButton.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin") + $radioButton.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "ButtonFontSize") + $radioButton.ToolTip = $entryInfo.Description if ($entryInfo.Checked -eq $true) { @@ -524,9 +582,11 @@ function Invoke-WPFUIElements { $checkBox = New-Object Windows.Controls.CheckBox $checkBox.Name = $entryInfo.Name $checkBox.Content = $entryInfo.Content - $checkBox.FontSize = $theme.FontSize + + $checkBox.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSize") $checkBox.ToolTip = $entryInfo.Description - $checkBox.Margin = $theme.CheckBoxMargin + $checkBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "CheckBoxMargin") + if ($entryInfo.Checked -eq $true) { $checkBox.IsChecked = $entryInfo.Checked } diff --git a/functions/public/Invoke-WPFUnInstall.ps1 b/functions/public/Invoke-WPFUnInstall.ps1 index 7180ecf20e..41796df325 100644 --- a/functions/public/Invoke-WPFUnInstall.ps1 +++ b/functions/public/Invoke-WPFUnInstall.ps1 @@ -1,9 +1,12 @@ function Invoke-WPFUnInstall { + param( + [Parameter(Mandatory=$false)] + [PSObject[]]$PackagesToUninstall = $($sync.selectedApps | Foreach-Object { $SortedAppsHashtable.$_ }) + ) <# .SYNOPSIS Uninstalls the selected programs - #> if($sync.ProcessRunning) { @@ -12,9 +15,7 @@ function Invoke-WPFUnInstall { return } - $PackagesToInstall = (Get-WinUtilCheckBoxes)["Install"] - - if ($PackagesToInstall.Count -eq 0) { + if ($PackagesToUninstall.Count -eq 0) { $WarningMsg = "Please select the program(s) to uninstall" [System.Windows.MessageBox]::Show($WarningMsg, $AppTitle, [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning) return @@ -22,7 +23,7 @@ function Invoke-WPFUnInstall { $ButtonType = [System.Windows.MessageBoxButton]::YesNo $MessageboxTitle = "Are you sure?" - $Messageboxbody = ("This will uninstall the following applications: `n $($PackagesToInstall | Format-Table | Out-String)") + $Messageboxbody = ("This will uninstall the following applications: `n $($PackagesToUninstall | Select-Object Name, Description| Out-String)") $MessageIcon = [System.Windows.MessageBoxImage]::Information $confirm = [System.Windows.MessageBox]::Show($Messageboxbody, $MessageboxTitle, $ButtonType, $MessageIcon) @@ -30,9 +31,9 @@ function Invoke-WPFUnInstall { if($confirm -eq "No") {return} $ChocoPreference = $($sync.WPFpreferChocolatey.IsChecked) - Invoke-WPFRunspace -ArgumentList @(("PackagesToInstall", $PackagesToInstall),("ChocoPreference", $ChocoPreference)) -DebugPreference $DebugPreference -ScriptBlock { - param($PackagesToInstall, $ChocoPreference, $DebugPreference) - if ($PackagesToInstall.count -eq 1) { + Invoke-WPFRunspace -ArgumentList @(("PackagesToUninstall", $PackagesToUninstall),("ChocoPreference", $ChocoPreference)) -DebugPreference $DebugPreference -ScriptBlock { + param($PackagesToUninstall, $ChocoPreference, $DebugPreference) + if ($PackagesToUninstall.count -eq 1) { $sync.form.Dispatcher.Invoke([action]{ Set-WinUtilTaskbaritem -state "Indeterminate" -value 0.01 -overlay "logo" }) } else { $sync.form.Dispatcher.Invoke([action]{ Set-WinUtilTaskbaritem -state "Normal" -value 0.01 -overlay "logo" }) @@ -41,7 +42,7 @@ function Invoke-WPFUnInstall { $packagesWinget = [System.Collections.ArrayList]::new() $packagesChoco = [System.Collections.ArrayList]::new() - foreach ($package in $PackagesToInstall) { + foreach ($package in $PackagesToUninstall) { if ($ChocoPreference) { if ($package.choco -eq "na") { $packagesWinget.add($package.winget) @@ -62,7 +63,7 @@ function Invoke-WPFUnInstall { } } return $packagesWinget, $packagesChoco - }.Invoke($PackagesToInstall) + }.Invoke($PackagesToUninstall) try { $sync.ProcessRunning = $true diff --git a/scripts/main.ps1 b/scripts/main.ps1 index 120a2410b8..cd0520fa76 100644 --- a/scripts/main.ps1 +++ b/scripts/main.ps1 @@ -115,32 +115,34 @@ Invoke-WinutilThemeChange -init $true $noimage = "https://images.emojiterra.com/google/noto-emoji/unicode-15/color/512px/1f4e6.png" $noimage = [Windows.Media.Imaging.BitmapImage]::new([Uri]::new($noimage)) -# Extract unique categories from the applications configuration -$uniqueCategories = $sync.configs.applications.PSObject.Properties.Value | - Where-Object { $_.Category } | - Select-Object -Unique -ExpandProperty Category - -# Create a custom PSCustomObject to simulate category-level checkboxes -$categoryConfig = @{} - -foreach ($category in $uniqueCategories) { - # Sanitize the category name for use in the Name property (remove spaces, special characters) - $sanitizedCategoryName = $category -replace '\W', '_' - - $categoryConfig[$sanitizedCategoryName] = [PSCustomObject]@{ - Category = "Categories" - Content = $category - } - $sync.configs.appnavigation | Add-Member -MemberType NoteProperty -Name $sanitizedCategoryName -Value $categoryConfig[$sanitizedCategoryName] -Force +$sync.Buttons = @{} +$SortedAppsHashtable = [ordered]@{} +$sortedProperties = $sync.configs.applications.PSObject.Properties | Sort-Object { $_.Value.Content } +$sortedProperties | ForEach-Object { + $SortedAppsHashtable[$_.Name] = $_.Value } # Now call the function with the final merged config Invoke-WPFUIElements -configVariable $sync.configs.appnavigation -targetGridName "appscategory" -columncount 1 -Invoke-WPFUIElements -configVariable $sync.configs.applications -targetGridName "appspanel" -columncount 1 + +Invoke-WPFUIApps -Apps $SortedAppsHashtable -targetGridName "appspanel" Invoke-WPFUIElements -configVariable $sync.configs.tweaks -targetGridName "tweakspanel" -columncount 2 Invoke-WPFUIElements -configVariable $sync.configs.feature -targetGridName "featurespanel" -columncount 2 +$sync.SortbyCategory.Add_Checked({ + Write-Host "Sort By Category" + $sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ + Invoke-WPFUIApps -Apps $SortedAppsHashtable -targetGridName "appspanel" + }) | Out-Null +}) +$sync.SortbyAlphabet.Add_Checked({ + Write-Host "Sort By Alphabet" + $sync.Form.Dispatcher.BeginInvoke([System.Windows.Threading.DispatcherPriority]::Background, [action]{ + Invoke-WPFUIApps -Apps $SortedAppsHashtable -targetGridName "appspanel" -alphabetical $true + }) +}) + #=========================================================================== # Store Form Objects In PowerShell #=========================================================================== @@ -450,73 +452,18 @@ if ($sync["ISOLanguage"].Items.Count -eq 1) { } $sync["ISOLanguage"].SelectedIndex = 0 - -# Load Checkboxes and Labels outside of the Filter function only once on startup for performance reasons -$filter = Get-WinUtilVariables -Type CheckBox -$CheckBoxes = ($sync.GetEnumerator()).where{ $psitem.Key -in $filter } - -$filter = Get-WinUtilVariables -Type Label -$labels = @{} -($sync.GetEnumerator()).where{$PSItem.Key -in $filter} | ForEach-Object {$labels[$_.Key] = $_.Value} - -$allCategories = $checkBoxes.Name | ForEach-Object {$sync.configs.applications.$_} | Select-Object -Unique -ExpandProperty category - $sync["SearchBar"].Add_TextChanged({ if ($sync.SearchBar.Text -ne "") { $sync.SearchBarClearButton.Visibility = "Visible" } else { $sync.SearchBarClearButton.Visibility = "Collapsed" } - - $activeApplications = @() - - $textToSearch = $sync.SearchBar.Text.ToLower() - - foreach ($CheckBox in $CheckBoxes) { - # Skip if the checkbox is null, it doesn't have content or it is the prefer Choco checkbox - if ($CheckBox -eq $null -or $CheckBox.Value -eq $null -or $CheckBox.Value.Content -eq $null -or $CheckBox.Name -eq "WPFpreferChocolatey") { - continue - } - - $checkBoxName = $CheckBox.Key - $textBlockName = $checkBoxName + "Link" - - # Retrieve the corresponding text block based on the generated name - $textBlock = $sync[$textBlockName] - - if ($CheckBox.Value.Content.ToString().ToLower().Contains($textToSearch)) { - $CheckBox.Value.Visibility = "Visible" - $activeApplications += $sync.configs.applications.$checkboxName - # Set the corresponding text block visibility - if ($textBlock -ne $null -and $textBlock -is [System.Windows.Controls.TextBlock]) { - $textBlock.Visibility = "Visible" - } - } else { - $CheckBox.Value.Visibility = "Collapsed" - # Set the corresponding text block visibility - if ($textBlock -ne $null -and $textBlock -is [System.Windows.Controls.TextBlock]) { - $textBlock.Visibility = "Collapsed" - } - } - } - - $activeCategories = $activeApplications | Select-Object -ExpandProperty category -Unique - - foreach ($category in $activeCategories) { - $sync[$category].Visibility = "Visible" - } - if ($activeCategories) { - $inactiveCategories = Compare-Object -ReferenceObject $allCategories -DifferenceObject $activeCategories -PassThru - } else { - $inactiveCategories = $allCategories - } - foreach ($category in $inactiveCategories) { - $sync[$category].Visibility = "Collapsed" - } + Search-AppsByNameOrDescription -SearchString $sync.SearchBar.Text }) $sync["Form"].Add_Loaded({ param($e) + $sync.Form.MinWidth = "1000" $sync["Form"].MaxWidth = [Double]::PositiveInfinity $sync["Form"].MaxHeight = [Double]::PositiveInfinity }) @@ -643,5 +590,10 @@ $sync["SponsorMenuItem"].Add_Click({ Show-CustomDialog -Message $authorInfo -EnableScroll $true }) +#Initialize List to store the Names of the selected Apps on the Install Tab +$sync.selectedApps = [System.Collections.Generic.List[string]]::new() +$sync.ShowOnlySeleced = $false + + $sync["Form"].ShowDialog() | out-null Stop-Transcript