Last active
August 17, 2022 17:56
-
-
Save robinovitch61/5f630485cf655c6ff330168ed0218945 to your computer and use it in GitHub Desktop.
Bubble Tea Demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
tea "github.com/charmbracelet/bubbletea" | |
"github.com/charmbracelet/lipgloss" | |
"io/ioutil" | |
"log" | |
"net/http" | |
"os" | |
"os/exec" | |
"runtime" | |
"time" | |
) | |
type windowSize struct{ width, height int } | |
type mousePos struct{ x, y int } | |
// Global app state | |
type model struct { | |
lastKeyPress string | |
windowSize windowSize | |
mousePos mousePos | |
loading bool | |
wow wowResponseMsg | |
err error | |
} | |
// Init returns the first command on startup | |
func (m model) Init() tea.Cmd { | |
return fetchWowResponseCmd | |
} | |
// Update is called whenever a tea.Msg is received, either from a command or | |
// from a user action like a key press | |
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | |
// Log the type of the message received | |
debug(fmt.Sprintf("message %T", msg)) | |
switch msg := msg.(type) { | |
// Is it a window resize message? | |
case tea.WindowSizeMsg: | |
m.windowSize = windowSize{width: msg.Width, height: msg.Height} | |
// Is it a mouse event? | |
case tea.MouseMsg: | |
m.mousePos = mousePos{x: msg.X, y: msg.Y} | |
// Is it a key press? | |
case tea.KeyMsg: | |
// Exit the program with ctrl+c | |
switch msg.Type { | |
case tea.KeyCtrlC: | |
return m, tea.Quit | |
} | |
m.lastKeyPress = msg.String() | |
switch msg.String() { | |
// User requests new Owen wow! | |
case "W": | |
m.loading = true | |
// Note how uncommenting this instead of returning a new command causes the app to hang! | |
// newWow, err := getNewWowResponse() | |
// if err != nil { | |
// m.err = err | |
// } | |
// m.wow = newWow | |
// m.loading = false | |
// return m, nil | |
return m, fetchWowResponseCmd | |
// User requests opening the video | |
case "B": | |
// this should probably be run in a command! | |
openInBrowser(m.wow.Video.P1) | |
} | |
// Did the great Owen deliver us a new Wow? | |
case wowResponseMsg: | |
m.wow = msg | |
m.loading = false | |
// Did Owen get angry? | |
case errMsg: | |
m.err = msg | |
} | |
return m, nil | |
} | |
func (m model) View() string { | |
if m.err != nil { | |
return m.err.Error() | |
} | |
movieInfo := fmt.Sprintf("\nMovie: loading...\nLine: loading...\n") | |
if !m.loading { | |
movieInfo = fmt.Sprintf("\nMovie: %s\nLine: '%s'\n", m.wow.Movie, m.wow.FullLine) | |
} | |
contentView := lipgloss.JoinVertical(lipgloss.Left, | |
"OWEN WILSON WOW UTILITY", | |
"Type Capital W like Wow to Get New Wow", | |
"Type Capital B like Browser to Open Video", | |
movieInfo, | |
fmt.Sprintf("Last key pressed: %s", m.lastKeyPress), | |
fmt.Sprintf("Window Size: %v by %v", m.windowSize.width, m.windowSize.height), | |
fmt.Sprintf("Mouse Position: (%v, %v)", m.mousePos.x, m.mousePos.y), | |
"\nctrl+c to quit", | |
) | |
// this would horizontally center all the content on the screen | |
// horizontallyCenteredView := lipgloss.PlaceHorizontal(m.windowSize.width, lipgloss.Center, contentView) | |
styledView := lipgloss.NewStyle().Padding(2, 2).Render(contentView) | |
return styledView | |
} | |
func main() { | |
p := tea.NewProgram( | |
model{loading: true}, | |
tea.WithMouseAllMotion(), | |
// tea.WithAltScreen(), // uncomment for full screen | |
) | |
if err := p.Start(); err != nil { | |
fmt.Printf("Alas, there's been an error on startup: %v", err) | |
os.Exit(1) | |
} | |
debug("") | |
debug("~~START UP COMPLETE~~") | |
} | |
// COMMANDS AND MESSAGES | |
type errMsg error | |
type wowResponseMsg struct { | |
Movie string `json:"movie"` | |
Year int `json:"year"` | |
ReleaseDate string `json:"release_date"` | |
Director string `json:"director"` | |
Character string `json:"character"` | |
MovieDuration string `json:"movie_duration"` | |
Timestamp string `json:"timestamp"` | |
FullLine string `json:"full_line"` | |
CurrentWowInMovie int `json:"current_wow_in_movie"` | |
TotalWowsInMovie int `json:"total_wows_in_movie"` | |
Poster string `json:"poster"` | |
Video struct { | |
P string `json:"1080p"` | |
P1 string `json:"720p"` | |
P2 string `json:"480p"` | |
P3 string `json:"360p"` | |
} `json:"video"` | |
Audio string `json:"audio"` | |
} | |
func getNewWowResponse() (wowResponseMsg, error) { | |
client := &http.Client{} | |
req, err := http.NewRequest("GET", "https://owen-wilson-wow-api.herokuapp.com/wows/random", nil) | |
if err != nil { | |
return wowResponseMsg{}, err | |
} | |
res, err := client.Do(req) | |
if err != nil { | |
return wowResponseMsg{}, err | |
} | |
body, err := ioutil.ReadAll(res.Body) | |
if err != nil { | |
return wowResponseMsg{}, err | |
} | |
var wow []wowResponseMsg | |
if err := json.Unmarshal(body, &wow); err != nil { | |
return wowResponseMsg{}, err | |
} | |
// simulate slowness and see how nothing else hangs while waiting | |
time.Sleep(time.Second * 1) | |
return wow[0], nil | |
} | |
func fetchWowResponseCmd() tea.Msg { | |
wow, err := getNewWowResponse() | |
if err != nil { | |
return errMsg(err) | |
} | |
return wow | |
} | |
// UTILITY FUNCTIONS | |
func debug(msg string) { | |
f, err := tea.LogToFile("wow.log", "") | |
if err != nil { | |
fmt.Println("fatal:", err) | |
os.Exit(1) | |
} | |
log.Printf("%q", msg) | |
defer f.Close() | |
} | |
func openInBrowser(url string) { | |
var err error | |
switch runtime.GOOS { | |
case "linux": | |
err = exec.Command("xdg-open", url).Start() | |
case "windows": | |
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() | |
case "darwin": | |
err = exec.Command("open", url).Start() | |
default: | |
err = fmt.Errorf("unsupported platform") | |
} | |
if err != nil { | |
os.Exit(1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment