big refacto

This commit is contained in:
GitBluub
2024-05-05 19:54:18 +02:00
parent 76a8209b90
commit bed5152899
8 changed files with 496 additions and 283 deletions
+52
View File
@@ -0,0 +1,52 @@
package main
import "github.com/charmbracelet/bubbles/key"
type ListKeyMap struct {
Query key.Binding
BookmarkToggle key.Binding
ReadToggle key.Binding
ReadLaterToggle key.Binding
IgnoreToggle key.Binding
PreviewPost key.Binding
GoToFeeds key.Binding
GoToPosts key.Binding
}
func NewListKeyMap() *ListKeyMap {
return &ListKeyMap{
GoToFeeds: key.NewBinding(
key.WithKeys("f"),
key.WithHelp("f", "go to feeds"),
),
GoToPosts: key.NewBinding(
key.WithKeys("p"),
key.WithHelp("p", "go to posts"),
),
PreviewPost: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "preview post"),
),
Query: key.NewBinding(
key.WithKeys("/"),
key.WithHelp("/", "query posts"),
),
BookmarkToggle: key.NewBinding(
key.WithKeys("b"),
key.WithHelp("b", "toggle bookmarked"),
),
ReadToggle: key.NewBinding(
key.WithKeys("r"),
key.WithHelp("r", "toggle mark as read"),
),
IgnoreToggle: key.NewBinding(
key.WithKeys("x", "d"),
key.WithHelp("d/x", "ignore post"),
),
ReadLaterToggle: key.NewBinding(
key.WithKeys("m"),
key.WithHelp("m", "add to read later"),
),
}
}
+36 -229
View File
@@ -2,7 +2,6 @@ package main
import (
"os"
"strings"
"time"
"github.com/charmbracelet/bubbles/key"
@@ -10,39 +9,53 @@ import (
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
huh "github.com/charmbracelet/huh"
"github.com/google/uuid"
. "github.com/zoryia/vex/tui/models"
. "github.com/zoryia/vex/tui/pages"
"github.com/zoryia/vex/tui/pages/auth"
"github.com/zoryia/vex/tui/pages/feeds"
"github.com/zoryia/vex/tui/pages/preview"
)
type Model struct {
list list.Model
textInput textinput.Model
err error
auth auth.Model
page VexPage
query string
feeds []Feed
entries []Entry
tags []string
keys *ListKeyMap
Preview preview.Model
// entries
list list.Model
queryInput textinput.Model
err error
page VexPage
tags []string
keys *ListKeyMap
Preview preview.Model
Feeds feeds.Model
Auth auth.Model
}
func New() *Model {
func queryInput() textinput.Model {
ti := textinput.New()
ti.Placeholder = "Search Query"
ti.CharLimit = 156
ti.Width = 56
return &Model{textInput: ti, auth: auth.New(), page: ENTRIES, keys: NewListKeyMap(), Preview: preview.Model{Viewport: viewport.New(0, 0)}}
return ti
}
func New() *Model {
return &Model{
queryInput: queryInput(),
page: LOGIN,
keys: NewListKeyMap(),
Preview: preview.Model{Viewport: viewport.New(0, 0)},
Feeds: feeds.New(),
Auth: auth.New(),
}
}
func (m Model) getEverything() tea.Cmd {
return func() tea.Msg {
return tea.Batch(getEntries(m.auth.Jwt)) // getTags, getFeeds)
return tea.Batch(getEntries(m.Auth.Jwt)) // getTags, getFeeds)
}
}
@@ -50,6 +63,7 @@ func (m *Model) initList(width int, height int) {
m.list = list.New([]list.Item{}, list.NewDefaultDelegate(), width, height)
m.list.Title = "Posts"
m.list.SetFilteringEnabled(false)
m.list.DisableQuitKeybindings()
m.list.AdditionalShortHelpKeys = func() []key.Binding {
return []key.Binding{
m.keys.Query,
@@ -58,6 +72,7 @@ func (m *Model) initList(width int, height int) {
}
m.list.AdditionalFullHelpKeys = func() []key.Binding {
return []key.Binding{
m.keys.GoToFeeds,
m.keys.ReadToggle,
m.keys.ReadLaterToggle,
m.keys.BookmarkToggle,
@@ -75,219 +90,11 @@ func (m *Model) initList(width int, height int) {
}
func (m Model) Init() tea.Cmd {
return tea.Batch(m.auth.LoginForm.Init(), m.auth.RegisterForm.Init(), checkJwt(m.auth.Jwt))
}
func (m Model) handleSearchCompletion() (Model, tea.Cmd) {
var cmd tea.Cmd
queryWords := strings.Split(m.textInput.Value(), " ")
if len(queryWords) == 0 {
return m, cmd
}
lastWord := queryWords[len(queryWords)-1]
if lastWord == "tag:" {
var feed string
huh.NewSelect[string]().
Title("Pick a feed.").
Options(
huh.NewOption("United States", "US"),
huh.NewOption("Germany", "DE"),
huh.NewOption("Brazil", "BR"),
huh.NewOption("Canada", "CA"),
).
Value(&feed).Run()
m.textInput.SetValue(m.textInput.Value() + feed)
m.textInput.CursorEnd()
}
if lastWord == "feed:" {
var feed string
var feeds = []string{"Devops", "System", "Angular"}
huh.NewSelect[string]().
Title("Pick a feed.").
Options(
huh.NewOptions(feeds...)...,
).
Value(&feed).Run()
m.textInput.SetValue(m.textInput.Value() + feed)
m.textInput.CursorEnd()
}
return m, cmd
}
func (m *Model) deleteWordBackward() {
if m.textInput.Position() == 0 || len(m.textInput.Value()) == 0 {
return
}
// TODO: wtf are other echo modes, dont care
//if m.textInput.EchoMode != textinput.EchoNormal {
// m.deleteBeforeCursor()
// return
//}
// Linter note: it's critical that we acquire the initial cursor position
// here prior to altering it via SetCursor() below. As such, moving this
// call into the corresponding if clause does not apply here.
oldPos := m.textInput.Position() //nolint:ifshort
m.textInput.SetCursor(oldPos - 1)
// ECHO character?
for m.textInput.Value()[m.textInput.Position()] == ' ' {
if m.textInput.Position() <= 0 {
break
}
// ignore series of whitespace before cursor
m.textInput.SetCursor(m.textInput.Position() - 1)
}
for m.textInput.Position() > 0 {
if m.textInput.Value()[m.textInput.Position()] != ' ' {
m.textInput.SetCursor(m.textInput.Position() - 1)
} else {
if m.textInput.Position() > 0 {
// keep the previous space
m.textInput.SetCursor(m.textInput.Position() + 1)
}
break
}
}
if oldPos > len(m.textInput.Value()) {
m.textInput.SetValue(m.textInput.Value()[:m.textInput.Position()])
} else {
m.textInput.SetValue(m.textInput.Value()[:m.textInput.Position()] + m.textInput.Value()[oldPos:])
}
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
var blurredNow = false
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.initList(msg.Width, msg.Height-2)
m.textInput.Width = msg.Width - 5
m.Preview.Viewport.Width = msg.Width
m.Preview.Viewport.Height = msg.Height - m.Preview.VerticalMarginHeight()
case invalidJwtMsg:
m.auth.Jwt = new(string)
m.page = LOGIN
return m, nil
case loginSuccessMsg:
*m.auth.Jwt = msg.string
m.page = FEEDS
return m, m.getEverything()
case registerSuccessMsg:
*m.auth.Jwt = msg.string
m.page = FEEDS
return m, m.getEverything()
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
case tea.KeyCtrlT:
if m.page == LOGIN {
m.page = REGISTER
} else if m.page == REGISTER {
m.page = LOGIN
}
case tea.KeyEnter:
if m.textInput.Focused() {
// Get entries with query
m.textInput.Blur()
blurredNow = true
}
}
switch {
case key.Matches(msg, m.textInput.KeyMap.DeleteCharacterBackward): //TODO: add only when query
words := strings.Split(m.textInput.Value(), " ")
if len(words) > 0 && (strings.HasPrefix(words[len(words)-1], "tag:") || strings.HasPrefix(words[len(words)-1], "feed:")) {
m.deleteWordBackward()
}
case key.Matches(msg, m.keys.IgnoreToggle) && m.page == "FEEDS":
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, ignorePost(m.auth.Jwt, e))
case key.Matches(msg, m.keys.ReadToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, toggleRead(m.auth.Jwt, e))
case key.Matches(msg, m.keys.ReadLaterToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, toggleReadLater(m.auth.Jwt, e))
case key.Matches(msg, m.keys.BookmarkToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, toggleBookmark(m.auth.Jwt, e))
case key.Matches(msg, m.keys.Query):
m.textInput.Focus()
m.textInput.SetValue("")
case key.Matches(msg, m.keys.PreviewPost) && m.textInput.Focused() == false && blurredNow == false:
var e = m.list.SelectedItem().(Entry)
m.Preview.Entry = e
m.Preview.Viewport.SetContent(e.Content)
m.page = PREVIEW
}
}
// Process the form
// LOGIN
if m.page == LOGIN {
form, cmd := m.auth.LoginForm.Update(msg)
if f, ok := form.(*huh.Form); ok {
m.auth.LoginForm = f
cmds = append(cmds, cmd)
}
if m.auth.LoginForm.State == huh.StateCompleted {
username := m.auth.LoginForm.GetString("email")
password := m.auth.LoginForm.GetString("password")
cmds = append(cmds, login(username, password))
}
}
if m.page == REGISTER {
// Process the form
// LOGIN
registerForm, cmd := m.auth.RegisterForm.Update(msg)
if f, ok := registerForm.(*huh.Form); ok {
m.auth.RegisterForm = f
cmds = append(cmds, cmd)
}
if m.auth.RegisterForm.State == huh.StateCompleted {
username := m.auth.RegisterForm.GetString("username")
password := m.auth.RegisterForm.GetString("password")
email := m.auth.RegisterForm.GetString("email")
cmds = append(cmds, register(username, password, email))
}
}
var cmd tea.Cmd
m.Preview.Viewport, cmd = m.Preview.Viewport.Update(msg)
cmds = append(cmds, cmd)
m.list, cmd = m.list.Update(msg)
cmds = append(cmds, cmd)
m.textInput, cmd = m.textInput.Update(msg)
cmds = append(cmds, cmd)
switch msg := msg.(type) {
case tea.KeyMsg:
_ = msg
m, cmd = m.handleSearchCompletion()
cmds = append(cmds, cmd)
}
return m, tea.Batch(cmds...)
return tea.Batch(
m.Auth.LoginForm.Init(),
m.Auth.RegisterForm.Init(),
checkJwt(m.Auth.Jwt),
)
}
func main() {
+90
View File
@@ -0,0 +1,90 @@
package main
import (
"strings"
tea "github.com/charmbracelet/bubbletea"
huh "github.com/charmbracelet/huh"
)
func (m Model) handleSearchCompletion() (Model, tea.Cmd) {
var cmd tea.Cmd
queryWords := strings.Split(m.queryInput.Value(), " ")
if len(queryWords) == 0 {
return m, cmd
}
lastWord := queryWords[len(queryWords)-1]
if lastWord == "tag:" {
var feed string
huh.NewSelect[string]().
Title("Pick a feed.").
Options(
huh.NewOption("United States", "US"),
huh.NewOption("Germany", "DE"),
huh.NewOption("Brazil", "BR"),
huh.NewOption("Canada", "CA"),
).
Value(&feed).Run()
m.queryInput.SetValue(m.queryInput.Value() + feed)
m.queryInput.CursorEnd()
}
if lastWord == "feed:" {
var feed string
var feeds = []string{"Devops", "System", "Angular"}
huh.NewSelect[string]().
Title("Pick a feed.").
Options(
huh.NewOptions(feeds...)...,
).
Value(&feed).Run()
m.queryInput.SetValue(m.queryInput.Value() + feed)
m.queryInput.CursorEnd()
}
return m, cmd
}
func (m *Model) deleteWordBackward() {
if m.queryInput.Position() == 0 || len(m.queryInput.Value()) == 0 {
return
}
// TODO: wtf are other echo modes, dont care
//if m.textInput.EchoMode != textinput.EchoNormal {
// m.deleteBeforeCursor()
// return
//}
// Linter note: it's critical that we acquire the initial cursor position
// here prior to altering it via SetCursor() below. As such, moving this
// call into the corresponding if clause does not apply here.
oldPos := m.queryInput.Position() //nolint:ifshort
m.queryInput.SetCursor(oldPos - 1)
// ECHO character?
for m.queryInput.Value()[m.queryInput.Position()] == ' ' {
if m.queryInput.Position() <= 0 {
break
}
// ignore series of whitespace before cursor
m.queryInput.SetCursor(m.queryInput.Position() - 1)
}
for m.queryInput.Position() > 0 {
if m.queryInput.Value()[m.queryInput.Position()] != ' ' {
m.queryInput.SetCursor(m.queryInput.Position() - 1)
} else {
if m.queryInput.Position() > 0 {
// keep the previous space
m.queryInput.SetCursor(m.queryInput.Position() + 1)
}
break
}
}
if oldPos > len(m.queryInput.Value()) {
m.queryInput.SetValue(m.queryInput.Value()[:m.queryInput.Position()])
} else {
m.queryInput.SetValue(m.queryInput.Value()[:m.queryInput.Position()] + m.queryInput.Value()[oldPos:])
}
}
+215
View File
@@ -0,0 +1,215 @@
package main
import (
"strings"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
huh "github.com/charmbracelet/huh"
. "github.com/zoryia/vex/tui/models"
. "github.com/zoryia/vex/tui/pages"
)
func (m Model) LoginUpdate(msg tea.Msg) (tea.Model, []tea.Cmd) {
var cmds []tea.Cmd
if m.page != LOGIN {
return m, nil
}
return m, cmds
}
func (m Model) GlobalUpdate(msg tea.Msg) (Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.initList(msg.Width, msg.Height-2)
m.queryInput.Width = msg.Width - 5
m.Preview.Viewport.Width = msg.Width
m.Preview.Viewport.Height = msg.Height - m.Preview.VerticalMarginHeight()
m.Feeds.List.SetWidth(msg.Width)
m.Feeds.List.SetHeight(msg.Height)
case invalidJwtMsg:
m.Auth.Jwt = new(string)
m.page = LOGIN
return m, nil
case loginSuccessMsg:
*m.Auth.Jwt = msg.string
m.page = ENTRIES
return m, m.getEverything()
case registerSuccessMsg:
*m.Auth.Jwt = msg.string
m.page = ENTRIES
return m, m.getEverything()
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}
}
return m, nil
}
func loginUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlT:
m.page = REGISTER
}
}
form, cmd := m.Auth.LoginForm.Update(msg)
if f, ok := form.(*huh.Form); ok {
m.Auth.LoginForm = f
cmds = append(cmds, cmd)
}
if m.Auth.LoginForm.State == huh.StateCompleted {
username := m.Auth.LoginForm.GetString("email")
password := m.Auth.LoginForm.GetString("password")
cmds = append(cmds, login(username, password))
}
return m, tea.Batch(cmds...)
}
func registerUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlT:
m.page = LOGIN
}
}
// Process the form
// LOGIN
registerForm, cmd := m.Auth.RegisterForm.Update(msg)
if f, ok := registerForm.(*huh.Form); ok {
m.Auth.RegisterForm = f
cmds = append(cmds, cmd)
}
if m.Auth.RegisterForm.State == huh.StateCompleted {
username := m.Auth.RegisterForm.GetString("username")
password := m.Auth.RegisterForm.GetString("password")
email := m.Auth.RegisterForm.GetString("email")
cmds = append(cmds, register(username, password, email))
}
return m, tea.Batch(cmds...)
}
func entriesUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
var blurredNow = false
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter:
if m.queryInput.Focused() {
// Get entries with query
m.queryInput.Blur()
blurredNow = true
}
}
switch {
case key.Matches(msg, m.queryInput.KeyMap.DeleteCharacterBackward) && m.queryInput.Focused():
words := strings.Split(m.queryInput.Value(), " ")
if len(words) > 0 && (strings.HasPrefix(words[len(words)-1], "tag:") || strings.HasPrefix(words[len(words)-1], "feed:")) {
m.deleteWordBackward()
}
}
if m.queryInput.Focused() == false {
switch {
case key.Matches(msg, m.keys.GoToFeeds):
m.page = FEEDS
case key.Matches(msg, m.keys.IgnoreToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, ignorePost(m.Auth.Jwt, e))
case key.Matches(msg, m.keys.ReadToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, toggleRead(m.Auth.Jwt, e))
case key.Matches(msg, m.keys.ReadLaterToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, toggleReadLater(m.Auth.Jwt, e))
case key.Matches(msg, m.keys.BookmarkToggle):
var e = m.list.SelectedItem().(Entry)
cmds = append(cmds, toggleBookmark(m.Auth.Jwt, e))
case key.Matches(msg, m.keys.Query):
m.queryInput.Focus()
m.queryInput.SetValue("")
case key.Matches(msg, m.keys.PreviewPost) && m.queryInput.Focused() == false && blurredNow == false:
var e = m.list.SelectedItem().(Entry)
m.Preview.Entry = e
m.Preview.Viewport.SetContent(e.Content)
m.page = PREVIEW
}
}
m, cmd = m.handleSearchCompletion()
cmds = append(cmds, cmd)
}
m.list, cmd = m.list.Update(msg)
cmds = append(cmds, cmd)
m.queryInput, cmd = m.queryInput.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func feedsUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
func tagsUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
func previewUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
var cmds []tea.Cmd
var cmd tea.Cmd
m.Preview.Viewport, cmd = m.Preview.Viewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func ignoredUpdate(m Model, msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
type updateFunc func(Model, tea.Msg) (Model, tea.Cmd)
func getUpdateMap() map[VexPage]updateFunc {
updateMap := make(map[VexPage]updateFunc)
updateMap[LOGIN] = loginUpdate
updateMap[REGISTER] = registerUpdate
updateMap[ENTRIES] = entriesUpdate
updateMap[FEEDS] = feedsUpdate
updateMap[TAGS] = tagsUpdate
updateMap[IGNORED] = ignoredUpdate
updateMap[PREVIEW] = previewUpdate
return updateMap
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
updateMap := getUpdateMap()
var cmds []tea.Cmd
var cmd tea.Cmd
m, cmd = m.GlobalUpdate(msg)
cmds = append(cmds, cmd)
m, cmd = updateMap[m.page](m, msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
+4 -6
View File
@@ -1,8 +1,6 @@
package main
import (
"fmt"
"github.com/charmbracelet/lipgloss"
. "github.com/zoryia/vex/tui/pages"
)
@@ -10,15 +8,15 @@ import (
func (m Model) AuthView() string {
return lipgloss.JoinHorizontal(
lipgloss.Left,
m.auth.LoginForm.View(),
m.auth.RegisterForm.View(),
m.Auth.LoginForm.View(),
m.Auth.RegisterForm.View(),
)
}
func (m Model) EntriesView() string {
return lipgloss.JoinVertical(lipgloss.Left, m.textInput.View(), m.list.View())
return lipgloss.JoinVertical(lipgloss.Left, m.queryInput.View(), m.list.View())
}
func (m Model) FeedsView() string {
return fmt.Sprintf("%s ", *m.auth.Jwt)
return m.Feeds.View()
}
func (m Model) TagsView() string {
return ""