[WIP] Layout
This commit is contained in:
@ -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
9
common/component.go
Normal 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)
|
||||||
|
}
|
||||||
@ -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"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
4
main.go
4
main.go
@ -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)
|
||||||
|
|||||||
@ -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()
|
|
||||||
}
|
|
||||||
@ -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
45
pages/main.go
Normal 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()
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
@ -104,31 +128,61 @@ func (p *TaskEditorPage) 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.KeyMsg:
|
case SwitchModeMsg:
|
||||||
switch {
|
switch Mode(msg) {
|
||||||
case key.Matches(msg, p.common.Keymap.Back):
|
case ModeNormal:
|
||||||
model, err := p.common.PageStack.Pop()
|
p.mode = ModeNormal
|
||||||
if err != nil {
|
case ModeInsert:
|
||||||
slog.Error("page stack empty")
|
p.mode = ModeInsert
|
||||||
return nil, tea.Quit
|
|
||||||
}
|
|
||||||
return model, BackCmd
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f, cmd := p.form.Update(msg)
|
switch p.mode {
|
||||||
if f, ok := f.(*huh.Form); ok {
|
case ModeNormal:
|
||||||
p.form = f
|
switch msg := msg.(type) {
|
||||||
cmds = append(cmds, cmd)
|
case tea.KeyMsg:
|
||||||
|
switch {
|
||||||
|
case key.Matches(msg, p.common.Keymap.Back):
|
||||||
|
model, err := p.common.PopPage()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("page stack empty")
|
||||||
|
return nil, tea.Quit
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, cmd := p.form.Update(msg)
|
||||||
|
if f, ok := f.(*huh.Form); ok {
|
||||||
|
p.form = f
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cmd tea.Cmd
|
||||||
|
p.statusline, cmd = p.statusline.Update(msg)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
if p.form.State == huh.StateCompleted {
|
if p.form.State == huh.StateCompleted {
|
||||||
if p.edit {
|
cmds = append(cmds, p.updateTasksCmd)
|
||||||
cmds = append(cmds, p.editTaskCmd)
|
model, err := p.common.PopPage()
|
||||||
} else {
|
|
||||||
cmds = append(cmds, p.addTaskCmd)
|
|
||||||
}
|
|
||||||
model, err := p.common.PageStack.Pop()
|
|
||||||
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 {
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user