summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Bütow <1224732+snonux@users.noreply.github.com>2025-06-21 23:16:30 +0300
committerPaul Bütow <1224732+snonux@users.noreply.github.com>2025-06-21 23:16:30 +0300
commitd9e09559ad92df8284ac526a8ea4de359a761497 (patch)
tree88cc9f2193833efe0e1a53db3d91c3e6aba01d00 /internal
parent449f343c7da6071599c5c8266f3f9b43e0b0a315 (diff)
Add blinking done effect and update tag hotkey
Diffstat (limited to 'internal')
-rw-r--r--internal/ui/table.go88
-rw-r--r--internal/ui/table_test.go8
2 files changed, 78 insertions, 18 deletions
diff --git a/internal/ui/table.go b/internal/ui/table.go
index 3b1dacf..d0c62a0 100644
--- a/internal/ui/table.go
+++ b/internal/ui/table.go
@@ -72,6 +72,11 @@ type Model struct {
undoStack []string
+ blinkID int
+ blinkRow int
+ blinkOn bool
+ blinkCount int
+
cellExpanded bool
windowHeight int
@@ -96,6 +101,11 @@ type Model struct {
// editDoneMsg is emitted when the external editor process finishes.
type editDoneMsg struct{ err error }
+type blinkMsg struct{}
+
+const blinkInterval = 250 * time.Millisecond
+const blinkCycles = 8
+
// editCmd returns a command that edits the task and sends an
// editDoneMsg once the process is complete.
func editCmd(id int) tea.Cmd {
@@ -103,6 +113,10 @@ func editCmd(id int) tea.Cmd {
return tea.ExecProcess(c, func(err error) tea.Msg { return editDoneMsg{err: err} })
}
+func blinkCmd() tea.Cmd {
+ return tea.Tick(blinkInterval, func(time.Time) tea.Msg { return blinkMsg{} })
+}
+
// New creates a new UI model with the provided rows.
func New(filters []string) (Model, error) {
m := Model{filters: filters}
@@ -223,6 +237,29 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
_ = msg.err
m.reload()
return m, nil
+ case blinkMsg:
+ if m.blinkID != 0 {
+ m.blinkOn = !m.blinkOn
+ m.blinkCount++
+ m.updateBlinkRow()
+ if m.blinkCount >= blinkCycles {
+ id := m.blinkID
+ m.blinkID = 0
+ m.blinkOn = false
+ m.blinkCount = 0
+ for _, tsk := range m.tasks {
+ if tsk.ID == id {
+ m.undoStack = append(m.undoStack, tsk.UUID)
+ break
+ }
+ }
+ task.Done(id)
+ m.reload()
+ return m, nil
+ }
+ return m, blinkCmd()
+ }
+ return m, nil
case tea.KeyMsg:
if m.annotating {
switch msg.Type {
@@ -460,14 +497,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if row := m.tbl.SelectedRow(); row != nil {
idStr := ansi.Strip(row[0])
if id, err := strconv.Atoi(idStr); err == nil {
- task.Done(id)
- for _, tsk := range m.tasks {
- if tsk.ID == id {
- m.undoStack = append(m.undoStack, tsk.UUID)
- break
- }
- }
- m.reload()
+ m.blinkID = id
+ m.blinkRow = m.tbl.Cursor()
+ m.blinkOn = true
+ m.blinkCount = 0
+ m.updateBlinkRow()
+ return m, blinkCmd()
}
}
case "U":
@@ -542,14 +577,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.updateTableHeight()
return m, nil
case "t":
- m.theme = RandomTheme()
- m.applyTheme()
- m.reload()
- return m, nil
- case "T":
- m.theme = m.defaultTheme
- m.applyTheme()
- m.reload()
+ if row := m.tbl.SelectedRow(); row != nil {
+ idStr := ansi.Strip(row[0])
+ if id, err := strconv.Atoi(idStr); err == nil {
+ m.tagsID = id
+ m.tagsEditing = true
+ m.tagsInput.SetValue("")
+ m.tagsInput.Focus()
+ m.updateTableHeight()
+ return m, nil
+ }
+ }
return m, nil
case "/", "?":
m.searching = true
@@ -675,8 +713,7 @@ func (m Model) View() string {
"A: replace annotations",
"p: set priority",
"f: change filter",
- "t: randomize theme",
- "T: reset theme",
+ "t: edit tags",
"/, ?: search",
"n/N: next/prev search match",
"esc: close help/search",
@@ -764,6 +801,9 @@ func (m Model) taskToRow(t task.Task) atable.Row {
if t.Start != "" {
style = style.Background(lipgloss.Color(m.theme.StartBG))
}
+ if t.ID == m.blinkID && m.blinkOn {
+ style = style.Reverse(true)
+ }
age := ""
if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil {
@@ -896,6 +936,9 @@ func (m Model) taskToRowSearch(t task.Task, re *regexp.Regexp, styles atable.Sty
if t.Start != "" {
rowStyle = rowStyle.Background(lipgloss.Color(m.theme.StartBG))
}
+ if t.ID == m.blinkID && m.blinkOn {
+ rowStyle = rowStyle.Reverse(true)
+ }
age := ""
if ts, err := time.Parse("20060102T150405Z", t.Entry); err == nil {
@@ -1007,6 +1050,15 @@ func (m *Model) updateSelectionHighlight(prevRow, newRow, prevCol, newCol int) {
m.tbl.SetRows(rows)
}
+func (m *Model) updateBlinkRow() {
+ if m.blinkRow < 0 || m.blinkRow >= len(m.tasks) || m.tbl.Rows() == nil {
+ return
+ }
+ rows := m.tbl.Rows()
+ rows[m.blinkRow] = m.taskToRowSearch(m.tasks[m.blinkRow], m.searchRegex, m.tblStyles, -1)
+ m.tbl.SetRows(rows)
+}
+
// updateTableHeight recalculates the table height based on the current window
// size and which auxiliary views are open.
func (m *Model) updateTableHeight() {
diff --git a/internal/ui/table_test.go b/internal/ui/table_test.go
index 89e1af6..3bf6f3e 100644
--- a/internal/ui/table_test.go
+++ b/internal/ui/table_test.go
@@ -164,6 +164,10 @@ func TestDoneHotkey(t *testing.T) {
mv, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'D'}})
m = mv.(Model)
+ for i := 0; i < blinkCycles; i++ {
+ mv, _ = m.Update(blinkMsg{})
+ m = mv.(Model)
+ }
data, err := os.ReadFile(doneFile)
if err != nil {
@@ -209,6 +213,10 @@ func TestUndoHotkey(t *testing.T) {
mv, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'D'}})
m = mv.(Model)
+ for i := 0; i < blinkCycles; i++ {
+ mv, _ = m.Update(blinkMsg{})
+ m = mv.(Model)
+ }
mv, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'U'}})
m = mv.(Model)