From 7d0e4df4ec981a31b6dcbe39bde06c56c0506a7c Mon Sep 17 00:00:00 2001 From: Marterich <47688561+Marterich@users.noreply.github.com> Date: Sun, 27 Oct 2024 21:24:20 +0100 Subject: [PATCH] Add a Popup Dropdown for Selected Apps with the ability to deselect them --- .../public/Invoke-WPFSelectedAppsUpdate.ps1 | 92 +++++++++++++++++++ .../public/Invoke-WPFSelectedLabelUpdate.ps1 | 41 --------- functions/public/Invoke-WPFUIApps.ps1 | 60 +++++++++--- scripts/start.ps1 | 2 + 4 files changed, 141 insertions(+), 54 deletions(-) create mode 100644 functions/public/Invoke-WPFSelectedAppsUpdate.ps1 delete mode 100644 functions/public/Invoke-WPFSelectedLabelUpdate.ps1 diff --git a/functions/public/Invoke-WPFSelectedAppsUpdate.ps1 b/functions/public/Invoke-WPFSelectedAppsUpdate.ps1 new file mode 100644 index 0000000000..25c8245b03 --- /dev/null +++ b/functions/public/Invoke-WPFSelectedAppsUpdate.ps1 @@ -0,0 +1,92 @@ +function Invoke-WPFSelectedAppsUpdate { + <# + .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" selectedAppLabel 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-WPFSelectedAppsUpdate -type "Remove" -checkbox $this}) + OR + Invoke-WPFSelectedAppsUpdate -type "Add" -checkbox $specificCheckbox + #> + param ( + $type, + $checkbox + ) + function Add-SelectedAppsMenuItem { + <# + .SYNOPSIS + This is a helper function that generates and adds the Menu Items to the Selected Apps Popup. + + .Parameter name + The actual Name of an App like "Chrome" or "Brave" + This name is contained in the "Content" property inside the applications.json + .PARAMETER key + The key which identifies an app object in applications.json + For Chrome this would be "WPFInstallchrome" because "WPFInstall" is prepended automatically for each key in applications.json + #> + + param ([string]$name, [string]$key) + + $selectedAppGrid = New-Object Windows.Controls.Grid + + $selectedAppGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition -Property @{Width = "*"})) + $selectedAppGrid.ColumnDefinitions.Add((New-Object System.Windows.Controls.ColumnDefinition -Property @{Width = "30"})) + + # Sets the name to the Content as well as the Tooltip, because the parent Popup Border has a fixed width and text could "overflow". + # With the tooltip, you can still read the whole entry on hover + $selectedAppLabel = New-Object Windows.Controls.Label + $selectedAppLabel.Content = $name + $selectedAppLabel.ToolTip = $name + $selectedAppLabel.HorizontalAlignment = "Left" + $selectedAppLabel.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + [System.Windows.Controls.Grid]::SetColumn($selectedAppLabel, 0) + $selectedAppGrid.Children.Add($selectedAppLabel) + + $selectedAppRemoveButton = New-Object Windows.Controls.Button + $selectedAppRemoveButton.FontFamily = "Segoe MDL2 Assets" + $selectedAppRemoveButton.Content = [string]([char]0xE711) + $selectedAppRemoveButton.HorizontalAlignment = "Center" + $selectedAppRemoveButton.Tag = $key + $selectedAppRemoveButton.ToolTip = "Remove the App from Selection" + $selectedAppRemoveButton.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $selectedAppRemoveButton.SetResourceReference([Windows.Controls.Control]::StyleProperty, "HoverButtonStyle") + + # Highlight the Remove icon on Hover + $selectedAppRemoveButton.Add_MouseEnter({ $this.Foreground = "Red" }) + $selectedAppRemoveButton.Add_MouseLeave({ $this.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") }) + $selectedAppRemoveButton.Add_Click({ + $sync.($this.Tag).isChecked = $false # On click of the remove button, we only have to uncheck the corresponding checkbox. This will kick of all neccessary changes to update the UI + }) + [System.Windows.Controls.Grid]::SetColumn($selectedAppRemoveButton, 1) + $selectedAppGrid.Children.Add($selectedAppRemoveButton) + # Add new Element to Popup + $sync.selectedAppsstackPanel.Children.Add($selectedAppGrid) + } + $selectedAppsButton = $sync.WPFselectedAppsButton + # Get the actual Name from the selectedAppLabel 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 + $selectedAppsButton.Content = "Selected Apps: $count" + # On every change, remove all entries inside the Popup Menu. This is done, so we can keep the alphabetical order even if elements are selected in a random way + $sync.selectedAppsstackPanel.Children.Clear() + $sync.SelectedApps | Foreach-Object { Add-SelectedAppsMenuItem -name $($sync.configs.applicationsHashtable.$_.Content) -key $_ } + +} diff --git a/functions/public/Invoke-WPFSelectedLabelUpdate.ps1 b/functions/public/Invoke-WPFSelectedLabelUpdate.ps1 deleted file mode 100644 index cdc11b262e..0000000000 --- a/functions/public/Invoke-WPFSelectedLabelUpdate.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -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 { $sync.configs.applicationsHashtable.$_.Content }) -join "`n" - } else { - $SelectedLabel.ToolTip = $Null - } -} diff --git a/functions/public/Invoke-WPFUIApps.ps1 b/functions/public/Invoke-WPFUIApps.ps1 index 3b9cdfbb52..22be82a955 100644 --- a/functions/public/Invoke-WPFUIApps.ps1 +++ b/functions/public/Invoke-WPFUIApps.ps1 @@ -185,18 +185,52 @@ function Invoke-WPFUIApps { $null = $wrapPanelTop.Children.Add($button) $sync[$config.Name] = $button } + + $selectedAppsButton = New-Object Windows.Controls.Button + $selectedAppsButton.Name = "WPFselectedAppsButton" + $selectedAppsButton.Content = "Selected Apps: 0" + $selectedAppsButton.SetResourceReference([Windows.Controls.Control]::FontSizeProperty, "FontSizeHeading") + $selectedAppsButton.SetResourceReference([Windows.Controls.Control]::MarginProperty, "TabContentMargin") + $selectedAppsButton.SetResourceReference([Windows.Controls.Control]::ForegroundProperty, "MainForegroundColor") + $selectedAppsButton.HorizontalAlignment = "Center" + $selectedAppsButton.VerticalAlignment = "Center" + + $selectedAppsPopup = New-Object Windows.Controls.Primitives.Popup + $selectedAppsPopup.IsOpen = $false + $selectedAppsPopup.PlacementTarget = $selectedAppsButton + $selectedAppsPopup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom + $selectedAppsPopup.AllowsTransparency = $true + + $selectedAppsBorder = New-Object Windows.Controls.Border + $selectedAppsBorder.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "MainBackgroundColor") + $selectedAppsBorder.SetResourceReference([Windows.Controls.Control]::BorderBrushProperty, "MainForegroundColor") + $selectedAppsBorder.SetResourceReference([Windows.Controls.Control]::BorderThicknessProperty, "ButtonBorderThickness") + $selectedAppsBorder.Width = 200 + $selectedAppsBorder.Padding = 5 + $selectedAppsPopup.Child = $selectedAppsBorder + $sync.selectedAppsPopup = $selectedAppsPopup + + $sync.selectedAppsstackPanel = New-Object Windows.Controls.StackPanel + $selectedAppsBorder.Child = $sync.selectedAppsstackPanel + + # Toggle selectedAppsPopup open/close with button + $selectedAppsButton.Add_Click({ + $sync.selectedAppsPopup.IsOpen = -not $sync.selectedAppsPopup.IsOpen + }) + # Close selectedAppsPopup when mouse leaves both button and selectedAppsPopup + $selectedAppsButton.Add_MouseLeave({ + if (-not $sync.selectedAppsPopup.IsMouseOver) { + $sync.selectedAppsPopup.IsOpen = $false + } + }) + $selectedAppsPopup.Add_MouseLeave({ + if (-not $selectedAppsButton.IsMouseOver) { + $sync.selectedAppsPopup.IsOpen = $false + } + }) - $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 + $null = $wrapPanelTop.Children.Add($selectedAppsButton) + $sync.$($selectedAppsButton.Name) = $selectedAppsButton [Windows.Controls.DockPanel]::SetDock($wrapPanelTop, [Windows.Controls.Dock]::Top) $null = $dockPanel.Children.Add($wrapPanelTop) @@ -338,13 +372,13 @@ function Invoke-WPFUIApps { $checkBox.SetResourceReference([Windows.Controls.Control]::MarginProperty, "AppTileMargins") $checkBox.SetResourceReference([Windows.Controls.Control]::StyleProperty, "CollapsedCheckBoxStyle") $checkbox.Add_Checked({ - Invoke-WPFSelectedLabelUpdate -type "Add" -checkbox $this + Invoke-WPFSelectedAppsUpdate -type "Add" -checkbox $this $borderElement = $this.Parent.Parent $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallSelectedColor") }) $checkbox.Add_Unchecked({ - Invoke-WPFSelectedLabelUpdate -type "Remove" -checkbox $this + Invoke-WPFSelectedAppsUpdate -type "Remove" -checkbox $this $borderElement = $this.Parent.Parent $borderElement.SetResourceReference([Windows.Controls.Control]::BackgroundProperty, "AppInstallUnselectedColor") }) diff --git a/scripts/start.ps1 b/scripts/start.ps1 index 37512ebf9d..2e55646ba1 100644 --- a/scripts/start.ps1 +++ b/scripts/start.ps1 @@ -43,6 +43,8 @@ $sync.selectedApps = [System.Collections.Generic.List[string]]::new() $sync.ShowOnlySeleced = $false $sync.currentTab = "Install" $sync.ShowOnlySelected = $false +$sync.selectedAppsStackPanel +$sync.selectedAppsPopup if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {