diff --git a/cli/cli.go b/cli/cli.go index 43c83b0..bc37449 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -43,6 +43,18 @@ func (p *Program) LoadTimerList() error { return nil } +func (p *Program) WriteLists() error { + err := p.WriteTimerList() + if dErr := p.WriteDoneList(); dErr != nil { + if err == nil { + return fmt.Errorf("Error writing Done list %w", dErr) + } else { + return fmt.Errorf("Error writing Both lists (Timer: %s; Done: %s) (%w)", err.Error(), dErr.Error(), err) + } + } + return nil +} + func (p *Program) WriteTimerList() error { return p.TimerList.WriteToFilename(p.timerPath) } diff --git a/ui/list_timers.go b/ui/list_timers.go index 362ae49..5bee077 100644 --- a/ui/list_timers.go +++ b/ui/list_timers.go @@ -53,6 +53,15 @@ type listTimersScreen struct { type ListTimersMsg ScreenMsg +func NewListTimersMsg(data interface{}, err error) ListTimersMsg { + return ListTimersMsg{ + source: ListTimersId, + command: CmdArchiveTimer, + data: data, + err: err, + } +} + func NewListTimersScreen(u *Ui) *listTimersScreen { s := listTimersScreen{ ui: u, @@ -115,6 +124,7 @@ func (s *listTimersScreen) Init() wandle.Cmd { func (s *listTimersScreen) Update(msg wandle.Msg) wandle.Cmd { switch msg := msg.(type) { case ScreenMsg: + s.err = msg.err case termbox.Event: return s.handleTermboxEvent(msg) } @@ -172,7 +182,17 @@ func (s *listTimersScreen) View(style wandle.Style) { selectedStatus = fmt.Sprintf("%s (%d / %d selected)", selectedStatus, len(s.selected), s.fullFilterList.Size()) } wandle.Print(1, h-2, style, selectedStatus) - help := "[T]oggle Display, [p]roject(+), [c]ontext(@), [t]ags(:), [A]rchive Selected, [Ctrl+A]: Select All/None, [Ctrl+I]: Invert Selection" + var archiveText string + if s.areSelectedInSameList() { + if s.areSelectedInDoneList() { + archiveText = "Un[A]rchive Selected, " + } else { + archiveText = "[A]rchive Selected, " + } + } else { + archiveText = "Not in Same List" + } + help := fmt.Sprintf("[T]oggle Display, [p]roject(+), [c]ontext(@), [t]ags(:), %s[Ctrl+A]: Select All/None, [Ctrl+I]: Invert Selection", archiveText) wandle.Print(1, h-1, style, help) s.scrollbar.View(style) @@ -188,7 +208,10 @@ func (s *listTimersScreen) View(style wandle.Style) { if s.confirm.IsActive() { s.confirm.View(style) } - wandle.Print(1, h-2, style, s.msg) + wandle.Print(1, h-3, style, s.msg) + if s.err != nil { + wandle.Print(1, h-4, ErrStyle, s.err.Error()) + } } func (s *listTimersScreen) ViewTimer(x, y int, style wandle.Style, tmr *timertxt.Timer) { @@ -204,7 +227,12 @@ func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd { return s.confirm.Update(msg) } if s.choiceMenu.IsActive() { - return s.choiceMenu.Update(msg) + if msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc { + s.choiceMenu.SetActive(false) + return wandle.EmptyCmd + } else { + return s.choiceMenu.Update(msg) + } } if s.tagEditor.IsActive() { return s.tagEditor.Update(msg) @@ -252,6 +280,8 @@ func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd { return nil } else if msg.Ch == 'G' { s.cursor = s.fullFilterList.Size() - 1 + } else if msg.Ch == 'g' { + s.cursor = 0 } else if msg.Ch == 't' { // Edit tag(s) return s.showEditTagsChoice() @@ -301,10 +331,10 @@ func (s *listTimersScreen) showArchiveSelected() wandle.Cmd { return func() wandle.Msg { if len(s.selected) > 0 { s.confirm.SetTitle(fmt.Sprintf("Archive %d Timers?", len(s.selected))) - s.confirm.SetMessage("Are you sure you want to archive these timers?") + s.confirm.SetMessage("Are you sure you want to archive these timers? (y/n)") } else { s.confirm.SetTitle("Archive Timer?") - s.confirm.SetMessage("Are you sure you want to archive this timer?") + s.confirm.SetMessage("Are you sure you want to archive this timer? (y/n)") } s.confirm.SetOkCommand(func() wandle.Msg { s.confirm.SetVisible(false) @@ -319,25 +349,35 @@ func (s *listTimersScreen) showArchiveSelected() wandle.Cmd { } } func (s *listTimersScreen) doArchiveSelected() wandle.Cmd { - return func() wandle.Msg { - selected := len(s.selected) - if selected == 0 { - if s.cursor < s.fullFilterList.Size() { - var selTimer *timertxt.Timer - if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { - - } - } - } else { - for i := range s.selected { - if tmr, err := s.fullFilterList.GetTimer(i); err == nil { - + archiveTimer := func(t *timertxt.Timer) error { + if remErr := s.timerList.RemoveTimer(*t); remErr != nil { + return remErr + } + s.doneList.AddTimer(t) + return nil + } + selected := len(s.selected) + if selected == 0 { + if s.cursor < s.fullFilterList.Size() { + var selTimer *timertxt.Timer + if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { + if archErr := archiveTimer(selTimer); archErr != nil { + s.err = archErr + return wandle.EmptyCmd + } + } + } + } else { + for i := range s.selected { + if tmr, err := s.fullFilterList.GetTimer(i); err == nil { + if err := archiveTimer(tmr); err != nil { + s.err = err + return wandle.EmptyCmd } } } - return nil } - //return wandle.EmptyCmd + return wandle.EmptyCmd } func (s *listTimersScreen) showEditTagsChoice() wandle.Cmd { @@ -393,12 +433,19 @@ func (s *listTimersScreen) showEditTagsChoice() wandle.Cmd { removeTag.SetCommand(func() wandle.Msg { s.choiceMenu.ClearOptions() s.choiceMenu.SetTitle("Choose Tag to Remove") + for k, v := range tags { + opt := widdles.NewMenuItem(fmt.Sprintf("%s: %s", k, v)) + opt.SetCommand(func() wandle.Msg { + s.removeTagOnSelectedTimers(k) + return wandle.EmptyCmd + }) + s.choiceMenu.AddOption(opt) + } + s.choiceMenu.SetActive(true) return wandle.EmptyCmd }) s.choiceMenu.AddOption(removeTag) s.choiceMenu.SetActive(true) - //tags := s.fullList.GetTagKeys() - //_ = tags return wandle.EmptyCmd } @@ -425,127 +472,112 @@ func (s *listTimersScreen) gotoSettingsScreen() wandle.Msg { } } -func (s *listTimersScreen) getSelectedTimerTags() map[string][]string { - ret := make(map[string][]string) - selected := len(s.selected) - if selected == 0 { - if s.cursor < s.fullFilterList.Size() { - var selTimer *timertxt.Timer - if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { - for k, v := range selTimer.AdditionalTags { - ret[k] = []string{v} - } - } - } - } else { - for i := range s.selected { - if tmr, err := s.fullFilterList.GetTimer(i); err == nil { - for k, v := range tmr.AdditionalTags { - if !util.StringSliceContains(ret[k], v) { - ret[k] = append(ret[k], v) - } - } - } - } - } - return ret -} -func (s *listTimersScreen) updateTagOnSelectedTimers(key, val string) { - selected := len(s.selected) - if selected == 0 { - if s.cursor < s.fullFilterList.Size() { - var selTimer *timertxt.Timer - if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { - selTimer.AdditionalTags[key] = val - } - } - } else { - for i := range s.selected { - if tmr, err := s.fullFilterList.GetTimer(i); err == nil { - tmr.AdditionalTags[key] = val - } - } - } +// Writes the lists through the program, putting errors in s.err +func (s *listTimersScreen) writeLists() { var errText string - if err := s.ui.program.WriteTimerList(); err != nil { + if err := s.ui.program.WriteLists(); err != nil { errText = fmt.Sprintf("Errors Writing Lists (%v)", err) } - if err := s.ui.program.WriteDoneList(); err != nil { - if len(errText) == 0 { - errText = fmt.Sprintf("Errors Writing Lists (%v)", err) - } else { - errText = fmt.Sprintf("%s (%v)", errText, err) - } - } if len(errText) > 0 { s.err = errors.New(errText) } } -func (s *listTimersScreen) getSelectedTimerProjects() []string { - var ret []string +func (s *listTimersScreen) getSelectedTimers() []*timertxt.Timer { + var ret []*timertxt.Timer selected := len(s.selected) if selected == 0 { if s.cursor < s.fullFilterList.Size() { var selTimer *timertxt.Timer if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { - for _, v := range selTimer.Projects { - ret = append(ret, v) - } + ret = append(ret, selTimer) } } } else { for i := range s.selected { if tmr, err := s.fullFilterList.GetTimer(i); err == nil { - for _, v := range tmr.Projects { - if !util.StringSliceContains(ret, v) { - ret = append(ret, v) - } - } + ret = append(ret, tmr) } } } return ret } +func (s *listTimersScreen) getSelectedTimerTags() map[string][]string { + ret := make(map[string][]string) + sel := s.getSelectedTimers() + for _, tmr := range sel { + for k, v := range tmr.AdditionalTags { + ret[k] = util.AppendStringIfDistinct(ret[k], v) + } + } + return ret +} +func (s *listTimersScreen) updateTagOnSelectedTimers(key, val string) { + sel := s.getSelectedTimers() + for _, tmr := range sel { + tmr.AdditionalTags[key] = val + } + s.writeLists() +} +func (s *listTimersScreen) removeTagOnSelectedTimers(key string) { + sel := s.getSelectedTimers() + for _, tmr := range sel { + if _, ok := tmr.AdditionalTags[key]; ok { + delete(tmr.AdditionalTags, key) + } + } + s.writeLists() +} +func (s *listTimersScreen) getSelectedTimerProjects() []string { + var ret []string + sel := s.getSelectedTimers() + for _, tmr := range sel { + for _, v := range tmr.Contexts { + ret = util.AppendStringIfDistinct(ret, v) + } + } + return ret +} func (s *listTimersScreen) getSelectedTimerContexts() []string { var ret []string - selected := len(s.selected) - if selected == 0 { - if s.cursor < s.fullFilterList.Size() { - var selTimer *timertxt.Timer - if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { - for _, v := range selTimer.Contexts { - ret = append(ret, v) - } - } - } - } else { - for i := range s.selected { - if tmr, err := s.fullFilterList.GetTimer(i); err == nil { - for _, v := range tmr.Contexts { - if !util.StringSliceContains(ret, v) { - ret = append(ret, v) - } - } - } + sel := s.getSelectedTimers() + for _, tmr := range sel { + for _, v := range tmr.Contexts { + ret = util.AppendStringIfDistinct(ret, v) } } return ret } func (s *listTimersScreen) getSelectedTimerDuration() time.Duration { - selected := len(s.selected) - if selected == 0 { - if s.cursor < s.fullFilterList.Size() { - var selTimer *timertxt.Timer - if selTimer, s.err = s.fullFilterList.GetTimer(s.cursor); s.err == nil { - return util.Round(selTimer.Duration()) - } - } - } + sel := s.getSelectedTimers() var ret time.Duration - for i := range s.selected { - if tmr, err := s.fullFilterList.GetTimer(i); err == nil { - ret = util.AddDurations(ret, util.Round(tmr.Duration())) - } + for _, tmr := range sel { + ret = util.AddDurations(ret, util.Round(tmr.Duration())) } return ret } + +// Returns true if all selected timers are done +func (s *listTimersScreen) areSelectedInDoneList() bool { + sel := s.getSelectedTimers() + for i := range sel { + if s.timerList.Contains(sel[i]) { + return false + } + } + return true +} + +// Return true if all selected timers are from the same list (file) +func (s *listTimersScreen) areSelectedInSameList() bool { + sel := s.getSelectedTimers() + var inActive, inDone int + for i := range sel { + if s.timerList.Contains(sel[i]) { + inActive++ + } + if s.doneList.Contains(sel[i]) { + inDone++ + } + } + return inActive == 0 || inDone == 0 +} diff --git a/ui/ui.go b/ui/ui.go index e9263f1..21aff27 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -22,11 +22,26 @@ const ( CmdCanceled = ScreenMsgCommand(iota) CmdSaved + // ListTimers Commands + CmdArchiveTimer + // Goto Screen Commands CmdGotoSettings CmdGotoTimerList ) +// Styles +var ( + DefaultStyle = wandle.NewStyle( + termbox.RGBToAttribute(uint8(0), uint8(255), uint8(0)), + termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)), + ) + ErrStyle = wandle.NewStyle( + termbox.RGBToAttribute(uint8(255), uint8(0), uint8(0)), + termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)), + ) +) + func RunTUI(p *cli.Program) error { ui := NewUi(p) ui.debug = true @@ -71,10 +86,7 @@ func NewUi(p *cli.Program) *Ui { ui.screens[sId] = s ui.currScreen = sId ui.wandle = wandle.NewProgram(s) - ui.wandle.Style(wandle.NewStyle( - termbox.RGBToAttribute(uint8(0), uint8(255), uint8(0)), - termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)), - )) + ui.wandle.Style(DefaultStyle) return ui } diff --git a/ui/widdle_addtagtotimers.go b/ui/widdle_addtagtotimers.go index 600f76b..af79b4f 100644 --- a/ui/widdle_addtagtotimers.go +++ b/ui/widdle_addtagtotimers.go @@ -138,7 +138,10 @@ func (w *PromptForTagWiddle) Measure() { } func (w *PromptForTagWiddle) handleTermboxEvent(msg termbox.Event) wandle.Cmd { - if msg.Key == termbox.KeyEnter { + if msg.Key == termbox.KeyEsc { + w.Done() + return wandle.EmptyCmd + } else if msg.Key == termbox.KeyEnter { if w.keyInput.IsEditable() { w.keyInput.SetActive(false) w.keyInput.SetEditable(false) diff --git a/util/helpers.go b/util/helpers.go index a5ad83e..61112c1 100644 --- a/util/helpers.go +++ b/util/helpers.go @@ -441,3 +441,9 @@ func StringSliceContains(sl []string, val string) bool { } return false } +func AppendStringIfDistinct(sl []string, val string) []string { + if !StringSliceContains(sl, val) { + return append(sl, val) + } + return sl +}