diff options
Diffstat (limited to 'ui/views')
-rw-r--r-- | ui/views/auth.go | 590 | ||||
-rw-r--r-- | ui/views/play.go | 427 | ||||
-rw-r--r-- | ui/views/tabs.go | 32 | ||||
-rw-r--r-- | ui/views/views.go | 144 |
4 files changed, 0 insertions, 1193 deletions
diff --git a/ui/views/auth.go b/ui/views/auth.go deleted file mode 100644 index a695466..0000000 --- a/ui/views/auth.go +++ /dev/null @@ -1,590 +0,0 @@ -package views - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -const ( - SignInTab TabType = iota - SignUpTab -) - -// AuthModel is the main container model for both login and signup tabsuth -type AuthModel struct { - loginModel loginModel - signupModel signupModel - activeTab TabType - width int - height int -} - -// Model holds the state for login page -type loginModel struct { - username textinput.Model - password textinput.Model - focus int - err error - isLoading bool - token string - width int - height int -} - -// Model holds the state for signup page -type signupModel struct { - loginModel - confirmPassword textinput.Model -} - -// Response from API -type authResponse struct { - Token string `json:"token"` - Error string `json:"error"` -} - -// Initialize AuthModel which contains both tabs -func NewAuthModel(width, height int) AuthModel { - return AuthModel{ - loginModel: initLoginModel(width, height), - signupModel: initSignupModel(width, height), - activeTab: SignInTab, - width: width, - height: height, - } -} - -// Initialize loginModel -func initLoginModel(width, height int) loginModel { - username := textinput.New() - username.Prompt = " " - username.TextStyle = inputStyle - username.Placeholder = "mario.rossi" - username.Focus() - username.CharLimit = 156 - username.Width = 30 - - password := textinput.New() - password.Prompt = " " - password.TextStyle = inputStyle - password.Placeholder = "*****" - password.EchoMode = textinput.EchoPassword - password.CharLimit = 156 - password.Width = 30 - - return loginModel{ - username: username, - password: password, - focus: 0, - err: nil, - isLoading: false, - token: "", - width: width, - height: height, - } -} - -// Initialize signupModel -func initSignupModel(width, height int) signupModel { - inputStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#7EE2A8")) - - username := textinput.New() - username.Prompt = " " - username.TextStyle = inputStyle - username.Placeholder = "mario.rossi" - username.Focus() - username.CharLimit = 156 - username.Width = 30 - - password := textinput.New() - password.Prompt = " " - password.TextStyle = inputStyle - password.Placeholder = "*****" - password.EchoMode = textinput.EchoPassword - password.CharLimit = 156 - password.Width = 30 - - confirmPassword := textinput.New() - confirmPassword.Prompt = " " - confirmPassword.TextStyle = inputStyle - confirmPassword.Placeholder = "*****" - confirmPassword.EchoMode = textinput.EchoPassword - confirmPassword.CharLimit = 156 - confirmPassword.Width = 30 - - return signupModel{ - loginModel: loginModel{ - username: username, - password: password, - focus: 0, - err: nil, - isLoading: false, - token: "", - width: width, - height: height, - }, - confirmPassword: confirmPassword, - } -} - -// Init function for AuthModel -func (m AuthModel) Init() tea.Cmd { - ClearScreen() - return textinput.Blink -} - -func (m AuthModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd - - if exit := handleExit(msg); exit != nil { - return m, exit - } - - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.width = msg.Width - m.height = msg.Height - return m, nil - - case tea.KeyMsg: - switch msg.String() { - case "alt+1": - // Switch to sign-in tab - if m.activeTab != SignInTab { - m.activeTab = SignInTab - m.loginModel.focus = 0 - m.loginModel.username.Focus() - m.loginModel.password.Blur() - m.signupModel.username.Blur() - m.signupModel.password.Blur() - m.signupModel.confirmPassword.Blur() - } - return m, nil - - case "alt+2": - // Switch to sign-up tab - if m.activeTab != SignUpTab { - m.activeTab = SignUpTab - m.signupModel.focus = 0 - m.signupModel.username.Focus() - m.signupModel.password.Blur() - m.signupModel.confirmPassword.Blur() - m.loginModel.username.Blur() - m.loginModel.password.Blur() - } - return m, nil - - } - } - - if m.activeTab == SignInTab { - var cmd tea.Cmd - m.loginModel, cmd = m.loginModel.Update(msg) - cmds = append(cmds, cmd) - } else { - var cmd tea.Cmd - m.signupModel, cmd = m.signupModel.Update(msg) - cmds = append(cmds, cmd) - } - - return m, tea.Batch(cmds...) -} - -// View function for AuthModel -func (m AuthModel) View() string { - width, height := m.width, m.height - - // Get the content of the active tab - var tabContent string - if m.activeTab == SignInTab { - tabContent = m.loginModel.renderContent() - } else { - tabContent = m.signupModel.renderContent() - } - - // Create the window with tab content - ui := lipgloss.JoinVertical(lipgloss.Center, - getTabsRow([]string{"Sign In", "Sign Up"}, m.activeTab), - windowStyle.Width(getFormWidth(width)).Render(tabContent), - ) - - // Center logo and form in available space - contentHeight := lipgloss.Height(logo) + lipgloss.Height(ui) + 2 - paddingTop := (height - contentHeight) / 2 - if paddingTop < 0 { - paddingTop = 0 - } - - // Combine logo and tabs with vertical centering - output := lipgloss.NewStyle(). - MarginTop(paddingTop). - Render( - lipgloss.JoinVertical(lipgloss.Center, - getLogo(m.width), - lipgloss.PlaceHorizontal(width, lipgloss.Center, ui), - ), - ) - - return output -} - -// Update function for loginModel -func (m loginModel) Update(msg tea.Msg) (loginModel, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - case tea.KeyUp: - m.focus = (m.focus - 1) % 2 - if m.focus < 0 { - m.focus = 1 - } - m.updateFocus() - case tea.KeyDown: - m.focus = (m.focus + 1) % 2 - m.updateFocus() - case tea.KeyEnter: - if !m.isLoading { - m.isLoading = true - return m, m.loginCallback() - } - case tea.KeyTab: - m.focus = (m.focus + 1) % 2 - m.updateFocus() - } - case authResponse: - m.isLoading = false - if msg.Error != "" { - m.err = fmt.Errorf(msg.Error) - m.focus = 0 - m.updateFocus() - } else { - m.token = msg.Token - ClearScreen() - f, err := os.OpenFile(".rahannarc", os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - m.err = err - break - } - defer f.Close() - f.Write([]byte(m.token)) - return m, SwitchModelCmd(NewPlayModel(m.width, m.height)) - } - case error: - m.isLoading = false - m.err = msg - m.focus = 0 - m.updateFocus() - } - - var cmd tea.Cmd - m.username, cmd = m.username.Update(msg) - cmdPassword := tea.Batch(cmd) - m.password, cmd = m.password.Update(msg) - return m, tea.Batch(cmd, cmdPassword) -} - -// Update function for signupModel -func (m signupModel) Update(msg tea.Msg) (signupModel, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - case tea.KeyUp: - m.focus = (m.focus - 1) % 3 - if m.focus < 0 { - m.focus = 2 - } - m.updateFocus() - case tea.KeyDown: - m.focus = (m.focus + 1) % 3 - m.updateFocus() - case tea.KeyEnter: - if !m.isLoading { - m.isLoading = true - return m, m.signupCallback() - } - case tea.KeyTab: - m.focus = (m.focus + 1) % 3 - m.updateFocus() - } - case authResponse: - m.isLoading = false - if msg.Error != "" { - m.err = fmt.Errorf(msg.Error) - m.focus = 0 - m.updateFocus() - } else { - m.token = msg.Token - ClearScreen() - f, err := os.OpenFile(".rahannarc", os.O_WRONLY|os.O_CREATE, 0644) - if err != nil { - m.err = err - break - } - defer f.Close() - f.Write([]byte(m.token)) - return m, SwitchModelCmd(NewPlayModel(m.width, m.height)) - } - case error: - m.isLoading = false - m.err = msg - m.focus = 0 - m.updateFocus() - } - - var cmds []tea.Cmd - var cmd tea.Cmd - - m.username, cmd = m.username.Update(msg) - cmds = append(cmds, cmd) - - m.password, cmd = m.password.Update(msg) - cmds = append(cmds, cmd) - - m.confirmPassword, cmd = m.confirmPassword.Update(msg) - cmds = append(cmds, cmd) - - return m, tea.Batch(cmds...) -} - -// Helper function to update input focus for signup -func (m *signupModel) updateFocus() { - m.username.Blur() - m.password.Blur() - m.confirmPassword.Blur() - - switch m.focus { - case 0: - m.username.Focus() - case 1: - m.password.Focus() - case 2: - m.confirmPassword.Focus() - } -} - -// Helper function to update input focus for signin -func (m *loginModel) updateFocus() { - m.username.Blur() - m.password.Blur() - - switch m.focus { - case 0: - m.username.Focus() - case 1: - m.password.Focus() - } -} - -// Login API callback -func (m loginModel) loginCallback() tea.Cmd { - return func() tea.Msg { - url := os.Getenv("API_BASE") + "/auth/login" - - payload, err := json.Marshal(map[string]string{ - "username": m.username.Value(), - "password": m.password.Value(), - }) - - if err != nil { - return authResponse{Error: err.Error()} - } - - resp, err := http.Post(url, "application/json", bytes.NewReader(payload)) - if err != nil { - return authResponse{Error: err.Error()} - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var response authResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return authResponse{Error: fmt.Sprintf("HTTP error: %d, unable to decode body", resp.StatusCode)} - } - return authResponse{Error: response.Error} - } - - var response authResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return authResponse{Error: fmt.Sprintf("Error decoding JSON: %v", err)} - } - - return response - } -} - -// Signup API callback -func (m signupModel) signupCallback() tea.Cmd { - return func() tea.Msg { - // Validate that passwords match - if m.password.Value() != m.confirmPassword.Value() { - return authResponse{Error: "Passwords do not match"} - } - - url := os.Getenv("API_BASE") + "/auth/register" - - payload, err := json.Marshal(map[string]string{ - "username": m.username.Value(), - "password": m.password.Value(), - }) - - if err != nil { - return authResponse{Error: err.Error()} - } - - resp, err := http.Post(url, "application/json", bytes.NewReader(payload)) - if err != nil { - return authResponse{Error: err.Error()} - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { - var response authResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return authResponse{Error: fmt.Sprintf("HTTP error: %d, unable to decode body", resp.StatusCode)} - } - return authResponse{Error: response.Error} - } - - var response authResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return authResponse{Error: fmt.Sprintf("Error decoding JSON: %v", err)} - } - - return response - } -} - -// Render content of the login tab -func (m loginModel) renderContent() string { - formWidth := getFormWidth(m.width) - - // Styles - titleStyle := lipgloss.NewStyle(). - Bold(true). - Foreground(lipgloss.Color("#7ee2a8")). - Align(lipgloss.Center). - Width(formWidth - 4) // Account for padding - - labelStyle := lipgloss.NewStyle(). - Width(10). - Align(lipgloss.Right) - - inputWrapStyle := lipgloss.NewStyle(). - Align(lipgloss.Center). - Width(formWidth - 4) // Account for padding - - statusStyle := lipgloss.NewStyle(). - Align(lipgloss.Center). - Bold(true). - Width(formWidth - 4) // Account for padding - - // Error message - formError := "" - if m.err != nil { - formError = fmt.Sprintf("Error: %v", m.err.Error()) - } - - // Status message - statusMsg := fmt.Sprintf("Press %s to login", lipgloss.NewStyle().Italic(true).Render("Enter")) - if m.isLoading { - statusMsg = "Logging in..." - } - - form := lipgloss.JoinVertical(lipgloss.Center, - titleStyle.Render("Sign in to your account"), - "\n", - errorStyle.Align(lipgloss.Center).Width(formWidth-4).Render(formError), - inputWrapStyle.Render( - lipgloss.JoinHorizontal(lipgloss.Left, - labelStyle.Render("Username:"), - m.username.View(), - ), - ), - inputWrapStyle.Render( - lipgloss.JoinHorizontal(lipgloss.Left, - labelStyle.Render("Password:"), - m.password.View(), - ), - ), - "\n", - statusStyle.Render(statusMsg), - ) - - return form -} - -// Render content of the signup tab -func (m signupModel) renderContent() string { - formWidth := getFormWidth(m.width) - - // Styles - titleStyle := lipgloss.NewStyle(). - Bold(true). - Foreground(lipgloss.Color("#7ee2a8")). - Align(lipgloss.Center). - Width(formWidth - 4) // Account for padding - - labelStyle := lipgloss.NewStyle(). - Width(16). - Align(lipgloss.Right) - - inputWrapStyle := lipgloss.NewStyle(). - Align(lipgloss.Center). - Width(formWidth - 4) // Account for padding - - statusStyle := lipgloss.NewStyle(). - Align(lipgloss.Center). - Bold(true). - Width(formWidth - 4) // Account for padding - - // Error message - formError := "" - if m.err != nil { - formError = fmt.Sprintf("Error: %v", m.err.Error()) - } - - // Status message - statusMsg := fmt.Sprintf("Press %s to register", lipgloss.NewStyle().Italic(true).Render("Enter")) - if m.isLoading { - statusMsg = "Creating account..." - } - - form := lipgloss.JoinVertical(lipgloss.Center, - titleStyle.Render("Create a new account"), - "\n", - errorStyle.Align(lipgloss.Center).Width(formWidth-4).Render(formError), - inputWrapStyle.Render( - lipgloss.JoinHorizontal(lipgloss.Left, - labelStyle.Render("Username:"), - m.username.View(), - ), - ), - inputWrapStyle.Render( - lipgloss.JoinHorizontal(lipgloss.Left, - labelStyle.Render("Password:"), - m.password.View(), - ), - ), - inputWrapStyle.Render( - lipgloss.JoinHorizontal(lipgloss.Left, - labelStyle.Render("Confirm:"), - m.confirmPassword.View(), - ), - ), - "\n", - statusStyle.Render(statusMsg), - ) - - return form -} diff --git a/ui/views/play.go b/ui/views/play.go deleted file mode 100644 index 389c302..0000000 --- a/ui/views/play.go +++ /dev/null @@ -1,427 +0,0 @@ -package views - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "net/http" - "os" - - "github.com/boozec/rahanna/api/database" - "github.com/boozec/rahanna/network" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -var chess string = ` - A B C D E F G H -+---------------+ -8 |♜ ♞ ♝ ♛ ♚ ♝ ♞ ♜| 8 -7 |♟ ♟ ♟ ♟ ♟ ♟ ♟ ♟| 7 -6 |. . . . . . . .| 6 -5 |. . . . . . . .| 5 -4 |. . . . . . . .| 4 -3 |. . . . . . . .| 3 -2 |♙ ♙ ♙ ♙ ♙ ♙ ♙ ♙| 2 -1 |♖ ♘ ♗ ♕ ♔ ♗ ♘ ♖| 1 -+---------------+ - A B C D E F G H -` - -type playKeyMap struct { - EnterNewGame key.Binding - StartNewGame key.Binding - GoLogout key.Binding - Quit key.Binding -} - -type playResponse struct { - Name string `json:"name"` - Error string `json:"error"` -} - -var defaultGameKeyMap = playKeyMap{ - EnterNewGame: key.NewBinding( - key.WithKeys("alt+E", "alt+e"), - key.WithHelp("Alt+E", "Enter a play using code"), - ), - StartNewGame: key.NewBinding( - key.WithKeys("alt+s", "alt+s"), - key.WithHelp("Alt+S", "Start a new play"), - ), - GoLogout: key.NewBinding( - key.WithKeys("alt+Q", "alt+q"), - key.WithHelp("Alt+Q", "Logout"), - ), - Quit: key.NewBinding( - key.WithKeys("Q", "q"), - key.WithHelp(" Q", "Quit"), - ), -} - -type PlayModelPage int - -const ( - LandingPage PlayModelPage = iota - InsertCodePage - StartGamePage -) - -type PlayModel struct { - width int - height int - err error - keys playKeyMap - namePrompt textinput.Model - page PlayModelPage - isLoading bool - playName string - play *database.Game -} - -func NewPlayModel(width, height int) PlayModel { - namePrompt := textinput.New() - namePrompt.Prompt = " " - namePrompt.TextStyle = inputStyle - namePrompt.Placeholder = "rectangular-lake" - namePrompt.Focus() - namePrompt.CharLimit = 23 - namePrompt.Width = 23 - - return PlayModel{ - width: width, - height: height, - err: nil, - keys: defaultGameKeyMap, - namePrompt: namePrompt, - page: LandingPage, - isLoading: false, - playName: "", - play: nil, - } -} - -func (m PlayModel) Init() tea.Cmd { - ClearScreen() - return textinput.Blink -} - -func (m PlayModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if exit := handleExit(msg); exit != nil { - return m, exit - } - - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.width = msg.Width - m.height = msg.Height - return m, nil - - case tea.KeyMsg: - switch { - case key.Matches(msg, m.keys.EnterNewGame): - m.page = InsertCodePage - return m, nil - case key.Matches(msg, m.keys.StartNewGame): - m.page = StartGamePage - if !m.isLoading { - m.isLoading = true - return m, m.newGameCallback() - } - case key.Matches(msg, m.keys.GoLogout): - return m, m.logout() - case key.Matches(msg, m.keys.Quit): - return m, tea.Quit - case msg.Type == tea.KeyEnter: - if m.page == InsertCodePage { - if !m.isLoading { - m.isLoading = true - return m, m.enterGame() - } - } - } - case playResponse: - m.isLoading = false - m.err = nil - if msg.Error != "" { - m.err = fmt.Errorf(msg.Error) - if msg.Error == "unauthorized" { - return m, m.logout() - } - } else { - m.playName = msg.Name - } - return m, nil - case database.Game: - m.isLoading = false - m.play = &msg - m.err = nil - return m, nil - case error: - m.isLoading = false - m.err = msg - } - - var cmd tea.Cmd = nil - - if m.page == InsertCodePage { - m.namePrompt, cmd = m.namePrompt.Update(msg) - } - - return m, tea.Batch(cmd) -} - -func (m PlayModel) View() string { - formWidth := getFormWidth(m.width) - - var content string - base := lipgloss.NewStyle().Align(lipgloss.Center).Width(m.width) - - switch m.page { - case LandingPage: - content = chess - m.namePrompt.Blur() - case InsertCodePage: - m.namePrompt.Focus() - var statusMsg string - if m.isLoading { - statusMsg = "Loading..." - content = base. - Render( - lipgloss.NewStyle(). - Align(lipgloss.Center). - Bold(true). - Render(statusMsg), - ) - } else if m.play != nil { - statusMsg = fmt.Sprintf("You are playing versus %s", lipgloss.NewStyle().Foreground(lipgloss.Color("#e67e22")).Render(m.play.Player1.Username)) - content = base. - Render( - lipgloss.NewStyle(). - Align(lipgloss.Center). - Width(m.width). - Bold(true). - Render(statusMsg), - ) - } else { - statusMsg = fmt.Sprintf("Press %s to join", lipgloss.NewStyle().Italic(true).Render("Enter")) - content = base. - Render( - lipgloss.JoinVertical(lipgloss.Left, - lipgloss.NewStyle().Width(23).Render("Insert play code:"), - m.namePrompt.View(), - lipgloss.NewStyle(). - Align(lipgloss.Center). - PaddingTop(2). - Width(23). - Bold(true). - Render(statusMsg), - ), - ) - } - - case StartGamePage: - var statusMsg string - if m.isLoading { - statusMsg = "Loading..." - } else if m.playName != "" { - statusMsg = fmt.Sprintf("Share `%s` to your friend", lipgloss.NewStyle().Italic(true).Foreground(lipgloss.Color("#F39C12")).Render(m.playName)) - } - - content = base. - Render(statusMsg) - } - - var windowContent string - if m.err != nil { - formError := fmt.Sprintf("Error: %v", m.err.Error()) - windowContent = lipgloss.JoinVertical( - lipgloss.Center, - windowStyle.Width(formWidth).Render(lipgloss.JoinVertical( - lipgloss.Center, - errorStyle.Align(lipgloss.Center).Width(formWidth-4).Render(formError), - content, - )), - ) - } else { - windowContent = lipgloss.JoinVertical( - lipgloss.Center, - windowStyle.Width(formWidth).Render(lipgloss.JoinVertical( - lipgloss.Center, - content, - )), - ) - } - - enterKey := fmt.Sprintf("%s %s", altCodeStyle.Render(m.keys.EnterNewGame.Help().Key), m.keys.EnterNewGame.Help().Desc) - startKey := fmt.Sprintf("%s %s", altCodeStyle.Render(m.keys.StartNewGame.Help().Key), m.keys.StartNewGame.Help().Desc) - logoutKey := fmt.Sprintf("%s %s", altCodeStyle.Render(m.keys.GoLogout.Help().Key), m.keys.GoLogout.Help().Desc) - quitKey := fmt.Sprintf("%s %s", altCodeStyle.Render(m.keys.Quit.Help().Key), m.keys.Quit.Help().Desc) - - // Vertically align the buttons - buttons := lipgloss.JoinVertical( - lipgloss.Left, - enterKey, - startKey, - logoutKey, - quitKey, - ) - - centeredContent := lipgloss.JoinVertical( - lipgloss.Center, - getLogo(m.width), - windowContent, - lipgloss.NewStyle().MarginTop(2).Render(buttons), - ) - - return lipgloss.Place( - m.width, - m.height, - lipgloss.Center, - lipgloss.Center, - centeredContent, - ) -} - -func (m PlayModel) newGameCallback() tea.Cmd { - return func() tea.Msg { - f, err := os.Open(".rahannarc") - if err != nil { - return playResponse{Error: err.Error()} - } - defer f.Close() - - scanner := bufio.NewScanner(f) - var authorization string - for scanner.Scan() { - authorization = scanner.Text() - } - - if err := scanner.Err(); err != nil { - fmt.Println("Error during scanning:", err) - } - - url := os.Getenv("API_BASE") + "/play" - - port, err := network.GetRandomAvailablePort() - if err != nil { - return playResponse{Error: err.Error()} - } - - payload, err := json.Marshal(map[string]string{ - "ip": fmt.Sprintf("%s:%d", network.GetOutboundIP().String(), port), - }) - - if err != nil { - return playResponse{Error: err.Error()} - } - - req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) - if err != nil { - return playResponse{Error: err.Error()} - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) - - client := &http.Client{} - - resp, err := client.Do(req) - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var response playResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return playResponse{Error: fmt.Sprintf("HTTP error: %d, unable to decode body", resp.StatusCode)} - } - return playResponse{Error: response.Error} - } - - var response playResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return playResponse{Error: fmt.Sprintf("Error decoding JSON: %v", err)} - } - - return response - } -} - -func (m PlayModel) enterGame() tea.Cmd { - return func() tea.Msg { - f, err := os.Open(".rahannarc") - if err != nil { - return playResponse{Error: err.Error()} - } - defer f.Close() - - scanner := bufio.NewScanner(f) - var authorization string - for scanner.Scan() { - authorization = scanner.Text() - } - - if err := scanner.Err(); err != nil { - fmt.Println("Error during scanning:", err) - } - - url := os.Getenv("API_BASE") + "/enter-game" - - port, err := network.GetRandomAvailablePort() - if err != nil { - return playResponse{Error: err.Error()} - } - - payload, err := json.Marshal(map[string]string{ - "ip": fmt.Sprintf("%s:%d", network.GetOutboundIP().String(), port), - "name": m.namePrompt.Value(), - }) - - if err != nil { - return playResponse{Error: err.Error()} - } - - req, err := http.NewRequest("POST", url, bytes.NewReader(payload)) - if err != nil { - return playResponse{Error: err.Error()} - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) - - client := &http.Client{} - - resp, err := client.Do(req) - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var response playResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return playResponse{Error: fmt.Sprintf("HTTP error: %d, unable to decode body", resp.StatusCode)} - } - return playResponse{Error: response.Error} - } - - var response database.Game - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return playResponse{Error: fmt.Sprintf("Error decoding JSON: %v", err)} - } - - return response - } -} - -func (m PlayModel) logout() tea.Cmd { - if err := os.Remove(".rahannarc"); err != nil { - return nil - } - return SwitchModelCmd(NewAuthModel(m.width, m.height+1)) -} diff --git a/ui/views/tabs.go b/ui/views/tabs.go deleted file mode 100644 index 13e3672..0000000 --- a/ui/views/tabs.go +++ /dev/null @@ -1,32 +0,0 @@ -package views - -import ( - "fmt" - - "github.com/charmbracelet/lipgloss" -) - -type TabType int - -var ( - tabStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(highlightColor).Padding(0, 2) - inactiveTabStyle = tabStyle - activeTabStyle = tabStyle -) - -func getTabsRow(tabsText []string, activeTab TabType) string { - tabs := make([]string, len(tabsText)) - - for i, tab := range tabsText { - if TabType(i) == activeTab { - tabs[i] = fmt.Sprintf("%s %s", altCodeStyle.Render(fmt.Sprintf("Alt+%d", i+1)), lipgloss.NewStyle().Bold(true).Foreground(highlightColor).Render(tab)) - tabs[i] = activeTabStyle.Foreground(highlightColor).Render(tabs[i]) - } else { - tabs[i] = fmt.Sprintf("%s %s", altCodeStyle.Render(fmt.Sprintf("Alt+%d", i+1)), lipgloss.NewStyle().Render(tab)) - tabs[i] = inactiveTabStyle.Foreground(highlightColor).Render(tabs[i]) - } - } - - return lipgloss.JoinHorizontal(lipgloss.Top, tabs...) - -} diff --git a/ui/views/views.go b/ui/views/views.go deleted file mode 100644 index fa70035..0000000 --- a/ui/views/views.go +++ /dev/null @@ -1,144 +0,0 @@ -package views - -import ( - "errors" - "os" - - "os/exec" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "golang.org/x/term" -) - -var logo = ` -▗▄▄▖ ▗▄▖ ▗▖ ▗▖ ▗▄▖ ▗▖ ▗▖▗▖ ▗▖ ▗▄▖ -▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▛▚▖▐▌▐▛▚▖▐▌▐▌ ▐▌ -▐▛▀▚▖▐▛▀▜▌▐▛▀▜▌▐▛▀▜▌▐▌ ▝▜▌▐▌ ▝▜▌▐▛▀▜▌ -▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌▐▌ ▐▌ -` - -var ( - highlightColor = lipgloss.Color("#7ee2a8") - errorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ff0000")) - altCodeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#666666")).Bold(true) - windowStyle = lipgloss.NewStyle().BorderForeground(highlightColor).Padding(2, 0).Align(lipgloss.Center).Border(lipgloss.RoundedBorder()) - inputStyle = lipgloss.NewStyle().Foreground(highlightColor) -) - -// Get terminal size dynamically -func GetTerminalSize() (width, height int) { - fd := int(os.Stdin.Fd()) - if w, h, err := term.GetSize(fd); err == nil { - return w, h - } - return 80, 24 // Default size if detection fails -} - -// Clear terminal screen -func ClearScreen() { - if len(os.Getenv("DEBUG")) == 0 { - cmd := exec.Command("clear") - if os.Getenv("OS") == "Windows_NT" { - cmd = exec.Command("cmd", "/c", "cls") - } - cmd.Stdout = os.Stdout - cmd.Run() - } -} - -func getFormWidth(width int) int { - formWidth := width * 2 / 3 - if formWidth > 80 { - formWidth = 80 // Cap at 80 chars for readability - } else if formWidth < 40 { - formWidth = width - 4 // For small terminals - } - - return formWidth -} - -type RahannaModel struct { - width int - height int - currentModel tea.Model - auth AuthModel - play PlayModel -} - -func NewRahannaModel() RahannaModel { - width, height := GetTerminalSize() - - auth := NewAuthModel(width, height) - play := NewPlayModel(width, height) - - var currentModel tea.Model = auth - - if _, err := os.Stat(".rahannarc"); !errors.Is(err, os.ErrNotExist) { - currentModel = play - } - - return RahannaModel{ - width: width, - height: height, - currentModel: currentModel, - auth: auth, - play: play, - } -} - -func (m RahannaModel) Init() tea.Cmd { - return m.currentModel.Init() -} - -type switchModel struct { - model tea.Model -} - -func SwitchModelCmd(model tea.Model) tea.Cmd { - s := switchModel{ - model: model, - } - - return func() tea.Msg { - return s - } -} - -func (m RahannaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case switchModel: - m.currentModel = msg.model - return m, nil - } - var cmd tea.Cmd - m.currentModel, cmd = m.currentModel.Update(msg) - return m, cmd -} - -func (m RahannaModel) View() string { - return m.currentModel.View() -} - -func handleExit(msg tea.Msg) tea.Cmd { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c": - return tea.Quit - } - } - - return nil -} - -func getLogo(width int) string { - logoStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#7ee2a8")). - Bold(true). - Align(lipgloss.Center). - Width(width) - - return logoStyle.Render(logo) - -} |