[WIP] Layout
This commit is contained in:
@ -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
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
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user