package tui import ( "errors" "tailscale-vpn/internal/pages" "tailscale-vpn/internal/services" tea "charm.land/bubbletea/v2" ) func (m TUIInterface) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case pages.HomePageMsg: m.Page = pageHome m.MenuItems = pages.HomeMenuItems(m.VPNConnected, m.HasServers) return m, nil case pages.SettingsPageMsg: m.Page = pageSettings m.SettingsEditMode = "" m.SettingsConfirmDelete = false m.SettingsSelected = 0 return m, loadServersCmd(m.Services) case pages.SelectServerPageMsg: m.Page = pageSelectServer m.SelectServerServers = m.SettingsServers m.SelectServerSelected = 0 for i, srv := range m.SelectServerServers { if m.HasSelectedServer && srv.ID == m.SelectedServer.ID { m.SelectServerSelected = i break } } return m, nil case pages.ServerSelectedMsg: m.SelectedServer = msg.Server m.HasSelectedServer = msg.Server.ID != "" if msg.Err != nil { m.VPNError = msg.Err } return m, func() tea.Msg { return pages.HomePageMsg{} } case pages.ServerSelectionCanceledMsg: return m, func() tea.Msg { return pages.HomePageMsg{} } case pages.ServerListLoadedMsg: m.SettingsServers = msg.Servers m.HasServers = len(msg.Servers) > 0 if msg.Err != nil { m.VPNError = msg.Err } return m, nil case pages.ServerEditMsg: m.SettingsEditMode = msg.Mode m.SettingsConfirmDelete = false if msg.Mode == "edit" { m.SettingsEditingServer = msg.Server m.SettingsFormName = msg.Server.Name m.SettingsFormHost = msg.Server.Host } else { m.SettingsEditingServer = services.Server{} m.SettingsFormName = "" m.SettingsFormHost = "" } m.SettingsFormField = 0 return m, nil case pages.ServerSaveMsg: m.SettingsEditMode = "" if msg.Err != nil { m.VPNError = msg.Err } return m, loadServersCmd(m.Services) case pages.ServerDeleteMsg: m.SettingsConfirmDelete = false if msg.Err != nil { m.VPNError = msg.Err } else { if m.SettingsSelected >= len(m.SettingsServers) { m.SettingsSelected = max(len(m.SettingsServers)-1, 0) } } return m, loadServersCmd(m.Services) case tea.WindowSizeMsg: m.WindowWidth = msg.Width m.WindowHeight = msg.Height return m, nil case tea.KeyPressMsg: if m.Page == pageHome { return m.updateHomePage(msg) } if m.Page == pageSettings { return m.updateSettingsPage(msg) } if m.Page == pageSelectServer { return m.updateSelectServerPage(msg) } case tea.PasteMsg: if m.Page == pageSettings && m.SettingsEditMode != "" { if m.SettingsFormField == 0 { m.SettingsFormName += msg.Content } else if m.SettingsFormField == 1 { m.SettingsFormHost += msg.Content } return m, nil } case pages.VPNStatusMsg: m.VPNConnected = msg.Connected m.VPNLoading = false if msg.Err != nil { m.VPNError = msg.Err } else { m.VPNError = nil } m.MenuItems = pages.HomeMenuItems(m.VPNConnected, m.HasServers) return m, nil case pages.VPNToggleMsg: m.VPNConnected = msg.Connected m.VPNToggleLoading = false if msg.Err != nil { m.VPNError = msg.Err } else { m.VPNError = nil } m.MenuItems = pages.HomeMenuItems(m.VPNConnected, m.HasServers) return m, nil } return m, nil } func (m TUIInterface) updateHomePage(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { if m.VPNLoading || m.VPNToggleLoading { if msg.String() == "ctrl+c" { m.Quitting = true return m, tea.Quit } return m, nil } switch msg.String() { case "up", "k": if m.Selected > 0 { m.Selected-- } return m, nil case "down", "j": if m.Selected < len(m.MenuItems)-1 { m.Selected++ } return m, nil case "enter": if len(m.MenuItems) > 0 { switch m.MenuItems[m.Selected].Key { case "select-server": return m, func() tea.Msg { return pages.SelectServerPageMsg{} } case "settings": return m, func() tea.Msg { return pages.SettingsPageMsg{} } case "on": m.VPNToggleLoading = true m.VPNError = nil return m, turnOnVPNCmd(m.Services) case "off": m.VPNToggleLoading = true m.VPNError = nil return m, turnOffVPNCmd(m.Services) } } return m, nil case "r": m.VPNLoading = true m.VPNError = nil return m, checkVPNStatusCmd(m.Services) case "ctrl+c", "esc": m.Quitting = true return m, tea.Quit } return m, nil } func (m TUIInterface) updateSelectServerPage(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "up", "k": if m.SelectServerSelected > 0 { m.SelectServerSelected-- } return m, nil case "down", "j": if m.SelectServerSelected < len(m.SelectServerServers)-1 { m.SelectServerSelected++ } return m, nil case "enter": if m.SelectServerSelected >= 0 && m.SelectServerSelected < len(m.SelectServerServers) { return m, selectServerCmd(m.Services, m.SelectServerServers[m.SelectServerSelected].ID) } return m, nil case "esc", "ctrl+c": return m, func() tea.Msg { return pages.ServerSelectionCanceledMsg{} } } return m, nil } func (m TUIInterface) updateSettingsPage(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) { if m.SettingsEditMode != "" { switch msg.String() { case "esc": m.SettingsEditMode = "" return m, nil case "enter": if m.SettingsFormField == 2 { if m.SettingsFormName == "" || m.SettingsFormHost == "" { m.VPNError = errors.New("name and host are required") return m, nil } if m.SettingsEditMode == "add" { return m, addServerCmd(m.Services, m.SettingsFormName, m.SettingsFormHost) } return m, updateServerCmd(m.Services, m.SettingsEditingServer.ID, m.SettingsFormName, m.SettingsFormHost) } else if m.SettingsFormField == 3 { m.SettingsEditMode = "" return m, nil } else { m.SettingsFormField = (m.SettingsFormField + 1) % 4 return m, nil } case "up", "k": m.SettingsFormField = (m.SettingsFormField - 1 + 4) % 4 return m, nil case "down", "j": m.SettingsFormField = (m.SettingsFormField + 1) % 4 return m, nil case "tab": m.SettingsFormField = (m.SettingsFormField + 1) % 4 return m, nil case "backtab": m.SettingsFormField = (m.SettingsFormField - 1 + 4) % 4 return m, nil case "backspace": if m.SettingsFormField == 0 && len(m.SettingsFormName) > 0 { m.SettingsFormName = m.SettingsFormName[:len(m.SettingsFormName)-1] } else if m.SettingsFormField == 1 && len(m.SettingsFormHost) > 0 { m.SettingsFormHost = m.SettingsFormHost[:len(m.SettingsFormHost)-1] } return m, nil case "ctrl+c": m.Quitting = true return m, tea.Quit default: if m.SettingsFormField == 0 || m.SettingsFormField == 1 { if msg.Text != "" { if m.SettingsFormField == 0 { m.SettingsFormName += msg.Text } else { m.SettingsFormHost += msg.Text } } } return m, nil } } switch msg.String() { case "esc": if m.SettingsConfirmDelete { m.SettingsConfirmDelete = false return m, nil } return m, func() tea.Msg { return pages.HomePageMsg{} } case "up", "k": if m.SettingsConfirmDelete { return m, nil } if m.SettingsSelected > 0 { m.SettingsSelected-- } return m, nil case "down", "j": if m.SettingsConfirmDelete { return m, nil } if m.SettingsSelected < len(m.SettingsServers)-1 { m.SettingsSelected++ } return m, nil case "a": if m.SettingsConfirmDelete { return m, nil } return m, func() tea.Msg { return pages.ServerEditMsg{Mode: "add"} } case "enter": if m.SettingsConfirmDelete { if m.SettingsSelected >= 0 && m.SettingsSelected < len(m.SettingsServers) { return m, deleteServerCmd(m.Services, m.SettingsServers[m.SettingsSelected].ID) } m.SettingsConfirmDelete = false return m, nil } if m.SettingsSelected >= 0 && m.SettingsSelected < len(m.SettingsServers) { return m, func() tea.Msg { return pages.ServerEditMsg{ Mode: "edit", Server: m.SettingsServers[m.SettingsSelected], } } } return m, nil case "d": if m.SettingsConfirmDelete { return m, nil } if len(m.SettingsServers) > 0 { m.SettingsConfirmDelete = true } return m, nil case "ctrl+c": m.Quitting = true return m, tea.Quit } return m, nil }