diff --git a/ui/ui.go b/ui/ui.go index b99bbbb..cb2311a 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -10,6 +10,7 @@ import ( "github.com/mrusme/gobbs/ui/header" "github.com/mrusme/gobbs/ui/views/posts" "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/views" @@ -109,13 +110,24 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg.Call { case cmd.WinOpen: - m.ctx.Logger.Debugln("received WinOpen") - ccmds = m.wm.Open( - msg.Target, - postshow.NewModel(m.ctx), - [4]int{3, 1, 4, 4}, - &msg, - ) + switch msg.Target { + case postshow.WIN_ID: + m.ctx.Logger.Debugln("received WinOpen") + ccmds = m.wm.Open( + msg.Target, + 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) default: if msg.Call < cmd.ViewFocus { @@ -130,8 +142,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Do nothing default: - m.ctx.Logger.Debugf("updating all with default: %v\n", msg) - cmds = append(cmds, m.wm.UpdateAll(msg)...) + m.ctx.Logger.Debugf("updating focused with default: %v\n", msg) + cmds = append(cmds, m.wm.UpdateFocused(msg)...) } v, vcmd := m.views[m.currentView].Update(msg) diff --git a/ui/windowmanager/windowmanager.go b/ui/windowmanager/windowmanager.go index 9105941..3fd6d82 100644 --- a/ui/windowmanager/windowmanager.go +++ b/ui/windowmanager/windowmanager.go @@ -69,8 +69,11 @@ func (wm *WM) Close(id string) (bool, []tea.Cmd) { tcmds = append(tcmds, cmd.New(cmd.WinClose, id).Tea()) wm.ctx.Loading = false - if wm.GetNumberOpen() == 0 { + nrOpen := wm.GetNumberOpen() + if nrOpen == 0 { tcmds = append(tcmds, cmd.New(cmd.ViewFocus, "*").Tea()) + } else { + tcmds = append(tcmds, wm.Focus(wm.stack[(nrOpen-1)].ID)...) } return true, tcmds } @@ -146,6 +149,21 @@ func (wm *WM) UpdateAll(msg tea.Msg) []tea.Cmd { 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 { var tcmd tea.Cmd var tcmds []tea.Cmd diff --git a/ui/windows/postcreate/postcreate.go b/ui/windows/postcreate/postcreate.go index 8b18b98..85bc3dc 100644 --- a/ui/windows/postcreate/postcreate.go +++ b/ui/windows/postcreate/postcreate.go @@ -2,12 +2,11 @@ package postcreate import ( "fmt" - "strconv" "strings" + "github.com/charmbracelet/bubbles/cursor" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textarea" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/glamour" "github.com/charmbracelet/lipgloss" @@ -16,7 +15,6 @@ import ( "github.com/mrusme/gobbs/models/reply" "github.com/mrusme/gobbs/ui/cmd" "github.com/mrusme/gobbs/ui/ctx" - "github.com/mrusme/gobbs/ui/helpers" ) var ( @@ -32,21 +30,6 @@ type KeyMap struct { } 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( key.WithKeys("ctrl+s"), key.WithHelp("ctrl+s", "reply"), @@ -56,13 +39,18 @@ var DefaultKeyMap = KeyMap{ type Model struct { ctx *ctx.Ctx keymap KeyMap - textarea textarea.Model + wh [2]int focused bool + textarea textarea.Model a *aggregator.Aggregator glam *glamour.TermRenderer activeReply *reply.Reply + replyToIdx int + + viewcache string + viewcacheTextareaXY []int } func (m Model) Init() tea.Cmd { @@ -79,10 +67,20 @@ func (m Model) Blur() { func NewModel(c *ctx.Ctx) Model { m := Model{ - ctx: c, - keymap: DefaultKeyMap, + ctx: c, + 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) return m @@ -96,79 +94,68 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, m.keymap.Reply): - replyToIdx, _ := strconv.Atoi(m.buffer) - - m.ctx.Logger.Debugf("replyToIdx: %d", replyToIdx) - - var irtID string = "" - var irtIRT string = "" - var irtSysIDX int = 0 - - if replyToIdx == 0 { - irtID = m.activePost.ID - irtSysIDX = m.activePost.SysIDX - } else { - irt := m.allReplies[(replyToIdx - 1)] - irtID = strconv.Itoa(replyToIdx + 1) - irtIRT = irt.InReplyTo - irtSysIDX = irt.SysIDX - } - - r := reply.Reply{ - ID: irtID, - InReplyTo: irtIRT, - Body: m.textarea.Value(), - SysIDX: irtSysIDX, - } - err := m.a.CreateReply(&r) - if err != nil { - m.ctx.Logger.Error(err) - } - - m.textarea.Reset() - m.buffer = "" - m.WMClose("reply") - return m, nil + // replyToIdx, _ := strconv.Atoi(m.buffer) + // + // m.ctx.Logger.Debugf("replyToIdx: %d", replyToIdx) + // + // var irtID string = "" + // var irtIRT string = "" + // var irtSysIDX int = 0 + // + // if replyToIdx == 0 { + // irtID = m.activePost.ID + // irtSysIDX = m.activePost.SysIDX + // } else { + // irt := m.allReplies[(replyToIdx - 1)] + // irtID = strconv.Itoa(replyToIdx + 1) + // irtIRT = irt.InReplyTo + // irtSysIDX = irt.SysIDX + // } + // + // r := reply.Reply{ + // ID: irtID, + // InReplyTo: irtIRT, + // Body: m.textarea.Value(), + // SysIDX: irtSysIDX, + // } + // err := m.a.CreateReply(&r) + // if err != nil { + // m.ctx.Logger.Error(err) + // } + // + // m.textarea.Reset() + // m.buffer = "" + // m.WMClose("reply") + // return m, nil } case tea.WindowSizeMsg: - m.ctx.Logger.Debug("received WindowSizeMsg") - viewportWidth := m.ctx.Content[0] - 9 - viewportHeight := m.ctx.Content[1] - 10 - - 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 + m.wh[0] = msg.Width + m.wh[1] = msg.Height + m.ctx.Logger.Debugf("received WindowSizeMsg: %v\n", m.wh) case cmd.Command: m.ctx.Logger.Debugf("got command: %v\n", msg) switch msg.Call { - case cmd.WinRefreshData: - if msg.Target == "post" { - m.activePost = msg.GetArg("post").(*post.Post) - m.ctx.Logger.Debugf("loading post: %v", m.activePost.ID) - m.ctx.Loading = true - return m, m.loadPost(m.activePost) + case cmd.WinOpen: + if msg.Target == WIN_ID { + return m, m.textarea.Focus() + } + case cmd.WinClose: + if msg.Target == WIN_ID { + m.textarea.Reset() + return m, nil } - return m, nil case cmd.WinFocus: - if msg.Target == "post" { + if msg.Target == WIN_ID || + msg.Target == "*" { m.focused = true } return m, nil case cmd.WinBlur: - if msg.Target == "post" { + if msg.Target == WIN_ID || + msg.Target == "*" { m.focused = false } 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) } + case cursor.BlinkMsg: + m.ctx.Logger.Debugf("textarea is focused: %v\n", m.textarea.Focused()) + default: 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) - cmds = append(cmds, cmd) + if !m.textarea.Focused() { + cmds = append(cmds, m.textarea.Focus()) + } + m.textarea, tcmd = m.textarea.Update(msg) + cmds = append(cmds, tcmd) return m, tea.Batch(cmds...) } @@ -205,140 +198,57 @@ func (m Model) View() string { func (m Model) buildView(cached bool) string { var view strings.Builder = strings.Builder{} - var l string = "" - view.WriteString(lipgloss.JoinHorizontal( - lipgloss.Top, - l, - )) + // if cached && m.viewcache != "" { + // m.ctx.Logger.Debugln("Cached View()") + // + // 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 - if m.focused { - style = m.ctx.Theme.DialogBox.Titlebar.Focused - } else { - style = m.ctx.Theme.DialogBox.Titlebar.Blurred + title := "Reply" + if m.replyToIdx != 0 { + title += fmt.Sprintf(" to reply #%d", m.replyToIdx) } - titlebar := style.Align(lipgloss.Center). - Width(m.viewport.Width + 4). - Render("Post") + titlebar := m.ctx.Theme.DialogBox.Titlebar.Focused. + Align(lipgloss.Center). + 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. - Width(m.viewport.Width + 4). - Render("[#]r reply · esc close") + Width(m.wh[0]). + Render("ctrl+enter reply · esc close") - ui := lipgloss.JoinVertical( + replyWindow := lipgloss.JoinVertical( lipgloss.Center, titlebar, - viewportStyle.Render(m.viewport.View()), + m.textarea.View(), bottombar, ) - var tmp string - if m.focused { - tmp = helpers.PlaceOverlay(3, 2, - m.ctx.Theme.DialogBox.Window.Focused.Render(ui), - view.String(), true) - } else { - tmp = helpers.PlaceOverlay(3, 2, - m.ctx.Theme.DialogBox.Window.Blurred.Render(ui), - view.String(), true) - } + replyWindowX := 5 + replyWindowY := m.ctx.Screen[1] - 21 + + tmp := m.ctx.Theme.DialogBox.Window.Focused.Render(replyWindow) + + m.viewcacheTextareaXY[0] = replyWindowX + 1 + m.viewcacheTextareaXY[1] = replyWindowY + 2 + m.viewcacheTextareaXY[2] = textareaWidth + m.viewcacheTextareaXY[3] = textareaHeight view = strings.Builder{} view.WriteString(tmp) + // m.viewcache = view.String() + // return m.viewcache 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 -} diff --git a/ui/windows/postshow/postshow.go b/ui/windows/postshow/postshow.go index 983b66a..70e5cdf 100644 --- a/ui/windows/postshow/postshow.go +++ b/ui/windows/postshow/postshow.go @@ -2,7 +2,6 @@ package postshow import ( "fmt" - "strconv" "strings" "github.com/charmbracelet/bubbles/key" @@ -15,6 +14,7 @@ import ( "github.com/mrusme/gobbs/models/reply" "github.com/mrusme/gobbs/ui/cmd" "github.com/mrusme/gobbs/ui/ctx" + "github.com/mrusme/gobbs/ui/windows/postcreate" ) var ( @@ -30,32 +30,13 @@ var ( ) type KeyMap struct { - Refresh key.Binding - Select key.Binding - Esc key.Binding - Quit key.Binding - Reply key.Binding + Reply key.Binding } 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( - key.WithKeys("ctrl+s"), - key.WithHelp("ctrl+s", "reply"), + key.WithKeys("r"), + key.WithHelp("r", "reply"), ), } @@ -111,28 +92,28 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch { - case key.Matches(msg, m.keymap.Select): - if m.buffer != "" { - replyToID, err := strconv.Atoi(m.buffer) - if err != nil { - // TODO: Handle error - } - - if replyToID >= len(m.replyIDs) { - // TODO: Handle error - } - } - // m.WMOpen("reply") + case key.Matches(msg, m.keymap.Reply): + // if m.buffer != "" { + // replyToID, err := strconv.Atoi(m.buffer) + // if err != nil { + // // TODO: Handle error + // } + // + // if replyToID >= len(m.replyIDs) { + // // TODO: Handle error + // } + // } + 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.Debugf("buffer: %s", m.buffer) // m.viewcache = m.buildView(false) - return m, nil - - case key.Matches(msg, m.keymap.Esc), key.Matches(msg, m.keymap.Quit): - // m.WMClose("post") - return m, nil + return m, tea.Batch(cmds...) default: switch msg.String() { @@ -244,11 +225,11 @@ func (m Model) buildView(cached bool) string { style = m.ctx.Theme.DialogBox.Titlebar.Blurred } titlebar := style.Align(lipgloss.Center). - Width(m.viewport.Width + 4). + Width(m.wh[0]). Render("Post") bottombar := m.ctx.Theme.DialogBox.Bottombar. - Width(m.viewport.Width + 4). + Width(m.wh[0]). Render("[#]r reply · esc close") ui := lipgloss.JoinVertical(