package ui import ( "fmt" "strings" gemlog "git.travisshears.com/travisshears/gemlog-cli/gemlog" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/muesli/reflow/wordwrap" ) type EntryPageModel struct { entry gemlog.GemlogEntry ready bool viewport viewport.Model } func initialEntryPageModel() EntryPageModel { return EntryPageModel{} } func wrapGemtxt(gemtxt string, width int) string { return wordwrap.String(gemtxt, width) } func (m EntryPageModel) Update(msg tea.Msg, active bool, ctx *context) (EntryPageModel, tea.Cmd) { var ( cmd tea.Cmd cmds []tea.Cmd ) switch msg := msg.(type) { case GemLogLoaded: m.entry = msg.Log m.viewport.SetContent(wrapGemtxt(m.entry.Gemtxt, m.viewport.Width)) case tea.KeyMsg: if !active { return m, nil } switch msg.String() { case "left", "h": cmd := func() tea.Msg { return SwitchPages{Page: EntryList} } return m, cmd } case tea.WindowSizeMsg: headerHeight := lipgloss.Height(m.headerView("loading slug...")) footerHeight := lipgloss.Height(m.footerView()) verticalMarginHeight := headerHeight + footerHeight if !m.ready { // Since this program is using the full size of the viewport we // need to wait until we've received the window dimensions before // we can initialize the viewport. The initial dimensions come in // quickly, though asynchronously, which is why we wait for them // here. m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) m.viewport.YPosition = headerHeight m.viewport.SetContent(wrapGemtxt(m.entry.Gemtxt, msg.Width)) m.ready = true } else { m.viewport.Width = msg.Width m.viewport.Height = msg.Height - verticalMarginHeight } } // Handle keyboard and mouse events in the viewport m.viewport, cmd = m.viewport.Update(msg) cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } func (m EntryPageModel) View() string { if !m.ready { return "\n Initializing..." } return fmt.Sprintf("%s\n%s\n%s", m.headerView(m.entry.Slug), m.viewport.View(), m.footerView()) } var ( titleStyle = func() lipgloss.Style { b := lipgloss.RoundedBorder() b.Right = "├" return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) }() infoStyle = func() lipgloss.Style { b := lipgloss.RoundedBorder() b.Left = "┤" return titleStyle.BorderStyle(b) }() ) func (m EntryPageModel) headerView(slug string) string { title := titleStyle.Render(slug) line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title))) return lipgloss.JoinHorizontal(lipgloss.Center, title, line) } func (m EntryPageModel) footerView() string { info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info))) return lipgloss.JoinHorizontal(lipgloss.Center, line, info) } func max(a, b int) int { if a > b { return a } return b }