package tui import ( "fmt" "strings" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "tsplay/internal/discovery" ) func (m Model) updateLogin(msg tea.Msg) (tea.Model, tea.Cmd) { if key, ok := msg.(tea.KeyMsg); ok { switch key.String() { case "ctrl+c", "esc": return m, tea.Quit case "ctrl+n": // Cycle through discovered servers, filling the URL field. if len(m.discovered) > 1 { m.discIdx = (m.discIdx + 1) % len(m.discovered) m.inputs[0].SetValue(m.discovered[m.discIdx].Address) m.status = discoveryStatus(m.discovered, m.discIdx) } return m, nil case "ctrl+r": m.status = "Scanning the network for Jellyfin servers…" return m, discoverCmd() case "tab", "down": m.focusField(m.focus + 1) return m, textinput.Blink case "shift+tab", "up": m.focusField(m.focus - 1) return m, textinput.Blink case "enter": server := strings.TrimSpace(m.inputs[0].Value()) user := strings.TrimSpace(m.inputs[1].Value()) pass := m.inputs[2].Value() if server == "" || user == "" { m.status = "Server URL and username are required." return m, nil } if m.logging { return m, nil } m.logging = true m.status = "Connecting…" return m, m.doLogin(server, user, pass) } } // Feed the event to every input so the cursor blinks and the focused one // receives text. var cmds []tea.Cmd for i := range m.inputs { var c tea.Cmd m.inputs[i], c = m.inputs[i].Update(msg) cmds = append(cmds, c) } return m, tea.Batch(cmds...) } func (m *Model) focusField(i int) { n := len(m.inputs) i = ((i % n) + n) % n // wrap for j := range m.inputs { if j == i { m.inputs[j].Focus() } else { m.inputs[j].Blur() } } m.focus = i } // discoveryStatus formats the "found N servers" line shown under the form. func discoveryStatus(servers []discovery.Server, idx int) string { s := servers[idx] name := s.Name if name == "" { name = s.Address } if len(servers) == 1 { return "Found: " + name } return fmt.Sprintf("Found %d servers — using %s [%d/%d]", len(servers), name, idx+1, len(servers)) } func (m Model) viewLogin() string { var b strings.Builder b.WriteString(m.st.title.Render("tsplay") + m.st.muted.Render(" — Jellyfin terminal player")) b.WriteString("\n\n") for i := range m.inputs { b.WriteString(m.inputs[i].View()) b.WriteString("\n") } b.WriteString("\n") help := "tab move · enter sign in · esc quit" if len(m.discovered) > 1 { help = "tab move · ctrl+n next server · enter sign in · esc quit" } else { help += " · ctrl+r rescan" } b.WriteString(m.st.help.Render(help)) body := m.st.box.Render(b.String()) // Discovery messages are informational (accent); real errors stay red. footer := "" if m.status != "" { style := m.st.accent if strings.HasPrefix(m.status, "Login failed") || strings.HasPrefix(m.status, "No servers") { style = m.st.bad } footer = "\n" + style.Render(m.status) } return lipgloss.JoinVertical(lipgloss.Left, body, footer) }