Initial commit
This commit is contained in:
105
pages/contextPicker.go
Normal file
105
pages/contextPicker.go
Normal file
@ -0,0 +1,105 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"slices"
|
||||
"tasksquire/common"
|
||||
"tasksquire/taskwarrior"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type ContextPickerPage struct {
|
||||
common *common.Common
|
||||
contexts taskwarrior.Contexts
|
||||
form *huh.Form
|
||||
}
|
||||
|
||||
func NewContextPickerPage(common *common.Common) *ContextPickerPage {
|
||||
p := &ContextPickerPage{
|
||||
common: common,
|
||||
contexts: common.TW.GetContexts(),
|
||||
}
|
||||
|
||||
// if allowAdd {
|
||||
// fields = append(fields, huh.NewInput().
|
||||
// Key("input").
|
||||
// Title("Input").
|
||||
// Prompt(fmt.Sprintf("Enter a new %s", header)).
|
||||
// Inline(false),
|
||||
// )
|
||||
// }
|
||||
|
||||
selected := common.TW.GetActiveContext().Name
|
||||
options := make([]string, 0)
|
||||
for _, c := range p.contexts {
|
||||
options = append(options, c.Name)
|
||||
}
|
||||
slices.Sort(options)
|
||||
|
||||
p.form = huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Key("context").
|
||||
Options(huh.NewOptions(options...)...).
|
||||
Title("Contexts").
|
||||
Description("Choose a context").
|
||||
Value(&selected),
|
||||
),
|
||||
).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(true)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *ContextPickerPage) Init() tea.Cmd {
|
||||
return p.form.Init()
|
||||
}
|
||||
|
||||
func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, p.common.Keymap.Back):
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, BackCmd
|
||||
}
|
||||
}
|
||||
|
||||
f, cmd := p.form.Update(msg)
|
||||
if f, ok := f.(*huh.Form); ok {
|
||||
p.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if p.form.State == huh.StateCompleted {
|
||||
cmds = append(cmds, p.updateContextCmd)
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
return p, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (p *ContextPickerPage) View() string {
|
||||
return p.common.Styles.Main.Render(p.form.View())
|
||||
}
|
||||
|
||||
func (p *ContextPickerPage) updateContextCmd() tea.Msg {
|
||||
return UpdateContextMsg(p.common.TW.GetContext(p.form.GetString("context")))
|
||||
}
|
||||
|
||||
type UpdateContextMsg *taskwarrior.Context
|
||||
122
pages/datePicker.go
Normal file
122
pages/datePicker.go
Normal file
@ -0,0 +1,122 @@
|
||||
package pages
|
||||
|
||||
// import (
|
||||
// "tasksquire/common"
|
||||
|
||||
// "github.com/charmbracelet/bubbles/textinput"
|
||||
// tea "github.com/charmbracelet/bubbletea"
|
||||
// "github.com/charmbracelet/lipgloss"
|
||||
// datepicker "github.com/ethanefung/bubble-datepicker"
|
||||
// )
|
||||
|
||||
// type Model struct {
|
||||
// focus focus
|
||||
// input textinput.Model
|
||||
// datepicker datepicker.Model
|
||||
// }
|
||||
|
||||
// var inputStyles = lipgloss.NewStyle().Padding(1, 1, 0)
|
||||
|
||||
// func initializeModel() tea.Model {
|
||||
// dp := datepicker.New(time.Now())
|
||||
|
||||
// input := textinput.New()
|
||||
// input.Placeholder = "YYYY-MM-DD (enter date)"
|
||||
// input.Focus()
|
||||
// input.Width = 20
|
||||
|
||||
// return Model{
|
||||
// focus: FocusInput,
|
||||
// input: input,
|
||||
// datepicker: dp,
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (m Model) Init() tea.Cmd {
|
||||
// return textinput.Blink
|
||||
// }
|
||||
|
||||
// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// var cmd tea.Cmd
|
||||
|
||||
// switch msg := msg.(type) {
|
||||
// case tea.WindowSizeMsg:
|
||||
// // TODO figure out how we want to size things
|
||||
// // we'll probably want both bubbles to be vertically stacked
|
||||
// // and to take as much room as the can
|
||||
// return m, nil
|
||||
// case tea.KeyMsg:
|
||||
// switch msg.String() {
|
||||
// case "ctrl+c", "q":
|
||||
// return m, tea.Quit
|
||||
// case "tab":
|
||||
// if m.focus == FocusInput {
|
||||
// m.focus = FocusDatePicker
|
||||
// m.input.Blur()
|
||||
// m.input.SetValue(m.datepicker.Time.Format(time.DateOnly))
|
||||
|
||||
// m.datepicker.SelectDate()
|
||||
// m.datepicker.SetFocus(datepicker.FocusHeaderMonth)
|
||||
// m.datepicker = m.datepicker
|
||||
// return m, nil
|
||||
|
||||
// }
|
||||
// case "shift+tab":
|
||||
// if m.focus == FocusDatePicker && m.datepicker.Focused == datepicker.FocusHeaderMonth {
|
||||
// m.focus = FocusInput
|
||||
// m.datepicker.Blur()
|
||||
|
||||
// m.input.Focus()
|
||||
// return m, nil
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// switch m.focus {
|
||||
// case FocusInput:
|
||||
// m.input, cmd = m.UpdateInput(msg)
|
||||
// case FocusDatePicker:
|
||||
// m.datepicker, cmd = m.UpdateDatepicker(msg)
|
||||
// case FocusNone:
|
||||
// // do nothing
|
||||
// }
|
||||
|
||||
// return m, cmd
|
||||
// }
|
||||
|
||||
// func (m Model) View() string {
|
||||
// return lipgloss.JoinVertical(lipgloss.Left, inputStyles.Render(m.input.View()), m.datepicker.View())
|
||||
// }
|
||||
|
||||
// func (m *Model) UpdateInput(msg tea.Msg) (textinput.Model, tea.Cmd) {
|
||||
// var cmd tea.Cmd
|
||||
|
||||
// m.input, cmd = m.input.Update(msg)
|
||||
|
||||
// val := m.input.Value()
|
||||
// t, err := time.Parse(time.DateOnly, strings.TrimSpace(val))
|
||||
// if err == nil {
|
||||
// m.datepicker.SetTime(t)
|
||||
// m.datepicker.SelectDate()
|
||||
// m.datepicker.Blur()
|
||||
// }
|
||||
// if err != nil && m.datepicker.Selected {
|
||||
// m.datepicker.UnselectDate()
|
||||
// }
|
||||
|
||||
// return m.input, cmd
|
||||
// }
|
||||
|
||||
// func (m *Model) UpdateDatepicker(msg tea.Msg) (datepicker.Model, tea.Cmd) {
|
||||
// var cmd tea.Cmd
|
||||
|
||||
// prev := m.datepicker.Time
|
||||
|
||||
// m.datepicker, cmd = m.datepicker.Update(msg)
|
||||
|
||||
// if prev != m.datepicker.Time {
|
||||
// m.input.SetValue(m.datepicker.Time.Format(time.DateOnly))
|
||||
// }
|
||||
|
||||
// return m.datepicker, cmd
|
||||
// }
|
||||
11
pages/page.go
Normal file
11
pages/page.go
Normal file
@ -0,0 +1,11 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func BackCmd() tea.Msg {
|
||||
return BackMsg{}
|
||||
}
|
||||
|
||||
type BackMsg struct{}
|
||||
100
pages/projectPicker.go
Normal file
100
pages/projectPicker.go
Normal file
@ -0,0 +1,100 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"tasksquire/common"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type ProjectPickerPage struct {
|
||||
common *common.Common
|
||||
form *huh.Form
|
||||
}
|
||||
|
||||
func NewProjectPickerPage(common *common.Common, activeProject string) *ProjectPickerPage {
|
||||
p := &ProjectPickerPage{
|
||||
common: common,
|
||||
}
|
||||
|
||||
var selected string
|
||||
if activeProject == "" {
|
||||
selected = "(none)"
|
||||
} else {
|
||||
selected = activeProject
|
||||
}
|
||||
|
||||
projects := common.TW.GetProjects()
|
||||
options := []string{"(none)"}
|
||||
options = append(options, projects...)
|
||||
|
||||
p.form = huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Key("project").
|
||||
Options(huh.NewOptions(options...)...).
|
||||
Title("Projects").
|
||||
Description("Choose a project").
|
||||
Value(&selected),
|
||||
),
|
||||
).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *ProjectPickerPage) Init() tea.Cmd {
|
||||
return p.form.Init()
|
||||
}
|
||||
|
||||
func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, p.common.Keymap.Back):
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, BackCmd
|
||||
}
|
||||
}
|
||||
|
||||
f, cmd := p.form.Update(msg)
|
||||
if f, ok := f.(*huh.Form); ok {
|
||||
p.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if p.form.State == huh.StateCompleted {
|
||||
cmds = append(cmds, p.updateProjectCmd)
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
return p, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (p *ProjectPickerPage) View() string {
|
||||
return p.common.Styles.Main.Render(p.form.View())
|
||||
}
|
||||
|
||||
func (p *ProjectPickerPage) updateProjectCmd() tea.Msg {
|
||||
project := p.form.GetString("project")
|
||||
if project == "(none)" {
|
||||
project = ""
|
||||
}
|
||||
return UpdateProjectMsg(project)
|
||||
}
|
||||
|
||||
type UpdateProjectMsg string
|
||||
201
pages/report.go
Normal file
201
pages/report.go
Normal file
@ -0,0 +1,201 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"tasksquire/common"
|
||||
"tasksquire/taskwarrior"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
type ReportPage struct {
|
||||
common *common.Common
|
||||
|
||||
activeReport *taskwarrior.Report
|
||||
activeContext *taskwarrior.Context
|
||||
activeProject string
|
||||
selectedTask *taskwarrior.Task
|
||||
|
||||
tasks taskwarrior.Tasks
|
||||
|
||||
taskTable table.Model
|
||||
tableStyle table.Styles
|
||||
keymap ReportKeys
|
||||
|
||||
subpage tea.Model
|
||||
subpageActive bool
|
||||
}
|
||||
|
||||
type ReportKeys struct {
|
||||
Quit key.Binding
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
Select key.Binding
|
||||
ToggleFocus key.Binding
|
||||
}
|
||||
|
||||
func NewReportPage(com *common.Common, report *taskwarrior.Report) *ReportPage {
|
||||
s := table.DefaultStyles()
|
||||
s.Header = s.Header.
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240")).
|
||||
BorderBottom(true).
|
||||
Bold(false)
|
||||
s.Selected = s.Selected.
|
||||
Foreground(lipgloss.Color("229")).
|
||||
Background(lipgloss.Color("57")).
|
||||
Bold(false)
|
||||
|
||||
keys := ReportKeys{
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys("q", "ctrl+c"),
|
||||
key.WithHelp("q, ctrl+c", "Quit"),
|
||||
),
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("k", "up"),
|
||||
key.WithHelp("↑/k", "Up"),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("j", "down"),
|
||||
key.WithHelp("↓/j", "Down"),
|
||||
),
|
||||
Select: key.NewBinding(
|
||||
key.WithKeys("enter"),
|
||||
key.WithHelp("enter", "Select"),
|
||||
),
|
||||
ToggleFocus: key.NewBinding(
|
||||
key.WithKeys("esc"),
|
||||
key.WithHelp("esc", "Toggle focus"),
|
||||
),
|
||||
}
|
||||
|
||||
return &ReportPage{
|
||||
common: com,
|
||||
activeReport: report,
|
||||
activeContext: com.TW.GetActiveContext(),
|
||||
activeProject: "",
|
||||
tableStyle: s,
|
||||
keymap: keys,
|
||||
}
|
||||
}
|
||||
|
||||
func (p ReportPage) Init() tea.Cmd {
|
||||
return p.getTasks()
|
||||
}
|
||||
|
||||
func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case BackMsg:
|
||||
p.subpageActive = false
|
||||
case TaskMsg:
|
||||
p.populateTaskTable(msg)
|
||||
case UpdateReportMsg:
|
||||
p.activeReport = msg
|
||||
cmds = append(cmds, p.getTasks())
|
||||
case UpdateContextMsg:
|
||||
p.activeContext = msg
|
||||
p.common.TW.SetContext(msg)
|
||||
cmds = append(cmds, p.getTasks())
|
||||
case UpdateProjectMsg:
|
||||
p.activeProject = string(msg)
|
||||
cmds = append(cmds, p.getTasks())
|
||||
case AddedTaskMsg:
|
||||
cmds = append(cmds, p.getTasks())
|
||||
case tea.WindowSizeMsg:
|
||||
p.taskTable.SetWidth(msg.Width - 2)
|
||||
p.taskTable.SetHeight(msg.Height - 4)
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, p.common.Keymap.Quit):
|
||||
return p, tea.Quit
|
||||
case key.Matches(msg, p.common.Keymap.SetReport):
|
||||
p.subpage = NewReportPickerPage(p.common, p.activeReport)
|
||||
p.subpage.Init()
|
||||
p.subpageActive = true
|
||||
p.common.PageStack.Push(p)
|
||||
return p.subpage, nil
|
||||
case key.Matches(msg, p.common.Keymap.SetContext):
|
||||
p.subpage = NewContextPickerPage(p.common)
|
||||
p.subpage.Init()
|
||||
p.subpageActive = true
|
||||
p.common.PageStack.Push(p)
|
||||
return p.subpage, nil
|
||||
case key.Matches(msg, p.common.Keymap.Add):
|
||||
p.subpage = NewTaskEditorPage(p.common, taskwarrior.Task{})
|
||||
p.subpage.Init()
|
||||
p.subpageActive = true
|
||||
p.common.PageStack.Push(p)
|
||||
return p.subpage, nil
|
||||
case key.Matches(msg, p.common.Keymap.SetProject):
|
||||
p.subpage = NewProjectPickerPage(p.common, p.activeProject)
|
||||
p.subpage.Init()
|
||||
p.subpageActive = true
|
||||
p.common.PageStack.Push(p)
|
||||
return p.subpage, nil
|
||||
}
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
p.taskTable, cmd = p.taskTable.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
if p.tasks != nil {
|
||||
p.selectedTask = (*taskwarrior.Task)(p.tasks[p.taskTable.Cursor()])
|
||||
}
|
||||
|
||||
return p, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (p ReportPage) View() string {
|
||||
return p.common.Styles.Main.Render(p.taskTable.View()) + "\n"
|
||||
}
|
||||
|
||||
func (p *ReportPage) populateTaskTable(tasks []*taskwarrior.Task) {
|
||||
columns := []table.Column{
|
||||
{Title: "ID", Width: 4},
|
||||
{Title: "Project", Width: 10},
|
||||
{Title: "Tags", Width: 10},
|
||||
{Title: "Prio", Width: 2},
|
||||
{Title: "Due", Width: 10},
|
||||
{Title: "Task", Width: 50},
|
||||
}
|
||||
var rows []table.Row
|
||||
for _, task := range tasks {
|
||||
rows = append(rows, table.Row{
|
||||
strconv.FormatInt(task.Id, 10),
|
||||
task.Project,
|
||||
strings.Join(task.Tags, ", "),
|
||||
task.Priority,
|
||||
task.Due,
|
||||
task.Description,
|
||||
})
|
||||
}
|
||||
|
||||
p.taskTable = table.New(
|
||||
table.WithColumns(columns),
|
||||
table.WithRows(rows),
|
||||
table.WithFocused(true),
|
||||
// table.WithHeight(7),
|
||||
// table.WithWidth(100),
|
||||
)
|
||||
p.taskTable.SetStyles(p.tableStyle)
|
||||
}
|
||||
|
||||
func (p *ReportPage) getTasks() tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
filters := []string{}
|
||||
if p.activeProject != "" {
|
||||
filters = append(filters, "project:"+p.activeProject)
|
||||
}
|
||||
p.tasks = p.common.TW.GetTasks(p.activeReport, filters...)
|
||||
return TaskMsg(p.tasks)
|
||||
}
|
||||
}
|
||||
|
||||
type TaskMsg taskwarrior.Tasks
|
||||
97
pages/reportPicker.go
Normal file
97
pages/reportPicker.go
Normal file
@ -0,0 +1,97 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"slices"
|
||||
"tasksquire/common"
|
||||
"tasksquire/taskwarrior"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type ReportPickerPage struct {
|
||||
common *common.Common
|
||||
reports taskwarrior.Reports
|
||||
form *huh.Form
|
||||
}
|
||||
|
||||
func NewReportPickerPage(common *common.Common, activeReport *taskwarrior.Report) *ReportPickerPage {
|
||||
p := &ReportPickerPage{
|
||||
common: common,
|
||||
reports: common.TW.GetReports(),
|
||||
}
|
||||
|
||||
selected := activeReport.Name
|
||||
|
||||
options := make([]string, 0)
|
||||
for _, r := range p.reports {
|
||||
options = append(options, r.Name)
|
||||
}
|
||||
slices.Sort(options)
|
||||
|
||||
p.form = huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewSelect[string]().
|
||||
Key("report").
|
||||
Options(huh.NewOptions(options...)...).
|
||||
Title("Reports").
|
||||
Description("Choose a report").
|
||||
Value(&selected),
|
||||
),
|
||||
).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *ReportPickerPage) Init() tea.Cmd {
|
||||
return p.form.Init()
|
||||
}
|
||||
|
||||
func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, p.common.Keymap.Back):
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, BackCmd
|
||||
}
|
||||
}
|
||||
|
||||
f, cmd := p.form.Update(msg)
|
||||
if f, ok := f.(*huh.Form); ok {
|
||||
p.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if p.form.State == huh.StateCompleted {
|
||||
cmds = append(cmds, []tea.Cmd{BackCmd, p.updateReportCmd}...)
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
return p, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (p *ReportPickerPage) View() string {
|
||||
return p.common.Styles.Main.Render(p.form.View())
|
||||
}
|
||||
|
||||
func (p *ReportPickerPage) updateReportCmd() tea.Msg {
|
||||
return UpdateReportMsg(p.common.TW.GetReport(p.form.GetString("report")))
|
||||
}
|
||||
|
||||
type UpdateReportMsg *taskwarrior.Report
|
||||
233
pages/taskEditor.go
Normal file
233
pages/taskEditor.go
Normal file
@ -0,0 +1,233 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"tasksquire/common"
|
||||
"tasksquire/taskwarrior"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type TaskEditorPage struct {
|
||||
common *common.Common
|
||||
task taskwarrior.Task
|
||||
form *huh.Form
|
||||
}
|
||||
|
||||
type TaskEditorKeys struct {
|
||||
Quit key.Binding
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
Select key.Binding
|
||||
ToggleFocus key.Binding
|
||||
}
|
||||
|
||||
func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditorPage {
|
||||
p := &TaskEditorPage{
|
||||
common: common,
|
||||
task: task,
|
||||
}
|
||||
|
||||
if p.task.Priority == "" {
|
||||
p.task.Priority = "(none)"
|
||||
}
|
||||
if p.task.Project == "" {
|
||||
p.task.Project = "(none)"
|
||||
}
|
||||
|
||||
priorityOptions := append([]string{"(none)"}, common.TW.GetPriorities()...)
|
||||
projectOptions := append([]string{"(none)"}, common.TW.GetProjects()...)
|
||||
tagOptions := common.TW.GetTags()
|
||||
|
||||
p.form = huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewInput().
|
||||
Title("Task").
|
||||
Value(&p.task.Description).
|
||||
Inline(true),
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Options(huh.NewOptions(priorityOptions...)...).
|
||||
Title("Priority").
|
||||
Value(&p.task.Priority),
|
||||
|
||||
huh.NewSelect[string]().
|
||||
Options(huh.NewOptions(projectOptions...)...).
|
||||
Title("Project").
|
||||
Value(&p.task.Project),
|
||||
|
||||
huh.NewMultiSelect[string]().
|
||||
Options(huh.NewOptions(tagOptions...)...).
|
||||
Title("Tags").
|
||||
Value(&p.task.Tags),
|
||||
|
||||
huh.NewInput().
|
||||
Title("Due").
|
||||
Value(&p.task.Due).
|
||||
Validate(validateDate).
|
||||
Inline(true),
|
||||
|
||||
huh.NewInput().
|
||||
Title("Scheduled").
|
||||
Value(&p.task.Scheduled).
|
||||
Validate(validateDate).
|
||||
Inline(true),
|
||||
|
||||
huh.NewInput().
|
||||
Title("Wait").
|
||||
Value(&p.task.Wait).
|
||||
Validate(validateDate).
|
||||
Inline(true),
|
||||
),
|
||||
).
|
||||
WithShowHelp(false).
|
||||
WithShowErrors(false)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) Init() tea.Cmd {
|
||||
return p.form.Init()
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, p.common.Keymap.Back):
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, BackCmd
|
||||
}
|
||||
}
|
||||
|
||||
f, cmd := p.form.Update(msg)
|
||||
if f, ok := f.(*huh.Form); ok {
|
||||
p.form = f
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
if p.form.State == huh.StateCompleted {
|
||||
cmds = append(cmds, p.addTaskCmd)
|
||||
model, err := p.common.PageStack.Pop()
|
||||
if err != nil {
|
||||
slog.Error("page stack empty")
|
||||
return nil, tea.Quit
|
||||
}
|
||||
return model, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
return p, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) View() string {
|
||||
return p.common.Styles.Main.Render(p.form.View())
|
||||
}
|
||||
|
||||
func (p *TaskEditorPage) addTaskCmd() tea.Msg {
|
||||
p.common.TW.AddTask(&p.task)
|
||||
return AddedTaskMsg{}
|
||||
}
|
||||
|
||||
type AddedTaskMsg struct{}
|
||||
|
||||
// TODO: move this to taskwarrior; add missing date formats
|
||||
func validateDate(s string) error {
|
||||
formats := []string{
|
||||
"2006-01-02",
|
||||
"2006-01-02T15:04",
|
||||
"20060102T150405Z",
|
||||
}
|
||||
|
||||
otherFormats := []string{
|
||||
"",
|
||||
"now",
|
||||
"today",
|
||||
"sod",
|
||||
"eod",
|
||||
"yesterday",
|
||||
"tomorrow",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
"sunday",
|
||||
"mon",
|
||||
"tue",
|
||||
"wed",
|
||||
"thu",
|
||||
"fri",
|
||||
"sat",
|
||||
"sun",
|
||||
"soy",
|
||||
"eoy",
|
||||
"soq",
|
||||
"eoq",
|
||||
"som",
|
||||
"eom",
|
||||
"socm",
|
||||
"eocm",
|
||||
"sow",
|
||||
"eow",
|
||||
"socw",
|
||||
"eocw",
|
||||
"soww",
|
||||
"eoww",
|
||||
"1st",
|
||||
"2nd",
|
||||
"3rd",
|
||||
"4th",
|
||||
"5th",
|
||||
"6th",
|
||||
"7th",
|
||||
"8th",
|
||||
"9th",
|
||||
"10th",
|
||||
"11th",
|
||||
"12th",
|
||||
"13th",
|
||||
"14th",
|
||||
"15th",
|
||||
"16th",
|
||||
"17th",
|
||||
"18th",
|
||||
"19th",
|
||||
"20th",
|
||||
"21st",
|
||||
"22nd",
|
||||
"23rd",
|
||||
"24th",
|
||||
"25th",
|
||||
"26th",
|
||||
"27th",
|
||||
"28th",
|
||||
"29th",
|
||||
"30th",
|
||||
"31st",
|
||||
}
|
||||
|
||||
for _, f := range formats {
|
||||
if _, err := time.Parse(f, s); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range otherFormats {
|
||||
if s == f {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("invalid date")
|
||||
}
|
||||
Reference in New Issue
Block a user