tui: fleshed out converation selection
This commit is contained in:
parent
7463b7502c
commit
69d3265b64
@ -253,6 +253,7 @@ func (m chatModel) Update(msg tea.Msg) (chatModel, tea.Cmd) {
|
||||
m.conversation = (*models.Conversation)(msg)
|
||||
cmds = append(cmds, m.loadMessages(m.conversation))
|
||||
case msgMessagesLoaded:
|
||||
m.selectedMessage = len(msg) - 1
|
||||
m.setMessages(msg)
|
||||
m.updateContent()
|
||||
m.content.GotoBottom()
|
||||
@ -407,7 +408,7 @@ func (m *chatModel) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
m.selectedMessage--
|
||||
m.updateContent()
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
scrollIntoView(&m.content, offset, 0.1)
|
||||
scrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
}
|
||||
return true, nil
|
||||
case "ctrl+j":
|
||||
@ -415,7 +416,7 @@ func (m *chatModel) handleMessagesKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
m.selectedMessage++
|
||||
m.updateContent()
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
scrollIntoView(&m.content, offset, 0.1)
|
||||
scrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
}
|
||||
return true, nil
|
||||
case "ctrl+r":
|
||||
@ -441,7 +442,7 @@ func (m *chatModel) handleInputKey(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
m.selectedMessage = len(m.messages) - 1
|
||||
}
|
||||
offset := m.messageOffsets[m.selectedMessage]
|
||||
scrollIntoView(&m.content, offset, 0.1)
|
||||
scrollIntoView(&m.content, offset, m.content.Height/2)
|
||||
}
|
||||
m.updateContent()
|
||||
m.input.Blur()
|
||||
|
@ -29,7 +29,8 @@ type conversationsModel struct {
|
||||
basemodel
|
||||
|
||||
conversations []loadedConversation
|
||||
cursor int // index of the currently selected message message
|
||||
cursor int // index of the currently selected conversation
|
||||
itemOffsets []int // keeps track of the viewport y offset of each rendered item
|
||||
|
||||
content viewport.Model
|
||||
}
|
||||
@ -59,13 +60,33 @@ func (m *conversationsModel) handleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
|
||||
case "j", "down":
|
||||
if m.cursor < len(m.conversations)-1 {
|
||||
m.cursor++
|
||||
if m.cursor == len(m.conversations)-1 {
|
||||
// if last conversation, simply scroll to the bottom
|
||||
m.content.GotoBottom()
|
||||
} else {
|
||||
// this hack positions the *next* conversatoin slightly
|
||||
// *off* the screen, ensuring the entire m.cursor is shown,
|
||||
// even if its height may not be constant due to wrapping.
|
||||
scrollIntoView(&m.content, m.itemOffsets[m.cursor+1], -1)
|
||||
}
|
||||
m.content.SetContent(m.renderConversationList())
|
||||
} else {
|
||||
m.cursor = len(m.conversations) - 1
|
||||
m.content.GotoBottom()
|
||||
}
|
||||
return true, nil
|
||||
case "k", "up":
|
||||
if m.cursor > 0 {
|
||||
m.cursor--
|
||||
if m.cursor == 0 {
|
||||
m.content.GotoTop()
|
||||
} else {
|
||||
scrollIntoView(&m.content, m.itemOffsets[m.cursor], 1)
|
||||
}
|
||||
m.content.SetContent(m.renderConversationList())
|
||||
} else {
|
||||
m.cursor = 0
|
||||
m.content.GotoTop()
|
||||
}
|
||||
return true, nil
|
||||
case "n":
|
||||
@ -186,7 +207,6 @@ func (m *conversationsModel) renderConversationList() string {
|
||||
{"Older", now.Sub(time.Time{})},
|
||||
}
|
||||
|
||||
// TODO: pick nice color
|
||||
categoryStyle := lipgloss.NewStyle().
|
||||
MarginBottom(1).
|
||||
Foreground(lipgloss.Color("170")).
|
||||
@ -201,8 +221,12 @@ func (m *conversationsModel) renderConversationList() string {
|
||||
untitledStyle := lipgloss.NewStyle().Faint(true).Italic(true)
|
||||
selectedStyle := lipgloss.NewStyle().Faint(true).Foreground(lipgloss.Color("6"))
|
||||
|
||||
var currentOffset int
|
||||
var currentCategory string
|
||||
m.itemOffsets = make([]int, len(m.conversations))
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteRune('\n')
|
||||
currentOffset += 1
|
||||
for i, c := range m.conversations {
|
||||
lastReplyAge := now.Sub(c.lastReply.CreatedAt)
|
||||
|
||||
@ -217,7 +241,10 @@ func (m *conversationsModel) renderConversationList() string {
|
||||
// print the category
|
||||
if category != currentCategory {
|
||||
currentCategory = category
|
||||
fmt.Fprintf(sb, "%s\n", categoryStyle.Render(currentCategory))
|
||||
heading := categoryStyle.Render(currentCategory)
|
||||
sb.WriteString(heading)
|
||||
currentOffset += height(heading)
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
|
||||
tStyle := titleStyle.Copy()
|
||||
@ -234,13 +261,17 @@ func (m *conversationsModel) renderConversationList() string {
|
||||
title = ">" + title[1:]
|
||||
}
|
||||
|
||||
heading := fmt.Sprintf(
|
||||
m.itemOffsets[i] = currentOffset
|
||||
item := itemStyle.Render(fmt.Sprintf(
|
||||
"%s\n%s",
|
||||
title,
|
||||
padding + ageStyle.Render(util.HumanTimeElapsedSince(lastReplyAge)),
|
||||
)
|
||||
sb.WriteString(itemStyle.Render(heading))
|
||||
sb.WriteRune('\n')
|
||||
padding+ageStyle.Render(util.HumanTimeElapsedSince(lastReplyAge)),
|
||||
))
|
||||
sb.WriteString(item)
|
||||
currentOffset += height(item)
|
||||
if i < len(m.conversations)-1 {
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func truncateToCellWidth(str string, width int, tail string) string {
|
||||
// fraction is the fraction of the total screen height into view the offset
|
||||
// should be scrolled into view. 0.5 = items will be snapped to middle of
|
||||
// view
|
||||
func scrollIntoView(vp *viewport.Model, offset int, fraction float32) {
|
||||
func scrollIntoView(vp *viewport.Model, offset int, edge int) {
|
||||
currentOffset := vp.YOffset
|
||||
if offset >= currentOffset && offset < currentOffset+vp.Height {
|
||||
return
|
||||
@ -81,9 +81,9 @@ func scrollIntoView(vp *viewport.Model, offset int, fraction float32) {
|
||||
distance := currentOffset - offset
|
||||
if distance < 0 {
|
||||
// we should scroll down until it just comes into view
|
||||
vp.SetYOffset(currentOffset - (distance + (vp.Height - int(float32(vp.Height)*fraction))) + 1)
|
||||
vp.SetYOffset(currentOffset - (distance + (vp.Height - edge)) + 1)
|
||||
} else {
|
||||
// we should scroll up
|
||||
vp.SetYOffset(currentOffset - distance - int(float32(vp.Height)*fraction))
|
||||
vp.SetYOffset(currentOffset - distance - edge)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user