smutok/internal/tui/tui.go(view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
package tui
import (
"context"
"github.com/charmbracelet/bubbles/table"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour"
"olexsmir.xyz/smutok/internal/store"
)
type Syncer interface {
Sync(ctx context.Context) error
}
type AppState int
const (
ArticlesView AppState = iota
FeedsView
ReadingView
)
type Model struct {
ctx context.Context
state AppState
isQutting bool
isSyncing bool
showErr bool
err error
syncer Syncer
store *store.Sqlite
articlesKind store.ArticleKind
articles []store.Article
glamur *glamour.TermRenderer
table table.Model // feeds, articles feed
viewport viewport.Model // article content reader
}
func NewModel(
ctx context.Context,
syncer Syncer,
sqlite *store.Sqlite,
) *Model {
tbl := table.New(table.WithFocused(true))
vp := viewport.New(0, 0)
return &Model{
ctx: ctx,
syncer: syncer,
store: sqlite,
glamur: &glamour.TermRenderer{},
table: tbl,
viewport: vp,
state: ArticlesView,
articlesKind: store.ArticleUnread,
isQutting: false,
showErr: false,
err: nil,
articles: nil,
isSyncing: false,
}
}
func (m *Model) Init() tea.Cmd {
return tea.Batch(
tea.SetWindowTitle("smutok"),
m.fetchArticles(m.articlesKind),
m.triggerSync(),
)
}
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case errMsg:
m.err = msg
m.showErr = true
return m, nil
case tea.WindowSizeMsg:
m.table.SetHeight(msg.Height)
m.table.SetWidth(msg.Width - 2)
m.viewport.Height = msg.Height
m.viewport.Width = msg.Width
return m, nil
case fetchedArticles:
m.articles = msg
m.setupTableWithArticles()
return m, nil
case triggerSync:
m.isSyncing = true
return m, m.triggerSync()
case finishedSync:
m.isSyncing = false
return m, m.fetchArticles(m.articlesKind)
case tea.KeyMsg:
// page specific keys
switch m.state {
case ArticlesView, FeedsView:
m.table, cmd = m.table.Update(msg)
case ReadingView:
m.viewport, cmd = m.viewport.Update(msg)
}
// global keys
switch msg.String() {
case "q":
m.isQutting = true
return m, tea.Quit
case "s":
return m, m.triggerSync()
}
}
return m, cmd
}
func (m *Model) View() string {
if m.isQutting {
return ""
}
var out string
switch m.state {
case ArticlesView, FeedsView:
out = m.table.View()
case ReadingView:
out = m.viewport.View()
}
return out
}
|