1
0
mirror of https://github.com/mrusme/neonmodem.git synced 2024-10-13 05:03:37 -04:00

Implemented postcreate window

This commit is contained in:
マリウス 2023-01-02 18:46:26 -05:00
parent f878acfe05
commit d9c07422d6
No known key found for this signature in database
GPG Key ID: 272ED814BF63261F
4 changed files with 177 additions and 256 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/mrusme/gobbs/ui/header" "github.com/mrusme/gobbs/ui/header"
"github.com/mrusme/gobbs/ui/views/posts" "github.com/mrusme/gobbs/ui/views/posts"
"github.com/mrusme/gobbs/ui/windowmanager" "github.com/mrusme/gobbs/ui/windowmanager"
"github.com/mrusme/gobbs/ui/windows/postcreate"
"github.com/mrusme/gobbs/ui/windows/postshow" "github.com/mrusme/gobbs/ui/windows/postshow"
"github.com/mrusme/gobbs/ui/views" "github.com/mrusme/gobbs/ui/views"
@ -109,13 +110,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg.Call { switch msg.Call {
case cmd.WinOpen: case cmd.WinOpen:
m.ctx.Logger.Debugln("received WinOpen") switch msg.Target {
ccmds = m.wm.Open( case postshow.WIN_ID:
msg.Target, m.ctx.Logger.Debugln("received WinOpen")
postshow.NewModel(m.ctx), ccmds = m.wm.Open(
[4]int{3, 1, 4, 4}, msg.Target,
&msg, postshow.NewModel(m.ctx),
) [4]int{3, 1, 4, 4},
&msg,
)
case postcreate.WIN_ID:
m.ctx.Logger.Debugln("received WinOpen")
ccmds = m.wm.Open(
msg.Target,
postcreate.NewModel(m.ctx),
[4]int{6, int(m.ctx.Content[1] / 3), 8, 4},
&msg,
)
}
m.ctx.Logger.Debugf("got back ccmds: %v\n", ccmds) m.ctx.Logger.Debugf("got back ccmds: %v\n", ccmds)
default: default:
if msg.Call < cmd.ViewFocus { if msg.Call < cmd.ViewFocus {
@ -130,8 +142,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Do nothing // Do nothing
default: default:
m.ctx.Logger.Debugf("updating all with default: %v\n", msg) m.ctx.Logger.Debugf("updating focused with default: %v\n", msg)
cmds = append(cmds, m.wm.UpdateAll(msg)...) cmds = append(cmds, m.wm.UpdateFocused(msg)...)
} }
v, vcmd := m.views[m.currentView].Update(msg) v, vcmd := m.views[m.currentView].Update(msg)

View File

@ -69,8 +69,11 @@ func (wm *WM) Close(id string) (bool, []tea.Cmd) {
tcmds = append(tcmds, cmd.New(cmd.WinClose, id).Tea()) tcmds = append(tcmds, cmd.New(cmd.WinClose, id).Tea())
wm.ctx.Loading = false wm.ctx.Loading = false
if wm.GetNumberOpen() == 0 { nrOpen := wm.GetNumberOpen()
if nrOpen == 0 {
tcmds = append(tcmds, cmd.New(cmd.ViewFocus, "*").Tea()) tcmds = append(tcmds, cmd.New(cmd.ViewFocus, "*").Tea())
} else {
tcmds = append(tcmds, wm.Focus(wm.stack[(nrOpen-1)].ID)...)
} }
return true, tcmds return true, tcmds
} }
@ -146,6 +149,21 @@ func (wm *WM) UpdateAll(msg tea.Msg) []tea.Cmd {
return tcmds return tcmds
} }
func (wm *WM) UpdateFocused(msg tea.Msg) []tea.Cmd {
var tcmd tea.Cmd
var tcmds []tea.Cmd
i := len(wm.stack) - 1
if i < 0 {
return tcmds
}
wm.stack[i].Win, tcmd = wm.stack[i].Win.Update(msg)
tcmds = append(tcmds, tcmd)
return tcmds
}
func (wm *WM) Resize(id string, w int, h int) []tea.Cmd { func (wm *WM) Resize(id string, w int, h int) []tea.Cmd {
var tcmd tea.Cmd var tcmd tea.Cmd
var tcmds []tea.Cmd var tcmds []tea.Cmd

View File

@ -2,12 +2,11 @@ package postcreate
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/charmbracelet/bubbles/cursor"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textarea" "github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
@ -16,7 +15,6 @@ import (
"github.com/mrusme/gobbs/models/reply" "github.com/mrusme/gobbs/models/reply"
"github.com/mrusme/gobbs/ui/cmd" "github.com/mrusme/gobbs/ui/cmd"
"github.com/mrusme/gobbs/ui/ctx" "github.com/mrusme/gobbs/ui/ctx"
"github.com/mrusme/gobbs/ui/helpers"
) )
var ( var (
@ -32,21 +30,6 @@ type KeyMap struct {
} }
var DefaultKeyMap = KeyMap{ var DefaultKeyMap = KeyMap{
Refresh: key.NewBinding(
key.WithKeys("ctrl+r"),
key.WithHelp("ctrl+r", "refresh"),
),
Select: key.NewBinding(
key.WithKeys("r", "enter"),
key.WithHelp("r/enter", "read"),
),
Esc: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "close"),
),
Quit: key.NewBinding(
key.WithKeys("q"),
),
Reply: key.NewBinding( Reply: key.NewBinding(
key.WithKeys("ctrl+s"), key.WithKeys("ctrl+s"),
key.WithHelp("ctrl+s", "reply"), key.WithHelp("ctrl+s", "reply"),
@ -56,13 +39,18 @@ var DefaultKeyMap = KeyMap{
type Model struct { type Model struct {
ctx *ctx.Ctx ctx *ctx.Ctx
keymap KeyMap keymap KeyMap
textarea textarea.Model wh [2]int
focused bool focused bool
textarea textarea.Model
a *aggregator.Aggregator a *aggregator.Aggregator
glam *glamour.TermRenderer glam *glamour.TermRenderer
activeReply *reply.Reply activeReply *reply.Reply
replyToIdx int
viewcache string
viewcacheTextareaXY []int
} }
func (m Model) Init() tea.Cmd { func (m Model) Init() tea.Cmd {
@ -79,10 +67,20 @@ func (m Model) Blur() {
func NewModel(c *ctx.Ctx) Model { func NewModel(c *ctx.Ctx) Model {
m := Model{ m := Model{
ctx: c, ctx: c,
keymap: DefaultKeyMap, keymap: DefaultKeyMap,
focused: false,
replyToIdx: 0,
viewcache: "",
viewcacheTextareaXY: []int{0, 0, 0, 0},
} }
m.textarea = textarea.New()
m.textarea.Placeholder = "Type in your reply ..."
m.textarea.Prompt = ""
m.a, _ = aggregator.New(m.ctx) m.a, _ = aggregator.New(m.ctx)
return m return m
@ -96,79 +94,68 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch { switch {
case key.Matches(msg, m.keymap.Reply): case key.Matches(msg, m.keymap.Reply):
replyToIdx, _ := strconv.Atoi(m.buffer) // replyToIdx, _ := strconv.Atoi(m.buffer)
//
m.ctx.Logger.Debugf("replyToIdx: %d", replyToIdx) // m.ctx.Logger.Debugf("replyToIdx: %d", replyToIdx)
//
var irtID string = "" // var irtID string = ""
var irtIRT string = "" // var irtIRT string = ""
var irtSysIDX int = 0 // var irtSysIDX int = 0
//
if replyToIdx == 0 { // if replyToIdx == 0 {
irtID = m.activePost.ID // irtID = m.activePost.ID
irtSysIDX = m.activePost.SysIDX // irtSysIDX = m.activePost.SysIDX
} else { // } else {
irt := m.allReplies[(replyToIdx - 1)] // irt := m.allReplies[(replyToIdx - 1)]
irtID = strconv.Itoa(replyToIdx + 1) // irtID = strconv.Itoa(replyToIdx + 1)
irtIRT = irt.InReplyTo // irtIRT = irt.InReplyTo
irtSysIDX = irt.SysIDX // irtSysIDX = irt.SysIDX
} // }
//
r := reply.Reply{ // r := reply.Reply{
ID: irtID, // ID: irtID,
InReplyTo: irtIRT, // InReplyTo: irtIRT,
Body: m.textarea.Value(), // Body: m.textarea.Value(),
SysIDX: irtSysIDX, // SysIDX: irtSysIDX,
} // }
err := m.a.CreateReply(&r) // err := m.a.CreateReply(&r)
if err != nil { // if err != nil {
m.ctx.Logger.Error(err) // m.ctx.Logger.Error(err)
} // }
//
m.textarea.Reset() // m.textarea.Reset()
m.buffer = "" // m.buffer = ""
m.WMClose("reply") // m.WMClose("reply")
return m, nil // return m, nil
} }
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.ctx.Logger.Debug("received WindowSizeMsg") m.wh[0] = msg.Width
viewportWidth := m.ctx.Content[0] - 9 m.wh[1] = msg.Height
viewportHeight := m.ctx.Content[1] - 10 m.ctx.Logger.Debugf("received WindowSizeMsg: %v\n", m.wh)
viewportStyle.Width(viewportWidth)
viewportStyle.Height(viewportHeight)
m.viewport = viewport.New(viewportWidth-4, viewportHeight-4)
m.viewport.Width = viewportWidth - 4
m.viewport.Height = viewportHeight + 1
// cmds = append(cmds, viewport.Sync(m.viewport))
case *post.Post:
m.ctx.Logger.Debug("got *post.Post")
m.activePost = msg
m.viewport.SetContent(m.renderViewport(m.activePost))
m.ctx.Loading = false
return m, nil
case cmd.Command: case cmd.Command:
m.ctx.Logger.Debugf("got command: %v\n", msg) m.ctx.Logger.Debugf("got command: %v\n", msg)
switch msg.Call { switch msg.Call {
case cmd.WinRefreshData: case cmd.WinOpen:
if msg.Target == "post" { if msg.Target == WIN_ID {
m.activePost = msg.GetArg("post").(*post.Post) return m, m.textarea.Focus()
m.ctx.Logger.Debugf("loading post: %v", m.activePost.ID) }
m.ctx.Loading = true case cmd.WinClose:
return m, m.loadPost(m.activePost) if msg.Target == WIN_ID {
m.textarea.Reset()
return m, nil
} }
return m, nil
case cmd.WinFocus: case cmd.WinFocus:
if msg.Target == "post" { if msg.Target == WIN_ID ||
msg.Target == "*" {
m.focused = true m.focused = true
} }
return m, nil return m, nil
case cmd.WinBlur: case cmd.WinBlur:
if msg.Target == "post" { if msg.Target == WIN_ID ||
msg.Target == "*" {
m.focused = false m.focused = false
} }
return m, nil return m, nil
@ -176,14 +163,20 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.ctx.Logger.Debugf("received unhandled command: %v\n", msg) m.ctx.Logger.Debugf("received unhandled command: %v\n", msg)
} }
case cursor.BlinkMsg:
m.ctx.Logger.Debugf("textarea is focused: %v\n", m.textarea.Focused())
default: default:
m.ctx.Logger.Debugf("received unhandled msg: %v\n", msg) m.ctx.Logger.Debugf("received unhandled msg: %v\n", msg)
} }
var cmd tea.Cmd var tcmd tea.Cmd
m.viewport, cmd = m.viewport.Update(msg) if !m.textarea.Focused() {
cmds = append(cmds, cmd) cmds = append(cmds, m.textarea.Focus())
}
m.textarea, tcmd = m.textarea.Update(msg)
cmds = append(cmds, tcmd)
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
} }
@ -205,140 +198,57 @@ func (m Model) View() string {
func (m Model) buildView(cached bool) string { func (m Model) buildView(cached bool) string {
var view strings.Builder = strings.Builder{} var view strings.Builder = strings.Builder{}
var l string = "" // if cached && m.viewcache != "" {
view.WriteString(lipgloss.JoinHorizontal( // m.ctx.Logger.Debugln("Cached View()")
lipgloss.Top, //
l, // m.textarea.SetWidth(m.viewcacheTextareaXY[2])
)) // m.textarea.SetHeight(m.viewcacheTextareaXY[3])
//
// return helpers.PlaceOverlay(
// m.viewcacheTextareaXY[0], m.viewcacheTextareaXY[1],
// m.textarea.View(), m.viewcache,
// false)
// }
var style lipgloss.Style title := "Reply"
if m.focused { if m.replyToIdx != 0 {
style = m.ctx.Theme.DialogBox.Titlebar.Focused title += fmt.Sprintf(" to reply #%d", m.replyToIdx)
} else {
style = m.ctx.Theme.DialogBox.Titlebar.Blurred
} }
titlebar := style.Align(lipgloss.Center). titlebar := m.ctx.Theme.DialogBox.Titlebar.Focused.
Width(m.viewport.Width + 4). Align(lipgloss.Center).
Render("Post") Width(m.wh[0]).
Render(title)
textareaWidth := m.wh[0] - 2
textareaHeight := 6
m.textarea.SetWidth(textareaWidth)
m.textarea.SetHeight(textareaHeight)
bottombar := m.ctx.Theme.DialogBox.Bottombar. bottombar := m.ctx.Theme.DialogBox.Bottombar.
Width(m.viewport.Width + 4). Width(m.wh[0]).
Render("[#]r reply · esc close") Render("ctrl+enter reply · esc close")
ui := lipgloss.JoinVertical( replyWindow := lipgloss.JoinVertical(
lipgloss.Center, lipgloss.Center,
titlebar, titlebar,
viewportStyle.Render(m.viewport.View()), m.textarea.View(),
bottombar, bottombar,
) )
var tmp string replyWindowX := 5
if m.focused { replyWindowY := m.ctx.Screen[1] - 21
tmp = helpers.PlaceOverlay(3, 2,
m.ctx.Theme.DialogBox.Window.Focused.Render(ui), tmp := m.ctx.Theme.DialogBox.Window.Focused.Render(replyWindow)
view.String(), true)
} else { m.viewcacheTextareaXY[0] = replyWindowX + 1
tmp = helpers.PlaceOverlay(3, 2, m.viewcacheTextareaXY[1] = replyWindowY + 2
m.ctx.Theme.DialogBox.Window.Blurred.Render(ui), m.viewcacheTextareaXY[2] = textareaWidth
view.String(), true) m.viewcacheTextareaXY[3] = textareaHeight
}
view = strings.Builder{} view = strings.Builder{}
view.WriteString(tmp) view.WriteString(tmp)
// m.viewcache = view.String()
// return m.viewcache
return view.String() return view.String()
} }
func (m *Model) renderViewport(p *post.Post) string {
var out string = ""
var err error
m.glam, err = glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(m.viewport.Width),
)
if err != nil {
m.ctx.Logger.Error(err)
m.glam = nil
}
adj := "writes"
if p.Subject[len(p.Subject)-1:] == "?" {
adj = "asks"
}
body, err := m.glam.Render(p.Body)
if err != nil {
m.ctx.Logger.Error(err)
body = p.Body
}
out += fmt.Sprintf(
" %s\n\n %s\n%s",
m.ctx.Theme.Post.Author.Render(
fmt.Sprintf("%s %s:", p.Author.Name, adj),
),
m.ctx.Theme.Post.Subject.Render(p.Subject),
body,
)
m.replyIDs = []string{p.ID}
m.activePost = p
out += m.renderReplies(0, p.Author.Name, &p.Replies)
return out
}
func (m *Model) renderReplies(
level int,
inReplyTo string,
replies *[]reply.Reply,
) string {
var out string = ""
if replies == nil {
return ""
}
for ri, re := range *replies {
var err error = nil
var body string = ""
var author string = ""
if re.Deleted {
body = "\n DELETED\n\n"
author = "DELETED"
} else {
body, err = m.glam.Render(re.Body)
if err != nil {
m.ctx.Logger.Error(err)
body = re.Body
}
author = re.Author.Name
}
m.replyIDs = append(m.replyIDs, re.ID)
m.allReplies = append(m.allReplies, &(*replies)[ri])
idx := len(m.replyIDs) - 1
out += fmt.Sprintf(
"\n\n %s %s%s%s\n%s",
m.ctx.Theme.Reply.Author.Render(
author,
),
lipgloss.NewStyle().
Foreground(m.ctx.Theme.Reply.Author.GetBackground()).
Render(fmt.Sprintf("writes in reply to %s:", inReplyTo)),
strings.Repeat(" ", (m.viewport.Width-len(author)-len(inReplyTo)-28)),
lipgloss.NewStyle().
Foreground(lipgloss.Color("#777777")).
Render(fmt.Sprintf("#%d", idx)),
body,
)
idx++
out += m.renderReplies(level+1, re.Author.Name, &re.Replies)
}
return out
}

View File

@ -2,7 +2,6 @@ package postshow
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/key"
@ -15,6 +14,7 @@ import (
"github.com/mrusme/gobbs/models/reply" "github.com/mrusme/gobbs/models/reply"
"github.com/mrusme/gobbs/ui/cmd" "github.com/mrusme/gobbs/ui/cmd"
"github.com/mrusme/gobbs/ui/ctx" "github.com/mrusme/gobbs/ui/ctx"
"github.com/mrusme/gobbs/ui/windows/postcreate"
) )
var ( var (
@ -30,32 +30,13 @@ var (
) )
type KeyMap struct { type KeyMap struct {
Refresh key.Binding Reply key.Binding
Select key.Binding
Esc key.Binding
Quit key.Binding
Reply key.Binding
} }
var DefaultKeyMap = KeyMap{ var DefaultKeyMap = KeyMap{
Refresh: key.NewBinding(
key.WithKeys("ctrl+r"),
key.WithHelp("ctrl+r", "refresh"),
),
Select: key.NewBinding(
key.WithKeys("r", "enter"),
key.WithHelp("r/enter", "read"),
),
Esc: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "close"),
),
Quit: key.NewBinding(
key.WithKeys("q"),
),
Reply: key.NewBinding( Reply: key.NewBinding(
key.WithKeys("ctrl+s"), key.WithKeys("r"),
key.WithHelp("ctrl+s", "reply"), key.WithHelp("r", "reply"),
), ),
} }
@ -111,28 +92,28 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyMsg: case tea.KeyMsg:
switch { switch {
case key.Matches(msg, m.keymap.Select): case key.Matches(msg, m.keymap.Reply):
if m.buffer != "" { // if m.buffer != "" {
replyToID, err := strconv.Atoi(m.buffer) // replyToID, err := strconv.Atoi(m.buffer)
if err != nil { // if err != nil {
// TODO: Handle error // // TODO: Handle error
} // }
//
if replyToID >= len(m.replyIDs) { // if replyToID >= len(m.replyIDs) {
// TODO: Handle error // // TODO: Handle error
} // }
} // }
// m.WMOpen("reply") cmd := cmd.New(cmd.WinOpen, postcreate.WIN_ID, cmd.Arg{
Name: "post",
Value: &m.activePost,
})
cmds = append(cmds, cmd.Tea())
m.ctx.Logger.Debugln("caching view") m.ctx.Logger.Debugln("caching view")
m.ctx.Logger.Debugf("buffer: %s", m.buffer) m.ctx.Logger.Debugf("buffer: %s", m.buffer)
// m.viewcache = m.buildView(false) // m.viewcache = m.buildView(false)
return m, nil return m, tea.Batch(cmds...)
case key.Matches(msg, m.keymap.Esc), key.Matches(msg, m.keymap.Quit):
// m.WMClose("post")
return m, nil
default: default:
switch msg.String() { switch msg.String() {
@ -244,11 +225,11 @@ func (m Model) buildView(cached bool) string {
style = m.ctx.Theme.DialogBox.Titlebar.Blurred style = m.ctx.Theme.DialogBox.Titlebar.Blurred
} }
titlebar := style.Align(lipgloss.Center). titlebar := style.Align(lipgloss.Center).
Width(m.viewport.Width + 4). Width(m.wh[0]).
Render("Post") Render("Post")
bottombar := m.ctx.Theme.DialogBox.Bottombar. bottombar := m.ctx.Theme.DialogBox.Bottombar.
Width(m.viewport.Width + 4). Width(m.wh[0]).
Render("[#]r reply · esc close") Render("[#]r reply · esc close")
ui := lipgloss.JoinVertical( ui := lipgloss.JoinVertical(