Compare commits
No commits in common. "main" and "v0.1.0" have entirely different histories.
17
Makefile
17
Makefile
|
@ -1,21 +1,6 @@
|
||||||
# This is what we want to name the binary output
|
|
||||||
BINARY=gime
|
|
||||||
|
|
||||||
# These are the values we want to pass for VERSION and BUILD
|
|
||||||
# git tag 1.0.1
|
|
||||||
# git commit -am "One more change after the tags"
|
|
||||||
VERSION="beta" # `git describe --tags`
|
|
||||||
BUILD=`date +%FT%T%z`
|
|
||||||
|
|
||||||
# Setup the -ldflags option for go build here, interpolate the variable values
|
|
||||||
LDFLAGS=-ldflags "-w -s -X cmd.Version=${VERSION} -X cmd.Build=${BUILD}"
|
|
||||||
|
|
||||||
# Builds the project
|
|
||||||
gime:
|
gime:
|
||||||
go build ${LDFLAGS} -o build/${BINARY}
|
go build -o build/gime *.go
|
||||||
|
|
||||||
runui:
|
|
||||||
cd build && ./gime ui
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm build/*
|
rm build/*
|
||||||
|
|
41
cli/cli.go
41
cli/cli.go
|
@ -27,31 +27,15 @@ func (p *Program) Initialize() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) GetTimerFilePath() string {
|
|
||||||
return p.timerPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Program) LoadTimerList() error {
|
func (p *Program) LoadTimerList() error {
|
||||||
var err error
|
var err error
|
||||||
var tl *timertxt.TimerList
|
var tl timertxt.TimerList
|
||||||
tl, err = timertxt.LoadFromFilename(p.timerPath)
|
tl, err = timertxt.LoadFromFilename(p.timerPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tl.Sort(timertxt.SortUnfinishedStart)
|
tl.Sort(timertxt.SORT_UNFINISHED_START)
|
||||||
p.TimerList = tl
|
p.TimerList = &tl
|
||||||
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,18 +43,14 @@ func (p *Program) WriteTimerList() error {
|
||||||
return p.TimerList.WriteToFilename(p.timerPath)
|
return p.TimerList.WriteToFilename(p.timerPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) GetDoneFilePath() string {
|
|
||||||
return p.donePath
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Program) LoadDoneList() error {
|
func (p *Program) LoadDoneList() error {
|
||||||
var err error
|
var err error
|
||||||
var tl *timertxt.TimerList
|
var tl timertxt.TimerList
|
||||||
tl, err = timertxt.LoadFromFilename(p.donePath)
|
tl, err = timertxt.LoadFromFilename(p.donePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.DoneList = tl
|
p.DoneList = &tl
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,14 +102,14 @@ func (p *Program) GetFilteredTimerList(args []string) *timertxt.TimerList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list := p.TimerList.GetTimersInRange(start, end)
|
list := p.TimerList.GetTimersInRange(start, end)
|
||||||
|
|
||||||
if includeArchive {
|
if includeArchive {
|
||||||
if err = p.LoadDoneList(); err != nil {
|
if err = p.LoadDoneList(); err != nil {
|
||||||
fmt.Println("Error loading done.txt entries")
|
fmt.Println("Error loading done.txt entries")
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
doneList := p.DoneList.GetTimersInRange(start, end)
|
*list = append(*list, (*p.DoneList.GetTimersInRange(start, end))...)
|
||||||
list.Combine(doneList)
|
|
||||||
}
|
}
|
||||||
if len(contextFilters) > 0 {
|
if len(contextFilters) > 0 {
|
||||||
allFilters = append(allFilters, func(t timertxt.Timer) bool {
|
allFilters = append(allFilters, func(t timertxt.Timer) bool {
|
||||||
|
@ -165,13 +145,6 @@ func (p *Program) GetFilteredTimerList(args []string) *timertxt.TimerList {
|
||||||
return list.Filter(doFilters)
|
return list.Filter(doFilters)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) GetActiveOrMostRecent() (*timertxt.Timer, error) {
|
|
||||||
work, wErr := p.TimerList.GetActiveOrMostRecent()
|
|
||||||
if wErr == nil {
|
|
||||||
return work, nil
|
|
||||||
}
|
|
||||||
return p.GetMostRecentTimer()
|
|
||||||
}
|
|
||||||
func (p *Program) GetMostRecentTimer() (*timertxt.Timer, error) {
|
func (p *Program) GetMostRecentTimer() (*timertxt.Timer, error) {
|
||||||
work, wErr := p.TimerList.GetMostRecentTimer()
|
work, wErr := p.TimerList.GetMostRecentTimer()
|
||||||
if wErr == nil && work.FinishDate.IsZero() {
|
if wErr == nil && work.FinishDate.IsZero() {
|
||||||
|
|
|
@ -7,7 +7,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
@ -16,11 +15,8 @@ import (
|
||||||
// configCmd represents the config command
|
// configCmd represents the config command
|
||||||
var configCmd = &cobra.Command{
|
var configCmd = &cobra.Command{
|
||||||
Use: "config",
|
Use: "config",
|
||||||
Short: "Show or update configuration values",
|
Short: "Print all configuration values",
|
||||||
Long: `To set values just list them in key=value format.
|
RunE: opConfig,
|
||||||
For example:
|
|
||||||
gime config copytags=true roundto=30m`,
|
|
||||||
RunE: opConfig,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -28,36 +24,9 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opConfig(cmd *cobra.Command, args []string) error {
|
func opConfig(cmd *cobra.Command, args []string) error {
|
||||||
updConfig := make(map[string]string)
|
|
||||||
if len(args) > 0 {
|
|
||||||
// We're setting arguments
|
|
||||||
for _, a := range args {
|
|
||||||
pts := strings.Split(a, "=")
|
|
||||||
if len(pts) == 2 {
|
|
||||||
updConfig[pts[0]] = pts[1]
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("Unable to parse config values.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var settings []string
|
var settings []string
|
||||||
for k, v := range viper.AllSettings() {
|
for k, v := range viper.AllSettings() {
|
||||||
switch v.(type) {
|
settings = append(settings, fmt.Sprintf("%s: %s", k, v))
|
||||||
case bool:
|
|
||||||
if nv, ok := updConfig[k]; ok {
|
|
||||||
v = nv == "true"
|
|
||||||
viper.Set(k, v)
|
|
||||||
viper.WriteConfig()
|
|
||||||
}
|
|
||||||
settings = append(settings, fmt.Sprintf("%s: %t", k, v))
|
|
||||||
default:
|
|
||||||
if nv, ok := updConfig[k]; ok {
|
|
||||||
v = nv
|
|
||||||
viper.Set(k, v)
|
|
||||||
viper.WriteConfig()
|
|
||||||
}
|
|
||||||
settings = append(settings, fmt.Sprintf("%s: %s", k, v))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sort.Strings(settings)
|
sort.Strings(settings)
|
||||||
fmt.Println("Configuration File:", viper.ConfigFileUsed())
|
fmt.Println("Configuration File:", viper.ConfigFileUsed())
|
||||||
|
|
|
@ -6,10 +6,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,23 +22,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opEditor(cmd *cobra.Command, args []string) error {
|
func opEditor(cmd *cobra.Command, args []string) error {
|
||||||
p := cli.Program{}
|
fmt.Println("editor called")
|
||||||
if err := p.Initialize(); err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
file := p.GetTimerFilePath()
|
|
||||||
if len(args) > 0 {
|
|
||||||
if args[0] == "d" || args[0] == "done" {
|
|
||||||
file = p.GetDoneFilePath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editor := os.Getenv("EDITOR")
|
|
||||||
if editor == "" {
|
|
||||||
return fmt.Errorf("No EDITOR set")
|
|
||||||
}
|
|
||||||
fmt.Println("Starting", editor, file)
|
|
||||||
c := exec.Command(editor, file)
|
|
||||||
c.Stdin = os.Stdin
|
|
||||||
c.Stdout = os.Stdout
|
|
||||||
return c.Run()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,14 @@ import (
|
||||||
// i3statusCmd represents the i3status command
|
// i3statusCmd represents the i3status command
|
||||||
var i3statusCmd = &cobra.Command{
|
var i3statusCmd = &cobra.Command{
|
||||||
Use: "i3status",
|
Use: "i3status",
|
||||||
Short: "Output your timer.txt status in json for the i3 status bar",
|
Short: "",
|
||||||
RunE: opI3Status,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opI3Status,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -46,14 +52,14 @@ func opI3Status(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
state := "Idle"
|
state := "Idle"
|
||||||
wrk, err := p.GetActiveOrMostRecent()
|
wrk, err := p.GetMostRecentTimer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print("{\"icon\":\"time\",\"state\":\"Critical\", \"text\": \"Error loading timer entry\"}")
|
fmt.Print("{\"icon\":\"time\",\"state\":\"Critical\", \"text\": \"Error loading timer entry\"}")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var text string
|
var text string
|
||||||
if wrk.FinishDate.IsZero() {
|
if wrk.FinishDate.IsZero() {
|
||||||
wrkDur := util.Round(wrk.Duration())
|
wrkDur := wrk.Duration().Round(time.Minute * 15)
|
||||||
hrs := int(wrkDur.Hours())
|
hrs := int(wrkDur.Hours())
|
||||||
mins := int(wrkDur.Minutes()) - hrs*60
|
mins := int(wrkDur.Minutes()) - hrs*60
|
||||||
if hrs > 0 {
|
if hrs > 0 {
|
||||||
|
@ -91,7 +97,7 @@ func opI3Status(cmd *cobra.Command, args []string) error {
|
||||||
getListTotal := func(list *timertxt.TimerList) string {
|
getListTotal := func(list *timertxt.TimerList) string {
|
||||||
var isActive bool
|
var isActive bool
|
||||||
var total time.Duration
|
var total time.Duration
|
||||||
for _, v := range list.GetTimerSlice() {
|
for _, v := range *list {
|
||||||
dur := v.FinishDate.Sub(v.StartDate)
|
dur := v.FinishDate.Sub(v.StartDate)
|
||||||
if v.FinishDate.IsZero() {
|
if v.FinishDate.IsZero() {
|
||||||
dur = time.Now().Sub(v.StartDate)
|
dur = time.Now().Sub(v.StartDate)
|
||||||
|
@ -99,7 +105,7 @@ func opI3Status(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
total += dur
|
total += dur
|
||||||
}
|
}
|
||||||
total = util.Round(total)
|
total = total.Round(util.GetRoundToDuration())
|
||||||
if isActive {
|
if isActive {
|
||||||
return fmt.Sprintf("%.2f+", util.DurationToDecimal(total))
|
return fmt.Sprintf("%.2f+", util.DurationToDecimal(total))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -76,7 +76,7 @@ func opListTimers(cmd *cobra.Command, args []string) error {
|
||||||
list = list.Filter(filter)
|
list = list.Filter(filter)
|
||||||
|
|
||||||
dayTotals := make(map[string]time.Duration)
|
dayTotals := make(map[string]time.Duration)
|
||||||
for _, v := range list.GetTimerSlice() {
|
for _, v := range *list {
|
||||||
dur := v.FinishDate.Sub(v.StartDate)
|
dur := v.FinishDate.Sub(v.StartDate)
|
||||||
if v.FinishDate.IsZero() {
|
if v.FinishDate.IsZero() {
|
||||||
dur = time.Now().Sub(v.StartDate)
|
dur = time.Now().Sub(v.StartDate)
|
||||||
|
@ -84,12 +84,12 @@ func opListTimers(cmd *cobra.Command, args []string) error {
|
||||||
dayTotals[v.StartDate.Format("2006/01/02")] += dur
|
dayTotals[v.StartDate.Format("2006/01/02")] += dur
|
||||||
}
|
}
|
||||||
var oldDayStr, dayStr string
|
var oldDayStr, dayStr string
|
||||||
for _, v := range list.GetTimerSlice() {
|
for _, v := range *list {
|
||||||
oldDayStr = dayStr
|
oldDayStr = dayStr
|
||||||
dayStr = v.StartDate.Format("2006/01/02")
|
dayStr = v.StartDate.Format("2006/01/02")
|
||||||
if dayStr != oldDayStr {
|
if dayStr != oldDayStr {
|
||||||
// TODO:
|
// TODO:
|
||||||
wrkDur := util.Round(dayTotals[dayStr])
|
wrkDur := dayTotals[dayStr].Round(util.GetRoundToDuration())
|
||||||
fmtStr := dayStr + " ( %.2f )\n"
|
fmtStr := dayStr + " ( %.2f )\n"
|
||||||
fmt.Printf(fmtStr, util.DurationToDecimal(wrkDur))
|
fmt.Printf(fmtStr, util.DurationToDecimal(wrkDur))
|
||||||
}
|
}
|
||||||
|
|
88
cmd/mod.go
88
cmd/mod.go
|
@ -6,21 +6,21 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"git.bullercodeworks.com/brian/gime/util"
|
|
||||||
"git.bullercodeworks.com/brian/go-timertxt"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// modCmd represents the mod command
|
// modCmd represents the mod command
|
||||||
var modCmd = &cobra.Command{
|
var modCmd = &cobra.Command{
|
||||||
Use: "mod",
|
Use: "mod",
|
||||||
Short: "Modify a timer",
|
Short: "A brief description of your command",
|
||||||
RunE: opMod,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opMod,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -28,76 +28,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opMod(cmd *cobra.Command, args []string) error {
|
func opMod(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
fmt.Println("mod called")
|
||||||
p := cli.Program{}
|
|
||||||
err = p.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = p.LoadTimerList(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var timer *timertxt.Timer
|
|
||||||
var contexts, projects []string
|
|
||||||
id, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
|
||||||
// We didn't have a timer id, so try to modify the first active timer
|
|
||||||
active := p.TimerList.GetActiveTimers().GetTimerSlice()
|
|
||||||
if len(active) > 0 {
|
|
||||||
timer = active[0]
|
|
||||||
} else {
|
|
||||||
// And we don't have any active timers
|
|
||||||
return fmt.Errorf("No active timers, 'id' must be provided: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
args = args[1:]
|
|
||||||
if timer, err = p.TimerList.GetTimer(id); err != nil {
|
|
||||||
return fmt.Errorf("Error getting timer %d: %w", id, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var start, end time.Time
|
|
||||||
|
|
||||||
for _, v := range args {
|
|
||||||
pts := strings.Split(v, "=")
|
|
||||||
switch pts[0] {
|
|
||||||
case "beginning", "start":
|
|
||||||
if start, err = util.ParseFuzzyTime(pts[1]); err != nil {
|
|
||||||
return fmt.Errorf("Error parsing start time: %w", err)
|
|
||||||
}
|
|
||||||
case "stop", "finish", "end":
|
|
||||||
if end, err = util.ParseFuzzyTime(pts[1]); err != nil {
|
|
||||||
return fmt.Errorf("Error parsing end time: %w", err)
|
|
||||||
}
|
|
||||||
case "project", "projects":
|
|
||||||
projects = strings.Split(pts[1], ",")
|
|
||||||
case "context", "contexts":
|
|
||||||
contexts = strings.Split(pts[1], ",")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(contexts) > 0 {
|
|
||||||
for k := range contexts {
|
|
||||||
contexts[k] = strings.TrimPrefix(contexts[k], "@")
|
|
||||||
}
|
|
||||||
timer.Contexts = contexts
|
|
||||||
}
|
|
||||||
if len(projects) > 0 {
|
|
||||||
for k := range projects {
|
|
||||||
projects[k] = strings.TrimPrefix(projects[k], "+")
|
|
||||||
}
|
|
||||||
timer.Projects = projects
|
|
||||||
}
|
|
||||||
if !start.IsZero() {
|
|
||||||
timer.StartDate = start
|
|
||||||
}
|
|
||||||
if !end.IsZero() {
|
|
||||||
timer.FinishDate = end
|
|
||||||
timer.Finished = true
|
|
||||||
}
|
|
||||||
fmt.Println("Modified Timer:")
|
|
||||||
fmt.Println(util.TimerToFriendlyString(timer))
|
|
||||||
|
|
||||||
if err := p.WriteTimerList(); err != nil {
|
|
||||||
return fmt.Errorf("Error writing timer list: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
42
cmd/rm.go
42
cmd/rm.go
|
@ -6,18 +6,21 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"git.bullercodeworks.com/brian/gime/util"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rmCmd represents the rm command
|
// rmCmd represents the rm command
|
||||||
var rmCmd = &cobra.Command{
|
var rmCmd = &cobra.Command{
|
||||||
Use: "rm",
|
Use: "rm",
|
||||||
Short: "Remove a timer",
|
Short: "A brief description of your command",
|
||||||
RunE: opRemove,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opRemove,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -25,33 +28,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opRemove(cmd *cobra.Command, args []string) error {
|
func opRemove(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
fmt.Println("remove called")
|
||||||
p := cli.Program{}
|
|
||||||
err = p.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = p.LoadTimerList(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("No timer id given")
|
|
||||||
}
|
|
||||||
id, err := strconv.Atoi(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Invalid timer id given %s: %w", args[0], err)
|
|
||||||
}
|
|
||||||
t, err := p.TimerList.GetTimer(id)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Error getting timer with id %s: %w", args[0], err)
|
|
||||||
}
|
|
||||||
if err = p.TimerList.RemoveTimerById(id); err != nil {
|
|
||||||
return fmt.Errorf("Error Removing Timer: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("Timer removed")
|
|
||||||
fmt.Println(util.TimerToString(t))
|
|
||||||
if err := p.WriteTimerList(); err != nil {
|
|
||||||
return fmt.Errorf("Error writing timer list: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version string
|
Version = "2.0"
|
||||||
Build string
|
|
||||||
|
|
||||||
configFile string
|
configFile string
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "gime",
|
Use: "gime",
|
||||||
Short: "Timer.txt Client",
|
Short: "Timer.txt Client",
|
||||||
Long: fmt.Sprintf("Gime is a timer.txt client to make all aspects of timekeeping simpler.\nVersion: %s\nBuild Date: %s\n", Version, Build),
|
Long: "Gime is a timer.txt client to make all aspects of timekeeping simpler.",
|
||||||
RunE: opStatus,
|
RunE: opStatus,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -48,7 +46,6 @@ func initConfig() {
|
||||||
viper.SetDefault("timerfile", "timer.txt")
|
viper.SetDefault("timerfile", "timer.txt")
|
||||||
viper.SetDefault("donefile", "done.txt")
|
viper.SetDefault("donefile", "done.txt")
|
||||||
viper.SetDefault("reportfile", "report.txt")
|
viper.SetDefault("reportfile", "report.txt")
|
||||||
viper.SetDefault("copytags", false)
|
|
||||||
var firstDir string // In case we need to make directories
|
var firstDir string // In case we need to make directories
|
||||||
if configFile != "" {
|
if configFile != "" {
|
||||||
viper.SetConfigFile(configFile)
|
viper.SetConfigFile(configFile)
|
||||||
|
|
51
cmd/start.go
51
cmd/start.go
|
@ -6,19 +6,21 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"git.bullercodeworks.com/brian/gime/util"
|
|
||||||
"git.bullercodeworks.com/brian/go-timertxt"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// startCmd represents the start command
|
// startCmd represents the start command
|
||||||
var startCmd = &cobra.Command{
|
var startCmd = &cobra.Command{
|
||||||
Use: "start",
|
Use: "start",
|
||||||
Short: "Start a timer",
|
Short: "A brief description of your command",
|
||||||
RunE: opStart,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opStart,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -26,41 +28,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opStart(cmd *cobra.Command, args []string) error {
|
func opStart(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
fmt.Println("start called")
|
||||||
p := cli.Program{}
|
|
||||||
err = p.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = p.LoadTimerList(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var contexts, projects, strTags []string
|
|
||||||
t := timertxt.NewTimer()
|
|
||||||
if len(args) > 0 {
|
|
||||||
if start, err := util.ParseFuzzyTime(args[0]); err == nil {
|
|
||||||
t.StartDate = start
|
|
||||||
args = args[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contexts, args = util.GetContextsFromSlice(args)
|
|
||||||
projects, args = util.GetProjectsFromSlice(args)
|
|
||||||
strTags, args = util.GetAdditionalTagsFromSlice(args)
|
|
||||||
for _, v := range contexts {
|
|
||||||
t.Contexts = append(t.Contexts, strings.TrimPrefix(v, "@"))
|
|
||||||
}
|
|
||||||
for _, v := range projects {
|
|
||||||
t.Projects = append(t.Projects, strings.TrimPrefix(v, "+"))
|
|
||||||
}
|
|
||||||
for _, v := range strTags {
|
|
||||||
tgPts := strings.Split(v, ":")
|
|
||||||
t.AdditionalTags[tgPts[0]] = tgPts[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
p.TimerList.AddTimer(t)
|
|
||||||
if err := p.WriteTimerList(); err != nil {
|
|
||||||
return fmt.Errorf("Error writing timer list: %w", err)
|
|
||||||
}
|
|
||||||
fmt.Println("Started: ", util.TimerToString(t))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,23 +32,21 @@ func opStatus(cmd *cobra.Command, args []string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := p.LoadTimerList(); err != nil {
|
if err := p.LoadTimerList(); err != nil {
|
||||||
return fmt.Errorf("Error loading timer list: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
active := p.TimerList.GetActiveTimers().GetTimerSlice()
|
if len(*p.TimerList.GetActiveTimers()) == 0 {
|
||||||
if len(active) == 0 {
|
|
||||||
fmt.Println("No timers running")
|
fmt.Println("No timers running")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var currDur time.Duration
|
var currDur time.Duration
|
||||||
for _, v := range active {
|
for _, v := range *p.TimerList {
|
||||||
if v.ActiveToday() {
|
if v.ActiveToday() {
|
||||||
currDur += v.Duration()
|
currDur += v.Duration()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d := util.Round(currDur)
|
d := currDur.Round(util.GetRoundToDuration())
|
||||||
|
|
||||||
fmt.Printf("%s ( %.2f hrs )\n", time.Now().Format(time.Stamp), util.DurationToDecimal(d))
|
fmt.Printf("%s ( %.2f hrs )\n", time.Now().Format(time.Stamp), util.DurationToDecimal(d))
|
||||||
for _, v := range active {
|
for _, v := range *p.TimerList.GetActiveTimers() {
|
||||||
fmt.Println(util.TimerToFriendlyString(v))
|
fmt.Println(util.TimerToFriendlyString(v))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
58
cmd/stop.go
58
cmd/stop.go
|
@ -6,20 +6,21 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"git.bullercodeworks.com/brian/gime/util"
|
|
||||||
"git.bullercodeworks.com/brian/go-timertxt"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// stopCmd represents the stop command
|
// stopCmd represents the stop command
|
||||||
var stopCmd = &cobra.Command{
|
var stopCmd = &cobra.Command{
|
||||||
Use: "stop",
|
Use: "stop",
|
||||||
Short: "Stop a timer",
|
Short: "A brief description of your command",
|
||||||
RunE: opStop,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opStop,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -27,47 +28,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opStop(cmd *cobra.Command, args []string) error {
|
func opStop(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
fmt.Println("stop called")
|
||||||
p := cli.Program{}
|
|
||||||
err = p.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = p.LoadTimerList(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var wrk time.Time
|
|
||||||
end := time.Now()
|
|
||||||
id := -1
|
|
||||||
|
|
||||||
if len(args) > 0 {
|
|
||||||
if wrk, err = util.ParseFuzzyTime(args[0]); err != nil {
|
|
||||||
id, err = strconv.Atoi(args[0])
|
|
||||||
} else {
|
|
||||||
end = wrk
|
|
||||||
args = args[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("Stopping at : " + end.Format(time.RFC3339))
|
|
||||||
var timerIds []int
|
|
||||||
if id == -1 {
|
|
||||||
for _, v := range p.TimerList.GetActiveTimers().GetTimerSlice() {
|
|
||||||
timerIds = append(timerIds, v.Id)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timerIds = append(timerIds, id)
|
|
||||||
}
|
|
||||||
for _, v := range timerIds {
|
|
||||||
var stopped *timertxt.Timer
|
|
||||||
if stopped, err = p.TimerList.GetTimer(v); err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
}
|
|
||||||
stopped.FinishDate = end
|
|
||||||
stopped.Finished = true
|
|
||||||
fmt.Println("Stopped Timer:", util.TimerToFriendlyString(stopped))
|
|
||||||
}
|
|
||||||
if err = p.WriteTimerList(); err != nil {
|
|
||||||
return fmt.Errorf("Error writing timer list: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,19 +6,21 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"git.bullercodeworks.com/brian/gime/util"
|
|
||||||
"git.bullercodeworks.com/brian/go-timertxt"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// switchCmd represents the switch command
|
// switchCmd represents the switch command
|
||||||
var switchCmd = &cobra.Command{
|
var switchCmd = &cobra.Command{
|
||||||
Use: "switch",
|
Use: "switch",
|
||||||
Short: "Stop the current timer and start a new one copying the last one's parameters",
|
Short: "A brief description of your command",
|
||||||
RunE: opSwitch,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opSwitch,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -26,31 +28,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opSwitch(cmd *cobra.Command, args []string) error {
|
func opSwitch(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
fmt.Println("switch called")
|
||||||
p := cli.Program{}
|
return nil
|
||||||
err = p.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = p.LoadTimerList(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var timerIds []int
|
|
||||||
end := time.Now()
|
|
||||||
// Stop all running timers and start a new one with the given args
|
|
||||||
for _, v := range p.TimerList.GetActiveTimers().GetTimerSlice() {
|
|
||||||
timerIds = append(timerIds, v.Id)
|
|
||||||
}
|
|
||||||
fmt.Print("Stopping ", timerIds, "\n")
|
|
||||||
for _, v := range timerIds {
|
|
||||||
var stopped *timertxt.Timer
|
|
||||||
if stopped, err = p.TimerList.GetTimer(v); err != nil {
|
|
||||||
fmt.Println(err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
stopped.FinishDate = end
|
|
||||||
stopped.Finished = true
|
|
||||||
fmt.Println("Stopped Timer:", util.TimerToFriendlyString(stopped))
|
|
||||||
}
|
|
||||||
return opStart(cmd, args)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ func opShowTimers(cmd *cobra.Command, args []string) error {
|
||||||
list := p.GetFilteredTimerList(args)
|
list := p.GetFilteredTimerList(args)
|
||||||
var isActive bool
|
var isActive bool
|
||||||
var total time.Duration
|
var total time.Duration
|
||||||
for _, v := range list.GetTimerSlice() {
|
for _, v := range *list {
|
||||||
dur := v.FinishDate.Sub(v.StartDate)
|
dur := v.FinishDate.Sub(v.StartDate)
|
||||||
if v.FinishDate.IsZero() {
|
if v.FinishDate.IsZero() {
|
||||||
dur = time.Now().Sub(v.StartDate)
|
dur = time.Now().Sub(v.StartDate)
|
||||||
|
@ -45,7 +45,7 @@ func opShowTimers(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
total += dur
|
total += dur
|
||||||
}
|
}
|
||||||
total = util.Round(total)
|
total = total.Round(util.GetRoundToDuration())
|
||||||
if isActive {
|
if isActive {
|
||||||
fmt.Printf("%.2f+\n", util.DurationToDecimal(total))
|
fmt.Printf("%.2f+\n", util.DurationToDecimal(total))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -7,16 +7,20 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// toggleCmd represents the toggle command
|
// toggleCmd represents the toggle command
|
||||||
var toggleCmd = &cobra.Command{
|
var toggleCmd = &cobra.Command{
|
||||||
Use: "toggle",
|
Use: "toggle",
|
||||||
Short: "Toggle the most recent timer on and off",
|
Short: "A brief description of your command",
|
||||||
RunE: opToggle,
|
Long: `A longer description that spans multiple lines and likely contains examples
|
||||||
|
and usage of using your command. For example:
|
||||||
|
|
||||||
|
Cobra is a CLI library for Go that empowers applications.
|
||||||
|
This application is a tool to generate the needed files
|
||||||
|
to quickly create a Cobra application.`,
|
||||||
|
RunE: opToggle,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -24,37 +28,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opToggle(cmd *cobra.Command, args []string) error {
|
func opToggle(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
fmt.Println("toggle called")
|
||||||
p := cli.Program{}
|
return nil
|
||||||
err = p.Initialize()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = p.LoadTimerList(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wrk, err := p.GetMostRecentTimer()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Print("{\"icon\":\"time\",\"state\":\"Critical\", \"text\": \"Error loading timer entry\"}")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var startArgs []string
|
|
||||||
if wrk.Finished {
|
|
||||||
// Start a new timer with the same data
|
|
||||||
for _, v := range wrk.Contexts {
|
|
||||||
startArgs = append(startArgs, "@"+v)
|
|
||||||
}
|
|
||||||
for _, v := range wrk.Projects {
|
|
||||||
startArgs = append(startArgs, "+"+v)
|
|
||||||
}
|
|
||||||
if viper.GetBool("copytags") {
|
|
||||||
for k, v := range wrk.AdditionalTags {
|
|
||||||
startArgs = append(startArgs, k+":"+v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return opStart(cmd, startArgs)
|
|
||||||
} else {
|
|
||||||
// Stop the active timer
|
|
||||||
return opStop(cmd, []string{})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ Copyright © 2022 Brian Buller <brian@bullercodeworks.com>
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
"fmt"
|
||||||
"git.bullercodeworks.com/brian/gime/ui"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +21,6 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func opUi(cmd *cobra.Command, args []string) error {
|
func opUi(cmd *cobra.Command, args []string) error {
|
||||||
p := &cli.Program{}
|
fmt.Println("ui called")
|
||||||
return ui.RunTUI(p)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
10
go.mod
10
go.mod
|
@ -1,17 +1,11 @@
|
||||||
module git.bullercodeworks.com/brian/gime
|
module git.bullercodeworks.com/brian/gime
|
||||||
|
|
||||||
go 1.20
|
go 1.17
|
||||||
|
|
||||||
replace git.bullercodeworks.com/brian/go-timertxt => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/go-timertxt
|
replace git.bullercodeworks.com/brian/go-timertxt => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/go-timertxt
|
||||||
|
|
||||||
replace git.bullercodeworks.com/brian/wandle => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/wandle
|
|
||||||
|
|
||||||
replace git.bullercodeworks.com/brian/widdles => /home/brbuller/Development/go/src/git.bullercodeworks.com/brian/widdles
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.bullercodeworks.com/brian/go-timertxt v1.5.0
|
git.bullercodeworks.com/brian/go-timertxt v0.0.0-20210302170637-d35b67037e23
|
||||||
git.bullercodeworks.com/brian/wandle v1.0.3
|
|
||||||
git.bullercodeworks.com/brian/widdles v0.0.0-00010101000000-000000000000
|
|
||||||
github.com/br0xen/termbox-util v0.0.0-20200220160819-dc6d6950ba00
|
github.com/br0xen/termbox-util v0.0.0-20200220160819-dc6d6950ba00
|
||||||
github.com/br0xen/user-config v0.0.0-20170914134719-16e743ec93a2
|
github.com/br0xen/user-config v0.0.0-20170914134719-16e743ec93a2
|
||||||
github.com/muesli/go-app-paths v0.2.2
|
github.com/muesli/go-app-paths v0.2.2
|
||||||
|
|
7
main.go
7
main.go
|
@ -1,16 +1,11 @@
|
||||||
/*
|
/*
|
||||||
Copyright © 2022 Brian Buller <brian@bullercodeworks.com>
|
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "git.bullercodeworks.com/brian/gime/cmd"
|
import "git.bullercodeworks.com/brian/gime/cmd"
|
||||||
|
|
||||||
var (
|
|
||||||
VERSION string
|
|
||||||
BUILD string
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Execute()
|
cmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,583 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/util"
|
|
||||||
"git.bullercodeworks.com/brian/go-timertxt"
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
|
||||||
"git.bullercodeworks.com/brian/widdles"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
activeToggleActive = iota
|
|
||||||
activeToggleInactive
|
|
||||||
activeToggleAll
|
|
||||||
activeToggleErr
|
|
||||||
)
|
|
||||||
|
|
||||||
type listTimersScreen struct {
|
|
||||||
ui *Ui
|
|
||||||
|
|
||||||
initialized bool
|
|
||||||
menu *widdles.TopMenu
|
|
||||||
scrollbar *widdles.Scrollbar
|
|
||||||
|
|
||||||
cursor int
|
|
||||||
|
|
||||||
activeToggle int
|
|
||||||
fullList *timertxt.TimerList
|
|
||||||
timerList *timertxt.TimerList
|
|
||||||
doneList *timertxt.TimerList
|
|
||||||
|
|
||||||
fullFilterList *timertxt.TimerList
|
|
||||||
timerFilterList *timertxt.TimerList
|
|
||||||
doneFilterList *timertxt.TimerList
|
|
||||||
|
|
||||||
selected map[int]bool
|
|
||||||
|
|
||||||
confirm *widdles.ConfirmDialog
|
|
||||||
filter string
|
|
||||||
|
|
||||||
choiceMenu *widdles.MenuV
|
|
||||||
tagEditor *PromptForTagWiddle
|
|
||||||
//partManager *PartManager
|
|
||||||
|
|
||||||
msg string
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
menu: widdles.NewTopMenu(0, 0, 0),
|
|
||||||
scrollbar: widdles.NewScrollbar(0, 0, 0, 0),
|
|
||||||
selected: make(map[int]bool),
|
|
||||||
confirm: widdles.NewConfirmDialog("", ""),
|
|
||||||
|
|
||||||
choiceMenu: widdles.NewMenuV(0, 0, 0, 0),
|
|
||||||
tagEditor: NewPromptForTagWiddle(0, 0, widdles.AUTO_SIZE, widdles.AUTO_SIZE, "", ""),
|
|
||||||
}
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listTimersScreen) Init() wandle.Cmd {
|
|
||||||
if s.initialized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s.initialized = true
|
|
||||||
// Set up the top menu
|
|
||||||
fileMenu := s.menu.NewSubMenu("File")
|
|
||||||
settingsOption := widdles.NewMenuItem("Settings")
|
|
||||||
settingsOption.SetCommand(s.ui.GotoScreen(SettingsId))
|
|
||||||
fileMenu.AddOption(settingsOption)
|
|
||||||
quitOption := widdles.NewMenuItem("Quit")
|
|
||||||
quitOption.SetHotkey(widdles.NewHotkey(termbox.KeyCtrlC))
|
|
||||||
quitOption.SetCommand(func() wandle.Msg { return wandle.Quit() })
|
|
||||||
fileMenu.AddOption(quitOption)
|
|
||||||
s.menu.Measure()
|
|
||||||
// Timer Lists
|
|
||||||
s.timerList, s.doneList = s.ui.program.TimerList, s.ui.program.DoneList
|
|
||||||
s.fullList = timertxt.NewTimerList()
|
|
||||||
s.fullList.AddTimers(s.timerList.GetTimerSlice())
|
|
||||||
s.fullList.AddTimers(s.doneList.GetTimerSlice())
|
|
||||||
s.timerFilterList, s.doneFilterList = s.timerList, s.doneList
|
|
||||||
s.timerFilterList.Sort(timertxt.SortStartDateDesc)
|
|
||||||
s.doneFilterList.Sort(timertxt.SortStartDateDesc)
|
|
||||||
s.updateFullFilterList()
|
|
||||||
w, h := termbox.Size()
|
|
||||||
s.choiceMenu.SetBorder(wandle.BRD_CSIMPLE)
|
|
||||||
s.choiceMenu.SetX((w / 2) - 7)
|
|
||||||
s.choiceMenu.SetY((h / 2) - 7)
|
|
||||||
s.choiceMenu.SetWidth(widdles.AUTO_SIZE)
|
|
||||||
s.choiceMenu.SetHeight(widdles.AUTO_SIZE)
|
|
||||||
s.choiceMenu.SetPadding(0, 1, 0, 1)
|
|
||||||
s.tagEditor.SetX(w / 4)
|
|
||||||
s.tagEditor.SetY(h / 4)
|
|
||||||
s.tagEditor.SetWidth(w / 2)
|
|
||||||
s.tagEditor.SetHeight(h / 2)
|
|
||||||
s.confirm.EnableHotkeys()
|
|
||||||
s.confirm.SetX(w / 4)
|
|
||||||
s.confirm.SetY(h / 4)
|
|
||||||
s.confirm.SetWidth(w / 2)
|
|
||||||
s.confirm.SetHeight(h / 2)
|
|
||||||
s.updateFullFilterList()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listTimersScreen) View(style wandle.Style) {
|
|
||||||
_, h := termbox.Size()
|
|
||||||
y := 2
|
|
||||||
printedTimers := 0
|
|
||||||
if s.activeToggle == activeToggleAll || s.activeToggle == activeToggleActive {
|
|
||||||
wandle.Print(1, y, style.Bold(true), "Active Timers")
|
|
||||||
y++
|
|
||||||
for idx, tmr := range s.timerFilterList.GetTimerSlice() {
|
|
||||||
if y > h-2 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
st := style
|
|
||||||
if s.cursor == idx {
|
|
||||||
st = st.Invert()
|
|
||||||
}
|
|
||||||
if s.selected[idx] {
|
|
||||||
wandle.Print(1, y, st, "[✔] ")
|
|
||||||
} else {
|
|
||||||
wandle.Print(1, y, st, "[ ] ")
|
|
||||||
}
|
|
||||||
s.ViewTimer(5, y, st, tmr)
|
|
||||||
y++
|
|
||||||
}
|
|
||||||
y++
|
|
||||||
printedTimers = s.timerFilterList.Size()
|
|
||||||
}
|
|
||||||
if s.activeToggle == activeToggleAll || s.activeToggle == activeToggleInactive {
|
|
||||||
wandle.Print(1, y, style.Bold(true), "Done Timers")
|
|
||||||
y++
|
|
||||||
for idx, tmr := range s.doneFilterList.GetTimerSlice() {
|
|
||||||
if y > h-3 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
st := style
|
|
||||||
if s.cursor == printedTimers+idx {
|
|
||||||
st = st.Invert()
|
|
||||||
}
|
|
||||||
if s.selected[printedTimers+idx] {
|
|
||||||
wandle.Print(1, y, st, "[✔] ")
|
|
||||||
} else {
|
|
||||||
wandle.Print(1, y, st, "[ ] ")
|
|
||||||
}
|
|
||||||
s.ViewTimer(5, y, st, tmr)
|
|
||||||
y++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedStatus := fmt.Sprintf("%s", s.getSelectedTimerDuration())
|
|
||||||
if len(s.selected) > 0 {
|
|
||||||
selectedStatus = fmt.Sprintf("%s (%d / %d selected)", selectedStatus, len(s.selected), s.fullFilterList.Size())
|
|
||||||
}
|
|
||||||
wandle.Print(1, h-2, style, selectedStatus)
|
|
||||||
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)
|
|
||||||
if s.menu.IsActive() {
|
|
||||||
s.menu.View(style)
|
|
||||||
}
|
|
||||||
if s.choiceMenu.IsActive() {
|
|
||||||
s.choiceMenu.View(style)
|
|
||||||
}
|
|
||||||
if s.tagEditor.IsActive() {
|
|
||||||
s.tagEditor.View(style)
|
|
||||||
}
|
|
||||||
if s.confirm.IsActive() {
|
|
||||||
s.confirm.View(style)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
var tags []string
|
|
||||||
for _, k := range util.SortedTagKeyList(tmr.AdditionalTags) {
|
|
||||||
tags = append(tags, fmt.Sprintf("%s:%s", k, tmr.AdditionalTags[k]))
|
|
||||||
}
|
|
||||||
wandle.Print(x, y, style, fmt.Sprintf("%s %s %s %s %s", tmr.StartDate.Format(time.Stamp), tmr.Duration(), tmr.Contexts, tmr.Projects, strings.Join(tags, "; ")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listTimersScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
|
||||||
if s.confirm.IsActive() {
|
|
||||||
return s.confirm.Update(msg)
|
|
||||||
}
|
|
||||||
if s.choiceMenu.IsActive() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if (msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc) || s.menu.IsActive() {
|
|
||||||
return s.menu.Update(msg)
|
|
||||||
}
|
|
||||||
switch msg.Type {
|
|
||||||
case termbox.EventKey:
|
|
||||||
if msg.Key == termbox.KeyEnter {
|
|
||||||
// TODO: Edit the entry
|
|
||||||
/*
|
|
||||||
if s.cursor >= 0 && s.cursor < s.timerFilterList.Size()+s.doneFilterList.Size() {
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} else if msg.Key == termbox.KeySpace {
|
|
||||||
// (un)Select the entry
|
|
||||||
if v := s.selected[s.cursor]; v {
|
|
||||||
delete(s.selected, s.cursor)
|
|
||||||
} else {
|
|
||||||
s.selected[s.cursor] = true
|
|
||||||
}
|
|
||||||
if s.cursor < s.fullFilterList.Size()-1 {
|
|
||||||
s.cursor++
|
|
||||||
}
|
|
||||||
} else if msg.Ch == 'T' {
|
|
||||||
s.activeToggle = (s.activeToggle + 1) % activeToggleErr
|
|
||||||
s.updateFullFilterList()
|
|
||||||
} else if msg.Ch == 'A' {
|
|
||||||
return s.showArchiveSelected()
|
|
||||||
} else if msg.Key == termbox.KeyArrowUp || msg.Ch == 'k' {
|
|
||||||
if s.cursor > 0 {
|
|
||||||
s.cursor--
|
|
||||||
} else {
|
|
||||||
s.cursor = 0
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
} else if msg.Key == termbox.KeyArrowDown || msg.Ch == 'j' {
|
|
||||||
if s.cursor < s.fullFilterList.Size()-1 {
|
|
||||||
s.cursor++
|
|
||||||
} else {
|
|
||||||
s.cursor = s.fullFilterList.Size() - 1
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
} else if msg.Ch == 'p' {
|
|
||||||
// Edit project(s)
|
|
||||||
// TODO: Prompt for Choice: Add/Edit/Remove
|
|
||||||
projs := s.fullList.GetProjects()
|
|
||||||
_ = projs
|
|
||||||
} else if msg.Ch == 'c' {
|
|
||||||
// Edit context(s)
|
|
||||||
// TODO: Prompt for choice: Add/Edit/Remove
|
|
||||||
ctxts := s.fullList.GetContexts()
|
|
||||||
_ = ctxts
|
|
||||||
} else if msg.Key == termbox.KeyCtrlA {
|
|
||||||
if len(s.selected) != s.fullFilterList.Size() {
|
|
||||||
// Select None
|
|
||||||
for k := range s.selected {
|
|
||||||
delete(s.selected, k)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Select All
|
|
||||||
for i := 0; i < s.fullFilterList.Size(); i++ {
|
|
||||||
s.selected[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i := 0; i < s.fullFilterList.Size(); i++ {
|
|
||||||
if v := s.selected[i]; v {
|
|
||||||
delete(s.selected, i)
|
|
||||||
} else {
|
|
||||||
s.selected[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if msg.Key == termbox.KeyCtrlI {
|
|
||||||
for i := 0; i < s.fullFilterList.Size(); i++ {
|
|
||||||
if v := s.selected[i]; v {
|
|
||||||
delete(s.selected, i)
|
|
||||||
} else {
|
|
||||||
s.selected[i] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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? (y/n)")
|
|
||||||
} else {
|
|
||||||
s.confirm.SetTitle("Archive 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)
|
|
||||||
return s.doArchiveSelected()
|
|
||||||
})
|
|
||||||
s.confirm.SetCancelCommand(func() wandle.Msg {
|
|
||||||
s.confirm.SetVisible(false)
|
|
||||||
return wandle.EmptyCmd
|
|
||||||
})
|
|
||||||
s.confirm.SetVisible(true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (s *listTimersScreen) doArchiveSelected() wandle.Cmd {
|
|
||||||
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 wandle.EmptyCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listTimersScreen) showEditTagsChoice() wandle.Cmd {
|
|
||||||
tags := s.getSelectedTimerTags()
|
|
||||||
var showTagEditor = func(key, val string, multival bool) wandle.Cmd {
|
|
||||||
return func() wandle.Msg {
|
|
||||||
s.tagEditor.SetTag(key, val)
|
|
||||||
s.tagEditor.SetDoneCommand(func() wandle.Msg {
|
|
||||||
s.updateTagOnSelectedTimers(s.tagEditor.GetTag())
|
|
||||||
s.tagEditor.Done()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
s.tagEditor.SetCancelCommand(func() wandle.Msg {
|
|
||||||
s.tagEditor.SetActive(false)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
s.choiceMenu.SetActive(false)
|
|
||||||
s.tagEditor.SetActive(true)
|
|
||||||
s.tagEditor.SetMultiVal(multival)
|
|
||||||
return wandle.EmptyCmd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.choiceMenu.SetTitle("")
|
|
||||||
s.choiceMenu.ClearOptions()
|
|
||||||
addTag := widdles.NewMenuItem("[A]dd New Tag")
|
|
||||||
addTag.SetHotkey(widdles.NewHotkeyCh('a'))
|
|
||||||
addTag.SetCommand(showTagEditor("", "", false))
|
|
||||||
s.choiceMenu.AddOption(addTag)
|
|
||||||
editTag := widdles.NewMenuItem("[E]dit Tag")
|
|
||||||
editTag.SetHotkey(widdles.NewHotkeyCh('e'))
|
|
||||||
editTag.SetEnabled(len(tags) > 0)
|
|
||||||
editTag.SetCommand(func() wandle.Msg {
|
|
||||||
s.choiceMenu.ClearOptions()
|
|
||||||
s.choiceMenu.SetTitle("Choose Tag to Edit")
|
|
||||||
for k, v := range tags {
|
|
||||||
var vals string
|
|
||||||
var multival bool
|
|
||||||
if len(v) == 1 {
|
|
||||||
vals = v[0]
|
|
||||||
} else {
|
|
||||||
vals = ""
|
|
||||||
multival = true
|
|
||||||
}
|
|
||||||
opt := widdles.NewMenuItem(fmt.Sprintf("%s (%s)", k, vals))
|
|
||||||
opt.SetCommand(showTagEditor(k, vals, multival))
|
|
||||||
s.choiceMenu.AddOption(opt)
|
|
||||||
}
|
|
||||||
return wandle.EmptyCmd
|
|
||||||
})
|
|
||||||
s.choiceMenu.AddOption(editTag)
|
|
||||||
removeTag := widdles.NewMenuItem("[R]emove Tag")
|
|
||||||
removeTag.SetHotkey(widdles.NewHotkeyCh('r'))
|
|
||||||
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)
|
|
||||||
return wandle.EmptyCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listTimersScreen) updateFullFilterList() {
|
|
||||||
s.fullFilterList = timertxt.NewTimerList()
|
|
||||||
switch s.activeToggle {
|
|
||||||
case activeToggleAll:
|
|
||||||
s.fullFilterList.Combine(s.timerFilterList)
|
|
||||||
s.fullFilterList.Combine(s.doneFilterList)
|
|
||||||
case activeToggleActive:
|
|
||||||
s.fullFilterList.Combine(s.timerFilterList)
|
|
||||||
case activeToggleInactive:
|
|
||||||
s.fullFilterList.Combine(s.doneFilterList)
|
|
||||||
}
|
|
||||||
if s.cursor >= s.fullFilterList.Size() {
|
|
||||||
s.cursor = s.fullFilterList.Size() - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *listTimersScreen) gotoSettingsScreen() wandle.Msg {
|
|
||||||
return ScreenMsg{
|
|
||||||
source: ListTimersId,
|
|
||||||
command: CmdGotoSettings,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writes the lists through the program, putting errors in s.err
|
|
||||||
func (s *listTimersScreen) writeLists() {
|
|
||||||
var errText string
|
|
||||||
if err := s.ui.program.WriteLists(); err != nil {
|
|
||||||
errText = fmt.Sprintf("Errors Writing Lists (%v)", err)
|
|
||||||
}
|
|
||||||
if len(errText) > 0 {
|
|
||||||
s.err = errors.New(errText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
ret = append(ret, selTimer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i := range s.selected {
|
|
||||||
if tmr, err := s.fullFilterList.GetTimer(i); err == nil {
|
|
||||||
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
|
|
||||||
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 {
|
|
||||||
sel := s.getSelectedTimers()
|
|
||||||
var ret time.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
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
|
||||||
"git.bullercodeworks.com/brian/widdles"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
type settingsScreen struct {
|
|
||||||
ui *Ui
|
|
||||||
initialized bool
|
|
||||||
menu *widdles.TopMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
type SettingsMsg ScreenMsg
|
|
||||||
|
|
||||||
func NewSettingsScreen(u *Ui) *settingsScreen {
|
|
||||||
return &settingsScreen{
|
|
||||||
ui: u,
|
|
||||||
menu: widdles.NewTopMenu(0, 0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsScreen) Init() wandle.Cmd {
|
|
||||||
if s.initialized {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s.initialized = true
|
|
||||||
// Set up the top menu
|
|
||||||
fileMenu := s.menu.NewSubMenu("File")
|
|
||||||
quitOption := widdles.NewMenuItem("Quit")
|
|
||||||
quitOption.SetHotkey(widdles.NewHotkey(termbox.KeyCtrlC))
|
|
||||||
quitOption.SetCommand(func() wandle.Msg { return wandle.Quit() })
|
|
||||||
fileMenu.AddOption(quitOption)
|
|
||||||
gotoMenu := s.menu.NewSubMenu("Goto")
|
|
||||||
timerListOption := widdles.NewMenuItem("Timer List")
|
|
||||||
timerListOption.SetCommand(s.ui.GotoScreen(ListTimersId))
|
|
||||||
gotoMenu.AddOption(timerListOption)
|
|
||||||
s.menu.Measure()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsScreen) Update(msg wandle.Msg) wandle.Cmd {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case ScreenMsg:
|
|
||||||
case termbox.Event:
|
|
||||||
return s.handleTermboxEvent(msg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsScreen) View(style wandle.Style) {
|
|
||||||
wandle.Print(1, 1, style, "Settings")
|
|
||||||
if s.menu.IsActive() {
|
|
||||||
s.menu.View(style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsScreen) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
|
||||||
if (msg.Type == termbox.EventKey && msg.Key == termbox.KeyEsc) || s.menu.IsActive() {
|
|
||||||
return s.menu.Update(msg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *settingsScreen) gotoTimerList() wandle.Msg {
|
|
||||||
return ScreenMsg{
|
|
||||||
source: SettingsId,
|
|
||||||
command: CmdGotoTimerList,
|
|
||||||
}
|
|
||||||
}
|
|
140
ui/ui.go
140
ui/ui.go
|
@ -1,140 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/gime/cli"
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Screen/Source Ids
|
|
||||||
const (
|
|
||||||
ListTimersId = ScreenMsgSource(iota << 8)
|
|
||||||
SettingsId
|
|
||||||
ErrorId
|
|
||||||
)
|
|
||||||
|
|
||||||
// Commands
|
|
||||||
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
|
|
||||||
if err := ui.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Exiting
|
|
||||||
fmt.Printf("Done\n")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ui struct {
|
|
||||||
debug bool
|
|
||||||
wandle *wandle.Program
|
|
||||||
program *cli.Program
|
|
||||||
err error
|
|
||||||
|
|
||||||
screens map[ScreenMsgSource]wandle.Screen
|
|
||||||
|
|
||||||
prevScreen, currScreen ScreenMsgSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUi(p *cli.Program) *Ui {
|
|
||||||
ui := &Ui{
|
|
||||||
screens: make(map[ScreenMsgSource]wandle.Screen),
|
|
||||||
program: p,
|
|
||||||
err: p.Initialize(),
|
|
||||||
}
|
|
||||||
var s wandle.Screen
|
|
||||||
var sId ScreenMsgSource
|
|
||||||
if ui.err != nil {
|
|
||||||
s, sId = NewSettingsScreen(ui), SettingsId
|
|
||||||
} else {
|
|
||||||
if ui.err = ui.program.LoadTimerList(); ui.err != nil {
|
|
||||||
s, sId = NewSettingsScreen(ui), SettingsId
|
|
||||||
} else if ui.err = ui.program.LoadDoneList(); ui.err != nil {
|
|
||||||
s, sId = NewSettingsScreen(ui), SettingsId
|
|
||||||
} else {
|
|
||||||
s, sId = NewListTimersScreen(ui), ListTimersId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ui.screens[sId] = s
|
|
||||||
ui.currScreen = sId
|
|
||||||
ui.wandle = wandle.NewProgram(s)
|
|
||||||
ui.wandle.Style(DefaultStyle)
|
|
||||||
return ui
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Ui) GotoScreen(id ScreenMsgSource) func() wandle.Msg {
|
|
||||||
u.prevScreen, u.currScreen = u.currScreen, id
|
|
||||||
if s, ok := u.screens[id]; ok {
|
|
||||||
return wandle.SwitchScreenCmd(s)
|
|
||||||
}
|
|
||||||
var s wandle.Screen
|
|
||||||
switch id {
|
|
||||||
case ListTimersId:
|
|
||||||
s = NewListTimersScreen(u)
|
|
||||||
case SettingsId:
|
|
||||||
s = NewSettingsScreen(u)
|
|
||||||
}
|
|
||||||
u.screens[id] = s
|
|
||||||
return wandle.SwitchScreenCmd(s)
|
|
||||||
}
|
|
||||||
func (u *Ui) CanGoBack() bool {
|
|
||||||
return u.prevScreen > 0
|
|
||||||
}
|
|
||||||
func (u *Ui) GoBack() func() wandle.Msg {
|
|
||||||
if !u.CanGoBack() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return u.GotoScreen(u.prevScreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Ui) Log(v string) error {
|
|
||||||
f, err := os.OpenFile("gime.log", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
_, err = f.WriteString(fmt.Sprintf("%s: %s\n", time.Now().Format(time.RFC3339), v))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Ui) Start() error {
|
|
||||||
return u.wandle.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenMsg struct {
|
|
||||||
source ScreenMsgSource
|
|
||||||
command ScreenMsgCommand
|
|
||||||
data interface{}
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScreenMsgSource int
|
|
||||||
type ScreenMsgCommand int
|
|
|
@ -1,223 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
|
||||||
"git.bullercodeworks.com/brian/widdles"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A widdle to prompt the user for a tag key and value
|
|
||||||
*/
|
|
||||||
type PromptForTagWiddle struct {
|
|
||||||
active bool
|
|
||||||
x, y, w, h int
|
|
||||||
|
|
||||||
origKey, origVal string
|
|
||||||
|
|
||||||
keyInput *widdles.ToggleField
|
|
||||||
valInput *widdles.ToggleField
|
|
||||||
|
|
||||||
cancelButton *widdles.Button
|
|
||||||
doneButton *widdles.Button
|
|
||||||
|
|
||||||
multipleValues bool
|
|
||||||
|
|
||||||
msg string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ widdles.Widdle = (*PromptForTagWiddle)(nil)
|
|
||||||
|
|
||||||
func NewPromptForTagWiddle(x, y, w, h int, key, val string) *PromptForTagWiddle {
|
|
||||||
keyInp := widdles.NewToggleField("Key", key, 0, 0, 0, 0)
|
|
||||||
keyInp.SetActive(true)
|
|
||||||
return &PromptForTagWiddle{
|
|
||||||
x: x, y: y, w: w, h: h,
|
|
||||||
origKey: key, origVal: val,
|
|
||||||
keyInput: keyInp,
|
|
||||||
valInput: widdles.NewToggleField("Value", val, 0, 0, 0, 0),
|
|
||||||
cancelButton: widdles.NewButton("Cancel", 0, 0, 0, 0),
|
|
||||||
doneButton: widdles.NewButton("Done", 0, 0, 0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *PromptForTagWiddle) Init() wandle.Cmd {
|
|
||||||
return func() wandle.Msg {
|
|
||||||
w.Measure()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) Update(msg wandle.Msg) wandle.Cmd {
|
|
||||||
if w.active {
|
|
||||||
// Make sure a widdle is active
|
|
||||||
var found bool
|
|
||||||
for _, wdl := range []widdles.Widdle{w.keyInput, w.valInput, w.cancelButton, w.doneButton} {
|
|
||||||
if wdl.IsActive() {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
w.keyInput.SetActive(true)
|
|
||||||
}
|
|
||||||
if msg, ok := msg.(termbox.Event); ok {
|
|
||||||
return w.handleTermboxEvent(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) View(style wandle.Style) {
|
|
||||||
title := "Add Tag"
|
|
||||||
if w.origKey != "" {
|
|
||||||
title = "Edit Tag"
|
|
||||||
}
|
|
||||||
wandle.TitledBorderFilled(title, w.x, w.y, w.x+w.w, w.y+w.h, style, wandle.BRD_CSIMPLE)
|
|
||||||
w.keyInput.View(style)
|
|
||||||
w.valInput.View(style)
|
|
||||||
if w.multipleValues {
|
|
||||||
red := wandle.NewStyle(
|
|
||||||
termbox.RGBToAttribute(uint8(200), uint8(0), uint8(0)),
|
|
||||||
termbox.RGBToAttribute(uint8(0), uint8(0), uint8(0)),
|
|
||||||
)
|
|
||||||
wandle.Print(w.valInput.GetX(), w.valInput.GetY()+1, red, "Changing a tag on timers that have different values!")
|
|
||||||
}
|
|
||||||
w.cancelButton.View(style)
|
|
||||||
w.doneButton.View(style)
|
|
||||||
wandle.Print(w.x+1, w.y+w.h-2, style, w.msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *PromptForTagWiddle) IsActive() bool { return w.active }
|
|
||||||
func (w *PromptForTagWiddle) SetActive(b bool) { w.active = b }
|
|
||||||
func (w *PromptForTagWiddle) Focusable() bool { return true }
|
|
||||||
func (w *PromptForTagWiddle) SetX(x int) {
|
|
||||||
w.x = x
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) GetX() int { return w.x }
|
|
||||||
func (w *PromptForTagWiddle) SetY(y int) {
|
|
||||||
w.y = y
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) GetY() int { return w.y }
|
|
||||||
func (w *PromptForTagWiddle) SetHeight(h int) {
|
|
||||||
w.h = h
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) GetHeight() int {
|
|
||||||
//if w.h == widdles.AUTO_SIZE { }
|
|
||||||
return w.h
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) SetWidth(v int) {
|
|
||||||
w.w = v
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) GetWidth() int {
|
|
||||||
//if w.w == widdles.AUTO_SIZE { }
|
|
||||||
return w.w
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) Measure() {
|
|
||||||
w.keyInput.SetX(w.x + 1)
|
|
||||||
w.keyInput.SetY(w.y + 1)
|
|
||||||
w.keyInput.SetWidth(w.w - 2)
|
|
||||||
w.keyInput.SetHeight(1)
|
|
||||||
|
|
||||||
w.valInput.SetX(w.x + 1)
|
|
||||||
w.valInput.SetY(w.y + 2)
|
|
||||||
w.valInput.SetWidth(w.w - 2)
|
|
||||||
w.valInput.SetHeight(1)
|
|
||||||
|
|
||||||
w.doneButton.SetX(w.x + w.w - 9)
|
|
||||||
w.doneButton.SetY(w.y + w.h - 1)
|
|
||||||
w.doneButton.SetWidth(8)
|
|
||||||
w.doneButton.SetHeight(1)
|
|
||||||
|
|
||||||
w.cancelButton.SetX(w.doneButton.GetX() - 12)
|
|
||||||
w.cancelButton.SetY(w.y + w.h - 1)
|
|
||||||
w.cancelButton.SetWidth(10)
|
|
||||||
w.cancelButton.SetHeight(1)
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) SetPos(x, y int) { w.x, w.y = x, y }
|
|
||||||
func (w *PromptForTagWiddle) SetSize(wdt, hgt int) { w.w, w.h = wdt, hgt }
|
|
||||||
|
|
||||||
func (w *PromptForTagWiddle) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
|
||||||
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)
|
|
||||||
w.valInput.SetActive(true)
|
|
||||||
w.valInput.SetEditable(true)
|
|
||||||
return wandle.EmptyCmd
|
|
||||||
} else if w.valInput.IsEditable() {
|
|
||||||
w.valInput.SetActive(false)
|
|
||||||
w.valInput.SetEditable(false)
|
|
||||||
if w.keyInput.GetValue() != "" && w.valInput.GetValue() != "" {
|
|
||||||
w.doneButton.SetActive(true)
|
|
||||||
} else {
|
|
||||||
w.cancelButton.SetActive(true)
|
|
||||||
}
|
|
||||||
return wandle.EmptyCmd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
widdles := []widdles.Widdle{w.keyInput, w.valInput, w.cancelButton, w.doneButton}
|
|
||||||
for _, wdl := range widdles {
|
|
||||||
if wdl.IsActive() {
|
|
||||||
if ret := wdl.Update(msg); ret != nil {
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown || msg.Key == termbox.KeyArrowRight {
|
|
||||||
for i := range widdles {
|
|
||||||
if widdles[i].IsActive() {
|
|
||||||
return func() wandle.Msg {
|
|
||||||
widdles[i].SetActive(false)
|
|
||||||
next := ((i + 1) + len(widdles)) % len(widdles)
|
|
||||||
widdles[next].SetActive(true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp || msg.Key == termbox.KeyArrowLeft {
|
|
||||||
for i := range widdles {
|
|
||||||
if widdles[i].IsActive() {
|
|
||||||
return func() wandle.Msg {
|
|
||||||
widdles[i].SetActive(false)
|
|
||||||
next := ((i - 1) + len(widdles)) % len(widdles)
|
|
||||||
widdles[next].SetActive(true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) SetTag(key, val string) {
|
|
||||||
w.origKey, w.origVal = key, val
|
|
||||||
w.keyInput.SetValue(key)
|
|
||||||
if key == "" && val == "" {
|
|
||||||
w.keyInput.SetEditable(true)
|
|
||||||
}
|
|
||||||
w.valInput.SetValue(val)
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) GetTag() (string, string) {
|
|
||||||
return w.keyInput.GetValue(), w.valInput.GetValue()
|
|
||||||
}
|
|
||||||
func (w *PromptForTagWiddle) SetMultiVal(v bool) { w.multipleValues = v }
|
|
||||||
|
|
||||||
func (w *PromptForTagWiddle) ClearCancelCommand() { w.SetCancelCommand(wandle.EmptyCmd) }
|
|
||||||
func (w *PromptForTagWiddle) SetCancelCommand(cmd func() wandle.Msg) { w.cancelButton.SetCommand(cmd) }
|
|
||||||
func (w *PromptForTagWiddle) ClearDoneCommand() { w.SetDoneCommand(wandle.EmptyCmd) }
|
|
||||||
func (w *PromptForTagWiddle) SetDoneCommand(cmd func() wandle.Msg) { w.doneButton.SetCommand(cmd) }
|
|
||||||
|
|
||||||
func (w *PromptForTagWiddle) Done() {
|
|
||||||
w.origKey, w.origVal = "", ""
|
|
||||||
w.multipleValues = false
|
|
||||||
w.keyInput.SetValue("")
|
|
||||||
w.valInput.SetValue("")
|
|
||||||
w.ClearCancelCommand()
|
|
||||||
w.ClearDoneCommand()
|
|
||||||
w.SetActive(false)
|
|
||||||
}
|
|
|
@ -1,145 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.bullercodeworks.com/brian/wandle"
|
|
||||||
"git.bullercodeworks.com/brian/widdles"
|
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* PartManager is for adding/editing/removing groups of strings
|
|
||||||
*/
|
|
||||||
type PartManager struct {
|
|
||||||
active bool
|
|
||||||
visible bool
|
|
||||||
|
|
||||||
x, y, w, h int
|
|
||||||
|
|
||||||
label string
|
|
||||||
typeString string
|
|
||||||
options []string
|
|
||||||
selOptions map[int]bool
|
|
||||||
input *widdles.ToggleField
|
|
||||||
|
|
||||||
cursor int
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ widdles.Widdle = (*PartManager)(nil)
|
|
||||||
|
|
||||||
func NewPartManager(label string, x, y, w, h int) *PartManager {
|
|
||||||
return &PartManager{
|
|
||||||
label: label,
|
|
||||||
selOptions: make(map[int]bool),
|
|
||||||
input: widdles.NewToggleField("Value:", "", x+1, y+2, w-2, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *PartManager) Init() wandle.Cmd { return nil }
|
|
||||||
func (w *PartManager) Update(msg wandle.Msg) wandle.Cmd {
|
|
||||||
if w.active {
|
|
||||||
if msg, ok := msg.(termbox.Event); ok {
|
|
||||||
return w.handleTermboxEvent(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (w *PartManager) handleTermboxEvent(msg termbox.Event) wandle.Cmd {
|
|
||||||
if w.cursor == 0 {
|
|
||||||
if opt := w.input.Update(msg); opt != nil {
|
|
||||||
return opt
|
|
||||||
} else {
|
|
||||||
//return w.handleKeyPress(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w.handleKeyPress(msg)
|
|
||||||
}
|
|
||||||
func (w *PartManager) handleKeyPress(msg termbox.Event) wandle.Cmd {
|
|
||||||
if msg.Key == termbox.KeyEsc {
|
|
||||||
return func() wandle.Msg {
|
|
||||||
w.Hide()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else if msg.Ch == 'j' || msg.Key == termbox.KeyArrowDown {
|
|
||||||
if w.cursor < len(w.options) {
|
|
||||||
w.cursor = w.cursor + 1
|
|
||||||
}
|
|
||||||
} else if msg.Ch == 'k' || msg.Key == termbox.KeyArrowUp {
|
|
||||||
if w.cursor > 0 {
|
|
||||||
w.cursor = w.cursor - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w.IsActive() {
|
|
||||||
return func() wandle.Msg { return nil }
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (w *PartManager) View(style wandle.Style) {
|
|
||||||
if w.visible {
|
|
||||||
wandle.TitledBorderFilled(w.label, w.x, w.y, w.x+w.w, w.y+w.h, style, wandle.BRD_SIMPLE)
|
|
||||||
st := style
|
|
||||||
if w.cursor == 0 {
|
|
||||||
st = st.Invert()
|
|
||||||
}
|
|
||||||
w.input.View(st)
|
|
||||||
y := w.input.GetY() + 1
|
|
||||||
wandle.Print(w.x+1, y, style, fmt.Sprintf("Add %s:", w.typeString))
|
|
||||||
y++
|
|
||||||
for i := range w.options {
|
|
||||||
st := style
|
|
||||||
if w.cursor-1 == i {
|
|
||||||
st = st.Invert()
|
|
||||||
}
|
|
||||||
wandle.Print(w.x+3, y, st, w.options[i])
|
|
||||||
y++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (w *PartManager) IsActive() bool { return w.active }
|
|
||||||
func (w *PartManager) SetActive(b bool) { w.active = b }
|
|
||||||
func (w *PartManager) Focusable() bool { return true }
|
|
||||||
func (w *PartManager) SetX(x int) {
|
|
||||||
w.x = x
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PartManager) GetX() int { return w.x }
|
|
||||||
func (w *PartManager) SetY(y int) {
|
|
||||||
w.y = y
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PartManager) GetY() int { return w.y }
|
|
||||||
func (w *PartManager) SetHeight(h int) {
|
|
||||||
w.h = h
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PartManager) GetHeight() int { return w.h }
|
|
||||||
func (w *PartManager) SetWidth(v int) {
|
|
||||||
w.w = v
|
|
||||||
w.Measure()
|
|
||||||
}
|
|
||||||
func (w *PartManager) GetWidth() int { return w.w }
|
|
||||||
func (w *PartManager) Measure() {
|
|
||||||
w.input.SetX(w.x + 1)
|
|
||||||
w.input.SetY(w.y + 1)
|
|
||||||
w.input.SetWidth(w.w - 2)
|
|
||||||
w.input.SetHeight(1)
|
|
||||||
}
|
|
||||||
func (w *PartManager) SetPos(x, y int) { w.x, w.y = x, y }
|
|
||||||
func (w *PartManager) SetSize(wdt, hgt int) { w.w, w.h = wdt, hgt }
|
|
||||||
|
|
||||||
func (w *PartManager) SetLabel(lbl string) { w.label = lbl }
|
|
||||||
func (w *PartManager) SetType(tp string) { w.typeString = tp }
|
|
||||||
func (w *PartManager) SetValue(val string) { w.input.SetValue(val) }
|
|
||||||
func (w *PartManager) GetValue() string { return w.input.GetValue() }
|
|
||||||
func (w *PartManager) SetOptions(opts []string) { w.options = opts }
|
|
||||||
func (w *PartManager) SetVisible(is bool) { w.visible = is }
|
|
||||||
func (w *PartManager) IsVisible() bool { return w.visible }
|
|
||||||
func (w *PartManager) Show() {
|
|
||||||
w.SetVisible(true)
|
|
||||||
w.SetActive(true)
|
|
||||||
}
|
|
||||||
func (w *PartManager) Hide() {
|
|
||||||
w.SetVisible(false)
|
|
||||||
w.SetActive(false)
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -13,12 +12,6 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
ROUND_UP = 1
|
|
||||||
ROUND_EITHER = 0
|
|
||||||
ROUND_DOWN = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
func PromptUser(text string) string {
|
func PromptUser(text string) string {
|
||||||
var resp string
|
var resp string
|
||||||
fmt.Println(text)
|
fmt.Println(text)
|
||||||
|
@ -64,34 +57,6 @@ func TimerToString(t *timertxt.Timer) string {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func Round(dur time.Duration) time.Duration {
|
|
||||||
roundDur := GetRoundToDuration()
|
|
||||||
wrk := dur.Round(roundDur)
|
|
||||||
switch GetRoundDirection() {
|
|
||||||
case ROUND_UP:
|
|
||||||
if wrk < dur {
|
|
||||||
return wrk + roundDur
|
|
||||||
}
|
|
||||||
case ROUND_DOWN:
|
|
||||||
if wrk > dur {
|
|
||||||
return wrk - roundDur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return wrk
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRoundDirection() int {
|
|
||||||
dir := viper.GetString("round")
|
|
||||||
switch dir {
|
|
||||||
case "up":
|
|
||||||
return ROUND_UP
|
|
||||||
case "down":
|
|
||||||
return ROUND_DOWN
|
|
||||||
default:
|
|
||||||
return ROUND_EITHER
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRoundToDuration() time.Duration {
|
func GetRoundToDuration() time.Duration {
|
||||||
var dur time.Duration
|
var dur time.Duration
|
||||||
dur, _ = time.ParseDuration(viper.GetString("roundto"))
|
dur, _ = time.ParseDuration(viper.GetString("roundto"))
|
||||||
|
@ -102,12 +67,6 @@ func DurationToDecimal(dur time.Duration) float64 {
|
||||||
mins := dur.Minutes() - (dur.Hours() * 60)
|
mins := dur.Minutes() - (dur.Hours() * 60)
|
||||||
return dur.Hours() + (mins / 60)
|
return dur.Hours() + (mins / 60)
|
||||||
}
|
}
|
||||||
func AddDurations(dur1, dur2 time.Duration) time.Duration {
|
|
||||||
return time.Duration(int64(dur1) + int64(dur2))
|
|
||||||
}
|
|
||||||
func SubDurations(dur1, dur2 time.Duration) time.Duration {
|
|
||||||
return time.Duration(int64(dur1) - int64(dur2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContextsFromSlice pulls all '@' (contexts) out of the
|
// GetContextsFromSlice pulls all '@' (contexts) out of the
|
||||||
// string slice and return those contexts and the remaining
|
// string slice and return those contexts and the remaining
|
||||||
|
@ -221,7 +180,7 @@ func TimerToFriendlyString(t *timertxt.Timer) string {
|
||||||
} else {
|
} else {
|
||||||
dur = t.FinishDate.Sub(t.StartDate)
|
dur = t.FinishDate.Sub(t.StartDate)
|
||||||
}
|
}
|
||||||
dur = Round(dur)
|
dur = dur.Round(GetRoundToDuration())
|
||||||
return fmt.Sprintf("% 2d. %s - %s [ %s] [ %s] [ %s] %s ( %.2f )", t.Id, start, end, contexts, projects, tags, t.Notes, DurationToDecimal(dur))
|
return fmt.Sprintf("% 2d. %s - %s [ %s] [ %s] [ %s] %s ( %.2f )", t.Id, start, end, contexts, projects, tags, t.Notes, DurationToDecimal(dur))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,27 +382,3 @@ func BuildFilterFromArgs(args []string) func(*timertxt.Timer) bool {
|
||||||
}
|
}
|
||||||
return doFilters
|
return doFilters
|
||||||
}
|
}
|
||||||
|
|
||||||
func SortedTagKeyList(m map[string]string) []string {
|
|
||||||
var ret []string
|
|
||||||
for k := range m {
|
|
||||||
ret = append(ret, k)
|
|
||||||
}
|
|
||||||
sort.Strings(ret)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringSliceContains(sl []string, val string) bool {
|
|
||||||
for i := range sl {
|
|
||||||
if sl[i] == val {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
func AppendStringIfDistinct(sl []string, val string) []string {
|
|
||||||
if !StringSliceContains(sl, val) {
|
|
||||||
return append(sl, val)
|
|
||||||
}
|
|
||||||
return sl
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue