Compare commits
3 Commits
f5d297e6ab
...
feat/taske
| Author | SHA1 | Date | |
|---|---|---|---|
| 70b6ee9bc7 | |||
| 2baf3859fd | |||
| 2940711b26 |
@ -27,6 +27,10 @@ type Styles struct {
|
|||||||
Form *huh.Theme
|
Form *huh.Theme
|
||||||
TableStyle TableStyle
|
TableStyle TableStyle
|
||||||
|
|
||||||
|
Tab lipgloss.Style
|
||||||
|
ActiveTab lipgloss.Style
|
||||||
|
TabBar lipgloss.Style
|
||||||
|
|
||||||
ColumnFocused lipgloss.Style
|
ColumnFocused lipgloss.Style
|
||||||
ColumnBlurred lipgloss.Style
|
ColumnBlurred lipgloss.Style
|
||||||
ColumnInsert lipgloss.Style
|
ColumnInsert lipgloss.Style
|
||||||
@ -71,6 +75,19 @@ func NewStyles(config *taskwarrior.TWConfig) *Styles {
|
|||||||
|
|
||||||
styles.Form = formTheme
|
styles.Form = formTheme
|
||||||
|
|
||||||
|
styles.Tab = lipgloss.NewStyle().
|
||||||
|
Padding(0, 1).
|
||||||
|
Foreground(lipgloss.Color("240"))
|
||||||
|
|
||||||
|
styles.ActiveTab = styles.Tab.
|
||||||
|
Foreground(lipgloss.Color("252")).
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
|
styles.TabBar = lipgloss.NewStyle().
|
||||||
|
Border(lipgloss.NormalBorder(), false, false, true, false).
|
||||||
|
BorderForeground(lipgloss.Color("240")).
|
||||||
|
MarginBottom(1)
|
||||||
|
|
||||||
styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
styles.ColumnFocused = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
||||||
styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1)
|
styles.ColumnBlurred = lipgloss.NewStyle().Border(lipgloss.HiddenBorder(), true).Padding(1)
|
||||||
styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
styles.ColumnInsert = lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), true).Padding(1)
|
||||||
|
|||||||
@ -637,6 +637,11 @@ func (m *MultiSelect) GetValue() any {
|
|||||||
return *m.value
|
return *m.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsFiltering returns true if the multi-select is currently filtering.
|
||||||
|
func (m *MultiSelect) IsFiltering() bool {
|
||||||
|
return m.filtering
|
||||||
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
func min(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
|||||||
@ -37,6 +37,7 @@ type Picker struct {
|
|||||||
title string
|
title string
|
||||||
filterByDefault bool
|
filterByDefault bool
|
||||||
baseItems []list.Item
|
baseItems []list.Item
|
||||||
|
focused bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type PickerOption func(*Picker)
|
type PickerOption func(*Picker)
|
||||||
@ -53,6 +54,24 @@ func WithOnCreate(onCreate func(string) tea.Cmd) PickerOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Picker) Focus() tea.Cmd {
|
||||||
|
p.focused = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Picker) Blur() tea.Cmd {
|
||||||
|
p.focused = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Picker) GetValue() string {
|
||||||
|
item := p.list.SelectedItem()
|
||||||
|
if item == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return item.FilterValue()
|
||||||
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
c *common.Common,
|
c *common.Common,
|
||||||
title string,
|
title string,
|
||||||
@ -82,6 +101,7 @@ func New(
|
|||||||
itemProvider: itemProvider,
|
itemProvider: itemProvider,
|
||||||
onSelect: onSelect,
|
onSelect: onSelect,
|
||||||
title: title,
|
title: title,
|
||||||
|
focused: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.TW.GetConfig().Get("uda.tasksquire.picker.filter_by_default") == "yes" {
|
if c.TW.GetConfig().Get("uda.tasksquire.picker.filter_by_default") == "yes" {
|
||||||
@ -92,6 +112,14 @@ func New(
|
|||||||
opt(p)
|
opt(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.filterByDefault {
|
||||||
|
// Manually trigger filter mode on the list so it doesn't require a global key press
|
||||||
|
var cmd tea.Cmd
|
||||||
|
p.list, cmd = p.list.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'i'}})
|
||||||
|
// We can ignore the command here as it's likely just for blinking, which will happen on Init anyway
|
||||||
|
_ = cmd
|
||||||
|
}
|
||||||
|
|
||||||
p.Refresh()
|
p.Refresh()
|
||||||
|
|
||||||
return p
|
return p
|
||||||
@ -134,27 +162,26 @@ func (p *Picker) SetSize(width, height int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Picker) Init() tea.Cmd {
|
func (p *Picker) Init() tea.Cmd {
|
||||||
if p.filterByDefault {
|
|
||||||
return func() tea.Msg {
|
|
||||||
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'i'}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Picker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (p *Picker) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
if !p.focused {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
// If filtering, let the list handle keys (including Enter to stop filtering)
|
// If filtering, let the list handle keys (including Enter to stop filtering)
|
||||||
if p.list.FilterState() == list.Filtering {
|
if p.list.FilterState() == list.Filtering {
|
||||||
if key.Matches(msg, p.common.Keymap.Ok) {
|
// if key.Matches(msg, p.common.Keymap.Ok) {
|
||||||
items := p.list.VisibleItems()
|
// items := p.list.VisibleItems()
|
||||||
if len(items) == 1 {
|
// if len(items) == 1 {
|
||||||
return p, p.handleSelect(items[0])
|
// return p, p.handleSelect(items[0])
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
break // Pass to list.Update
|
break // Pass to list.Update
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +216,12 @@ func (p *Picker) handleSelect(item list.Item) tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Picker) View() string {
|
func (p *Picker) View() string {
|
||||||
title := p.common.Styles.Form.Focused.Title.Render(p.title)
|
var title string
|
||||||
|
if p.focused {
|
||||||
|
title = p.common.Styles.Form.Focused.Title.Render(p.title)
|
||||||
|
} else {
|
||||||
|
title = p.common.Styles.Form.Blurred.Title.Render(p.title)
|
||||||
|
}
|
||||||
return lipgloss.JoinVertical(lipgloss.Left, title, p.list.View())
|
return lipgloss.JoinVertical(lipgloss.Left, title, p.list.View())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
"github.com/charmbracelet/bubbles/key"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MainPage struct {
|
type MainPage struct {
|
||||||
@ -13,6 +14,9 @@ type MainPage struct {
|
|||||||
|
|
||||||
taskPage common.Component
|
taskPage common.Component
|
||||||
timePage common.Component
|
timePage common.Component
|
||||||
|
currentTab int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMainPage(common *common.Common) *MainPage {
|
func NewMainPage(common *common.Common) *MainPage {
|
||||||
@ -24,6 +28,7 @@ func NewMainPage(common *common.Common) *MainPage {
|
|||||||
m.timePage = NewTimePage(common)
|
m.timePage = NewTimePage(common)
|
||||||
|
|
||||||
m.activePage = m.taskPage
|
m.activePage = m.taskPage
|
||||||
|
m.currentTab = 0
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
@ -37,17 +42,39 @@ func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
|
m.width = msg.Width
|
||||||
|
m.height = msg.Height
|
||||||
m.common.SetSize(msg.Width, msg.Height)
|
m.common.SetSize(msg.Width, msg.Height)
|
||||||
|
|
||||||
|
tabHeight := lipgloss.Height(m.renderTabBar())
|
||||||
|
contentHeight := msg.Height - tabHeight
|
||||||
|
if contentHeight < 0 {
|
||||||
|
contentHeight = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
newMsg := tea.WindowSizeMsg{Width: msg.Width, Height: contentHeight}
|
||||||
|
activePage, cmd := m.activePage.Update(newMsg)
|
||||||
|
m.activePage = activePage.(common.Component)
|
||||||
|
return m, cmd
|
||||||
|
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
// Only handle tab key for page switching when at the top level (no subpages active)
|
// Only handle tab key for page switching when at the top level (no subpages active)
|
||||||
if key.Matches(msg, m.common.Keymap.Next) && !m.common.HasSubpages() {
|
if key.Matches(msg, m.common.Keymap.Next) && !m.common.HasSubpages() {
|
||||||
if m.activePage == m.taskPage {
|
if m.activePage == m.taskPage {
|
||||||
m.activePage = m.timePage
|
m.activePage = m.timePage
|
||||||
|
m.currentTab = 1
|
||||||
} else {
|
} else {
|
||||||
m.activePage = m.taskPage
|
m.activePage = m.taskPage
|
||||||
|
m.currentTab = 0
|
||||||
}
|
}
|
||||||
// Re-size the new active page just in case
|
|
||||||
m.activePage.SetSize(m.common.Width(), m.common.Height())
|
tabHeight := lipgloss.Height(m.renderTabBar())
|
||||||
|
contentHeight := m.height - tabHeight
|
||||||
|
if contentHeight < 0 {
|
||||||
|
contentHeight = 0
|
||||||
|
}
|
||||||
|
m.activePage.SetSize(m.width, contentHeight)
|
||||||
|
|
||||||
// Trigger a refresh/init on switch? Maybe not needed if we keep state.
|
// Trigger a refresh/init on switch? Maybe not needed if we keep state.
|
||||||
// But we might want to refresh data.
|
// But we might want to refresh data.
|
||||||
return m, m.activePage.Init()
|
return m, m.activePage.Init()
|
||||||
@ -60,6 +87,22 @@ func (m *MainPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, cmd
|
return m, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MainPage) View() string {
|
func (m *MainPage) renderTabBar() string {
|
||||||
return m.activePage.View()
|
var tabs []string
|
||||||
|
headers := []string{"Tasks", "Time"}
|
||||||
|
|
||||||
|
for i, header := range headers {
|
||||||
|
style := m.common.Styles.Tab
|
||||||
|
if m.currentTab == i {
|
||||||
|
style = m.common.Styles.ActiveTab
|
||||||
|
}
|
||||||
|
tabs = append(tabs, style.Render(header))
|
||||||
|
}
|
||||||
|
|
||||||
|
row := lipgloss.JoinHorizontal(lipgloss.Top, tabs...)
|
||||||
|
return m.common.Styles.TabBar.Width(m.common.Width()).Render(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MainPage) View() string {
|
||||||
|
return lipgloss.JoinVertical(lipgloss.Left, m.renderTabBar(), m.activePage.View())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tasksquire/components/input"
|
"tasksquire/components/input"
|
||||||
|
"tasksquire/components/picker"
|
||||||
"tasksquire/components/timestampeditor"
|
"tasksquire/components/timestampeditor"
|
||||||
"tasksquire/taskwarrior"
|
"tasksquire/taskwarrior"
|
||||||
|
|
||||||
@ -41,6 +42,8 @@ type TaskEditorPage struct {
|
|||||||
area int
|
area int
|
||||||
areaPicker *areaPicker
|
areaPicker *areaPicker
|
||||||
areas []area
|
areas []area
|
||||||
|
|
||||||
|
infoViewport viewport.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage {
|
func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPage {
|
||||||
@ -56,7 +59,7 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag
|
|||||||
tagOptions := p.common.TW.GetTags()
|
tagOptions := p.common.TW.GetTags()
|
||||||
|
|
||||||
p.areas = []area{
|
p.areas = []area{
|
||||||
NewTaskEdit(p.common, &p.task),
|
NewTaskEdit(p.common, &p.task, p.task.Uuid == ""),
|
||||||
NewTagEdit(p.common, &p.task.Tags, tagOptions),
|
NewTagEdit(p.common, &p.task.Tags, tagOptions),
|
||||||
NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until),
|
NewTimeEdit(p.common, &p.task.Due, &p.task.Scheduled, &p.task.Wait, &p.task.Until),
|
||||||
NewDetailsEdit(p.common, &p.task),
|
NewDetailsEdit(p.common, &p.task),
|
||||||
@ -68,6 +71,11 @@ func NewTaskEditorPage(com *common.Common, task taskwarrior.Task) *TaskEditorPag
|
|||||||
|
|
||||||
p.areaPicker = NewAreaPicker(com, []string{"Task", "Tags", "Dates"})
|
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
|
p.columnCursor = 1
|
||||||
if p.task.Uuid == "" {
|
if p.task.Uuid == "" {
|
||||||
p.mode = modeInsert
|
p.mode = modeInsert
|
||||||
@ -94,10 +102,20 @@ func (p *TaskEditorPage) SetSize(width, height int) {
|
|||||||
} else {
|
} else {
|
||||||
p.colHeight = height - p.common.Styles.ColumnFocused.GetVerticalFrameSize()
|
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 {
|
func (p *TaskEditorPage) Init() tea.Cmd {
|
||||||
return nil
|
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) {
|
func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
@ -110,12 +128,20 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
p.mode = mode(msg)
|
p.mode = mode(msg)
|
||||||
case prevColumnMsg:
|
case prevColumnMsg:
|
||||||
p.columnCursor--
|
p.columnCursor--
|
||||||
|
maxCols := 2
|
||||||
|
if p.task.Uuid != "" {
|
||||||
|
maxCols = 3
|
||||||
|
}
|
||||||
if p.columnCursor < 0 {
|
if p.columnCursor < 0 {
|
||||||
p.columnCursor = len(p.areas) - 1
|
p.columnCursor = maxCols - 1
|
||||||
}
|
}
|
||||||
case nextColumnMsg:
|
case nextColumnMsg:
|
||||||
p.columnCursor++
|
p.columnCursor++
|
||||||
if p.columnCursor > len(p.areas)-1 {
|
maxCols := 2
|
||||||
|
if p.task.Uuid != "" {
|
||||||
|
maxCols = 3
|
||||||
|
}
|
||||||
|
if p.columnCursor >= maxCols {
|
||||||
p.columnCursor = 0
|
p.columnCursor = 0
|
||||||
}
|
}
|
||||||
case prevAreaMsg:
|
case prevAreaMsg:
|
||||||
@ -166,20 +192,26 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
picker, cmd := p.areaPicker.Update(msg)
|
picker, cmd := p.areaPicker.Update(msg)
|
||||||
p.areaPicker = picker.(*areaPicker)
|
p.areaPicker = picker.(*areaPicker)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
} else {
|
} else if p.columnCursor == 1 {
|
||||||
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
||||||
p.areas[p.area] = model.(area)
|
p.areas[p.area] = model.(area)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
|
} else if p.columnCursor == 2 {
|
||||||
|
p.infoViewport.LineUp(1)
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
case key.Matches(msg, p.common.Keymap.Down):
|
case key.Matches(msg, p.common.Keymap.Down):
|
||||||
if p.columnCursor == 0 {
|
if p.columnCursor == 0 {
|
||||||
picker, cmd := p.areaPicker.Update(msg)
|
picker, cmd := p.areaPicker.Update(msg)
|
||||||
p.areaPicker = picker.(*areaPicker)
|
p.areaPicker = picker.(*areaPicker)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
} else {
|
} else if p.columnCursor == 1 {
|
||||||
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
||||||
p.areas[p.area] = model.(area)
|
p.areas[p.area] = model.(area)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
|
} else if p.columnCursor == 2 {
|
||||||
|
p.infoViewport.LineDown(1)
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,25 +244,31 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
picker, cmd := p.areaPicker.Update(msg)
|
picker, cmd := p.areaPicker.Update(msg)
|
||||||
p.areaPicker = picker.(*areaPicker)
|
p.areaPicker = picker.(*areaPicker)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
} else {
|
} else if p.columnCursor == 1 {
|
||||||
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
model, cmd := p.areas[p.area].Update(prevFieldMsg{})
|
||||||
p.areas[p.area] = model.(area)
|
p.areas[p.area] = model.(area)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
}
|
}
|
||||||
|
return p, nil
|
||||||
case key.Matches(msg, p.common.Keymap.Next):
|
case key.Matches(msg, p.common.Keymap.Next):
|
||||||
if p.columnCursor == 0 {
|
if p.columnCursor == 0 {
|
||||||
picker, cmd := p.areaPicker.Update(msg)
|
picker, cmd := p.areaPicker.Update(msg)
|
||||||
p.areaPicker = picker.(*areaPicker)
|
p.areaPicker = picker.(*areaPicker)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
} else {
|
} else if p.columnCursor == 1 {
|
||||||
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
model, cmd := p.areas[p.area].Update(nextFieldMsg{})
|
||||||
p.areas[p.area] = model.(area)
|
p.areas[p.area] = model.(area)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
}
|
}
|
||||||
|
return p, nil
|
||||||
case key.Matches(msg, p.common.Keymap.Ok):
|
case key.Matches(msg, p.common.Keymap.Ok):
|
||||||
|
isFiltering := p.areas[p.area].IsFiltering()
|
||||||
model, cmd := p.areas[p.area].Update(msg)
|
model, cmd := p.areas[p.area].Update(msg)
|
||||||
if p.area != 3 {
|
if p.area != 3 {
|
||||||
p.areas[p.area] = model.(area)
|
p.areas[p.area] = model.(area)
|
||||||
|
if isFiltering {
|
||||||
|
return p, cmd
|
||||||
|
}
|
||||||
return p, tea.Batch(cmd, nextField())
|
return p, tea.Batch(cmd, nextField())
|
||||||
}
|
}
|
||||||
return p, cmd
|
return p, cmd
|
||||||
@ -241,6 +279,10 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
picker, cmd := p.areaPicker.Update(msg)
|
picker, cmd := p.areaPicker.Update(msg)
|
||||||
p.areaPicker = picker.(*areaPicker)
|
p.areaPicker = picker.(*areaPicker)
|
||||||
return p, cmd
|
return p, cmd
|
||||||
|
} else if p.columnCursor == 2 {
|
||||||
|
var cmd tea.Cmd
|
||||||
|
p.infoViewport, cmd = p.infoViewport.Update(msg)
|
||||||
|
return p, cmd
|
||||||
} else {
|
} else {
|
||||||
model, cmd := p.areas[p.area].Update(msg)
|
model, cmd := p.areas[p.area].Update(msg)
|
||||||
p.areas[p.area] = model.(area)
|
p.areas[p.area] = model.(area)
|
||||||
@ -253,29 +295,31 @@ func (p *TaskEditorPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
func (p *TaskEditorPage) View() string {
|
func (p *TaskEditorPage) View() string {
|
||||||
var focusedStyle, blurredStyle lipgloss.Style
|
var focusedStyle, blurredStyle lipgloss.Style
|
||||||
if p.mode == modeInsert {
|
if p.mode == modeInsert {
|
||||||
focusedStyle = p.common.Styles.ColumnInsert.Width(p.colWidth).Height(p.colHeight)
|
focusedStyle = p.common.Styles.ColumnInsert
|
||||||
} else {
|
} else {
|
||||||
focusedStyle = p.common.Styles.ColumnFocused.Width(p.colWidth).Height(p.colHeight)
|
focusedStyle = p.common.Styles.ColumnFocused
|
||||||
}
|
}
|
||||||
blurredStyle = p.common.Styles.ColumnBlurred.Width(p.colWidth).Height(p.colHeight)
|
blurredStyle = p.common.Styles.ColumnBlurred
|
||||||
// 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())
|
|
||||||
|
|
||||||
|
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 != "" {
|
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(
|
area = lipgloss.JoinHorizontal(
|
||||||
lipgloss.Top,
|
lipgloss.Top,
|
||||||
area,
|
area,
|
||||||
p.common.Styles.ColumnFocused.Render(p.common.TW.GetInformation(&p.task)),
|
infoView,
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs := ""
|
tabs := ""
|
||||||
@ -305,8 +349,11 @@ type area interface {
|
|||||||
tea.Model
|
tea.Model
|
||||||
SetCursor(c int)
|
SetCursor(c int)
|
||||||
GetName() string
|
GetName() string
|
||||||
|
IsFiltering() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type focusMsg struct{}
|
||||||
|
|
||||||
type areaPicker struct {
|
type areaPicker struct {
|
||||||
common *common.Common
|
common *common.Common
|
||||||
list list.Model
|
list list.Model
|
||||||
@ -378,26 +425,54 @@ func (a *areaPicker) View() string {
|
|||||||
return a.list.View()
|
return a.list.View()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EditableField interface {
|
||||||
|
tea.Model
|
||||||
|
Focus() tea.Cmd
|
||||||
|
Blur() tea.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
type taskEdit struct {
|
type taskEdit struct {
|
||||||
common *common.Common
|
common *common.Common
|
||||||
fields []huh.Field
|
fields []EditableField
|
||||||
cursor int
|
cursor int
|
||||||
|
|
||||||
|
projectPicker *picker.Picker
|
||||||
// newProjectName *string
|
// newProjectName *string
|
||||||
newAnnotation *string
|
newAnnotation *string
|
||||||
udaValues map[string]*string
|
udaValues map[string]*string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
|
func NewTaskEdit(com *common.Common, task *taskwarrior.Task, isNew bool) *taskEdit {
|
||||||
// newProject := ""
|
// newProject := ""
|
||||||
projectOptions := append([]string{"(none)"}, com.TW.GetProjects()...)
|
|
||||||
if task.Project == "" {
|
if task.Project == "" {
|
||||||
task.Project = "(none)"
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []picker.PickerOption{}
|
||||||
|
if isNew {
|
||||||
|
opts = append(opts, picker.WithFilterByDefault(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
projPicker := picker.New(com, "Project", itemProvider, onSelect, opts...)
|
||||||
|
projPicker.SetSize(70, 8)
|
||||||
|
projPicker.SelectItemByFilterValue(task.Project)
|
||||||
|
projPicker.Blur()
|
||||||
|
|
||||||
defaultKeymap := huh.NewDefaultKeyMap()
|
defaultKeymap := huh.NewDefaultKeyMap()
|
||||||
|
|
||||||
fields := []huh.Field{
|
fields := []EditableField{
|
||||||
huh.NewInput().
|
huh.NewInput().
|
||||||
Title("Task").
|
Title("Task").
|
||||||
Value(&task.Description).
|
Value(&task.Description).
|
||||||
@ -411,12 +486,7 @@ func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
|
|||||||
Prompt(": ").
|
Prompt(": ").
|
||||||
WithTheme(com.Styles.Form),
|
WithTheme(com.Styles.Form),
|
||||||
|
|
||||||
input.NewSelect(com).
|
projPicker,
|
||||||
Options(true, input.NewOptions(projectOptions...)...).
|
|
||||||
Title("Project").
|
|
||||||
Value(&task.Project).
|
|
||||||
WithKeyMap(defaultKeymap).
|
|
||||||
WithTheme(com.Styles.Form),
|
|
||||||
|
|
||||||
// huh.NewInput().
|
// huh.NewInput().
|
||||||
// Title("New Project").
|
// Title("New Project").
|
||||||
@ -509,8 +579,9 @@ func NewTaskEdit(com *common.Common, task *taskwarrior.Task) *taskEdit {
|
|||||||
WithTheme(com.Styles.Form))
|
WithTheme(com.Styles.Form))
|
||||||
|
|
||||||
t := taskEdit{
|
t := taskEdit{
|
||||||
common: com,
|
common: com,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
|
projectPicker: projPicker,
|
||||||
|
|
||||||
udaValues: udaValues,
|
udaValues: udaValues,
|
||||||
|
|
||||||
@ -527,6 +598,13 @@ func (t *taskEdit) GetName() string {
|
|||||||
return "Task"
|
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) {
|
func (t *taskEdit) SetCursor(c int) {
|
||||||
t.fields[t.cursor].Blur()
|
t.fields[t.cursor].Blur()
|
||||||
if c < 0 {
|
if c < 0 {
|
||||||
@ -538,11 +616,25 @@ func (t *taskEdit) SetCursor(c int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *taskEdit) Init() tea.Cmd {
|
func (t *taskEdit) Init() tea.Cmd {
|
||||||
return nil
|
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) {
|
func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
switch msg.(type) {
|
switch msg.(type) {
|
||||||
|
case focusMsg:
|
||||||
|
if len(t.fields) > 0 {
|
||||||
|
return t, t.fields[t.cursor].Focus()
|
||||||
|
}
|
||||||
case nextFieldMsg:
|
case nextFieldMsg:
|
||||||
if t.cursor == len(t.fields)-1 {
|
if t.cursor == len(t.fields)-1 {
|
||||||
t.fields[t.cursor].Blur()
|
t.fields[t.cursor].Blur()
|
||||||
@ -561,7 +653,7 @@ func (t *taskEdit) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
t.fields[t.cursor].Focus()
|
t.fields[t.cursor].Focus()
|
||||||
default:
|
default:
|
||||||
field, cmd := t.fields[t.cursor].Update(msg)
|
field, cmd := t.fields[t.cursor].Update(msg)
|
||||||
t.fields[t.cursor] = field.(huh.Field)
|
t.fields[t.cursor] = field.(EditableField)
|
||||||
return t, cmd
|
return t, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,6 +716,13 @@ func (t *tagEdit) GetName() string {
|
|||||||
return "Tags"
|
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) {
|
func (t *tagEdit) SetCursor(c int) {
|
||||||
t.fields[t.cursor].Blur()
|
t.fields[t.cursor].Blur()
|
||||||
if c < 0 {
|
if c < 0 {
|
||||||
@ -736,6 +835,10 @@ func (t *timeEdit) GetName() string {
|
|||||||
return "Dates"
|
return "Dates"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *timeEdit) IsFiltering() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *timeEdit) SetCursor(c int) {
|
func (t *timeEdit) SetCursor(c int) {
|
||||||
if len(t.fields) == 0 {
|
if len(t.fields) == 0 {
|
||||||
return
|
return
|
||||||
@ -868,6 +971,10 @@ func (d *detailsEdit) GetName() string {
|
|||||||
return "Details"
|
return "Details"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *detailsEdit) IsFiltering() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (d *detailsEdit) SetCursor(c int) {
|
func (d *detailsEdit) SetCursor(c int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1035,6 +1142,8 @@ func (d *detailsEdit) View() string {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
func (p *TaskEditorPage) updateTasksCmd() tea.Msg {
|
||||||
|
p.task.Project = p.areas[0].(*taskEdit).projectPicker.GetValue()
|
||||||
|
|
||||||
if p.task.Project == "(none)" {
|
if p.task.Project == "(none)" {
|
||||||
p.task.Project = ""
|
p.task.Project = ""
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user