diff --git a/go.mod b/go.mod index e8abe10..6e9b264 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/muesli/reflow v0.3.0 github.com/spf13/cobra v1.6.1 golang.org/x/exp v0.0.0-20230116083435-1de6713980de + golang.org/x/term v0.4.0 gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/yaml.v3 v3.0.1 ) @@ -42,6 +43,5 @@ require ( github.com/yuin/goldmark-emoji v1.0.1 // indirect golang.org/x/net v0.5.0 // indirect golang.org/x/sys v0.4.0 // indirect - golang.org/x/term v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect ) diff --git a/tui/pages/examples/examples.go b/tui/pages/examples/examples.go index bd85323..9d69067 100644 --- a/tui/pages/examples/examples.go +++ b/tui/pages/examples/examples.go @@ -4,6 +4,7 @@ package examples import ( "fmt" "math/rand" + "os" "strings" "time" @@ -20,6 +21,7 @@ import ( "github.com/charmbracelet/bubbles/progress" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "golang.org/x/term" ) const ( @@ -86,6 +88,10 @@ type tickK8SGet time.Time // New returns a new model of the examples page. // nolint: golint // model not used outside of this package func New(e tui.LoadedExamples, width, height int, c config.Provider) model { + physicalWidth, physicalHeight, _ := term.GetSize(int(os.Stdout.Fd())) + wP := physicalWidth - tui.AppStyle.GetHorizontalPadding() + hP := physicalHeight - tui.AppStyle.GetVerticalPadding() + listKeys := tui.NewListKeyMap() delegate := list.NewDefaultDelegate() @@ -121,11 +127,10 @@ func New(e tui.LoadedExamples, width, height int, c config.Provider) model { "Bean "+c.Version, "A FrangipaneTeam bin", width, - int(float64(height)*0.2), c, ) - footer := footer.New(width, int(float64(width)*0.2), listKeys) + footer := footer.New(width, listKeys) k8sCmdList = make(map[string]*k8sCmd) @@ -139,7 +144,7 @@ func New(e tui.LoadedExamples, width, height int, c config.Provider) model { header: header, footer: footer, errorPanel: errorpanel.New(width, int(float64(height)*0.6)), - markdown: md.New(width, int(float64(height)*0.6)), + markdown: md.New(wP, hP-20), width: width, height: height, config: c, @@ -325,29 +330,26 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.k8sCurrentFiles = file m.header.Notification = fmt.Sprintf("k %s @ %s", verb, time.Now().Format("15:04:05")) + m.header.NotificationOK = tui.RunningMark cmd = tools.Kubectl(verb, file, cmdID) return m, cmd } } case tea.WindowSizeMsg: - top, right, bottom, left := tui.AppStyle.GetMargin() + top, right, bottom, left := tui.AppStyle.GetPadding() m.width, m.height = msg.Width-left-right, msg.Height-top-bottom + centerH := m.height - lipgloss.Height(m.header.View()) - lipgloss.Height(m.footer.View()) - m.header.Width = m.width - m.header.Height = int(float64(m.height) * 0.2) - m.footer.Width = m.width - m.footer.Height = int(float64(m.height) * 0.2) + m.header.Width = msg.Width + m.footer.Width = msg.Width m.markdown.Width = m.width - m.markdown.Viewport.Width = m.width - m.markdown.Viewport.Height = int(float64(m.height) * 0.6) - // m.footer.Help.Height = int(float64(m.height) * 0.15) + m.markdown.Viewport.Width = m.width - right - left + m.markdown.Viewport.Height = centerH - m.currentList.SetSize(m.width, int(float64(m.height)*0.6)) - m.errorPanel.Resize(m.width, int(float64(m.height)*0.6)) - // m.footer.Resize(m.width, int(float64(m.height)*0.15)) - // m.header.Resize(m.width, int(float64(m.height)*0.15)) + m.currentList.SetSize(m.width, centerH) + m.errorPanel.Resize(m.width, centerH) case tui.LoadedExamples: m.exampleList = msg.Examples @@ -392,7 +394,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.Verb { case "apply", "delete": m.k8sProgressMsg = "" - m.header.Notification = fmt.Sprintf("k %s @ %s ✓", msg.Verb, time.Now().Format("15:04:05")) + m.header.Notification = fmt.Sprintf("k %s @ %s", msg.Verb, time.Now().Format("15:04:05")) + m.header.NotificationOK = tui.CheckMark // cmd := m.currentList.NewStatusMessage(fmt.Sprintf("kubectl %s ok", msg.Verb)) return m, cmd @@ -400,7 +403,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.viewName = pK8S m.k8sCurrentIDView = msg.CmdID m.k8sProgressMsg = "" - m.header.Notification = fmt.Sprintf("k %s @ %s ✓", msg.Verb, time.Now().Format("15:04:05")) + m.header.Notification = fmt.Sprintf("k %s @ %s", msg.Verb, time.Now().Format("15:04:05")) + m.header.NotificationOK = tui.CheckMark if !m.tickRunning { m.tickRunning = true cmd = m.tickCmd() @@ -464,62 +468,81 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // View returns the string representation of the model. func (m model) View() string { + physicalWidth, physicalHeight, _ := term.GetSize(int(os.Stdout.Fd())) + doc := strings.Builder{} + h := lipgloss.Height + header := strings.Builder{} + footer := strings.Builder{} + center := strings.Builder{} + + // header + { + header.WriteString(m.header.View()) + } + + // footer + { + footer.WriteString(m.footer.View()) + } + + wP := physicalWidth - tui.AppStyle.GetHorizontalPadding() + hP := physicalHeight - tui.AppStyle.GetVerticalPadding() + + hCenter := hP - h(header.String()) - h(footer.String()) + var view string if m.errorRaised { - errorP := lipgloss.NewStyle().Height(m.currentList.Height()).Render(m.errorPanel.View()) - view = lipgloss.JoinVertical( - lipgloss.Center, - m.header.View(), - errorP, - m.footer.View(), - ) - - // return errorP + errorP := lipgloss.NewStyle(). + Height(int(float64(hCenter) * 0.5)). + Render(m.errorPanel.View()) + + center.WriteString(errorP) } else { switch m.viewName { case pViewPort: - view = lipgloss.JoinVertical( + ui := lipgloss.Place( + wP, + hCenter, + lipgloss.Center, lipgloss.Center, - m.header.View(), - m.markdown.Viewport.View(), - m.footer.View(), + lipgloss.NewStyle().Padding(1, 0, 1, 0).Render(m.markdown.Viewport.View()), ) + center.WriteString(lipgloss.NewStyle().Render(ui)) + case pK8S: cmd := k8sCmdList[m.k8sCurrentIDView] getOutput := "loading..." reloadOutput := "" - // w := lipgloss.Width h := "Using ressource : " + m.k8sCurrentKind h = lipgloss.NewStyle().Background(tui.RedColour).Margin(0, 0, 1, 0).Render(h) if cmd.done { reloadOutput = fmt.Sprintf("%s reloading... %s", m.progressK8SGet.View(), m.k8sProgressMsg) - reloadOutput = lipgloss.NewStyle().MaxWidth(m.width).Margin(1, 0, 1, 0).Render(reloadOutput) - getOutput = lipgloss.NewStyle().MaxWidth(m.width).Border(lipgloss.RoundedBorder()).Render(cmd.cmdOutput) + reloadOutput = lipgloss.NewStyle().MaxWidth(wP).Margin(1, 0, 1, 0).Render(reloadOutput) + cmd := lipgloss.NewStyle().Padding(2).Render(cmd.cmdOutput) + getOutput = lipgloss.NewStyle().MaxWidth(wP).Border(lipgloss.RoundedBorder()).Render(cmd) } ui := lipgloss.JoinVertical(lipgloss.Center, h, getOutput, reloadOutput) - dialog := lipgloss.Place(m.width, m.currentList.Height(), + dialog := lipgloss.Place(wP, hCenter, lipgloss.Center, lipgloss.Center, lipgloss.NewStyle().Render(ui), ) - view = lipgloss.JoinVertical( - lipgloss.Center, - m.header.View(), - dialog, - m.footer.View(), - ) + center.WriteString(dialog) case pRoot, pRessources: - view = lipgloss.JoinVertical( + ui := lipgloss.Place( + wP, + hCenter, lipgloss.Left, - m.header.View(), - m.currentList.View(), - m.footer.View(), + lipgloss.Top, + lipgloss.NewStyle().Padding(1, 0, 0, 0).Render(m.currentList.View()), ) + center.WriteString(lipgloss.NewStyle().Render(ui)) + case pPrintActions: yamlFile := m.currentList.SelectedItem().(*tui.Example).Title() @@ -558,17 +581,30 @@ func (m model) View() string { str..., ) - actions = lipgloss.NewStyle().Copy().Align(lipgloss.Center, lipgloss.Center).Foreground(tui.HighlightColour).Height(m.currentList.Height()).Width(m.width).Render(actions) - - view = lipgloss.JoinVertical( - lipgloss.Center, - m.header.View(), - actions, - m.footer.View(), - ) + actions = lipgloss.NewStyle().Copy().Align(lipgloss.Center, lipgloss.Center).Foreground(tui.HighlightColour).Height(hCenter).Width(wP).Render(actions) + center.WriteString(lipgloss.NewStyle().Render(actions)) } } - return tui.AppStyle.Render(view) + + // Render the document + doc.WriteString(lipgloss.JoinVertical( + lipgloss.Center, + header.String(), + center.String(), + footer.String(), + )) + + if physicalWidth > 0 { + tui.AppStyle = tui.AppStyle.MaxWidth(physicalWidth).MaxHeight(physicalHeight) + } + + if m.errorRaised { + return view + } + + // Okay, let's print it + return tui.AppStyle.Render(doc.String()) + // return lipgloss.NewStyle().Render(doc.String()) } func (m model) showExamples() (model, tea.Cmd) { diff --git a/tui/pages/footer/footer.go b/tui/pages/footer/footer.go index c5db859..a044d9e 100644 --- a/tui/pages/footer/footer.go +++ b/tui/pages/footer/footer.go @@ -3,6 +3,7 @@ package footer import ( "fmt" + "strings" "github.com/FrangipaneTeam/bean/tui" "github.com/charmbracelet/bubbles/help" @@ -10,13 +11,17 @@ import ( "github.com/charmbracelet/lipgloss" ) +var ( + subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"} +) + // Model is the model of the footer type Model struct { tea.Model - Message string - Width, Height int - Help help.Model - Keymap *tui.ListKeyMap + Message string + Width int + Help help.Model + Keymap *tui.ListKeyMap } // Init initializes the model @@ -25,7 +30,7 @@ func (m Model) Init() tea.Cmd { } // New creates a new footer model -func New(w, h int, km *tui.ListKeyMap) Model { +func New(w int, km *tui.ListKeyMap) Model { help := help.New() help.Styles.ShortSeparator = tui.Ellipsis help.Styles.ShortKey = tui.HelpText @@ -36,7 +41,6 @@ func New(w, h int, km *tui.ListKeyMap) Model { return Model{ Message: "FrangipaneTeam", Width: w, - Height: h, Help: help, Keymap: km, } @@ -49,21 +53,40 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { // View renders the model func (m Model) View() string { + footer := strings.Builder{} // panel := tui.BorderTop.Width(m.Width).Render(text) - message := fmt.Sprintf("• %s •", m.Message) + message := fmt.Sprintf( + "%s %s %s", + tui.Divider, strings.Trim(m.Message, "\n"), tui.Divider, + ) + + wP := m.Width - tui.AppStyle.GetHorizontalPadding() + f := lipgloss.NewStyle().Height(3).Width(wP) + + ui := lipgloss.Place( + wP, + 3, + lipgloss.Center, + lipgloss.Center, + lipgloss.NewStyle(). + Border(lipgloss.NormalBorder(), true, false, false, false). + BorderForeground(tui.BorderColour). + Foreground(subtle). + Render(message), + ) + banner := lipgloss.JoinVertical( lipgloss.Center, m.Help.View(m.Keymap), - tui.BorderTop.Width(m.Width).Render(""), - tui.HightlightTextStyle.Render(message), + ui, ) - return banner + footer.WriteString(f.Render(banner)) + return footer.String() } // Resize resizes the model func (m Model) Resize(width, height int) Model { m.Width = width - m.Height = height m.Help.Width = width return m diff --git a/tui/pages/header/header.go b/tui/pages/header/header.go index bc5a1dd..7f890db 100644 --- a/tui/pages/header/header.go +++ b/tui/pages/header/header.go @@ -26,8 +26,9 @@ type Model struct { activityFrom interface{} notifyCrds chan pages.NotifyActivity notifyExamples chan pages.NotifyActivity - Width, Height int + Width int Notification string + NotificationOK string // errorPanel errorpanel.Model // errorRaised bool config config.Provider @@ -46,7 +47,7 @@ func (m Model) Init() tea.Cmd { } // New creates a new header model -func New(title string, desc string, w int, h int, c config.Provider) Model { +func New(title string, desc string, w int, c config.Provider) Model { s := spinner.New() s.Spinner = spinner.Points s.Style = lipgloss.NewStyle().Foreground(tui.SpinnerColour) @@ -54,13 +55,13 @@ func New(title string, desc string, w int, h int, c config.Provider) Model { return Model{ Title: title, Description: desc, + Notification: "ready", + NotificationOK: tui.RunningMark, spinner: s, notifyCrds: make(chan pages.NotifyActivity), notifyExamples: make(chan pages.NotifyActivity), Width: w, - Height: h, - // errorPanel: errorpanel.New(w, h), - config: c, + config: c, } } @@ -145,16 +146,15 @@ func (m Model) View() string { notification := strings.Builder{} - if m.Notification != "" { - t := strings.Trim(m.Notification, "\n") - fmt.Fprintf(¬ification, "• %s", t) - } + t := strings.Trim(m.Notification, "\n") + fmt.Fprintf(¬ification, "%s %s %s", tui.Divider, t, m.NotificationOK) + wP := m.Width - tui.AppStyle.GetHorizontalPadding() header = lipgloss.JoinHorizontal( lipgloss.Top, - lipgloss.NewStyle().Width(m.Width/2).Align(lipgloss.Left).Render(header), + lipgloss.NewStyle().Width(wP/2).Align(lipgloss.Left).Render(header), tui.NotificationStyle. - Width(m.Width/2). + Width(wP/2). Align(lipgloss.Right). Render(notification.String()), ) @@ -163,14 +163,15 @@ func (m Model) View() string { lipgloss.Center, lipgloss.NewStyle(). MarginBottom(1). - Width(m.Width-4). + Width(wP). Align(lipgloss.Center). Render(nameVersion), header, + // tui.BorderBottom.Width(wP).String(), ) - border := tui.BorderBottom.Width(m.Width).Render(banner) + banner += "\n" + tui.BorderBottom.Width(wP).String() - return border + return banner } type tickMsg time.Time diff --git a/tui/pages/md/md.go b/tui/pages/md/md.go index c4b8a1e..312603f 100644 --- a/tui/pages/md/md.go +++ b/tui/pages/md/md.go @@ -25,8 +25,8 @@ func New(w, h int) Model { vp := viewport.New(w, h) vp.Style = lipgloss.NewStyle(). BorderStyle(lipgloss.RoundedBorder()). - BorderForeground(tui.BorderColour). - PaddingRight(2) + BorderForeground(tui.BorderColour) + // PaddingRight(2) return Model{ Width: w, Height: h, diff --git a/tui/theme.go b/tui/theme.go index 380e490..9953d9c 100644 --- a/tui/theme.go +++ b/tui/theme.go @@ -7,7 +7,7 @@ import ( var ( PrimaryColour = lipgloss.Color("#3a1577") SecondaryColour = lipgloss.Color("#5b1689") - BorderColour = lipgloss.Color("#CBEDD5") + BorderColour = lipgloss.Color("#807d8a") FeintColour = lipgloss.Color("#807d8a") VeryFeintColour = lipgloss.Color("#5e5e5e") TextColour = lipgloss.Color("#f6f5fc") @@ -16,20 +16,39 @@ var ( AmberColour = lipgloss.Color("#e68a35") GreenColour = lipgloss.Color("#26a621") RedColour = lipgloss.Color("#FD8A8A") - SpinnerColour = lipgloss.Color("#439A97") + SpinnerColour = lipgloss.Color("#FF5F87") + DividerColour = lipgloss.Color("#FF5F87") + NotificationColour = lipgloss.Color("#FF5F87") + special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"} - AppStyle = lipgloss.NewStyle().Margin(2) + AppStyle = lipgloss.NewStyle().Padding(2, 2) TextStyle = lipgloss.NewStyle().Foreground(TextColour) FeintTextStyle = lipgloss.NewStyle().Foreground(FeintColour) VeryFeintTextStyle = lipgloss.NewStyle().Foreground(VeryFeintColour) HightlightTextStyle = lipgloss.NewStyle().Foreground(HighlightColour) + Divider = lipgloss.NewStyle(). + SetString("•"). + Padding(0, 1). + Foreground(DividerColour). + String() + Ellipsis = HightlightTextStyle.Copy() HelpText = TextStyle.Copy() HelpFeintText = FeintTextStyle.Copy() // Error + CheckMark = lipgloss.NewStyle().SetString("✓"). + Foreground(special). + PaddingRight(1). + String() + + RunningMark = lipgloss.NewStyle().SetString("?"). + Foreground(AmberColour). + PaddingRight(1). + String() + Label = lipgloss.NewStyle(). Background(RedColour). Foreground(TextColour). @@ -53,9 +72,5 @@ var ( // Foreground(lipgloss.AdaptiveColor{Light: "#343433", Dark: "#C1C6B2"}). // Background(lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#353533"}) NotificationStyle = lipgloss.NewStyle(). - // Inherit(StatusBarStyle). - Foreground(lipgloss.Color(RedColour)) - // Background(lipgloss.Color("#FF5F87")) - // Padding(0, 1). - // MarginRight(1) + Foreground(lipgloss.Color(NotificationColour)) )