main.go

  1package main
  2
  3import (
  4	"fmt"
  5	"log"
  6	"os"
  7
  8	"github.com/charmbracelet/bubbles/textarea"
  9	"github.com/charmbracelet/bubbles/textinput"
 10	tea "github.com/charmbracelet/bubbletea"
 11	"github.com/charmbracelet/lipgloss"
 12)
 13
 14// --- STYLES ---
 15// Define styles for different parts of the UI using lipgloss.
 16var (
 17	// Style for the main container/dialog box.
 18	dialogBoxStyle = lipgloss.NewStyle().
 19		Border(lipgloss.RoundedBorder()).
 20		BorderForeground(lipgloss.Color("#874BFD")).
 21		Padding(1, 0).
 22		BorderTop(true).
 23		BorderLeft(true).
 24		BorderRight(true).
 25		BorderBottom(true)
 26
 27	// Style for the text labels (e.g., "To:", "Subject:").
 28	labelStyle = lipgloss.NewStyle().Padding(0, 1)
 29
 30	// Style for the help text at the bottom.
 31	helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
 32
 33	// Style for success messages.
 34	successStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("42")).Bold(true)
 35
 36	// Style for error/sending messages.
 37	infoStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
 38)
 39
 40// --- MODEL ---
 41// The model represents the state of our TUI application.
 42type model struct {
 43	// Inputs for the email fields.
 44	recipientInput textinput.Model
 45	subjectInput   textinput.Model
 46	bodyArea       textarea.Model
 47
 48	// index tracks which input is currently focused.
 49	// 0: recipient, 1: subject, 2: body
 50	index int
 51
 52	// State flags
 53	sending bool // Is the email currently being "sent"?
 54	sent    bool // Has the email been "sent"?
 55	err     error // Any error that occurred.
 56}
 57
 58// initialModel creates the starting state of the application.
 59func initialModel() model {
 60	// --- Recipient Input ---
 61	ti := textinput.New()
 62	ti.Placeholder = "recipient@example.com"
 63	ti.Focus() // Start with the recipient field focused.
 64	ti.CharLimit = 156
 65	ti.Width = 50
 66	ti.Prompt = "To:      "
 67
 68	// --- Subject Input ---
 69	si := textinput.New()
 70	si.Placeholder = "Hello there!"
 71	si.CharLimit = 156
 72	si.Width = 50
 73	si.Prompt = "Subject: "
 74
 75	// --- Body Text Area ---
 76	ta := textarea.New()
 77	ta.Placeholder = "Dear friend, ..."
 78	ta.SetWidth(50)
 79	ta.SetHeight(10)
 80
 81	return model{
 82		recipientInput: ti,
 83		subjectInput:   si,
 84		bodyArea:       ta,
 85		index:          0,
 86		err:            nil,
 87	}
 88}
 89
 90// --- BUBBLETEA METHODS ---
 91
 92// Init is the first function that will be called. It returns a command.
 93// We'll use it to make the cursor blink.
 94func (m model) Init() tea.Cmd {
 95	return textinput.Blink
 96}
 97
 98// Update handles all user input and updates the model accordingly.
 99func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
100	var cmd tea.Cmd
101	var cmds []tea.Cmd
102
103	switch msg := msg.(type) {
104	// Handle key presses
105	case tea.KeyMsg:
106		switch msg.Type {
107		// Quit the application
108		case tea.KeyCtrlC, tea.KeyEsc:
109			return m, tea.Quit
110
111		// Trigger the "send" action
112		case tea.KeyCtrlS:
113			// If we are not already sending, start the sending process.
114			if !m.sending {
115				m.sending = true
116				// In a real app, this is where you'd send the email.
117				// We'll return a command that simulates a network operation.
118				// For now, we'll just pretend it was successful after a short delay.
119				return m, func() tea.Msg {
120					// time.Sleep(time.Second * 2) // Simulate network delay
121					return "sent" // Send a message back on success
122				}
123			}
124
125		// Handle Tab and Shift+Tab to navigate between fields
126		case tea.KeyTab, tea.KeyShiftTab:
127			if msg.Type == tea.KeyTab {
128				m.index = (m.index + 1) % 3
129			} else {
130				m.index--
131				if m.index < 0 {
132					m.index = 2
133				}
134			}
135
136			// Blur all inputs
137			m.recipientInput.Blur()
138			m.subjectInput.Blur()
139			m.bodyArea.Blur()
140
141			// Focus the correct input based on the index
142			switch m.index {
143			case 0:
144				m.recipientInput.Focus()
145			case 1:
146				m.subjectInput.Focus()
147			case 2:
148				m.bodyArea.Focus()
149			}
150			return m, nil
151		}
152
153	// Handle the "sent" message from our simulated send command
154	case string:
155		if msg == "sent" {
156			m.sending = false
157			m.sent = true
158			// Quit after a short delay to show the success message.
159			return m, tea.Sequence(
160				func() tea.Msg {
161					// time.Sleep(time.Second)
162					return nil
163				},
164				tea.Quit,
165			)
166		}
167	}
168
169	// Pass the message to the focused input field for handling.
170	switch m.index {
171	case 0:
172		m.recipientInput, cmd = m.recipientInput.Update(msg)
173	case 1:
174		m.subjectInput, cmd = m.subjectInput.Update(msg)
175	case 2:
176		m.bodyArea, cmd = m.bodyArea.Update(msg)
177	}
178	cmds = append(cmds, cmd)
179
180	return m, tea.Batch(cmds...)
181}
182
183// View renders the UI based on the current model state.
184func (m model) View() string {
185	// If the email was sent, just show a success message.
186	if m.sent {
187		return successStyle.Render("\n   ✓ Email sent successfully! \n\n")
188	}
189
190	// If we are "sending", show an info message.
191	if m.sending {
192		return infoStyle.Render("\n   Sending email... \n\n")
193	}
194
195	// Otherwise, render the form.
196	var s string
197
198	s += "\n"
199	s += m.recipientInput.View() + "\n"
200	s += m.subjectInput.View() + "\n"
201	s += m.bodyArea.View()
202
203	help := fmt.Sprintf("\n\n %s | %s | %s | %s \n",
204		"Tab: Next Field", "Esc: Quit", "Ctrl+S: Send", helpStyle.Render("Focus: "+m.focusString()))
205	s += helpStyle.Render(help)
206
207	return dialogBoxStyle.Render(s)
208}
209
210// Helper function to show which field is focused.
211func (m model) focusString() string {
212	switch m.index {
213	case 0:
214		return "Recipient"
215	case 1:
216		return "Subject"
217	case 2:
218		return "Body"
219	default:
220		return ""
221	}
222}
223
224// --- MAIN FUNCTION ---
225func main() {
226	// Initialize the Bubble Tea program.
227	p := tea.NewProgram(initialModel())
228
229	// Run the program and handle any errors.
230	if _, err := p.Run(); err != nil {
231		log.Fatalf("Alas, there's been an error: %v", err)
232		os.Exit(1)
233	}
234}