[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

@ -23,15 +23,6 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage {
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 {
@ -59,6 +50,10 @@ func NewContextPickerPage(common *common.Common) *ContextPickerPage {
return p
}
func (p *ContextPickerPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *ContextPickerPage) Init() tea.Cmd {
return p.form.Init()
}
@ -67,10 +62,12 @@ func (p *ContextPickerPage) 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 tea.KeyMsg:
switch {
case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
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 {
cmds = append(cmds, p.updateContextCmd)
model, err := p.common.PageStack.Pop()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
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
}
func (p *ProjectPickerPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *ProjectPickerPage) Init() tea.Cmd {
return p.form.Init()
}
@ -54,10 +58,12 @@ func (p *ProjectPickerPage) 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 tea.KeyMsg:
switch {
case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
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 {
cmds = append(cmds, p.updateProjectCmd)
model, err := p.common.PageStack.Pop()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
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()
}
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
switch msg := msg.(type) {
case tea.WindowSizeMsg:
p.taskTable.SetWidth(msg.Width - 2)
p.taskTable.SetHeight(msg.Height - 4)
p.SetSize(msg.Width, msg.Height)
case BackMsg:
p.subpageActive = false
case TaskMsg:
@ -105,9 +111,7 @@ func (p ReportPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case UpdateProjectMsg:
p.activeProject = string(msg)
cmds = append(cmds, p.getTasks())
case AddedTaskMsg:
cmds = append(cmds, p.getTasks())
case EditedTaskMsg:
case UpdatedTasksMsg:
cmds = append(cmds, p.getTasks())
case tea.KeyMsg:
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.Init()
p.subpageActive = true
p.common.PageStack.Push(p)
p.common.PushPage(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)
p.common.PushPage(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)
p.common.PushPage(p)
return p.subpage, nil
case key.Matches(msg, p.common.Keymap.Edit):
p.subpage = NewTaskEditorPage(p.common, *p.selectedTask)
p.subpage.Init()
p.subpageActive = true
p.common.PageStack.Push(p)
p.common.PushPage(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)
p.common.PushPage(p)
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)
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()])
} else {
p.selectedTask = nil
}
return p, tea.Batch(cmds...)
}
func (p ReportPage) View() string {
return p.common.Styles.Main.Render(p.taskTable.View()) + "\n"
func (p *ReportPage) View() string {
// 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) {

View File

@ -47,6 +47,10 @@ func NewReportPickerPage(common *common.Common, activeReport *taskwarrior.Report
return p
}
func (p *ReportPickerPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *ReportPickerPage) Init() tea.Cmd {
return p.form.Init()
}
@ -55,10 +59,12 @@ func (p *ReportPickerPage) 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 tea.KeyMsg:
switch {
case key.Matches(msg, p.common.Keymap.Back):
model, err := p.common.PageStack.Pop()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
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 {
cmds = append(cmds, []tea.Cmd{BackCmd, p.updateReportCmd}...)
model, err := p.common.PageStack.Pop()
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
return nil, tea.Quit

View File

@ -8,15 +8,27 @@ import (
"time"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
)
type Mode int
const (
ModeNormal Mode = iota
ModeInsert
ModeAddTag
ModeAddProject
)
type TaskEditorPage struct {
common *common.Common
task taskwarrior.Task
form *huh.Form
edit bool
common *common.Common
task taskwarrior.Task
form *huh.Form
mode Mode
statusline tea.Model
}
type TaskEditorKeys struct {
@ -31,10 +43,7 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
p := &TaskEditorPage{
common: common,
task: task,
}
if task.Uuid != "" {
p.edit = true
mode: ModeInsert,
}
if p.task.Priority == "" {
@ -53,6 +62,12 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
huh.NewInput().
Title("Task").
Value(&p.task.Description).
Validate(func(desc string) error {
if desc == "" {
return fmt.Errorf("task description is required")
}
return nil
}).
Inline(true),
huh.NewSelect[string]().
@ -90,12 +105,21 @@ func NewTaskEditorPage(common *common.Common, task taskwarrior.Task) *TaskEditor
),
).
WithShowHelp(false).
WithShowErrors(false).
WithShowErrors(true).
// use styles from common
WithHeight(30).
WithWidth(50).
WithTheme(p.common.Styles.Form)
p.statusline = NewStatusLine(common, p.mode)
return p
}
func (p *TaskEditorPage) SetSize(width, height int) {
p.common.SetSize(width, height)
}
func (p *TaskEditorPage) Init() tea.Cmd {
return p.form.Init()
}
@ -104,31 +128,61 @@ 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
case SwitchModeMsg:
switch Mode(msg) {
case ModeNormal:
p.mode = ModeNormal
case ModeInsert:
p.mode = ModeInsert
}
}
f, cmd := p.form.Update(msg)
if f, ok := f.(*huh.Form); ok {
p.form = f
cmds = append(cmds, cmd)
switch p.mode {
case ModeNormal:
switch msg := msg.(type) {
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.edit {
cmds = append(cmds, p.editTaskCmd)
} else {
cmds = append(cmds, p.addTaskCmd)
}
model, err := p.common.PageStack.Pop()
cmds = append(cmds, p.updateTasksCmd)
model, err := p.common.PopPage()
if err != nil {
slog.Error("page stack empty")
return nil, tea.Quit
@ -140,22 +194,87 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
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)
return AddedTaskMsg{}
return UpdatedTasksMsg{}
}
func (p *TaskEditorPage) editTaskCmd() tea.Msg {
p.common.TW.ModifyTask(&p.task)
return EditedTaskMsg{}
func (p *TaskEditorPage) switchModeCmd(mode Mode) tea.Cmd {
return func() tea.Msg {
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
func validateDate(s string) error {