got it working!

This commit is contained in:
jms 2020-10-26 17:59:46 -05:00
parent 686728dc6d
commit 71848481d2
5 changed files with 372 additions and 495 deletions

View File

@ -16,49 +16,40 @@ limitations under the License.
package cmd
import (
"encoding/json"
"io/ioutil"
//"encoding/json"
//"io/ioutil"
"encoding/json"
"fmt"
input "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
te "github.com/muesli/termenv"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"io/ioutil"
"log"
"pgm/data"
"pgm/logger"
//"io/ioutil"
//"github.com/spf13/viper"
//"log"
"os"
//"pgm/data"
//"pgm/logger"
"os"
)
const focusedTextColor = "212"
const focusedTextColor = "205"
var (
color = te.ColorProfile().Color
focusedPrompt = te.String("> ").Foreground(color("205")).String()
blurredPrompt = "> "
color = te.ColorProfile().Color
focusedSubmitButton = "[ " + te.String("Submit").Foreground(color("205")).String() + " ]"
blurredSubmitButton = "[ " + te.String("Submit").Foreground(color("240")).String() + " ]"
)
type dupeFileMsg string
type dbCfg struct {
index int
dbFileName input.Model
hostName input.Model
dbName input.Model
userName input.Model
secret input.Model
submitButton string
}
// addCmd represents the add command
var addCmd = &cobra.Command{
Use: "add",
@ -70,17 +61,23 @@ Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
result := make(chan string, 1)
// This is where we'll listen for the choice the user makes in the Bubble
// Tea program.
var cfg []string
result := make(chan []string, 1)
if err := tea.NewProgram(initialModel()).Start(); err != nil {
if err := tea.NewProgram(initialModel(result)).Start(); err != nil {
fmt.Printf("could not start program: %s\n", err)
os.Exit(1)
}
// Print out the final choice.
if r := <-result; r != "" {
fmt.Printf("\n---\nYou chose %s!\n", r)
if cfg = <-result; len(cfg) !=0 {
configWriter(cfg)
}
},
}
@ -99,59 +96,78 @@ func init() {
}
func (m dbCfg) Init() tea.Cmd {
type hostCfgFile struct {
index int
fn input.Model
hostname input.Model
dbname input.Model
username input.Model
secret input.Model
submitButton string
Filename chan []string
}
func initialModel(Filename chan []string ) hostCfgFile {
fn := input.NewModel()
fn.Placeholder = "filename (no spaces)"
fn.Focus()
fn.Prompt = focusedPrompt
fn.TextColor = focusedTextColor
fn.CharLimit = 32
hn := input.NewModel()
hn.Placeholder = "hostname"
hn.Prompt = blurredPrompt
hn.CharLimit = 64
dbn := input.NewModel()
dbn.Placeholder = "dbname"
dbn.Prompt = blurredPrompt
dbn.CharLimit = 64
un := input.NewModel()
un.Placeholder = "username"
un.Prompt = blurredPrompt
un.CharLimit = 30
secret := input.NewModel()
secret.Placeholder = "secret"
secret.Prompt = blurredPrompt
secret.CharLimit = 99
return hostCfgFile{0, fn, hn, dbn, un, secret,blurredSubmitButton, Filename}
}
func (m hostCfgFile) Init() tea.Cmd {
return tea.Batch(
input.Blink(m.dbFileName),
input.Blink(m.hostName),
input.Blink(m.dbName),
input.Blink(m.userName),
input.Blink(m.fn),
input.Blink(m.hostname),
input.Blink(m.dbname),
input.Blink(m.username),
input.Blink(m.secret),
)
}
func (m dbCfg) View() string {
s := "\n"
inputs := []string{
input.View(m.dbFileName),
input.View(m.hostName),
input.View(m.dbName),
input.View(m.userName),
input.View(m.secret),
}
for i := 0; i < len(inputs); i++ {
s += inputs[i]
if i < len(inputs)-1 {
s += "\n"
}
}
s += "\n\n" + m.submitButton + "\n"
return s
}
func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m hostCfgFile) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
case "ctrl+c", "esc":
close(m.Filename)
return m, tea.Quit
// Cycle between inputs
case "tab", "shift+tab", "enter", "up", "down":
inputs := []input.Model{
m.dbFileName,
m.hostName,
m.dbName,
m.userName,
m.fn,
m.hostname,
m.dbname,
m.username,
m.secret,
}
@ -160,6 +176,11 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Did the user press enter while the submit button was focused?
// If so, exit.
if s == "enter" && m.index == len(inputs) {
var cfg []string
for _, i := range inputs{
cfg = append(cfg, i.Value())
}
m.Filename <- cfg
return m, tea.Quit
}
@ -190,10 +211,10 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
inputs[i].TextColor = ""
}
m.dbFileName = inputs[0]
m.hostName = inputs[1]
m.dbName = inputs[2]
m.userName = inputs[3]
m.fn = inputs[0]
m.hostname = inputs[1]
m.dbname = inputs[2]
m.username = inputs[3]
m.secret = inputs[4]
if m.index == len(inputs) {
@ -211,68 +232,77 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}
func updateInputs(msg tea.Msg, m dbCfg) (dbCfg, tea.Cmd) {
// Pass messages and models through to text input components. Only text inputs
// with Focus() set will respond, so it's safe to simply update all of them
// here without any further logic.
func updateInputs(msg tea.Msg, m hostCfgFile) (hostCfgFile, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)
m.dbFileName, cmd = input.Update(msg, m.dbFileName)
m.fn, cmd = input.Update(msg, m.fn)
cmds = append(cmds, cmd)
m.hostName, cmd = input.Update(msg, m.hostName)
m.hostname, cmd = input.Update(msg, m.hostname)
cmds = append(cmds, cmd)
m.dbName, cmd = input.Update(msg, m.dbName)
m.dbname, cmd = input.Update(msg, m.dbname)
cmds = append(cmds, cmd)
m.userName, cmd = input.Update(msg, m.userName)
m.username, cmd = input.Update(msg, m.username)
cmds = append(cmds, cmd)
m.secret, cmd = input.Update(msg, m.secret)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func initialModel() dbCfg {
dbFileName := input.NewModel()
dbFileName.Placeholder = "database file name (no spaces)"
dbFileName.Focus()
func (m hostCfgFile) View() string {
s := "\n"
inputs := []string{
input.View(m.fn),
input.View(m.hostname),
input.View(m.dbname),
input.View(m.username),
input.View(m.secret),
}
hostName := input.NewModel()
hostName.Placeholder = "hostname"
dbName := input.NewModel()
dbName.Placeholder = "database name on host"
userName := input.NewModel()
userName.Placeholder = "username"
secret := input.NewModel()
secret.Placeholder = "secret"
return dbCfg{0, dbFileName, hostName, dbName, userName, secret, blurredSubmitButton}
for i := 0; i < len(inputs); i++ {
s += inputs[i]
if i < len(inputs)-1 {
s += "\n"
}
}
s += "\n\n" + m.submitButton + "\n"
return s
}
// Writes configs to .pgm/hosts directory
func configWriter(t []input.Model){
//
//// Writes configs to .pgm/hosts directory
func configWriter(s []string){
h := viper.GetString("hostsDir")
var c data.HostDetails
var fp string
var cfg []byte
c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()}
cfgFileName := t[0].Value()
var isEmpty bool
isEmpty = checkEmpty(s)
if isEmpty == true {
logger.Logger("[LOG] one or more fields was empty when writing file: " + s[0] )
fmt.Println("one or more fields was empty when submitted, please try again")
os.Exit(0)
}
c = data.HostDetails{s[4], s[1], s[2], s[3]}
cfgFileName := s[0]
if _, err := os.Stat(h + "/" + cfgFileName); os.IsNotExist(err) {
fp = h + "/" + cfgFileName
} else {
e := "dupe"
return dupeFileMsg(e)
logger.Logger("[LOG] duplicate file name found: " + cfgFileName)
os.Exit(1)
}
_ = cfg
f, err := os.Create(fp)
@ -294,3 +324,16 @@ func configWriter(t []input.Model){
fmt.Println("failed to log")
}
}
// looks through user inputs for zero length strings
// if any one is empty we do not accept the input and exit.
func checkEmpty(s []string ) bool {
for _, ip := range s {
if len(ip) < 1 {
return true
} else {
continue
}
}
return false
}

View File

@ -16,13 +16,41 @@ limitations under the License.
package cmd
import (
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/muesli/termenv"
"github.com/spf13/cobra"
"pgm/data"
"pgm/loader"
"pgm/logger"
"io/ioutil"
"os"
"pgm/data"
"pgm/logger"
"strconv"
"strings"
"time"
)
type host struct {
tic int
t string
choices []string
choice int
chosen bool
Quitting bool
Frames int
File chan string
}
var (
term = termenv.ColorProfile()
subtle = makeFgStyle("241")
dot = colorFg(" • ", "236")
)
// billyCmd represents the billy command
var loadCmd = &cobra.Command{
Use: "load",
@ -34,20 +62,30 @@ Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
var dbcfg string
var title string
var pgHosts []string
var fn string
result := make(chan string, 1)
pgHosts = data.ReadHosts()
title = "choose db to load, \"l\" or enter will load your choice"
err := loader.Loader(pgHosts, title, dbcfg)
if err != nil {
logger.Logger("[ERROR] loading error for testing, ignore.")
var hostChoice host
hostChoice = host{60, title, pgHosts, 0, false, false, 0, result }
if err := tea.NewProgram(hostChoice).Start(); err != nil {
logger.Logger("[ERROR] tea error occured")
os.Exit(1)
}
// Print out the final choice.
if fn = <-result; len(fn) !=0 {
logger.Logger("[LOG] loading file: " + fn)
}
os.Exit(0)
},
}
func init() {
rootCmd.AddCommand(loadCmd)
@ -61,3 +99,183 @@ func init() {
// is called directly, e.g.:
// billyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
func (m host) Init() tea.Cmd {
return tick()
}
func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "esc":
m.Quitting = true
return m, tea.Quit
case "j", "down":
m.choice += 1
if m.choice > len(m.choices) {
m.choice = len(m.choices)
}
case "k", "up":
m.choice -= 1
if m.choice < 0 {
m.choice = 0
}
case "l", "enter":
m.File <- m.choices[m.choice]
return m, tea.Quit
}
case tickMsg:
if m.tic == 0 {
m.Quitting = true
return m, tea.Quit
}
m.tic -= 1
return m, tick()
}
return m, nil
}
// Update loop for the second view after a choice has been made
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
if !m.chosen {
return choicesUpdate(msg, m)
}
return m, tea.Quit
}
// Views return a string based on data in the host. That string which will be
// rendered to the terminal.
func (m host) View() string {
var s string
if m.Quitting {
logger.Logger("[LOG] leaving pgm")
return "leaving pgm..."
}
if !m.chosen {
s = choicesView(m)
} else {
m.Quitting = true
logger.Logger("[LOG] leaving pgm")
return "leaving pgm..."
}
return s
}
// Creates the menu for choosing a file from your configs.
// Exits after 60 seconds.
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
func choicesView(m host) string {
var menu string
c := m.choice
tpl := m.t + "\n\n"
tpl += "%s\n\n"
tpl += "menu exits in %s seconds\n\n"
tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
for i, s := range m.choices {
menu = menu + "::" + checkbox(s, c == i)
}
mn := strings.Replace(menu, "::", "\n", -1)
choices := fmt.Sprintf(
mn,
)
return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
}
// the rest of these functions are to do with looks and aesthetics
// returns a neat checkbox
func checkbox(label string, checked bool) string {
if checked {
return colorFg("[x] "+label, "212")
}
return fmt.Sprintf("[ ] %s", label)
}
// fun colors
func colorFg(val, color string) string {
return termenv.String(val).Foreground(term.Color(color)).String()
}
// Return a function that will colorize the foreground of a given string.
func makeFgStyle(color string) func(string) string {
return termenv.Style{}.Foreground(term.Color(color)).Styled
}
// Frame event
func Frame() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return FrameMsg{}
})
}
type tickMsg struct{}
type FrameMsg struct{}
// exits if no choice is selected in a minute
func tick() tea.Cmd {
return tea.Tick(time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}
// loads a yaml file into the correct format for consumption.
// errs the pipeline step if there is not a configuration in this repository.
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
cfg, err := ioutil.ReadFile(c)
if err != nil {
logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
os.Exit(1)
}
json.Unmarshal(cfg, &p)
return p
}
// Simple struct for a view in the UI.
type Scr struct {
Query string `json:"query"`
Columns int `json:"columns"`
UIType string `json:"uiType"`
Title string `json:"title"`
Quitting bool
}
func (m Scr) Init() tea.Cmd {
return tick()
}
func (m Scr) View() string {
return m.Query
}
func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
return m, nil
}

View File

@ -66,7 +66,7 @@ func init() {
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.pgm.yaml)")
rootCmd.PersistentFlags().StringVar(&cfgFile, "hostCfgFile", "", "hostCfgFile file (default is $HOME/.pgm.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
@ -76,7 +76,7 @@ func init() {
viper.SetDefault("homeDir", homeDirectory)
err = viper.ReadInConfig()
if err != nil {
logger.Logger("[ERROR] viper config issue" + err.Error())
logger.Logger("[ERROR] viper hostCfgFile issue" + err.Error())
}
pgmDir := viper.GetString("pgm_dir")
logsDir := viper.GetString("logs_dir")
@ -89,10 +89,10 @@ func init() {
}
// initConfig reads in config file and ENV variables if set.
// initConfig reads in hostCfgFile file and ENV variables if set.
func InitConfig() {
if cfgFile != "" {
// Use config file from the flag.
// Use hostCfgFile file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
@ -101,7 +101,7 @@ func InitConfig() {
fmt.Println(err)
os.Exit(1)
}
// Search config in home directory with name ".pgm" (without extension).
// Search hostCfgFile in home directory with name ".pgm" (without extension).
viper.AddConfigPath(home)
viper.SetConfigName(".pgm")
@ -120,8 +120,8 @@ func InitConfig() {
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
// If a hostCfgFile file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
logger.Logger("[LOG] Using config file:"+ viper.ConfigFileUsed())
logger.Logger("[LOG] Using hostCfgFile file:"+ viper.ConfigFileUsed())
}
}

View File

@ -1,264 +0,0 @@
package loader
import (
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"pgm/ui"
"github.com/muesli/termenv"
"github.com/spf13/viper"
"io/ioutil"
"os"
"pgm/config"
"pgm/data"
"pgm/logger"
"strconv"
"strings"
"time"
)
type host struct {
tic int
t string
choices []string
choice int
chosen bool
Quitting bool
Frames int
File string
isTest bool
}
var (
term = termenv.ColorProfile()
subtle = makeFgStyle("241")
dot = colorFg(" • ", "236")
)
func Loader(t []string, s string, b string) error {
var hostChoice host
var isT bool
isT = setTest(s)
hostChoice = host{60, s, t, 0, false, false, 0, b, isT}
if err := tea.NewProgram(hostChoice).Start(); err != nil {
if isT == true {
return nil
}
logger.Logger("[ERROR] tea error occured")
os.Exit(1)
}
return nil
}
// method to enable go tests if set to true
// loader function will test that it can load the ui to chose a database
// and quits the ui
func setTest(s string) bool {
if s == "test" {
logger.Logger("[LOG] is test: true")
return true
}
logger.Logger("[LOG] is test: false")
return false
}
func (m host) Init() tea.Cmd {
return tick()
}
func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "esc":
m.Quitting = true
return m, tea.Quit
case "j", "down":
m.choice += 1
if m.choice > len(m.choices) {
m.choice = len(m.choices)
}
case "k", "up":
m.choice -= 1
if m.choice < 0 {
m.choice = 0
}
case "l", "enter":
m.File = m.choices[m.choice]
g := LoadUi(m)
ui.GraphicUi(g)
return m, tea.Quit
}
case tickMsg:
if m.isTest == true {
m.File = "test"
m.Quitting = true
return m, tea.Quit
}
if m.tic == 0 {
m.Quitting = true
return m, tea.Quit
}
m.tic -= 1
return m, tick()
}
return m, nil
}
// Update loop for the second view after a choice has been made
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
if !m.chosen {
return choicesUpdate(msg, m)
}
return m, tea.Quit
}
// Views return a string based on data in the host. That string which will be
// rendered to the terminal.
func (m host) View() string {
var s string
if m.Quitting {
logger.Logger("[LOG] leaving pgm")
return "leaving pgm..."
}
if !m.chosen {
s = choicesView(m)
} else {
m.Quitting = true
logger.Logger("[LOG] leaving pgm")
return "leaving pgm..."
}
return s
}
// Passes our config to the screen file.
// screen is tasked with rendering more in depth views.
func LoadUi(m host) data.HostDetails {
var g data.HostDetails
var h string
h, err := os.UserHomeDir()
if err != nil {
logger.Logger("[ERROR] uable to set home dir: " + err.Error())
os.Exit(0)
}
config.ReadConfig()
l := viper.GetString("hosts_dir")
filepath := h + "/" + l + "/" + m.File
loadhost(filepath, &g)
return g
}
// Creates the menu for choosing a file from your configs.
// Exits after 60 seconds.
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
func choicesView(m host) string {
var menu string
c := m.choice
tpl := m.t + "\n\n"
tpl += "%s\n\n"
tpl += "menu exits in %s seconds\n\n"
tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
for i, s := range m.choices {
menu = menu + "::" + checkbox(s, c == i)
}
mn := strings.Replace(menu, "::", "\n", -1)
choices := fmt.Sprintf(
mn,
)
return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
}
// the rest of these functions are to do with looks and aesthetics
// returns a neat checkbox
func checkbox(label string, checked bool) string {
if checked {
return colorFg("[x] "+label, "212")
}
return fmt.Sprintf("[ ] %s", label)
}
// fun colors
func colorFg(val, color string) string {
return termenv.String(val).Foreground(term.Color(color)).String()
}
// Return a function that will colorize the foreground of a given string.
func makeFgStyle(color string) func(string) string {
return termenv.Style{}.Foreground(term.Color(color)).Styled
}
// Frame event
func Frame() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return FrameMsg{}
})
}
type tickMsg struct{}
type FrameMsg struct{}
// exits if no choice is selected in a minute
func tick() tea.Cmd {
return tea.Tick(time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}
// loads a yaml file into the correct format for consumption.
// errs the pipeline step if there is not a configuration in this repository.
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
cfg, err := ioutil.ReadFile(c)
if err != nil {
logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
os.Exit(1)
}
json.Unmarshal(cfg, &p)
return p
}
// Simple struct for a view in the UI.
type Scr struct {
Query string `json:"query"`
Columns int `json:"columns"`
UIType string `json:"uiType"`
Title string `json:"title"`
Quitting bool
}
func (m Scr) Init() tea.Cmd {
return tick()
}
func (m Scr) View() string {
return m.Query
}
func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
return m, nil
}

120
ui/ui.go
View File

@ -1,120 +0,0 @@
package ui
import (
"fmt"
"os"
"pgm/data"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"github.com/muesli/termenv"
)
const content = `
Todays Menu
| Name | Price | Notes |
| --- | --- | --- |
| Tsukemono | $2 | Just an appetizer |
| Tomato Soup | $4 | Made with San Marzano tomatoes |
| Okonomiyaki | $4 | Takes a few minutes to make |
| Curry | $3 | We can add squash if youd like |
Seasonal Dishes
| Name | Price | Notes |
| --- | --- | --- |
| Steamed bitter melon | $2 | Not so bitter |
| Takoyaki | $3 | Fun to eat |
| Winter squash | $3 | Today it's pumpkin |
Desserts
| Name | Price | Notes |
| --- | --- | --- |
| Dorayaki | $4 | Looks good on rabbits |
| Banana Split | $5 | A classic |
| Cream Puff | $3 | Pretty creamy! |
All our dishes are made in-house by Karen, our chef. Most of our ingredients
are from are our garden, or the fish market down the street.
Some famous people that have eaten here lately:
* [x] René Redzepi
* [x] David Chang
* [ ] Jiro Ono (maybe some day)
Bon appétit!
`
var term = termenv.ColorProfile()
type example struct {
viewport viewport.Model
}
func newExample() (*example, error) {
vp := viewport.Model{Width: 78, Height: 20}
renderer, err := glamour.NewTermRenderer(glamour.WithStylePath("notty"))
if err != nil {
return nil, err
}
str, err := renderer.Render(content)
if err != nil {
return nil, err
}
vp.SetContent(str)
return &example{
viewport: vp,
}, nil
}
func (e example) Init() tea.Cmd {
return nil
}
func (e example) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "ctrl+c":
return e, tea.Quit
case "a":
default:
vp, _ := viewport.Update(msg, e.viewport)
e.viewport = vp
}
case tea.WindowSizeMsg:
e.viewport.Width = msg.Width
return e, nil
}
return e, nil
}
func (e example) View() string {
return viewport.View(e.viewport) + e.helpView()
}
func (e example) helpView() string {
return termenv.String("\n ↑/↓: Navigate • q: Quit\n").Foreground(term.Color("241")).String()
}
func GraphicUi(g data.HostDetails) {
model, err := newExample()
if err != nil {
fmt.Println("Could not intialize Bubble Tea model:", err)
os.Exit(1)
}
if err := tea.NewProgram(model).Start(); err != nil {
fmt.Println("Bummer, there's been an error:", err)
os.Exit(1)
}
}