Skip to content

Instantly share code, notes, and snippets.

@robinovitch61
Last active August 17, 2022 17:56
Show Gist options
  • Save robinovitch61/5f630485cf655c6ff330168ed0218945 to your computer and use it in GitHub Desktop.
Save robinovitch61/5f630485cf655c6ff330168ed0218945 to your computer and use it in GitHub Desktop.
Bubble Tea Demo
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