1060 lines
24 KiB
Go
1060 lines
24 KiB
Go
package pages
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"strings"
|
|
"tasksquire/common"
|
|
"time"
|
|
|
|
"tasksquire/taskwarrior"
|
|
|
|
"github.com/charmbracelet/bubbles/key"
|
|
"github.com/charmbracelet/bubbles/list"
|
|
"github.com/charmbracelet/bubbles/viewport"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/glamour"
|
|
"github.com/charmbracelet/huh"
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
type mode int
|
|
|
|
const (
|
|
modeNormal mode = iota
|
|
modeInsert
|
|
)
|
|
|
|
type TaskEditorPage struct {
|
|
common *common.Common
|
|
task taskwarrior.Task
|
|
|
|
colWidth int
|
|
colHeight int
|
|
|
|
mode mode
|
|
|
|
columnCursor int
|
|
|
|
area int
|
|
areaPicker *areaPicker
|
|
areas []area
|
|
}
|
|
|
|
func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage {
|
|
p := TaskEditorPage{
|
|
common: com,
|
|
task: task,
|
|
}
|
|
|
|
if p.task.Project == "" {
|
|
p.task.Project = "(none)"
|
|
}
|
|
|
|
tagOptions := p.common.TW.GetTags()
|
|
|
|
p.areas = []area{
|
|
NewTaskEdit(p.common, &p.task),
|
|
NewTagEdit(p.common, &p.task.Tags, tagOptions),
|
|
NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until),
|
|
NewDetailsEdit(p.common, &p.task),
|
|
}
|
|
|
|
// p.areaList = NewAreaList(common, areaItems)
|
|
// p.selectedArea = areaTask
|
|
// p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...)
|
|
|
|
p.areaPicker = NewAreaPicker(com, []string{"Task", "Tags", "Dates"})
|
|
|
|
p.columnCursor = 1
|
|
if p.task.Uuid == "" {
|
|
p.mode = modeInsert
|
|
} else {
|
|
p.mode = modeNormal
|
|
}
|
|
|
|
p.SetSize(com.Width(), com.Height())
|
|
|
|
return &p
|
|
}
|
|
|
|
func (p *TaskEditorPage) SetSize(width, height int) {
|
|
p.common.SetSize(width, height)
|
|
|
|
if width >= 70 {
|
|
p.colWidth = 70 - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
|
} else {
|
|
p.colWidth = width - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
|
}
|
|
|
|
if height >= 40 {
|
|
p.colHeight = 40 - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
|
} else {
|
|
p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
|
}
|
|
}
|
|
|
|
func (p *TaskEditorPage) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case tea.WindowSizeMsg:
|
|
p.SetSize(msg.Width, msg.Height)
|
|
case changeAreaMsg:
|
|
p.area = int(msg)
|
|
case changeModeMsg:
|
|
p.mode = mode(msg)
|
|
case prevColumnMsg:
|
|
p.columnCursor--
|
|
if p.columnCursor < 0 {
|
|
p.columnCursor = len(p.areas) - 1
|
|
}
|
|
case nextColumnMsg:
|
|
p.columnCursor++
|
|
if p.columnCursor > len(p.areas)-1 {
|
|
p.columnCursor = 0
|
|
}
|
|
case prevAreaMsg:
|
|
p.area--
|
|
if p.area < 0 {
|
|
p.area = len(p.areas) - 1
|
|
}
|
|
p.areas[p.area].SetCursor(-1)
|
|
case nextAreaMsg:
|
|
p.area++
|
|
if p.area > len(p.areas)-1 {
|
|
p.area = 0
|
|
}
|
|
p.areas[p.area].SetCursor(0)
|
|
}
|
|
|
|
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, changeMode(modeInsert)
|
|
case key.Matches(msg, p.common.Keymap.Ok):
|
|
model, err := p.common.PopPage()
|
|
if err != nil {
|
|
slog.Error("page stack empty")
|
|
return nil, tea.Quit
|
|
}
|
|
return model, p.updateTasksCmd
|
|
case key.Matches(msg, p.common.Keymap.Left):
|
|
return p, prevColumn()
|
|
case key.Matches(msg, p.common.Keymap.Right):
|
|
return p, nextColumn()
|
|
case key.Matches(msg, p.common.Keymap.Up):
|
|
if p.columnCursor == 0 {
|
|
picker, cmd := p.areaPicker.Update(msg)
|
|
p.areaPicker = picker.(*areaPicker)
|
|
return p, cmd
|
|
} else {
|
|
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
|
p.areas[p.area] = model.(area)
|
|
return p, cmd
|
|
}
|
|
case key.Matches(msg, p.common.Keymap.Down):
|
|
if p.columnCursor == 0 {
|
|
picker, cmd := p.areaPicker.Update(msg)
|
|
p.areaPicker = picker.(*areaPicker)
|
|
return p, cmd
|
|
} else {
|
|
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
|
p.areas[p.area] = model.(area)
|
|
return p, cmd
|
|
}
|
|
}
|
|
}
|
|
|
|
// var cmd tea.Cmd
|
|
// if p.columnCursor == 0 {
|
|
// p., cmd = p.areaList.Update(msg)
|
|
// p.selectedArea = p.areaList.(areaList).Area()
|
|
// cmds = append(cmds, cmd)
|
|
// } else {
|
|
// p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg)
|
|
// cmds = append(cmds, cmd)
|
|
// }
|
|
case modeInsert:
|
|
switch msg := msg.(type) {
|
|
case tea.KeyMsg:
|
|
switch {
|
|
case key.Matches(msg, p.common.Keymap.Back):
|
|
return p, changeMode(modeNormal)
|
|
case key.Matches(msg, p.common.Keymap.Prev):
|
|
if p.columnCursor == 0 {
|
|
picker, cmd := p.areaPicker.Update(msg)
|
|
p.areaPicker = picker.(*areaPicker)
|
|
return p, cmd
|
|
} else {
|
|
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
|
p.areas[p.area] = model.(area)
|
|
return p, cmd
|
|
}
|
|
case key.Matches(msg, p.common.Keymap.Next):
|
|
if p.columnCursor == 0 {
|
|
picker, cmd := p.areaPicker.Update(msg)
|
|
p.areaPicker = picker.(*areaPicker)
|
|
return p, cmd
|
|
} else {
|
|
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
|
p.areas[p.area] = model.(area)
|
|
return p, cmd
|
|
}
|
|
case key.Matches(msg, p.common.Keymap.Ok):
|
|
model, cmd := p.areas[p.area].Update(msg)
|
|
p.areas[p.area] = model.(area)
|
|
return p, tea.Batch(cmd, nextField())
|
|
}
|
|
}
|
|
|
|
if p.columnCursor == 0 {
|
|
picker, cmd := p.areaPicker.Update(msg)
|
|
p.areaPicker = picker.(*areaPicker)
|
|
return p, cmd
|
|
} else {
|
|
model, cmd := p.areas[p.area].Update(msg)
|
|
p.areas[p.area] = model.(area)
|
|
return p, cmd
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func (p *TaskEditorPage) View() string {
|
|
var focusedStyle, blurredStyle lipgloss.Style
|
|
if p.mode == modeInsert {
|
|
focusedStyle = p.common.Styles.ColumnInsert.Width(p.colWidth).Height(p.colHeight)
|
|
} else {
|
|
focusedStyle = p.common.Styles.ColumnFocused.Width(p.colWidth).Height(p.colHeight)
|
|
}
|
|
blurredStyle = p.common.Styles.ColumnBlurred.Width(p.colWidth).Height(p.colHeight)
|
|
// var picker, area string
|
|
var area string
|
|
if p.columnCursor == 0 {
|
|
// picker = focusedStyle.Render(p.areaPicker.View())
|
|
area = blurredStyle.Render(p.areas[p.area].View())
|
|
} else {
|
|
// picker = blurredStyle.Render(p.areaPicker.View())
|
|
area = focusedStyle.Render(p.areas[p.area].View())
|
|
|
|
}
|
|
|
|
if p.task.Uuid != "" {
|
|
area = lipgloss.JoinHorizontal(
|
|
lipgloss.Top,
|
|
area,
|
|
p.common.Styles.ColumnFocused.Render(p.common.TW.GetInformation(&p.task)),
|
|
)
|
|
|
|
}
|
|
|
|
// return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, lipgloss.JoinHorizontal(
|
|
// lipgloss.Center,
|
|
// picker,
|
|
// area,
|
|
// ))
|
|
return lipgloss.Place(p.common.Width(), p.common.Height(), lipgloss.Center, lipgloss.Center, area)
|
|
}
|
|
|
|
type area interface {
|
|
tea.Model
|
|
SetCursor(c int)
|
|
}
|
|
|
|
type areaPicker struct {
|
|
common *common.Common
|
|
list list.Model
|
|
}
|
|
|
|
type item string
|
|
|
|
func (i item) Title() string { return string(i) }
|
|
func (i item) Description() string { return "test" }
|
|
func (i item) FilterValue() string { return "" }
|
|
|
|
func NewAreaPicker(common *common.Common, items []string) *areaPicker {
|
|
listItems := make([]list.Item, len(items))
|
|
for i, itm := range items {
|
|
listItems[i] = item(itm)
|
|
}
|
|
|
|
list := list.New(listItems, list.DefaultDelegate{}, 20, 50)
|
|
list.SetFilteringEnabled(false)
|
|
list.SetShowStatusBar(false)
|
|
list.SetShowHelp(false)
|
|
list.SetShowPagination(false)
|
|
list.SetShowTitle(false)
|
|
|
|
return &areaPicker{
|
|
common: common,
|
|
list: list,
|
|
}
|
|
}
|
|
|
|
func (a *areaPicker) Area() int {
|
|
// switch a.list.SelectedItem() {
|
|
// case item("Task"):
|
|
// return areaTask
|
|
// case item("Tags"):
|
|
// return areaTags
|
|
// case item("Dates"):
|
|
// return areaTime
|
|
// default:
|
|
// return areaTask
|
|
// }
|
|
return 0
|
|
}
|
|
|
|
func (a *areaPicker) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (a *areaPicker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
var cmds []tea.Cmd
|
|
var cmd tea.Cmd
|
|
cursor := a.list.Cursor()
|
|
// switch msg.(type) {
|
|
// case nextFieldMsg:
|
|
// a.list, cmd = a.list.Update(a.list.KeyMap.CursorDown)
|
|
// case prevFieldMsg:
|
|
// a.list, cmd = a.list.Update(a.list.KeyMap.CursorUp)
|
|
// }
|
|
a.list, cmd = a.list.Update(msg)
|
|
cmds = append(cmds, cmd)
|
|
if cursor != a.list.Cursor() {
|
|
cmds = append(cmds, changeArea(a.Area()))
|
|
}
|
|
|
|
return a, tea.Batch(cmds...)
|
|
}
|
|
|
|
func (a *areaPicker) View() string {
|
|
return a.list.View()
|
|
}
|
|
|
|
type taskEdit struct {
|
|
common *common.Common
|
|
fields []huh.Field
|
|
cursor int
|
|
|
|
newProjectName *string
|
|
newAnnotation *string
|
|
udaValues map[string]*string
|
|
}
|
|
|
|
func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
|
|
newProject := ""
|
|
projectOptions := append([]string{"(none)"}, com.TW.GetProjects()...)
|
|
if task.Project == "" {
|
|
task.Project = "(none)"
|
|
}
|
|
|
|
defaultKeymap := huh.NewDefaultKeyMap()
|
|
|
|
fields := []huh.Field{
|
|
huh.NewInput().
|
|
Title("Task").
|
|
Value(&task.Description).
|
|
Validate(func(desc string) error {
|
|
if desc == "" {
|
|
return fmt.Errorf("task description is required")
|
|
}
|
|
return nil
|
|
}).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form),
|
|
|
|
huh.NewSelect[string]().
|
|
Options(huh.NewOptions(projectOptions...)...).
|
|
Title("Project").
|
|
Value(&task.Project).
|
|
WithKeyMap(defaultKeymap).
|
|
WithTheme(com.Styles.Form),
|
|
|
|
huh.NewInput().
|
|
Title("New Project").
|
|
Value(&newProject).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form),
|
|
}
|
|
|
|
udaValues := make(map[string]*string)
|
|
for _, uda := range com.Udas {
|
|
switch uda.Type {
|
|
case taskwarrior.UdaTypeNumeric:
|
|
val := ""
|
|
udaValues[uda.Name] = &val
|
|
fields = append(fields, huh.NewInput().
|
|
Title(uda.Label).
|
|
Value(udaValues[uda.Name]).
|
|
Validate(taskwarrior.ValidateNumeric).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form))
|
|
case taskwarrior.UdaTypeString:
|
|
if len(uda.Values) > 0 {
|
|
var val string
|
|
values := make([]string, len(uda.Values))
|
|
for i, v := range uda.Values {
|
|
values[i] = v
|
|
if v == "" {
|
|
values[i] = "(none)"
|
|
}
|
|
if v == uda.Default {
|
|
val = values[i]
|
|
}
|
|
}
|
|
if val == "" {
|
|
val = values[0]
|
|
}
|
|
if v, ok := task.Udas[uda.Name]; ok {
|
|
//TODO: handle uda types correctly
|
|
val = v.(string)
|
|
}
|
|
udaValues[uda.Name] = &val
|
|
|
|
fields = append(fields, huh.NewSelect[string]().
|
|
Options(huh.NewOptions(values...)...).
|
|
Title(uda.Label).
|
|
Value(udaValues[uda.Name]).
|
|
WithKeyMap(defaultKeymap).
|
|
WithTheme(com.Styles.Form))
|
|
} else {
|
|
val := ""
|
|
udaValues[uda.Name] = &val
|
|
fields = append(fields, huh.NewInput().
|
|
Title(uda.Label).
|
|
Value(udaValues[uda.Name]).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form))
|
|
}
|
|
case taskwarrior.UdaTypeDate:
|
|
val := ""
|
|
udaValues[uda.Name] = &val
|
|
fields = append(fields, huh.NewInput().
|
|
Title(uda.Label).
|
|
Value(udaValues[uda.Name]).
|
|
Validate(taskwarrior.ValidateDate).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form))
|
|
case taskwarrior.UdaTypeDuration:
|
|
val := ""
|
|
udaValues[uda.Name] = &val
|
|
fields = append(fields, huh.NewInput().
|
|
Title(uda.Label).
|
|
Value(udaValues[uda.Name]).
|
|
Validate(taskwarrior.ValidateDuration).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form))
|
|
}
|
|
}
|
|
|
|
newAnnotation := ""
|
|
fields = append(fields, huh.NewInput().
|
|
Title("New Annotation").
|
|
Value(&newAnnotation).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(com.Styles.Form))
|
|
|
|
t := taskEdit{
|
|
common: com,
|
|
fields: fields,
|
|
|
|
udaValues: udaValues,
|
|
|
|
newProjectName: &newProject,
|
|
newAnnotation: &newAnnotation,
|
|
}
|
|
|
|
t.fields[0].Focus()
|
|
|
|
return &t
|
|
}
|
|
|
|
func (t *taskEdit) SetCursor(c int) {
|
|
t.fields[t.cursor].Blur()
|
|
if c < 0 {
|
|
t.cursor = len(t.fields) - 1
|
|
} else {
|
|
t.cursor = c
|
|
}
|
|
t.fields[t.cursor].Focus()
|
|
}
|
|
|
|
func (t *taskEdit) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg.(type) {
|
|
case nextFieldMsg:
|
|
if t.cursor == len(t.fields)-1 {
|
|
t.fields[t.cursor].Blur()
|
|
return t, nextArea()
|
|
}
|
|
t.fields[t.cursor].Blur()
|
|
t.cursor++
|
|
t.fields[t.cursor].Focus()
|
|
case prevFieldMsg:
|
|
if t.cursor == 0 {
|
|
t.fields[t.cursor].Blur()
|
|
return t, prevArea()
|
|
}
|
|
t.fields[t.cursor].Blur()
|
|
t.cursor--
|
|
t.fields[t.cursor].Focus()
|
|
default:
|
|
field, cmd := t.fields[t.cursor].Update(msg)
|
|
t.fields[t.cursor] = field.(huh.Field)
|
|
return t, cmd
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
func (t *taskEdit) View() string {
|
|
views := make([]string, len(t.fields))
|
|
for i, field := range t.fields {
|
|
views[i] = field.View()
|
|
if i < len(t.fields)-1 {
|
|
views[i] += "\n"
|
|
}
|
|
}
|
|
return lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
views...,
|
|
)
|
|
}
|
|
|
|
type tagEdit struct {
|
|
common *common.Common
|
|
fields []huh.Field
|
|
|
|
cursor int
|
|
|
|
newTagsValue *string
|
|
}
|
|
|
|
func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit {
|
|
newTags := ""
|
|
|
|
defaultKeymap := huh.NewDefaultKeyMap()
|
|
|
|
t := tagEdit{
|
|
common: common,
|
|
fields: []huh.Field{
|
|
huh.NewMultiSelect[string]().
|
|
Options(huh.NewOptions(options...)...).
|
|
// Key("tags").
|
|
Title("Tags").
|
|
Value(selected).
|
|
Filterable(true).
|
|
WithKeyMap(defaultKeymap).
|
|
WithTheme(common.Styles.Form),
|
|
huh.NewInput().
|
|
Title("New Tags").
|
|
Value(&newTags).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(common.Styles.Form),
|
|
},
|
|
newTagsValue: &newTags,
|
|
}
|
|
|
|
return &t
|
|
}
|
|
|
|
func (t *tagEdit) SetCursor(c int) {
|
|
t.fields[t.cursor].Blur()
|
|
if c < 0 {
|
|
t.cursor = len(t.fields) - 1
|
|
} else {
|
|
t.cursor = c
|
|
}
|
|
t.fields[t.cursor].Focus()
|
|
}
|
|
|
|
func (t *tagEdit) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (t *tagEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg.(type) {
|
|
case nextFieldMsg:
|
|
if t.cursor == len(t.fields)-1 {
|
|
t.fields[t.cursor].Blur()
|
|
return t, nextArea()
|
|
}
|
|
t.fields[t.cursor].Blur()
|
|
t.cursor++
|
|
t.fields[t.cursor].Focus()
|
|
case prevFieldMsg:
|
|
if t.cursor == 0 {
|
|
t.fields[t.cursor].Blur()
|
|
return t, prevArea()
|
|
}
|
|
t.fields[t.cursor].Blur()
|
|
t.cursor--
|
|
t.fields[t.cursor].Focus()
|
|
default:
|
|
field, cmd := t.fields[t.cursor].Update(msg)
|
|
t.fields[t.cursor] = field.(huh.Field)
|
|
return t, cmd
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func (t tagEdit) View() string {
|
|
views := make([]string, len(t.fields))
|
|
for i, field := range t.fields {
|
|
views[i] = field.View()
|
|
}
|
|
return lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
views...,
|
|
)
|
|
}
|
|
|
|
type timeEdit struct {
|
|
common *common.Common
|
|
fields []huh.Field
|
|
|
|
cursor int
|
|
}
|
|
|
|
func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *string, until *string) *timeEdit {
|
|
// defaultKeymap := huh.NewDefaultKeyMap()
|
|
t := timeEdit{
|
|
common: common,
|
|
fields: []huh.Field{
|
|
huh.NewInput().
|
|
Title("Due").
|
|
Value(due).
|
|
Validate(taskwarrior.ValidateDate).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(common.Styles.Form),
|
|
huh.NewInput().
|
|
Title("Scheduled").
|
|
Value(scheduled).
|
|
Validate(taskwarrior.ValidateDate).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(common.Styles.Form),
|
|
huh.NewInput().
|
|
Title("Wait").
|
|
Value(wait).
|
|
Validate(taskwarrior.ValidateDate).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(common.Styles.Form),
|
|
huh.NewInput().
|
|
Title("Until").
|
|
Value(until).
|
|
Validate(taskwarrior.ValidateDate).
|
|
Inline(true).
|
|
Prompt(": ").
|
|
WithTheme(common.Styles.Form),
|
|
},
|
|
}
|
|
|
|
return &t
|
|
}
|
|
|
|
func (t *timeEdit) SetCursor(c int) {
|
|
t.fields[t.cursor].Blur()
|
|
if c < 0 {
|
|
t.cursor = len(t.fields) - 1
|
|
} else {
|
|
t.cursor = c
|
|
}
|
|
t.fields[t.cursor].Focus()
|
|
}
|
|
|
|
func (t *timeEdit) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (t *timeEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg.(type) {
|
|
case nextFieldMsg:
|
|
if t.cursor == len(t.fields)-1 {
|
|
t.fields[t.cursor].Blur()
|
|
return t, nextArea()
|
|
}
|
|
t.fields[t.cursor].Blur()
|
|
t.cursor++
|
|
t.fields[t.cursor].Focus()
|
|
case prevFieldMsg:
|
|
if t.cursor == 0 {
|
|
t.fields[t.cursor].Blur()
|
|
return t, prevArea()
|
|
}
|
|
t.fields[t.cursor].Blur()
|
|
t.cursor--
|
|
t.fields[t.cursor].Focus()
|
|
default:
|
|
field, cmd := t.fields[t.cursor].Update(msg)
|
|
t.fields[t.cursor] = field.(huh.Field)
|
|
return t, cmd
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func (t *timeEdit) View() string {
|
|
views := make([]string, len(t.fields))
|
|
for i, field := range t.fields {
|
|
views[i] = field.View()
|
|
}
|
|
return lipgloss.JoinVertical(
|
|
lipgloss.Left,
|
|
views...,
|
|
)
|
|
}
|
|
|
|
type detailsEdit struct {
|
|
com *common.Common
|
|
renderer *glamour.TermRenderer
|
|
vp viewport.Model
|
|
}
|
|
|
|
func NewDetailsEdit(com *common.Common, task *taskwarrior.Task) *detailsEdit {
|
|
renderer, err := glamour.NewTermRenderer(
|
|
// glamour.WithStandardStyle("light"),
|
|
glamour.WithAutoStyle(),
|
|
glamour.WithWordWrap(40),
|
|
)
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
return nil
|
|
}
|
|
|
|
vp := viewport.New(40, 30)
|
|
d := detailsEdit{
|
|
com: com,
|
|
renderer: renderer,
|
|
vp: vp,
|
|
}
|
|
|
|
return &d
|
|
}
|
|
|
|
func (d *detailsEdit) SetCursor(c int) {
|
|
}
|
|
|
|
func (d *detailsEdit) Init() tea.Cmd {
|
|
return nil
|
|
}
|
|
|
|
func (d *detailsEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg.(type) {
|
|
case nextFieldMsg:
|
|
return d, nextArea()
|
|
case prevFieldMsg:
|
|
return d, prevArea()
|
|
default:
|
|
var cmd tea.Cmd
|
|
d.vp, cmd = d.vp.Update(msg)
|
|
return d, cmd
|
|
}
|
|
}
|
|
|
|
func (d *detailsEdit) View() string {
|
|
dtls := `
|
|
# Cool Details!
|
|
## Things I need
|
|
- [ ] A thing
|
|
- [x] Done thing
|
|
|
|
## People
|
|
- pe1
|
|
- pe2
|
|
`
|
|
|
|
details, err := d.renderer.Render(dtls)
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
return "Could not parse markdown"
|
|
}
|
|
|
|
d.vp.SetContent(details)
|
|
return d.vp.View()
|
|
}
|
|
|
|
// func (p *TaskEditorPage) SetSize(width, height int) {
|
|
// p.common.SetSize(width, height)
|
|
// }
|
|
|
|
// func (p *TaskEditorPage) Init() tea.Cmd {
|
|
// // return p.form.Init()
|
|
// return nil
|
|
// }
|
|
|
|
// func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, 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
|
|
// }
|
|
// case changeAreaMsg:
|
|
// p.selectedArea = area(msg)
|
|
// p.columns = append([]tea.Model{p.areaList}, p.areas[p.selectedArea]...)
|
|
// case nextColumnMsg:
|
|
// p.columnCursor++
|
|
// if p.columnCursor > len(p.columns)-1 {
|
|
// p.columnCursor = 0
|
|
// }
|
|
// case prevColumnMsg:
|
|
// p.columnCursor--
|
|
// if p.columnCursor < 0 {
|
|
// p.columnCursor = len(p.columns) - 1
|
|
// }
|
|
// }
|
|
|
|
// 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.Left):
|
|
// return p, prevColumn()
|
|
// case key.Matches(msg, p.common.Keymap.Right):
|
|
// return p, nextColumn()
|
|
// }
|
|
// }
|
|
// case modeInsert:
|
|
// switch msg := msg.(type) {
|
|
// case tea.KeyMsg:
|
|
// switch {
|
|
// case key.Matches(msg, p.common.Keymap.Back):
|
|
// return p, p.switchModeCmd(modeNormal)
|
|
// }
|
|
// }
|
|
|
|
// var cmd tea.Cmd
|
|
// if p.columnCursor == 0 {
|
|
// p.areaList, cmd = p.areaList.Update(msg)
|
|
// p.selectedArea = p.areaList.(areaList).Area()
|
|
// cmds = append(cmds, cmd)
|
|
// } else {
|
|
// p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg)
|
|
// cmds = append(cmds, cmd)
|
|
// }
|
|
|
|
// }
|
|
|
|
// var cmd tea.Cmd
|
|
// if p.columnCursor == 0 {
|
|
// p.areaList, cmd = p.areaList.Update(msg)
|
|
// p.selectedArea = p.areaList.(areaList).Area()
|
|
// cmds = append(cmds, cmd)
|
|
// } else {
|
|
// p.areas[p.selectedArea][p.columnCursor-1], cmd = p.areas[p.selectedArea][p.columnCursor-1].Update(msg)
|
|
// cmds = append(cmds, 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 {
|
|
// // slog.Error("page stack empty")
|
|
// // return nil, tea.Quit
|
|
// // }
|
|
// // return model, tea.Batch(cmds...)
|
|
// // }
|
|
|
|
// return p, tea.Batch(cmds...)
|
|
// }
|
|
|
|
// func (p *TaskEditorPage) View() string {
|
|
// columns := make([]string, len(p.columns))
|
|
// for i, c := range p.columns {
|
|
// columns[i] = c.View()
|
|
// }
|
|
|
|
// return lipgloss.JoinVertical(
|
|
// lipgloss.Left,
|
|
// lipgloss.JoinHorizontal(
|
|
// lipgloss.Top,
|
|
// // 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(" . ")),
|
|
// columns...,
|
|
// ),
|
|
// p.statusline.View(),
|
|
// )
|
|
// }
|
|
|
|
func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
|
if p.task.Project == "(none)" {
|
|
p.task.Project = ""
|
|
}
|
|
|
|
for _, uda := range p.common.Udas {
|
|
if val, ok := p.areas[0].(*taskEdit).udaValues[uda.Name]; ok {
|
|
if *val == "(none)" {
|
|
*val = ""
|
|
}
|
|
p.task.Udas[uda.Name] = *val
|
|
}
|
|
}
|
|
|
|
if *(p.areas[0].(*taskEdit).newProjectName) != "" {
|
|
p.task.Project = *p.areas[0].(*taskEdit).newProjectName
|
|
}
|
|
|
|
if *(p.areas[1].(*tagEdit).newTagsValue) != "" {
|
|
newTags := strings.Split(*p.areas[1].(*tagEdit).newTagsValue, " ")
|
|
if len(newTags) > 0 {
|
|
p.task.Tags = append(p.task.Tags, newTags...)
|
|
}
|
|
}
|
|
|
|
if *(p.areas[0].(*taskEdit).newAnnotation) != "" {
|
|
p.task.Annotations = append(p.task.Annotations, taskwarrior.Annotation{
|
|
Entry: time.Now().Format("20060102T150405Z"),
|
|
Description: *(p.areas[0].(*taskEdit).newAnnotation),
|
|
})
|
|
|
|
// p.common.TW.AddTaskAnnotation(p.task.Uuid, *p.areas[0].(*taskEdit).newAnnotation)
|
|
}
|
|
|
|
p.common.TW.ImportTask(&p.task)
|
|
return UpdatedTasksMsg{}
|
|
}
|
|
|
|
// 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.Base.Render("NORMAL")
|
|
// case modeInsert:
|
|
// mode = s.common.Styles.Active.Inline(true).Reverse(true).Render("INSERT")
|
|
// }
|
|
// return lipgloss.JoinHorizontal(lipgloss.Left, mode, s.input.View())
|
|
// }
|
|
|
|
// // TODO: move this to taskwarrior; add missing date formats
|
|
|
|
// type itemDelegate struct{}
|
|
|
|
// func (d itemDelegate) Height() int { return 1 }
|
|
// func (d itemDelegate) Spacing() int { return 0 }
|
|
// func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil }
|
|
// func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
|
// i, ok := listItem.(item)
|
|
// if !ok {
|
|
// return
|
|
// }
|
|
|
|
// str := fmt.Sprintf("%s", i)
|
|
|
|
// fn := itemStyle.Render
|
|
// if index == m.Index() {
|
|
// fn = func(s ...string) string {
|
|
// return selectedItemStyle.Render("> " + strings.Join(s, " "))
|
|
// }
|
|
// }
|
|
|
|
// fmt.Fprint(w, fn(str))
|
|
// }
|
|
|
|
// var (
|
|
// titleStyle = lipgloss.NewStyle().MarginLeft(2)
|
|
// itemStyle = lipgloss.NewStyle().PaddingLeft(4)
|
|
// selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170"))
|
|
// paginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
|
|
// helpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
|
|
// quitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
|
|
// )
|
|
|
|
// type item string
|
|
|
|
// func (i item) FilterValue() string { return "" }
|