UI Work
This commit is contained in:
		
							
								
								
									
										12
									
								
								cli/cli.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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) | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
							
								
								
									
										20
									
								
								ui/ui.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user