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") }