1044 lines
26 KiB
Go
1044 lines
26 KiB
Go
// package components
|
|
//
|
|
// import (
|
|
// "fmt"
|
|
// "log/slog"
|
|
// "tasksquire/common"
|
|
// "time"
|
|
//
|
|
// "tasksquire/components/input"
|
|
// "tasksquire/components/picker"
|
|
// "tasksquire/components/timestampeditor"
|
|
// "tasksquire/taskwarrior"
|
|
//
|
|
// "github.com/charmbracelet/bubbles/key"
|
|
// "github.com/charmbracelet/bubbles/list"
|
|
// "github.com/charmbracelet/bubbles/textarea"
|
|
// "github.com/charmbracelet/bubbles/viewport"
|
|
// tea "github.com/charmbracelet/bubbletea"
|
|
// "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
|
|
//
|
|
// infoViewport viewport.Model
|
|
// }
|
|
//
|
|
// 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, p.task.Uuid == ""),
|
|
// 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.infoViewport = viewport.New(0, 0)
|
|
// if p.task.Uuid != "" {
|
|
// p.infoViewport.SetContent(p.common.TW.GetInformation(&p.task))
|
|
// }
|
|
//
|
|
// 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()
|
|
// }
|
|
//
|
|
// p.infoViewport.Width = width - p.colWidth - p.common.Styles.ColumnFocused.GetHorizontalFrameSize()*2 - 5
|
|
// if p.infoViewport.Width < 0 {
|
|
// p.infoViewport.Width = 0
|
|
// }
|
|
// p.infoViewport.Height = p.colHeight
|
|
// }
|
|
//
|
|
// func (p *TaskEditorPage) Init() tea.Cmd {
|
|
// var cmds []tea.Cmd
|
|
// for _, a := range p.areas {
|
|
// cmds = append(cmds, a.Init())
|
|
// }
|
|
// return tea.Batch(cmds...)
|
|
// }
|
|
//
|
|
// 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--
|
|
// maxCols := 2
|
|
// if p.task.Uuid != "" {
|
|
// maxCols = 3
|
|
// }
|
|
// if p.columnCursor < 0 {
|
|
// p.columnCursor = maxCols - 1
|
|
// }
|
|
// case nextColumnMsg:
|
|
// p.columnCursor++
|
|
// maxCols := 2
|
|
// if p.task.Uuid != "" {
|
|
// maxCols = 3
|
|
// }
|
|
// if p.columnCursor >= maxCols {
|
|
// 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.PrevPage):
|
|
// return p, prevArea()
|
|
// case key.Matches(msg, p.common.Keymap.NextPage):
|
|
// return p, nextArea()
|
|
// 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 if p.columnCursor == 1 {
|
|
// model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
|
// p.areas[p.area] = model.(area)
|
|
// return p, cmd
|
|
// } else if p.columnCursor == 2 {
|
|
// p.infoViewport.LineUp(1)
|
|
// return p, nil
|
|
// }
|
|
// 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 if p.columnCursor == 1 {
|
|
// model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
|
// p.areas[p.area] = model.(area)
|
|
// return p, cmd
|
|
// } else if p.columnCursor == 2 {
|
|
// p.infoViewport.LineDown(1)
|
|
// return p, nil
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// // 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):
|
|
// model, cmd := p.areas[p.area].Update(msg)
|
|
// p.areas[p.area] = model.(area)
|
|
// if cmd != nil {
|
|
// _, ok := cmd().(input.SuppressBackMsg)
|
|
// if ok {
|
|
// return p, nil
|
|
// }
|
|
// }
|
|
// 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 if p.columnCursor == 1 {
|
|
// model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
|
// p.areas[p.area] = model.(area)
|
|
// return p, cmd
|
|
// }
|
|
// return p, nil
|
|
// 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 if p.columnCursor == 1 {
|
|
// model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
|
// p.areas[p.area] = model.(area)
|
|
// return p, cmd
|
|
// }
|
|
// return p, nil
|
|
// case key.Matches(msg, p.common.Keymap.Ok):
|
|
// isFiltering := p.areas[p.area].IsFiltering()
|
|
// model, cmd := p.areas[p.area].Update(msg)
|
|
// if p.area != 3 {
|
|
// p.areas[p.area] = model.(area)
|
|
// if isFiltering {
|
|
// return p, cmd
|
|
// }
|
|
// return p, tea.Batch(cmd, nextField())
|
|
// }
|
|
// return p, cmd
|
|
// }
|
|
// }
|
|
//
|
|
// if p.columnCursor == 0 {
|
|
// picker, cmd := p.areaPicker.Update(msg)
|
|
// p.areaPicker = picker.(*areaPicker)
|
|
// return p, cmd
|
|
// } else if p.columnCursor == 2 {
|
|
// var cmd tea.Cmd
|
|
// p.infoViewport, cmd = p.infoViewport.Update(msg)
|
|
// 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
|
|
// } else {
|
|
// focusedStyle = p.common.Styles.ColumnFocused
|
|
// }
|
|
// blurredStyle = p.common.Styles.ColumnBlurred
|
|
//
|
|
// var area string
|
|
// if p.columnCursor == 1 {
|
|
// area = focusedStyle.Copy().Width(p.colWidth).Height(p.colHeight).Render(p.areas[p.area].View())
|
|
// } else {
|
|
// area = blurredStyle.Copy().Width(p.colWidth).Height(p.colHeight).Render(p.areas[p.area].View())
|
|
// }
|
|
//
|
|
// if p.task.Uuid != "" {
|
|
// var infoView string
|
|
// if p.columnCursor == 2 {
|
|
// infoView = focusedStyle.Copy().Width(p.infoViewport.Width).Height(p.infoViewport.Height).Render(p.infoViewport.View())
|
|
// } else {
|
|
// infoView = blurredStyle.Copy().Width(p.infoViewport.Width).Height(p.infoViewport.Height).Render(p.infoViewport.View())
|
|
// }
|
|
// area = lipgloss.JoinHorizontal(
|
|
// lipgloss.Top,
|
|
// area,
|
|
// infoView,
|
|
// )
|
|
// }
|
|
//
|
|
// tabs := ""
|
|
// for i, a := range p.areas {
|
|
// style := p.common.Styles.Base
|
|
// if i == p.area {
|
|
// style = style.Bold(true).Foreground(p.common.Styles.Palette.Accent.GetForeground())
|
|
// } else {
|
|
// style = style.Foreground(p.common.Styles.Palette.Muted.GetForeground())
|
|
// }
|
|
// tabs += style.Render(fmt.Sprintf(" %s ", a.GetName()))
|
|
// }
|
|
//
|
|
// page := lipgloss.JoinVertical(
|
|
// lipgloss.Left,
|
|
// tabs,
|
|
// area,
|
|
// )
|
|
//
|
|
// // 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, page)
|
|
// }
|
|
//
|
|
// type area interface {
|
|
// tea.Model
|
|
// SetCursor(c int)
|
|
// GetName() string
|
|
// IsFiltering() bool
|
|
// }
|
|
//
|
|
// type focusMsg struct{}
|
|
//
|
|
// 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 EditableField interface {
|
|
// tea.Model
|
|
// Focus() tea.Cmd
|
|
// Blur() tea.Cmd
|
|
// }
|
|
//
|
|
// type taskEdit struct {
|
|
// common *common.Common
|
|
// fields []EditableField
|
|
// cursor int
|
|
//
|
|
// projectPicker *picker.Picker
|
|
// // newProjectName *string
|
|
// newAnnotation *string
|
|
// udaValues map[string]*string
|
|
// }
|
|
//
|
|
// func NewTaskEdit(com *common.Common, task *taskwarrior.Task, isNew bool) *taskEdit {
|
|
// // newProject := ""
|
|
// if task.Project == "" {
|
|
// task.Project = "(none)"
|
|
// }
|
|
//
|
|
// itemProvider := func() []list.Item {
|
|
// projects := com.TW.GetProjects()
|
|
// items := []list.Item{picker.NewItem("(none)")}
|
|
// for _, proj := range projects {
|
|
// items = append(items, picker.NewItem(proj))
|
|
// }
|
|
// return items
|
|
// }
|
|
// onSelect := func(item list.Item) tea.Cmd {
|
|
// return nil
|
|
// }
|
|
// onCreate := func(newProject string) tea.Cmd {
|
|
// // The new project name will be used as the project value
|
|
// // when the task is saved
|
|
// return nil
|
|
// }
|
|
//
|
|
// opts := []picker.PickerOption{picker.WithOnCreate(onCreate)}
|
|
//
|
|
// // Check if task has a pre-filled project (e.g., from ProjectTaskPickerPage)
|
|
// hasPrefilledProject := task.Project != "" && task.Project != "(none)"
|
|
//
|
|
// if isNew && !hasPrefilledProject {
|
|
// // New task with no project → start in filter mode for quick project search
|
|
// opts = append(opts, picker.WithFilterByDefault(true))
|
|
// } else {
|
|
// // Either existing task OR new task with pre-filled project → show list with project selected
|
|
// opts = append(opts, picker.WithDefaultValue(task.Project))
|
|
// }
|
|
//
|
|
// projPicker := picker.New(com, "Project", itemProvider, onSelect, opts...)
|
|
// projPicker.SetSize(70, 8)
|
|
// projPicker.Blur()
|
|
//
|
|
// defaultKeymap := huh.NewDefaultKeyMap()
|
|
//
|
|
// fields := []EditableField{
|
|
// 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),
|
|
//
|
|
// projPicker,
|
|
//
|
|
// // huh.NewInput().
|
|
// // Title("New Project").
|
|
// // Value(&newProject).
|
|
// // Inline(true).
|
|
// // Prompt(": ").
|
|
// // WithTheme(com.Styles.Form),
|
|
// }
|
|
//
|
|
// udaValues := make(map[string]*string)
|
|
// for _, uda := range com.Udas {
|
|
// if uda.Name == "parenttask" {
|
|
// continue
|
|
// }
|
|
// 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,
|
|
// projectPicker: projPicker,
|
|
//
|
|
// udaValues: udaValues,
|
|
//
|
|
// // newProjectName: &newProject,
|
|
// newAnnotation: &newAnnotation,
|
|
// }
|
|
//
|
|
// t.fields[0].Focus()
|
|
//
|
|
// return &t
|
|
// }
|
|
//
|
|
// func (t *taskEdit) GetName() string {
|
|
// return "Task"
|
|
// }
|
|
//
|
|
// func (t *taskEdit) IsFiltering() bool {
|
|
// if f, ok := t.fields[t.cursor].(interface{ IsFiltering() bool }); ok {
|
|
// return f.IsFiltering()
|
|
// }
|
|
// return false
|
|
// }
|
|
//
|
|
// 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 {
|
|
// var cmds []tea.Cmd
|
|
// // Ensure focus on the active field (especially for the first one)
|
|
// if len(t.fields) > 0 {
|
|
// cmds = append(cmds, func() tea.Msg {
|
|
// return focusMsg{}
|
|
// })
|
|
// }
|
|
// for _, f := range t.fields {
|
|
// cmds = append(cmds, f.Init())
|
|
// }
|
|
// return tea.Batch(cmds...)
|
|
// }
|
|
//
|
|
// func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
// switch msg.(type) {
|
|
// case focusMsg:
|
|
// if len(t.fields) > 0 {
|
|
// return t, t.fields[t.cursor].Focus()
|
|
// }
|
|
// 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.(EditableField)
|
|
// 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
|
|
// }
|
|
//
|
|
// func NewTagEdit(common *common.Common, selected *[]string, options []string) *tagEdit {
|
|
// defaultKeymap := huh.NewDefaultKeyMap()
|
|
//
|
|
// t := tagEdit{
|
|
// common: common,
|
|
// fields: []huh.Field{
|
|
// input.NewMultiSelect(common).
|
|
// Options(true, input.NewOptions(options...)...).
|
|
// // Key("tags").
|
|
// Title("Tags").
|
|
// Value(selected).
|
|
// Filterable(true).
|
|
// WithKeyMap(defaultKeymap).
|
|
// WithTheme(common.Styles.Form),
|
|
// },
|
|
// }
|
|
//
|
|
// return &t
|
|
// }
|
|
//
|
|
// func (t *tagEdit) GetName() string {
|
|
// return "Tags"
|
|
// }
|
|
//
|
|
// func (t *tagEdit) IsFiltering() bool {
|
|
// if f, ok := t.fields[t.cursor].(interface{ IsFiltering() bool }); ok {
|
|
// return f.IsFiltering()
|
|
// }
|
|
// return false
|
|
// }
|
|
//
|
|
// 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 []*timestampeditor.TimestampEditor
|
|
//
|
|
// cursor int
|
|
//
|
|
// // Store task field pointers to update them
|
|
// due *string
|
|
// scheduled *string
|
|
// wait *string
|
|
// until *string
|
|
// }
|
|
//
|
|
// func NewTimeEdit(common *common.Common, due *string, scheduled *string, wait *string, until *string) *timeEdit {
|
|
// // Create timestamp editors for each date field
|
|
// dueEditor := timestampeditor.New(common).
|
|
// Title("Due").
|
|
// Description("When the task is due").
|
|
// ValueFromString(*due)
|
|
//
|
|
// scheduledEditor := timestampeditor.New(common).
|
|
// Title("Scheduled").
|
|
// Description("When to start working on the task").
|
|
// ValueFromString(*scheduled)
|
|
//
|
|
// waitEditor := timestampeditor.New(common).
|
|
// Title("Wait").
|
|
// Description("Hide task until this date").
|
|
// ValueFromString(*wait)
|
|
//
|
|
// untilEditor := timestampeditor.New(common).
|
|
// Title("Until").
|
|
// Description("Task expires after this date").
|
|
// ValueFromString(*until)
|
|
//
|
|
// t := timeEdit{
|
|
// common: common,
|
|
// fields: []*timestampeditor.TimestampEditor{
|
|
// dueEditor,
|
|
// scheduledEditor,
|
|
// waitEditor,
|
|
// untilEditor,
|
|
// },
|
|
// due: due,
|
|
// scheduled: scheduled,
|
|
// wait: wait,
|
|
// until: until,
|
|
// }
|
|
//
|
|
// // Focus the first field
|
|
// if len(t.fields) > 0 {
|
|
// t.fields[0].Focus()
|
|
// }
|
|
//
|
|
// return &t
|
|
// }
|
|
//
|
|
// func (t *timeEdit) GetName() string {
|
|
// return "Dates"
|
|
// }
|
|
//
|
|
// func (t *timeEdit) IsFiltering() bool {
|
|
// return false
|
|
// }
|
|
//
|
|
// func (t *timeEdit) SetCursor(c int) {
|
|
// if len(t.fields) == 0 {
|
|
// return
|
|
// }
|
|
//
|
|
// // Blur the current field
|
|
// t.fields[t.cursor].Blur()
|
|
//
|
|
// // Set new cursor position
|
|
// if c < 0 {
|
|
// t.cursor = len(t.fields) - 1
|
|
// } else {
|
|
// t.cursor = c
|
|
// }
|
|
//
|
|
// // Focus the new field
|
|
// 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 := msg.(type) {
|
|
// case nextFieldMsg:
|
|
// if t.cursor == len(t.fields)-1 {
|
|
// // Update task field before moving to next area
|
|
// t.syncToTaskFields()
|
|
// t.fields[t.cursor].Blur()
|
|
// return t, nextArea()
|
|
// }
|
|
// t.fields[t.cursor].Blur()
|
|
// t.cursor++
|
|
// t.fields[t.cursor].Focus()
|
|
// return t, nil
|
|
// case prevFieldMsg:
|
|
// if t.cursor == 0 {
|
|
// // Update task field before moving to previous area
|
|
// t.syncToTaskFields()
|
|
// t.fields[t.cursor].Blur()
|
|
// return t, prevArea()
|
|
// }
|
|
// t.fields[t.cursor].Blur()
|
|
// t.cursor--
|
|
// t.fields[t.cursor].Focus()
|
|
// return t, nil
|
|
// default:
|
|
// // Update the current timestamp editor
|
|
// model, cmd := t.fields[t.cursor].Update(msg)
|
|
// t.fields[t.cursor] = model.(*timestampeditor.TimestampEditor)
|
|
// return t, cmd
|
|
// }
|
|
// }
|
|
//
|
|
// func (t *timeEdit) 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...,
|
|
// )
|
|
// }
|
|
//
|
|
// // syncToTaskFields converts the timestamp editor values back to task field strings
|
|
// func (t *timeEdit) syncToTaskFields() {
|
|
// // Update the task fields with values from the timestamp editors
|
|
// // GetValueString() returns empty string for unset timestamps
|
|
// if len(t.fields) > 0 {
|
|
// *t.due = t.fields[0].GetValueString()
|
|
// }
|
|
// if len(t.fields) > 1 {
|
|
// *t.scheduled = t.fields[1].GetValueString()
|
|
// }
|
|
// if len(t.fields) > 2 {
|
|
// *t.wait = t.fields[2].GetValueString()
|
|
// }
|
|
// if len(t.fields) > 3 {
|
|
// *t.until = t.fields[3].GetValueString()
|
|
// }
|
|
// }
|
|
//
|
|
// type detailsEdit struct {
|
|
// com *common.Common
|
|
// vp viewport.Model
|
|
// ta textarea.Model
|
|
// details string
|
|
// // renderer *glamour.TermRenderer
|
|
// }
|
|
//
|
|
// 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(com.Width(), 40-com.Styles.ColumnFocused.GetVerticalFrameSize())
|
|
// ta := textarea.New()
|
|
// ta.SetWidth(70)
|
|
// ta.SetHeight(40 - com.Styles.ColumnFocused.GetVerticalFrameSize() - 2)
|
|
// ta.ShowLineNumbers = false
|
|
// ta.FocusedStyle.CursorLine = lipgloss.NewStyle()
|
|
// ta.Focus()
|
|
// if task.Udas["details"] != nil {
|
|
// ta.SetValue(task.Udas["details"].(string))
|
|
// }
|
|
// d := detailsEdit{
|
|
// com: com,
|
|
// // renderer: renderer,
|
|
// vp: vp,
|
|
// ta: ta,
|
|
// }
|
|
//
|
|
// return &d
|
|
// }
|
|
//
|
|
// func (d *detailsEdit) GetName() string {
|
|
// return "Details"
|
|
// }
|
|
//
|
|
// func (d *detailsEdit) IsFiltering() bool {
|
|
// return false
|
|
// }
|
|
//
|
|
// func (d *detailsEdit) SetCursor(c int) {
|
|
// }
|
|
//
|
|
// func (d *detailsEdit) Init() tea.Cmd {
|
|
// return textarea.Blink
|
|
// }
|
|
//
|
|
// 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 vpCmd, taCmd tea.Cmd
|
|
// d.vp, vpCmd = d.vp.Update(msg)
|
|
// d.ta, taCmd = d.ta.Update(msg)
|
|
// return d, tea.Batch(vpCmd, taCmd)
|
|
// }
|
|
// }
|
|
//
|
|
// func (d *detailsEdit) View() string {
|
|
// return d.ta.View()
|
|
// }
|
|
//
|
|
// func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
|
// p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue()
|
|
//
|
|
// 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
|
|
// }
|
|
// }
|
|
//
|
|
// // Sync timestamp fields from the timeEdit area (area 2)
|
|
// p.areas[2].(*timeEdit).syncToTaskFields()
|
|
//
|
|
// 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),
|
|
// })
|
|
// }
|
|
//
|
|
// if _, ok := p.task.Udas["details"]; ok || p.areas[3].(*detailsEdit).ta.Value() != "" {
|
|
// p.task.Udas["details"] = p.areas[3].(*detailsEdit).ta.Value()
|
|
// }
|
|
//
|
|
// p.common.TW.ImportTask(&p.task)
|
|
// return UpdatedTasksMsg{}
|
|
// }
|
|
//
|
|
// // type StatusLine struct { ... }
|
|
// // ...
|