[WIP] Layout

This commit is contained in:
Martin Pander
2024-05-22 16:20:57 +02:00
parent a23b76c3c9
commit 14dbfc406d
15 changed files with 378 additions and 160 deletions

View File

@ -4,24 +4,47 @@ import (
"context" "context"
"tasksquire/taskwarrior" "tasksquire/taskwarrior"
tea "github.com/charmbracelet/bubbletea"
) )
type Common struct { type Common struct {
Ctx context.Context Ctx context.Context
PageStack *Stack[tea.Model]
TW taskwarrior.TaskWarrior TW taskwarrior.TaskWarrior
Keymap *Keymap Keymap *Keymap
Styles *Styles Styles *Styles
pageStack *Stack[Component]
width int
height int
} }
func NewCommon(ctx context.Context, tw taskwarrior.TaskWarrior) *Common { func NewCommon(ctx context.Context, tw taskwarrior.TaskWarrior) *Common {
return &Common{ return &Common{
Ctx: ctx, Ctx: ctx,
PageStack: NewStack[tea.Model](),
TW: tw, TW: tw,
Keymap: NewKeymap(), Keymap: NewKeymap(),
Styles: NewStyles(tw.GetConfig()), Styles: NewStyles(tw.GetConfig()),
pageStack: NewStack[Component](),
} }
} }
func (c *Common) SetSize(width, height int) {
c.width = width
c.height = height
}
func (c *Common) Width() int {
return c.width
}
func (c *Common) Height() int {
return c.height
}
func (c *Common) PushPage(page Component) {
c.pageStack.Push(page)
}
func (c *Common) PopPage() (Component, error) {
return c.pageStack.Pop()
}

9
common/component.go Normal file
View File

@ -0,0 +1,9 @@
package common
import tea "github.com/charmbracelet/bubbletea"
type Component interface {
tea.Model
//help.KeyMap
SetSize(width int, height int)
}

View File

@ -8,12 +8,19 @@ import (
type Keymap struct { type Keymap struct {
Quit key.Binding Quit key.Binding
Back key.Binding Back key.Binding
Ok key.Binding
Input key.Binding
Add key.Binding Add key.Binding
Edit key.Binding Edit key.Binding
Up key.Binding
Down key.Binding
Left key.Binding
Right key.Binding
SetReport key.Binding SetReport key.Binding
SetContext key.Binding SetContext key.Binding
SetProject key.Binding SetProject key.Binding
Select key.Binding Select key.Binding
Insert key.Binding
} }
// NewKeymap creates a new Keymap. // NewKeymap creates a new Keymap.
@ -29,6 +36,16 @@ func NewKeymap() *Keymap {
key.WithHelp("esc", "Back"), key.WithHelp("esc", "Back"),
), ),
Ok: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "Ok"),
),
Input: key.NewBinding(
key.WithKeys(":"),
key.WithHelp(":", "Input"),
),
Add: key.NewBinding( Add: key.NewBinding(
key.WithKeys("a"), key.WithKeys("a"),
key.WithHelp("a", "Add new task"), key.WithHelp("a", "Add new task"),
@ -39,6 +56,26 @@ func NewKeymap() *Keymap {
key.WithHelp("e", "Edit task"), key.WithHelp("e", "Edit task"),
), ),
Up: key.NewBinding(
key.WithKeys("k", "up"),
key.WithHelp("↑/k", "Up"),
),
Down: key.NewBinding(
key.WithKeys("j", "down"),
key.WithHelp("↓/j", "Down"),
),
Left: key.NewBinding(
key.WithKeys("h", "left"),
key.WithHelp("←/h", "Left"),
),
Right: key.NewBinding(
key.WithKeys("l", "right"),
key.WithHelp("→/l", "Right"),
),
SetReport: key.NewBinding( SetReport: key.NewBinding(
key.WithKeys("r"), key.WithKeys("r"),
key.WithHelp("r", "Set report"), key.WithHelp("r", "Set report"),
@ -58,5 +95,10 @@ func NewKeymap() *Keymap {
key.WithKeys("enter"), key.WithKeys("enter"),
key.WithHelp("enter", "Select"), key.WithHelp("enter", "Select"),
), ),
Insert: key.NewBinding(
key.WithKeys("i"),
key.WithHelp("insert", "Insert mode"),
),
} }
} }

View File

@ -72,7 +72,6 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
styles.Main = lipgloss.NewStyle() styles.Main = lipgloss.NewStyle()
formTheme := huh.ThemeBase() formTheme := huh.ThemeBase()
formTheme.Focused.Card = formTheme.Focused.Card.BorderStyle(lipgloss.RoundedBorder()).BorderBottom(true).BorderTop(true)
formTheme.Focused.Title = formTheme.Focused.Title.Bold(true) formTheme.Focused.Title = formTheme.Focused.Title.Bold(true)
formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("> ") formTheme.Focused.SelectSelector = formTheme.Focused.SelectSelector.SetString("> ")
formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true) formTheme.Focused.SelectedOption = formTheme.Focused.SelectedOption.Bold(true)

View File

@ -6,7 +6,7 @@ import (
"os" "os"
"tasksquire/common" "tasksquire/common"
"tasksquire/model" "tasksquire/pages"
"tasksquire/taskwarrior" "tasksquire/taskwarrior"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
@ -32,7 +32,7 @@ func main() {
// slog.Error("Uh oh:", err) // slog.Error("Uh oh:", err)
// os.Exit(1) // os.Exit(1)
// } // }
m := model.NewMainModel(common) m := pages.NewMainPage(common)
if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil { if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
fmt.Println("Error running program:", err) fmt.Println("Error running program:", err)

View File

@ -1,53 +0,0 @@
package model
import (
tea "github.com/charmbracelet/bubbletea"
"tasksquire/common"
"tasksquire/pages"
"tasksquire/taskwarrior"
)
type MainModel struct {
common *common.Common
selectedPage tea.Model
selectedTask *taskwarrior.Task
selectedReport *taskwarrior.Report
selectedContext *taskwarrior.Context
}
func NewMainModel(common *common.Common) *MainModel {
m := &MainModel{
common: common,
selectedReport: common.TW.GetReport("next"),
selectedContext: common.TW.GetActiveContext(),
}
m.selectedPage = pages.NewReportPage(common, m.selectedReport)
return m
}
func (m MainModel) Init() tea.Cmd {
return m.selectedPage.Init()
}
func (m MainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
// switch msg := msg.(type) {
// case tea.KeyMsg:
// switch {
// case key.Matches(msg, m.common.Keymap.Add):
// case key.Matches(msg, m.common.Keymap.Edit):
// }
// }
m.selectedPage, cmd = m.selectedPage.Update(msg)
return m, cmd
}
func (m MainModel) View() string {
return m.selectedPage.View()
}

View File

@ -23,15 +23,6 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage {
contexts: common.TW.GetContexts(), 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 selected := common.TW.GetActiveContext().Name
options := make([]string, 0) options := make([]string, 0)
for _, c := range p.contexts { for _, c := range p.contexts {
@ -59,6 +50,10 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage {
return p return p
} }
func (p *ContextPickerPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *ContextPickerPage) Init() tea.Cmd { func (p *ContextPickerPage) Init() tea.Cmd {
return p.form.Init() return p.form.Init()
} }
@ -67,10 +62,12 @@ func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg:
p.SetSize(msg.Width, msg.Height)
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, p.common.Keymap.Back): case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit
@ -87,7 +84,7 @@ func (p *ContextPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if p.form.State == huh.StateCompleted { if p.form.State == huh.StateCompleted {
cmds = append(cmds, p.updateContextCmd) cmds = append(cmds, p.updateContextCmd)
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit

45
pages/main.go Normal file
View File

@ -0,0 +1,45 @@
package pages
import (
tea "github.com/charmbracelet/bubbletea"
"tasksquire/common"
)
type MainPage struct {
common *common.Common
activePage common.Component
}
func NewMainPage(common *common.Common) *MainPage {
m := &MainPage{
common: common,
}
m.activePage = NewReportPage(common, common.TW.GetReport(common.TW.GetConfig().Get("uda.tasksquire.report.default")))
return m
}
func (m *MainPage) Init() tea.Cmd {
return m.activePage.Init()
}
func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.common.SetSize(msg.Width, msg.Height)
}
activePage, cmd := m.activePage.Update(msg)
m.activePage = activePage.(common.Component)
return m, cmd
}
func (m *MainPage) View() string {
return m.activePage.View()
}

View File

@ -46,6 +46,10 @@ func NewProjectPickerPage(common *common.Common, activeProject string) *ProjectP
return p return p
} }
func (p *ProjectPickerPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *ProjectPickerPage) Init() tea.Cmd { func (p *ProjectPickerPage) Init() tea.Cmd {
return p.form.Init() return p.form.Init()
} }
@ -54,10 +58,12 @@ func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg:
p.SetSize(msg.Width, msg.Height)
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, p.common.Keymap.Back): case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit
@ -74,7 +80,7 @@ func (p *ProjectPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if p.form.State == huh.StateCompleted { if p.form.State == huh.StateCompleted {
cmds = append(cmds, p.updateProjectCmd) cmds = append(cmds, p.updateProjectCmd)
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit

View File

@ -80,16 +80,22 @@ func NewReportPage(com *common.Common, report *taskwarrior.Report) *ReportPage {
} }
} }
func (p ReportPage) Init() tea.Cmd { func (p *ReportPage) SetSize(width int, height int) {
p.common.SetSize(width, height)
p.taskTable.SetWidth(width - 2)
p.taskTable.SetHeight(height - 4)
}
func (p *ReportPage) Init() tea.Cmd {
return p.getTasks() return p.getTasks()
} }
func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (p *ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
p.taskTable.SetWidth(msg.Width - 2) p.SetSize(msg.Width, msg.Height)
p.taskTable.SetHeight(msg.Height - 4)
case BackMsg: case BackMsg:
p.subpageActive = false p.subpageActive = false
case TaskMsg: case TaskMsg:
@ -105,9 +111,7 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case UpdateProjectMsg: case UpdateProjectMsg:
p.activeProject = string(msg) p.activeProject = string(msg)
cmds = append(cmds, p.getTasks()) cmds = append(cmds, p.getTasks())
case AddedTaskMsg: case UpdatedTasksMsg:
cmds = append(cmds, p.getTasks())
case EditedTaskMsg:
cmds = append(cmds, p.getTasks()) cmds = append(cmds, p.getTasks())
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
@ -117,31 +121,31 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.subpage = NewReportPickerPage(p.common, p.activeReport) p.subpage = NewReportPickerPage(p.common, p.activeReport)
p.subpage.Init() p.subpage.Init()
p.subpageActive = true p.subpageActive = true
p.common.PageStack.Push(p) p.common.PushPage(p)
return p.subpage, nil return p.subpage, nil
case key.Matches(msg, p.common.Keymap.SetContext): case key.Matches(msg, p.common.Keymap.SetContext):
p.subpage = NewContextPickerPage(p.common) p.subpage = NewContextPickerPage(p.common)
p.subpage.Init() p.subpage.Init()
p.subpageActive = true p.subpageActive = true
p.common.PageStack.Push(p) p.common.PushPage(p)
return p.subpage, nil return p.subpage, nil
case key.Matches(msg, p.common.Keymap.Add): case key.Matches(msg, p.common.Keymap.Add):
p.subpage = NewTaskEditorPage(p.common, taskwarrior.Task{}) p.subpage = NewTaskEditorPage(p.common, taskwarrior.Task{})
p.subpage.Init() p.subpage.Init()
p.subpageActive = true p.subpageActive = true
p.common.PageStack.Push(p) p.common.PushPage(p)
return p.subpage, nil return p.subpage, nil
case key.Matches(msg, p.common.Keymap.Edit): case key.Matches(msg, p.common.Keymap.Edit):
p.subpage = NewTaskEditorPage(p.common, *p.selectedTask) p.subpage = NewTaskEditorPage(p.common, *p.selectedTask)
p.subpage.Init() p.subpage.Init()
p.subpageActive = true p.subpageActive = true
p.common.PageStack.Push(p) p.common.PushPage(p)
return p.subpage, nil return p.subpage, nil
case key.Matches(msg, p.common.Keymap.SetProject): case key.Matches(msg, p.common.Keymap.SetProject):
p.subpage = NewProjectPickerPage(p.common, p.activeProject) p.subpage = NewProjectPickerPage(p.common, p.activeProject)
p.subpage.Init() p.subpage.Init()
p.subpageActive = true p.subpageActive = true
p.common.PageStack.Push(p) p.common.PushPage(p)
return p.subpage, nil return p.subpage, nil
} }
} }
@ -150,15 +154,21 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
p.taskTable, cmd = p.taskTable.Update(msg) p.taskTable, cmd = p.taskTable.Update(msg)
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
if p.tasks != nil { if p.tasks != nil && len(p.tasks) > 0 {
p.selectedTask = (*taskwarrior.Task)(p.tasks[p.taskTable.Cursor()]) p.selectedTask = (*taskwarrior.Task)(p.tasks[p.taskTable.Cursor()])
} else {
p.selectedTask = nil
} }
return p, tea.Batch(cmds...) return p, tea.Batch(cmds...)
} }
func (p ReportPage) View() string { func (p *ReportPage) View() string {
return p.common.Styles.Main.Render(p.taskTable.View()) + "\n" // return p.common.Styles.Main.Render(p.taskTable.View()) + "\n"
if p.tasks == nil || len(p.tasks) == 0 {
return p.common.Styles.Main.Render("No tasks found")
}
return p.common.Styles.Main.Render(p.taskTable.View())
} }
func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) { func (p *ReportPage) populateTaskTable(tasks taskwarrior.Tasks) {

View File

@ -47,6 +47,10 @@ func NewReportPickerPage(common *common.Common, activeReport *taskwarrior.Report
return p return p
} }
func (p *ReportPickerPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *ReportPickerPage) Init() tea.Cmd { func (p *ReportPickerPage) Init() tea.Cmd {
return p.form.Init() return p.form.Init()
} }
@ -55,10 +59,12 @@ func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg:
p.SetSize(msg.Width, msg.Height)
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, p.common.Keymap.Back): case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit
@ -75,7 +81,7 @@ func (p *ReportPickerPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if p.form.State == huh.StateCompleted { if p.form.State == huh.StateCompleted {
cmds = append(cmds, []tea.Cmd{BackCmd, p.updateReportCmd}...) cmds = append(cmds, []tea.Cmd{BackCmd, p.updateReportCmd}...)
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit

View File

@ -8,15 +8,27 @@ import (
"time" "time"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh" "github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
)
type Mode int
const (
ModeNormal Mode = iota
ModeInsert
ModeAddTag
ModeAddProject
) )
type TaskEditorPage struct { type TaskEditorPage struct {
common *common.Common common *common.Common
task taskwarrior.Task task taskwarrior.Task
form *huh.Form form *huh.Form
edit bool mode Mode
statusline tea.Model
} }
type TaskEditorKeys struct { type TaskEditorKeys struct {
@ -31,10 +43,7 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
p := &TaskEditorPage{ p := &TaskEditorPage{
common: common, common: common,
task: task, task: task,
} mode: ModeInsert,
if task.Uuid != "" {
p.edit = true
} }
if p.task.Priority == "" { if p.task.Priority == "" {
@ -53,6 +62,12 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
huh.NewInput(). huh.NewInput().
Title("Task"). Title("Task").
Value(&p.task.Description). Value(&p.task.Description).
Validate(func(desc string) error {
if desc == "" {
return fmt.Errorf("task description is required")
}
return nil
}).
Inline(true), Inline(true),
huh.NewSelect[string](). huh.NewSelect[string]().
@ -90,12 +105,21 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
), ),
). ).
WithShowHelp(false). WithShowHelp(false).
WithShowErrors(false). WithShowErrors(true).
// use styles from common
WithHeight(30).
WithWidth(50).
WithTheme(p.common.Styles.Form) WithTheme(p.common.Styles.Form)
p.statusline = NewStatusLine(common, p.mode)
return p return p
} }
func (p *TaskEditorPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *TaskEditorPage) Init() tea.Cmd { func (p *TaskEditorPage) Init() tea.Cmd {
return p.form.Init() return p.form.Init()
} }
@ -103,16 +127,44 @@ func (p *TaskEditorPage) Init() tea.Cmd {
func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var cmds []tea.Cmd
switch msg := msg.(type) {
case SwitchModeMsg:
switch Mode(msg) {
case ModeNormal:
p.mode = ModeNormal
case ModeInsert:
p.mode = ModeInsert
}
}
switch p.mode {
case ModeNormal:
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, p.common.Keymap.Back): case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop() model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit
} }
return model, BackCmd return model, BackCmd
case key.Matches(msg, p.common.Keymap.Insert):
return p, p.switchModeCmd(ModeInsert)
case key.Matches(msg, p.common.Keymap.Ok):
p.form.State = huh.StateCompleted
case key.Matches(msg, p.common.Keymap.Down):
p.form.NextField()
case key.Matches(msg, p.common.Keymap.Up):
p.form.PrevField()
}
}
case ModeInsert:
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, p.common.Keymap.Back):
return p, p.switchModeCmd(ModeNormal)
} }
} }
@ -122,13 +174,15 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, cmd) cmds = append(cmds, cmd)
} }
if p.form.State == huh.StateCompleted {
if p.edit {
cmds = append(cmds, p.editTaskCmd)
} else {
cmds = append(cmds, p.addTaskCmd)
} }
model, err := p.common.PageStack.Pop()
var cmd tea.Cmd
p.statusline, cmd = p.statusline.Update(msg)
cmds = append(cmds, cmd)
if p.form.State == huh.StateCompleted {
cmds = append(cmds, p.updateTasksCmd)
model, err := p.common.PopPage()
if err != nil { if err != nil {
slog.Error("page stack empty") slog.Error("page stack empty")
return nil, tea.Quit return nil, tea.Quit
@ -140,22 +194,87 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
func (p *TaskEditorPage) View() string { func (p *TaskEditorPage) View() string {
return p.common.Styles.Main.Render(p.form.View()) return lipgloss.JoinVertical(
lipgloss.Left,
// lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceBackground(p.common.Styles.Warning.GetForeground())),
lipgloss.Place(p.common.Width(), p.common.Height()-1, 0.5, 0.5, p.form.View(), lipgloss.WithWhitespaceChars(".")),
p.statusline.View(),
)
} }
func (p *TaskEditorPage) addTaskCmd() tea.Msg { func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
p.common.TW.AddTask(&p.task) p.common.TW.AddTask(&p.task)
return AddedTaskMsg{} return UpdatedTasksMsg{}
} }
func (p *TaskEditorPage) editTaskCmd() tea.Msg { func (p *TaskEditorPage) switchModeCmd(mode Mode) tea.Cmd {
p.common.TW.ModifyTask(&p.task) return func() tea.Msg {
return EditedTaskMsg{} return SwitchModeMsg(mode)
}
} }
type AddedTaskMsg struct{} type UpdatedTasksMsg struct{}
type SwitchModeMsg Mode
type EditedTaskMsg struct{} type StatusLine struct {
common *common.Common
mode Mode
input textinput.Model
}
func NewStatusLine(common *common.Common, mode Mode) *StatusLine {
input := textinput.New()
input.Placeholder = ""
input.Prompt = ""
input.Blur()
return &StatusLine{
input: textinput.New(),
common: common,
mode: mode,
}
}
func (s *StatusLine) Init() tea.Cmd {
s.input.Blur()
return nil
}
func (s *StatusLine) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case SwitchModeMsg:
s.mode = Mode(msg)
switch s.mode {
case ModeNormal:
s.input.Blur()
case ModeInsert:
s.input.Focus()
}
case tea.KeyMsg:
switch {
case key.Matches(msg, s.common.Keymap.Back):
s.input.Blur()
case key.Matches(msg, s.common.Keymap.Input):
s.input.Focus()
}
}
s.input, cmd = s.input.Update(msg)
return s, cmd
}
func (s *StatusLine) View() string {
var mode string
switch s.mode {
case ModeNormal:
mode = s.common.Styles.Main.Render("NORMAL")
case ModeInsert:
mode = s.common.Styles.Active.Inline(true).Render("INSERT")
}
return lipgloss.JoinHorizontal(lipgloss.Left, mode, s.input.View())
}
// TODO: move this to taskwarrior; add missing date formats // TODO: move this to taskwarrior; add missing date formats
func validateDate(s string) error { func validateDate(s string) error {

View File

@ -10,9 +10,23 @@ type TWConfig struct {
config map[string]string config map[string]string
} }
var (
defaultConfig = map[string]string{
"uda.tasksquire.report.default": "next",
}
)
func NewConfig(config []string) *TWConfig { func NewConfig(config []string) *TWConfig {
cfg := parseConfig(config)
for key, value := range defaultConfig {
if _, ok := cfg[key]; !ok {
cfg[key] = value
}
}
return &TWConfig{ return &TWConfig{
config: parseConfig(config), config: cfg,
} }
} }

View File

@ -10,25 +10,25 @@ import (
) )
type Task struct { type Task struct {
Id int64 `json:"id"` Id int64 `json:"id,omitempty"`
Uuid string `json:"uuid"` Uuid string `json:"uuid,omitempty"`
Description string `json:"description"` Description string `json:"description,omitempty"`
Project string `json:"project"` Project string `json:"project,omitempty"`
Priority string `json:"priority"` Priority string `json:"priority,omitempty"`
Status string `json:"status"` Status string `json:"status,omitempty"`
Tags []string `json:"tags"` Tags []string `json:"tags,omitempty"`
Depends []string `json:"depends"` Depends []string `json:"depends,omitempty"`
Urgency float32 `json:"urgency"` Urgency float32 `json:"urgency,omitempty"`
Parent string `json:"parent"` Parent string `json:"parent,omitempty"`
Due string `json:"due"` Due string `json:"due,omitempty"`
Wait string `json:"wait"` Wait string `json:"wait,omitempty"`
Scheduled string `json:"scheduled"` Scheduled string `json:"scheduled,omitempty"`
Until string `json:"until"` Until string `json:"until,omitempty"`
Start string `json:"start"` Start string `json:"start,omitempty"`
End string `json:"end"` End string `json:"end,omitempty"`
Entry string `json:"entry"` Entry string `json:"entry,omitempty"`
Modified string `json:"modified"` Modified string `json:"modified,omitempty"`
Recur string `json:"recur"` Recur string `json:"recur,omitempty"`
} }
func (t *Task) GetString(fieldWFormat string) string { func (t *Task) GetString(fieldWFormat string) string {

View File

@ -1,6 +1,7 @@
package taskwarrior package taskwarrior
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log/slog" "log/slog"
@ -87,7 +88,7 @@ type TaskWarrior interface {
GetTasks(report *Report, filter ...string) Tasks GetTasks(report *Report, filter ...string) Tasks
AddTask(task *Task) error AddTask(task *Task) error
ModifyTask(task *Task) ImportTask(task *Task)
} }
type TaskSquire struct { type TaskSquire struct {
@ -325,20 +326,20 @@ func (ts *TaskSquire) AddTask(task *Task) error {
} }
// TODO error handling // TODO error handling
func (ts *TaskSquire) ModifyTask(task *Task) { func (ts *TaskSquire) ImportTask(task *Task) {
ts.mutex.Lock() ts.mutex.Lock()
defer ts.mutex.Unlock() defer ts.mutex.Unlock()
jsonStr, err := json.Marshal(Tasks{task}) tasks, err := json.Marshal(Tasks{task})
if err != nil { if err != nil {
slog.Error("Failed marshalling task:", err) slog.Error("Failed marshalling task:", err)
} }
cmd := exec.Command(twBinary, append([]string{"echo", string(jsonStr), "|"}, append(ts.defaultArgs, []string{"import", "-"}...)...)...) cmd := exec.Command(twBinary, append(ts.defaultArgs, []string{"import", "-"}...)...)
cmd.Stdin = bytes.NewBuffer(tasks)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
strOut := string(out)
if err != nil { if err != nil {
slog.Error("Failed modifying task:", err, strOut) slog.Error("Failed modifying task:", err, string(out))
} }
} }