functions/public/Open-PSWorkItemConsole.ps1
Function Open-PSWorkItemConsole { [cmdletbinding()] [Alias('wic')] [OutputType('None')] Param( [Parameter( HelpMessage = 'The path to the PSWorkItem SQLite database file. It must end in .db' )] [ValidatePattern('\.db$')] [ValidateScript( { Test-Path $_ }, ErrorMessage = 'Could not validate the database path.' )] [String]$Path = $PSWorkItemPath ) If ($host.name -ne 'ConsoleHost') { Write-Warning ($strings.InvalidHost -f $Host.Name) Return } #region initialize $scriptVer = (Get-Module PSWorkItem).version [Terminal.Gui.Application]::Init() [Terminal.Gui.Application]::QuitKey = 27 $window = [Terminal.Gui.Window]@{ Title = 'PSWorkItem Console' } #initialize an array to hold TUI controls $controls = @() #endregion #region status bar $StatusBar = [Terminal.Gui.StatusBar]::New( @( [Terminal.Gui.StatusItem]::New('Unknown', $(Get-Date -Format g), {}), [Terminal.Gui.StatusItem]::New('Unknown', 'ESC to quit or cancel', {}), [Terminal.Gui.StatusItem]::New('Unknown', "v$scriptVer", {}), [Terminal.Gui.StatusItem]::New('Unknown', 'Ready', {}) ) ) [Terminal.Gui.Application]::Top.Add($StatusBar) #endregion #region menu bar #Options $MenuItem0 = [Terminal.Gui.MenuItem]::New('Clear Form', '', { ClearForm }) $MenuItem0.ShortCut = [int][Terminal.Gui.Key]'CtrlMask' -bor [int][char]'X' $MenuItem1 = [Terminal.Gui.MenuItem]::New('Reset Form', '', { ResetForm }) $MenuItem1.ShortCut = [int][Terminal.Gui.Key]'CtrlMask' -bor [int][char]'R' $MenuItem3 = [Terminal.Gui.MenuItem]::New('Open _Database', '', { OpenDatabase }) $MenuItem3.ShortCut = [int][Terminal.Gui.Key]'CtrlMask' -bor [int][char]'D' $MenuItem2 = [Terminal.Gui.MenuItem]::New('_Quit', '', { [Terminal.Gui.Application]::RequestStop() }) $MenuItem2.ShortCut = [int][Terminal.Gui.Key]'CtrlMask' -bor [int][char]'Q' $MenuBarItem0 = [Terminal.Gui.MenuBarItem]::New('_Options', @($MenuItem3, $MenuItem0, $MenuItem1, $MenuItem2)) #Categories #3 Oct 2023 - add menu bar to manage categories $MenuItem8 = [Terminal.Gui.MenuItem]::New('Add Category', '', { AddCategory }) $MenuItem9 = [Terminal.Gui.MenuItem]::New('Remove Category', '', { RemoveCategory }) $MenuItem10 = [Terminal.Gui.MenuItem]::New('Set Category', '', { SetCategory }) $MenuBarItem2 = [Terminal.Gui.MenuBarItem]::New('_Categories', @($MenuItem8, $MenuItem10, $MenuItem9)) #Help $MenuItem3 = [Terminal.Gui.MenuItem]::New('A_bout', '', { ShowAbout }) $MenuItem4 = [Terminal.Gui.MenuItem]::New('_Documentation', '', { ShowHelp }) $MenuItem4.Shortcut = 'F1' $MenuItem5 = [Terminal.Gui.MenuItem]::New('Open Project _Repository', '', { Start-Process 'https://github.com/jdhitsolutions/PSWorkItem' }) $MenuItem6 = [Terminal.Gui.MenuItem]::New('Report a GitHub _Issue', '', { Start-Process 'https://github.com/jdhitsolutions/PSWorkItem/issues/new/choose' }) $MenuItem7 = [Terminal.Gui.MenuItem]::New('Open _Help Online', '', { Start-Process 'https://bit.ly/48zfOB8' }) $MenuItem8 = [Terminal.Gui.MenuItem]::New('_Show database detail', '', { ShowDatabaseDetail }) $MenuBarItem1 = [Terminal.Gui.MenuBarItem]::New('_Help', @($MenuItem4, $MenuItem7, $MenuItem5, $MenuItem6, $MenuItem8, $MenuItem3)) #define the menu bar $MenuBar = [Terminal.Gui.MenuBar]::New(@($MenuBarItem0, $MenuBarItem2, $MenuBarItem1)) #add to the window $Window.Add($MenuBar) #endregion #region main form $controls += $lblDescription = [Terminal.Gui.Label]@{ Text = 'Description:' X = 1 Y = 4 TabStop = $False } $controls += $txtDescription = [Terminal.Gui.TextField]@{ X = $lblDescription.Frame.Width + 2 Width = 30 Y = 4 TabStop = $True } $txtDescription.Add_MouseEnter({ $tip = $strings.tipDescription $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $txtDescription.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $controls += $chkClearDescription = [Terminal.Gui.CheckBox]@{ Text = 'C_lear' X = $txtDescription.X + ($txtDescription.Frame.Width + 2) Y = 4 TabStop = $False Visible = $False } $chkClearDescription.Add_MouseEnter({ $tip = $strings.tipClear $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $chkClearDescription.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $controls += $lblPath = [Terminal.Gui.Label]@{ Text = 'Database:' X = 1 Y = 11 TabStop = $false } $controls += $txtPath = [Terminal.Gui.TextField]@{ X = $lblDescription.Frame.Width + 2 Width = 40 Y = 11 Text = $Path TabStop = $True } $txtPath.Add_MouseEnter({ $tip = $strings.tipDatabasePath $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $txtPath.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $controls += $txtPath.Add_TextChanged({ #don't refresh until the user finishes typing the path if ($txtPath.Text.ToString() -match '\.db$') { $chkFilterTable.Checked = $False ClearForm RefreshTable RefreshCategoryList } }) #4 Jan 2024 Show required fields in a different color $cs = [Terminal.Gui.ColorScheme]::new() $cs.Normal = [Terminal.Gui.Attribute]::new('BrightYellow', 'Blue') $controls += $lblTaskName = [Terminal.Gui.Label]@{ Text = '*Name:' X = 1 Y = 2 TabStop = $False ColorScheme = $cs } $controls += $txtTaskName = [Terminal.Gui.TextField]@{ X = $lblDescription.Frame.Width + 2 Width = 25 Y = 2 TabIndex = 0 TabStop = $True } $txtTaskName.Add_MouseEnter({ $tip = $strings.tipTaskName $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $txtTaskName.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $OverDueColor = [Terminal.Gui.ColorScheme]::new() $OverDueColor.Normal = [Terminal.Gui.Attribute]::new('BrightRed', 'Blue') $controls += $lblOverDue = [Terminal.Gui.Label]@{ X = $txtTaskName.Frame.Right + 2 Width = 10 Y = 2 ColorScheme = $OverDueColor Text = 'OverDue' Visible = $False TabStop = $False CanFocus = $False } $controls += $lblCategory = [Terminal.Gui.Label]@{ Text = 'Category:' X = $txtDescription.Frame.Right + 4 Y = $lblTaskName.Y TabStop = $false CanFocus = $False ColorScheme = $cs } $lblCategory.Add_MouseEnter({ $tip = $strings.TipCategory $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $lblCategory.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $controls += $dropCategory = [Terminal.Gui.ListView]@{ Width = 15 Height = 8 X = $lblCategory.Frame.Right + 2 Y = $lblCategory.Y TabIndex = 1 TabStop = $True AllowsMultipleSelection = $False CanFocus = $True } $dropCategory.Add_SelectedItemChanged({ if ($chkFilterTable.Checked) { RefreshTable -FilterCategory $dropCategory.Source.ToList()[$dropCategory.SelectedItem] } }) $cs = [Terminal.Gui.ColorScheme]::new() <# Black Blue Green Cyan Red Magenta Brown Gray DarkGray BrightBlue BrightGreen BrightCyan BrightRed BrightMagenta BrightYellow White #> $fg= "BrightYellow" $bg = $window.ColorScheme.Normal.Background #"Blue" $cs.Normal = [Terminal.Gui.Attribute]::new($fg,$bg) $cs.Focus = [Terminal.Gui.Attribute]::new($fg,$bg) $cs.HotNormal = [Terminal.Gui.Attribute]::new($fg,$bg) $cs.HotFocus = [Terminal.Gui.Attribute]::new($fg,$bg) $cs.disabled = [Terminal.Gui.Attribute]::new($fg,$bg) #4 Jan 2024 - convert from Label to TextView to allow scrolling $controls += $lblReport = [Terminal.Gui.TextView]@{ AutoSize = $False Multiline = $True Width = 30 Height = [Terminal.Gui.Dim]::Percent(33) TextAlignment = 'Center' X = $dropCategory.Frame.right + 2 Y = $dropCategory.Y ColorScheme = $cs Text = (Get-PSWorkItemReport -Path $txtPath.Text.ToString() | Format-Table Category, Count, PctTotal | Out-String).Trim() TabStop = $False CanFocus = $True ReadOnly = $True } $lblReport.Add_MouseEnter({ $tip = $strings.tipReport $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $lblReport.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $controls += $chkWhatIf = [Terminal.Gui.CheckBox]@{ Text = 'WhatIf' X = 2 Y = 9 } $controls += $radioGrp = [Terminal.Gui.RadioGroup]@{ X = 2 Y = $txtDescription.Y + 2 Text = 'Due Date' RadioLabels = @('_Days Due', 'D_ue Date') SelectedItem = 0 DisplayMode = 'Vertical' TabStop = $True } $radioGrp.Add_SelectedItemChanged({ if ($radioGrp.SelectedItem -eq 0) { $txtDays.Enabled = $True $txtDueDate.Enabled = $False } else { $txtDays.Enabled = $False $txtDueDate.Enabled = $True } }) $controls += $txtDays = [Terminal.Gui.TextField]@{ Width = 4 #Updated 23 Feb 2024 to use the $PSWorkItemDefaultDays variable Text = $global:PSWorkItemDefaultDays Y = $txtDescription.Y + 2 X = $radioGrp.Frame.Width + 2 TabStop = $True } $controls += $txtDueDate = [Terminal.Gui.TextField]@{ Width = 20 Y = $txtDescription.Y + 3 X = $radioGrp.Frame.Width + 2 Enabled = $False TabStop = $True } $controls += $lblProgress = [Terminal.Gui.Label]@{ Text = 'Progress:' X = $txtDueDate.Frame.Right + 4 Y = $txtDays.Y TabStop = $false CanFocus = $False } $controls += $txtProgress = [Terminal.Gui.TextField]@{ Width = 5 Y = $txtDays.Y X = $lblProgress.Frame.Right + 2 TabStop = $False } $txtProgress.Add_MouseEnter({ $tip = $strings.tipProgress $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $txtProgress.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $controls += $chkFilterTable = [Terminal.Gui.CheckBox]@{ X = $TxtPath.X + $txtPath.Frame.Width + 2 Y = $txtPath.Y Text = 'Filter table' TabStop = $False } $chkFilterTable.Add_MouseEnter({ $tip = $strings.tipFilterTable $StatusBar.Items[3].Title = $tip [Terminal.Gui.Application]::Refresh() }) $chkFilterTable.Add_MouseLeave({ $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() }) $chkFilterTable.Add_Toggled({ if ($chkFilterTable.Checked) { RefreshTable -FilterCategory $dropCategory.Source.ToList()[$dropCategory.SelectedItem] } else { RefreshTable } }) #endregion #region buttons $controls += $btnAdd = [Terminal.Gui.Button]@{ X = 1 Y = $txtPath.Y + 2 Text = '_Add New PSWorkItem' TabStop = $True } $btnAdd.Add_Clicked({ if ($txtTaskName.Text.ToString() -match '\w+') { if ($chkWhatIf.Checked) { ShowWhatIf -Command New Return } $r = @{ Name = $txtTaskName.Text.ToString() Category = $dropCategory.Source.ToList()[$dropCategory.SelectedItem] Path = $txtPath.Text.ToString() ErrorAction = 'Stop' } if ($txtDescription.text.ToString() -match '\w+') { $r.Add('Description', $txtDescription.text.ToString()) } if ($txtDays.Enabled) { $r.Add('DaysDue', $txtDays.Text.ToString()) } else { $r.Add('DueDate', $txtDueDate.Text.ToString()) } $StatusBar.Items[3].Title = "Creating PSWorkItem $($txtTaskName.Text.ToString()) in $($r.Path)" $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 1000 Try { New-PSWorkItem @r ClearForm UpdateReport RefreshTable [Terminal.Gui.Application]::Refresh() } Catch { [Terminal.Gui.MessageBox]::ErrorQuery('Error', $_.Exception.Message) } } else { $StatusBar.Items[3].Title = $strings.MissingName $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 2000 $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() } }) $controls += $btnSet = [Terminal.Gui.Button]@{ X = $btnAdd.Frame.Width + 2 Y = $txtPath.Y + 2 Text = '_Set PSWorkItem' } $btnSet.Add_Clicked({ if ($txtTaskName.Text.ToString() -match '\w+') { if ($chkWhatIf.Checked -AND $chkClearDescription.Checked) { #save the current description $SavedDescription = $txtDescription.Text.ToString() #clear the field for the ShowWhatIF function $txtDescription.Text = '' [Terminal.Gui.Application]::Refresh() ShowWhatIf -command Set -id $TableView.table.rows[$TableView.SelectedRow].ID #reset the text field $txtDescription.Text = $SavedDescription [Terminal.Gui.Application]::Refresh() Return } elseif ($chkWhatIf.Checked) { ShowWhatIf -command Set -id $TableView.table.rows[$TableView.SelectedRow].ID Return } if ($chkClearDescription.Checked) { $r = @{ ID = $TableView.table.rows[$TableView.SelectedRow].ID Path = $txtPath.Text.ToString() ClearDescription = $True ErrorAction = 'Stop' } } else { $r = @{ ID = $TableView.table.rows[$TableView.SelectedRow].ID Name = $txtTaskName.Text.ToString() Category = $dropCategory.Source.ToList()[$dropCategory.SelectedItem] Path = $txtPath.Text.ToString() ErrorAction = 'Stop' } if ($txtDescription.text.ToString() -match '\w+') { $r.Add('Description', $txtDescription.text.ToString()) } if ($txtProgress.text.ToString() -match '\d') { $r.Add('Progress', $txtProgress.text.ToString()) } if ($txtDays.Enabled) { #calculate Date $due = (Get-Date).AddDays( $txtDays.Text.ToString()) $r.Add('DueDate', $due) } else { $r.Add('DueDate', $txtDueDate.Text.ToString()) } } #else normal set $StatusBar.Items[3].Title = "Updating PSWorkItem $($r.ID) in $($r.Path)" $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 1000 Try { Set-PSWorkItem @r Populate RefreshTable UpdateReport $StatusBar.Items[3].Title = 'Ready' $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() } Catch { [Terminal.Gui.MessageBox]::ErrorQuery('Error', $_.Exception.Message) } } else { $StatusBar.Items[3].Title = $strings.MissingName $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 2000 $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() } }) $controls += $btnComplete = [Terminal.Gui.Button]@{ X = $btnSet.Frame.Width + 25 Y = $txtPath.Y + 2 Text = '_Complete PSWorkItem' TabStop = $True } $btnComplete.Add_Clicked({ if ($txtTaskName.Text.ToString() -match '\w+') { if ($chkWhatIf.Checked) { ShowWhatIf -Command Complete -id $TableView.table.rows[$TableView.SelectedRow].ID Return } $r = @{ ID = $TableView.table.rows[$TableView.SelectedRow].ID Path = $txtPath.Text.ToString() ErrorAction = 'Stop' } $StatusBar.Items[3].Title = "Completing PSWorkItem $($r.ID) in $($r.Path)" $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 1000 Try { Complete-PSWorkItem @r ClearForm UpdateReport RefreshTable [Terminal.Gui.Application]::Refresh() } Catch { [Terminal.Gui.MessageBox]::ErrorQuery('Error', $_.Exception.Message) } } else { $StatusBar.Items[3].Title = $strings.NoWorkItemSelected $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 2000 $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() } }) $controls += $btnRemove = [Terminal.Gui.Button]@{ X = $btnComplete.Frame.Width + 44 Y = $txtPath.Y + 2 Text = '_Remove PSWorkItem' TabStop = $True } $btnRemove.Add_Clicked({ if ($txtTaskName.Text.ToString() -match '\w+') { if ($chkWhatIf.Checked) { ShowWhatIf -Command Remove -id $TableView.table.rows[$TableView.SelectedRow].ID Return } $r = @{ ID = $TableView.table.rows[$TableView.SelectedRow].ID Path = $txtPath.Text.ToString() ErrorAction = 'Stop' } if ($chkWhatIf.Checked) { $r.Add('WhatIf', $True) } $StatusBar.Items[3].Title = "Removing PSWorkItem $($r.ID) in $($r.Path)" $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 1000 Try { Remove-PSWorkItem @r } Catch { [Terminal.Gui.MessageBox]::ErrorQuery('Error', $_.Exception.Message) } ClearForm UpdateReport RefreshTable [Terminal.Gui.Application]::Refresh() } else { $StatusBar.Items[3].Title = $strings.NoWorkItemSelected $StatusBar.SetChildNeedsDisplay() [Terminal.Gui.Application]::Refresh() Start-Sleep -Milliseconds 2000 $StatusBar.Items[3].Title = 'Ready' [Terminal.Gui.Application]::Refresh() } }) #endregion #region table view #add a table showing work items $controls += $TableView = [Terminal.Gui.TableView]@{ X = 0 Y = $btnAdd.Y + 2 Width = [Terminal.Gui.Dim]::Fill() Height = [Terminal.Gui.Dim]::Fill() AutoSize = $False TabStop = $False MultiSelect = $False FullRowSelect = $True TextAlignment = 'Center' } #Keep table headers always in view $TableView.Style.AlwaysShowHeaders = $True $TableView.Style.ShowHorizontalHeaderOverline = $False $TableView.Style.ShowHorizontalHeaderUnderline = $True $TableView.Style.ShowVerticalHeaderLines = $False #event handlers $TableView.Add_SelectedCellChanged({ Populate }) $TableView.Add_MouseClick({ Param ($Mouse) $item = $TableView.table.rows[$TableView.SelectedRow] if ($Mouse.mouseEvent.Flags -eq [Terminal.Gui.MouseFlags]::Button3Clicked) { $Detail = Get-PSWorkItem -Path $txtPath.Text.ToString() -ID $item.ID | Select-Object -Property ID, Overdue, Description, Progress, Category, TaskCreated, TaskModified, Age, Due, TimeRemaining | Out-String $DetailDialog = [Terminal.Gui.Dialog]@{ Title = $item.name X = [Terminal.Gui.Pos]::Center() Y = [Terminal.Gui.Pos]::Center() Width = 50 Height = 20 TextAlignment = 'Left' Text = $Detail } $ok = [Terminal.Gui.Button]@{ Text = 'OK' } $ok.Add_Clicked({ $DetailDialog.RequestStop() }) $DetailDialog.AddButton($ok) [Terminal.Gui.Application]::Run($DetailDialog) } }) $window.Add($TableView) #endregion #region execute #add the controls to the main window $controls.foreach({ $window.Add($_) }) [Terminal.Gui.Application]::Top.Add($window) #set tab order $txtTaskName.TabIndex = 0 $dropCategory.TabIndex = 1 $txtDescription.TabIndex = 2 $txtDays.TabIndex = 3 $radioGrp.TabIndex = 4 $txtDueDate.TabIndex = 5 $chkWhatIf.TabIndex = 6 $btnAdd.TabIndex = 7 $btnSet.TabIndex = 8 $btnComplete.TabIndex = 9 $btnRemove.TabIndex = 10 #refresh data RefreshTable RefreshCategoryList UpdateReport $txtTaskName.SetFocus() #set a timer to update the status bar every 15 seconds $TimerToken = [Terminal.Gui.Application]::MainLoop.AddTimeout((New-TimeSpan -Seconds 15), { UpdateStatusTime }) #4 Jan 2024 If using -Debug export variables to the global scope for review If ($PSBoundParameters.ContainsKey("Debug")) { $global:wicWindow = $window $global:wicControls = $controls } [Terminal.Gui.Application]::Run() [Terminal.Gui.Application]::ShutDown() #endregion } #end function |