Skip to content

Commit

Permalink
convoluted time entry model
Browse files Browse the repository at this point in the history
  • Loading branch information
rusq committed Oct 19, 2024
1 parent 1016880 commit 05eb40d
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 2 deletions.
3 changes: 3 additions & 0 deletions cmd/slackdump/internal/ui/cfgui/updaters/bool.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package updaters

import tea "github.com/charmbracelet/bubbletea"

// BoolModel is a model for updating a boolean value. On startup, it sends
// a message to set the value to the opposite of the current value, and sends
// OnClose message. It has no view.
type BoolModel struct {
Value *bool
}
Expand Down
196 changes: 194 additions & 2 deletions cmd/slackdump/internal/ui/cfgui/updaters/date.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,204 @@
package updaters

import (
"fmt"
"log"
"regexp"
"strings"
"time"

"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
datepicker "github.com/ethanefung/bubble-datepicker"
)

type DateModel struct {
Value *time.Time
m datepicker.Model
Value *time.Time
dm datepicker.Model
finishing bool
timeEnabled bool
}

func NewDTTM(ptrTime *time.Time) DateModel {
m := datepicker.New(*ptrTime)
m.SelectDate()
return DateModel{
Value: ptrTime,
dm: m,
timeEnabled: true,
}
}

func (m DateModel) Init() tea.Cmd {
return m.dm.Init()
}

func (m DateModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd

switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "esc", "ctrl+c":
return m, OnClose
case "enter":
*m.Value = m.dm.Time
m.finishing = true
return m, OnClose
}
}

m.dm, cmd = m.dm.Update(msg)
cmds = append(cmds, cmd)

return m, tea.Batch(cmds...)
}

func (m DateModel) View() string {
var b strings.Builder
b.WriteString(m.dm.View())
if m.timeEnabled {
b.WriteString("\n\nTime: " + m.Value.Format("15:04:05") + " (UTC)")
}
b.WriteString("\n\n" + m.dm.Styles.Text.Render("Use arrow keys to navigate, tab/shift+tab to switch between fields, and enter to select."))
return b.String()
}

// KeyMap is the key bindings for different actions within the datepicker.
type KeyMap struct {
Up key.Binding
Right key.Binding
Down key.Binding
Left key.Binding
FocusPrev key.Binding
FocusNext key.Binding
Quit key.Binding
}

type TimeModel struct {
t time.Time
entry [6]int
maxnum [3]int
cursor int

KeyMap KeyMap

focused bool
finishing bool
}

func DefaultKeyMap() KeyMap {
return KeyMap{
Up: key.NewBinding(key.WithKeys("up", "k", "+")),
Right: key.NewBinding(key.WithKeys("right", "l")),
Down: key.NewBinding(key.WithKeys("down", "j", "-")),
Left: key.NewBinding(key.WithKeys("left", "h")),
FocusPrev: key.NewBinding(key.WithKeys("shift+tab")),
FocusNext: key.NewBinding(key.WithKeys("tab")),
Quit: key.NewBinding(key.WithKeys("ctrl+c", "q")),
}
}

func NewTime(t time.Time) *TimeModel {
return &TimeModel{
t: t,
entry: [6]int{0, 0, 0, 0, 0, 0},
maxnum: [3]int{23, 59, 59},
cursor: 0,
focused: false,
KeyMap: DefaultKeyMap(),
}
}

func (m *TimeModel) Focus() {
m.focused = true
}

func (m *TimeModel) Init() tea.Cmd {
return nil
}

var digitsRe = regexp.MustCompile(`\d`)

func (m *TimeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.KeyMap.Quit):
m.finishing = true
return m, tea.Quit
case key.Matches(msg, m.KeyMap.Left):
m.cursor--
if m.cursor < 0 {
m.cursor = 0
}
case key.Matches(msg, m.KeyMap.Right):
m.cursor++
if m.cursor > len(m.entry)-1 {
m.cursor = len(m.entry) - 1
}
case key.Matches(msg, m.KeyMap.Up):
currTuple := m.cursor / 2
isLo := m.cursor % 2
isHi := (1 - isLo)
number := m.entry[currTuple*2]*10 + m.entry[currTuple*2+1] + (isHi*10 + isLo)
log.Printf("+ number: %d, maxnum: %d, cursor: %d, entry@cursor: %d, hi: %d, lo: %d", number, m.maxnum[currTuple], m.cursor, m.entry[m.cursor], isHi, isLo)
if number <= m.maxnum[currTuple] && m.entry[m.cursor] < 9 {
m.entry[m.cursor]++
}
case key.Matches(msg, m.KeyMap.Down):
currTuple := m.cursor / 2
isLo := m.cursor % 2
isHi := (1 - isLo)
number := m.entry[currTuple*2]*10 + m.entry[currTuple*2+1] - (isHi*10 + isLo)
log.Printf("- number: %d, maxnum: %d, cursor: %d, entry@cursor: %d, hi: %d, lo: %d", number, m.maxnum[currTuple], m.cursor, m.entry[m.cursor], isHi, isLo)
if number >= 0 && m.entry[m.cursor] > 0 {
m.entry[m.cursor]--
}
case digitsRe.MatchString(msg.String()):
// TODO: validation
future := make([]int, 6)
copy(future, m.entry[:])
future[m.cursor] = int(msg.String()[0] - '0')
if tupleVal(future, m.cursor/2) <= m.maxnum[m.cursor/2] {
m.entry[m.cursor] = int(msg.String()[0] - '0')
}
if m.cursor < len(m.entry)-1 {
m.cursor++
}
}
}
return m, nil
}

func tupleVal(entry []int, tuple int) int {
if len(entry) < tuple*2+1 {
return -1
}
return entry[tuple*2]*10 + entry[tuple*2+1]
}

func (m *TimeModel) View() string {
if m.finishing {
return ""
}
var buf strings.Builder
buf.WriteString(cursor(m.cursor, 2, '+') + "\n")
fmt.Fprintf(&buf, "%d%d:%d%d:%d%d\n", m.entry[0], m.entry[1], m.entry[2], m.entry[3], m.entry[4], m.entry[5])
buf.WriteString(cursor(m.cursor, 2, '-'))

return buf.String()
}

func cursor(pos int, tupleSz int, char rune) string {
var buf strings.Builder
numTuples := pos / tupleSz
offset := pos % tupleSz

for i := 0; i < numTuples; i++ {
buf.WriteString(strings.Repeat(" ", tupleSz) + " ")
}
buf.WriteString(strings.Repeat(" ", offset) + string(char))
return buf.String()
}
23 changes: 23 additions & 0 deletions cmd/slackdump/internal/ui/cfgui/updaters/examples/date/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"fmt"
"time"

tea "github.com/charmbracelet/bubbletea"

"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui/updaters"
)

func main() {
var t time.Time = time.Now()
updaters.OnClose = tea.Quit

m := updaters.NewDTTM(&t)
mod, err := tea.NewProgram(m).Run()
if err != nil {
panic(err)
}
_ = mod
fmt.Printf("new value: %s\n", t)
}
28 changes: 28 additions & 0 deletions cmd/slackdump/internal/ui/cfgui/updaters/examples/time/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"log"
"os"
"time"

tea "github.com/charmbracelet/bubbletea"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui/updaters"
)

const logname = "time.log"

func main() {
logf, err := os.Create(logname)
if err != nil {
log.Fatal(err)
}
defer logf.Close()
log.SetOutput(logf)

m := updaters.NewTime(time.Now())
p, err := tea.NewProgram(m).Run()
if err != nil {
log.Fatal(err)
}
_ = p
}

0 comments on commit 05eb40d

Please sign in to comment.