package main import ( "fmt" tea "github.com/charmbracelet/bubbletea" "os" "time" ) type Mode int64 const ( Select Mode = iota Create Draw ) type Card struct { Front string Back string LastCorrect time.Time } type model struct { cards []Card // items on the to-do list cursor int // which to-do list item our cursor is pointing at selected map[int]struct{} // which to-do items are selected mode Mode } func initialModel() model { return model{ cards: []Card{ {Front: "Hello (JP)", Back: "Konnichi wa"}, {Front: "Hello (SP)", Back: "Hola"}, {Front: "Hello (DE)", Back: "Guten Tag"}, }, selected: make(map[int]struct{}), mode: Select, } } func (m *model) Init() tea.Cmd { // Just return `nil`, which means "no I/O right now, please." return nil } func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.mode == Draw { return m.updateDraw(msg) } // Select is default mode return m.updateSelect(msg) } func (m *model) updateDraw(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { // Is it a key press? case tea.KeyMsg: switch msg.String() { case "s": m.switchMode(Select) // These keys should exit the program. case "ctrl+c", "q": return m, tea.Quit } } return m, nil } func (m *model) switchMode(mode Mode) { // TODO: initialization stuff switch mode { case Select: m.mode = Select case Draw: m.mode = Draw case Create: m.mode = Create } } func (m *model) updateSelect(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { // Is it a key press? case tea.KeyMsg: switch msg.String() { // These keys should exit the program. case "ctrl+c", "q": return m, tea.Quit // The "up" and "k" keys move the cursor up case "up", "k": if m.cursor > 0 { m.cursor-- } // The "down" and "j" keys move the cursor down case "down", "j": if m.cursor < len(m.cards)-1 { m.cursor++ } case "d": m.switchMode(Draw) // The "enter" key and the spacebar (a literal space) toggle // the selected state for the item that the cursor is pointing at. case "enter", " ": _, ok := m.selected[m.cursor] if ok { delete(m.selected, m.cursor) } else { m.selected[m.cursor] = struct{}{} } } } // Return the updated model to the Bubble Tea runtime for processing. // Note that we're not returning a command. return m, nil } func (m *model) cardListToString() string { s := "" for i, card := range m.cards { if _, ok := m.selected[i]; ok { s += fmt.Sprintf("%s [%s]\n", card.Front, card.Back) } } return s } func (m *model) View() string { if m.mode == Draw { return m.viewDraw() } // Select is default mode return m.viewSelect() } func (m *model) viewDraw() string { s := "Draw Mode:\n\n" s += m.cardListToString() return s } func (m *model) viewSelect() string { s := "Select cards to query:\n\n" // Iterate over our choices for i, choice := range m.cards { // Is the cursor pointing at this choice? cursor := " " // no cursor if m.cursor == i { cursor = ">" // cursor! } // Is this choice selected? checked := " " // not selected if _, ok := m.selected[i]; ok { checked = "✓" // selected! } // Render the row s += fmt.Sprintf("%s [%s] %s\n", cursor, checked, choice.Front) } // The footer s += "\nPress c to Create (_unimplemented_).\nPress d to Draw.\nPress q to quit.\n" // Send the UI for rendering return s } func main() { initial := initialModel() p := tea.NewProgram(&initial) if err := p.Start(); err != nil { fmt.Printf("Alas, there's been an error: %v", err) os.Exit(1) } }