186 lines
4.4 KiB
Go
186 lines
4.4 KiB
Go
package pages
|
|
|
|
import (
|
|
"time"
|
|
|
|
"tasksquire/common"
|
|
"tasksquire/components/timetable"
|
|
"tasksquire/timewarrior"
|
|
|
|
"github.com/charmbracelet/bubbles/key"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
type TimePage struct {
|
|
common *common.Common
|
|
|
|
intervals timetable.Model
|
|
data timewarrior.Intervals
|
|
|
|
shouldSelectActive bool
|
|
}
|
|
|
|
func NewTimePage(com *common.Common) *TimePage {
|
|
p := &TimePage{
|
|
common: com,
|
|
}
|
|
|
|
p.populateTable(timewarrior.Intervals{})
|
|
return p
|
|
}
|
|
|
|
func (p *TimePage) Init() tea.Cmd {
|
|
return tea.Batch(p.getIntervals(), doTick())
|
|
}
|
|
|
|
func (p *TimePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
var cmds []tea.Cmd
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
p.SetSize(msg.Width, msg.Height)
|
|
case intervalsMsg:
|
|
p.data = timewarrior.Intervals(msg)
|
|
p.populateTable(p.data)
|
|
case RefreshIntervalsMsg:
|
|
cmds = append(cmds, p.getIntervals())
|
|
case tickMsg:
|
|
cmds = append(cmds, p.getIntervals())
|
|
cmds = append(cmds, doTick())
|
|
case tea.KeyMsg:
|
|
switch {
|
|
case key.Matches(msg, p.common.Keymap.Quit):
|
|
return p, tea.Quit
|
|
case key.Matches(msg, p.common.Keymap.StartStop):
|
|
row := p.intervals.SelectedRow()
|
|
if row != nil {
|
|
interval := (*timewarrior.Interval)(row)
|
|
if interval.IsActive() {
|
|
p.common.TimeW.StopTracking()
|
|
} else {
|
|
p.common.TimeW.ContinueInterval(interval.ID)
|
|
p.shouldSelectActive = true
|
|
}
|
|
return p, tea.Batch(p.getIntervals(), doTick())
|
|
}
|
|
case key.Matches(msg, p.common.Keymap.Delete):
|
|
row := p.intervals.SelectedRow()
|
|
if row != nil {
|
|
interval := (*timewarrior.Interval)(row)
|
|
p.common.TimeW.DeleteInterval(interval.ID)
|
|
return p, tea.Batch(p.getIntervals(), doTick())
|
|
}
|
|
case key.Matches(msg, p.common.Keymap.Edit):
|
|
row := p.intervals.SelectedRow()
|
|
if row != nil {
|
|
interval := (*timewarrior.Interval)(row)
|
|
editor := NewTimeEditorPage(p.common, interval)
|
|
p.common.PushPage(p)
|
|
return editor, editor.Init()
|
|
}
|
|
case key.Matches(msg, p.common.Keymap.Add):
|
|
interval := timewarrior.NewInterval()
|
|
interval.Start = time.Now().UTC().Format("20060102T150405Z")
|
|
editor := NewTimeEditorPage(p.common, interval)
|
|
p.common.PushPage(p)
|
|
return editor, editor.Init()
|
|
}
|
|
}
|
|
|
|
var cmd tea.Cmd
|
|
p.intervals, cmd = p.intervals.Update(msg)
|
|
cmds = append(cmds, cmd)
|
|
|
|
return p, tea.Batch(cmds...)
|
|
}
|
|
|
|
type RefreshIntervalsMsg struct{}
|
|
|
|
func refreshIntervals() tea.Msg {
|
|
return RefreshIntervalsMsg{}
|
|
}
|
|
|
|
func (p *TimePage) View() string {
|
|
if len(p.data) == 0 {
|
|
return p.common.Styles.Base.Render("No intervals found for today")
|
|
}
|
|
return p.intervals.View()
|
|
}
|
|
|
|
func (p *TimePage) SetSize(width int, height int) {
|
|
p.common.SetSize(width, height)
|
|
p.intervals.SetWidth(width - p.common.Styles.Base.GetHorizontalFrameSize())
|
|
p.intervals.SetHeight(height - p.common.Styles.Base.GetVerticalFrameSize())
|
|
}
|
|
|
|
func (p *TimePage) populateTable(intervals timewarrior.Intervals) {
|
|
var selectedStart string
|
|
currentIdx := p.intervals.Cursor()
|
|
if row := p.intervals.SelectedRow(); row != nil {
|
|
selectedStart = row.Start
|
|
}
|
|
|
|
columns := []timetable.Column{
|
|
{Title: "ID", Name: "id", Width: 4},
|
|
{Title: "Start", Name: "start", Width: 16},
|
|
{Title: "End", Name: "end", Width: 16},
|
|
{Title: "Duration", Name: "duration", Width: 10},
|
|
{Title: "Tags", Name: "tags", Width: 0}, // flexible width
|
|
}
|
|
|
|
p.intervals = timetable.New(
|
|
p.common,
|
|
timetable.WithColumns(columns),
|
|
timetable.WithIntervals(intervals),
|
|
timetable.WithFocused(true),
|
|
timetable.WithWidth(p.common.Width()-p.common.Styles.Base.GetVerticalFrameSize()),
|
|
timetable.WithHeight(p.common.Height()-p.common.Styles.Base.GetHorizontalFrameSize()),
|
|
timetable.WithStyles(p.common.Styles.TableStyle),
|
|
)
|
|
|
|
if len(intervals) > 0 {
|
|
newIdx := -1
|
|
|
|
if p.shouldSelectActive {
|
|
for i, interval := range intervals {
|
|
if interval.IsActive() {
|
|
newIdx = i
|
|
break
|
|
}
|
|
}
|
|
p.shouldSelectActive = false
|
|
}
|
|
|
|
if newIdx == -1 && selectedStart != "" {
|
|
for i, interval := range intervals {
|
|
if interval.Start == selectedStart {
|
|
newIdx = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if newIdx == -1 {
|
|
newIdx = currentIdx
|
|
}
|
|
|
|
if newIdx >= len(intervals) {
|
|
newIdx = len(intervals) - 1
|
|
}
|
|
if newIdx < 0 {
|
|
newIdx = 0
|
|
}
|
|
|
|
p.intervals.SetCursor(newIdx)
|
|
}
|
|
}
|
|
|
|
type intervalsMsg timewarrior.Intervals
|
|
|
|
func (p *TimePage) getIntervals() tea.Cmd {
|
|
return func() tea.Msg {
|
|
// ":day" is a timewarrior hint for "today"
|
|
intervals := p.common.TimeW.GetIntervals(":day")
|
|
return intervalsMsg(intervals)
|
|
}
|
|
}
|