package tui import ( "fmt" "strings" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" ) type State int const ( StateForm State = iota StateGenerating StateDone StateError ) type genResultMsg struct { result GenerateResult } type spinnerState struct { frame int } func (s *spinnerState) View() string { frames := []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} return frames[s.frame%len(frames)] } type Model struct { state State form FormModel result GenerateResult spinner spinnerState isDark bool width int height int } func New() Model { return Model{ state: StateForm, form: NewForm(), isDark: true, } } func (m Model) Init() tea.Cmd { return textinput.Blink } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.BackgroundColorMsg: m.isDark = msg.IsDark() m.form.SetDark(m.isDark) return m, nil case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height m.form.SetSize(msg.Width, msg.Height) return m, nil case tea.KeyPressMsg: if isQuit(msg) && m.state != StateGenerating { return m, tea.Quit } switch m.state { case StateForm: if isSubmit(msg) && m.form.focusIndex == fieldGenerateBtn { cfg, err := m.form.ToConfig() if err != nil { m.form.SetError(err.Error()) return m, nil } m.state = StateGenerating return m, func() tea.Msg { return genResultMsg{result: Generate(cfg)} } } if isSubmit(msg) && m.form.focusIndex == fieldCancelBtn { return m, tea.Quit } var cmd tea.Cmd m.form, cmd = m.form.Update(msg) return m, cmd case StateDone, StateError: return m, tea.Quit } case genResultMsg: m.result = msg.result if m.result.Error != nil { m.state = StateError } else { m.state = StateDone } return m, nil } if m.state == StateForm { var cmd tea.Cmd m.form, cmd = m.form.Update(msg) return m, cmd } return m, nil } func (m Model) View() tea.View { var content string switch m.state { case StateForm: content = m.form.View() case StateGenerating: content = m.renderGenerating() case StateDone: content = m.renderDone() case StateError: content = m.renderError() } v := tea.NewView(content) v.AltScreen = true return v } func (m Model) renderGenerating() string { var b strings.Builder b.WriteString(titleStyle.Render("dirmd")) b.WriteString("\n\n") b.WriteString(fmt.Sprintf(" %s Generating...\n", m.spinner.View())) b.WriteString("\n") b.WriteString(footerStyle.Render("Please wait.")) content := appBorder.Render(b.String()) if m.width > 0 && m.height > 0 { return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content) } return content } func (m Model) renderDone() string { var b strings.Builder b.WriteString(titleStyle.Render("dirmd")) b.WriteString("\n\n") b.WriteString(successStyle.Render("✓ Done")) b.WriteString("\n\n") for _, f := range m.result.Files { b.WriteString(fmt.Sprintf(" %s\n", f)) } if m.result.Uploaded { b.WriteString(successStyle.Render("\n Uploaded to Proton Drive")) } b.WriteString("\n\n") b.WriteString(footerStyle.Render("Press any key to exit.")) content := appBorder.Render(b.String()) if m.width > 0 && m.height > 0 { return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content) } return content } func (m Model) renderError() string { var b strings.Builder b.WriteString(titleStyle.Render("dirmd")) b.WriteString("\n\n") b.WriteString(errorStyle.Render("✗ Error")) b.WriteString("\n\n") b.WriteString(fmt.Sprintf(" %s\n", m.result.Error.Error())) b.WriteString("\n") b.WriteString(footerStyle.Render("Press any key to exit.")) content := appBorder.Render(b.String()) if m.width > 0 && m.height > 0 { return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, content) } return content }