Add conversation deletion to conversations view

This commit is contained in:
Matt Low 2024-07-08 06:40:52 +00:00
parent 4ef841e945
commit e59ce973b6
2 changed files with 125 additions and 2 deletions

View File

@ -0,0 +1,67 @@
package bubbles
import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type ConfirmPrompt struct {
Question string
Style lipgloss.Style
Payload interface{}
value bool
answered bool
focused bool
}
func NewConfirmPrompt(question string, payload interface{}) ConfirmPrompt {
return ConfirmPrompt{
Question: question,
Style: lipgloss.NewStyle(),
Payload: payload,
focused: true, // focus by default
}
}
type MsgConfirmPromptAnswered struct {
Value bool
Payload interface{}
}
func (b ConfirmPrompt) Update(msg tea.Msg) (ConfirmPrompt, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if !b.focused || b.answered {
return b, nil
}
switch msg.String() {
case "y", "Y":
b.value = true
b.answered = true
b.focused = false
return b, func() tea.Msg { return MsgConfirmPromptAnswered{true, b.Payload} }
case "n", "N", "esc":
b.value = false
b.answered = true
b.focused = false
return b, func() tea.Msg { return MsgConfirmPromptAnswered{false, b.Payload} }
}
}
return b, nil
}
func (b ConfirmPrompt) View() string {
return b.Style.Render(b.Question) + lipgloss.NewStyle().Faint(true).Render(" (y/n)")
}
func (b *ConfirmPrompt) Focus() {
b.focused = true
}
func (b *ConfirmPrompt) Blur() {
b.focused = false
}
func (b ConfirmPrompt) Focused() bool {
return b.focused
}

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"git.mlow.ca/mlow/lmcli/pkg/api" "git.mlow.ca/mlow/lmcli/pkg/api"
"git.mlow.ca/mlow/lmcli/pkg/tui/bubbles"
"git.mlow.ca/mlow/lmcli/pkg/tui/shared" "git.mlow.ca/mlow/lmcli/pkg/tui/shared"
"git.mlow.ca/mlow/lmcli/pkg/tui/styles" "git.mlow.ca/mlow/lmcli/pkg/tui/styles"
tuiutil "git.mlow.ca/mlow/lmcli/pkg/tui/util" tuiutil "git.mlow.ca/mlow/lmcli/pkg/tui/util"
@ -25,6 +26,13 @@ type (
msgConversationsLoaded ([]loadedConversation) msgConversationsLoaded ([]loadedConversation)
// sent when a conversation is selected // sent when a conversation is selected
msgConversationSelected api.Conversation msgConversationSelected api.Conversation
// sent when a conversation is deleted
msgConversationDeleted struct{}
)
// Prompt payloads
type (
deleteConversationPayload api.Conversation
) )
type Model struct { type Model struct {
@ -36,6 +44,8 @@ type Model struct {
itemOffsets []int // keeps track of the viewport y offset of each rendered item itemOffsets []int // keeps track of the viewport y offset of each rendered item
content viewport.Model content viewport.Model
confirmPrompt bubbles.ConfirmPrompt
} }
func Conversations(shared shared.Shared) Model { func Conversations(shared shared.Shared) Model {
@ -47,6 +57,14 @@ func Conversations(shared shared.Shared) Model {
} }
func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) { func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
if m.confirmPrompt.Focused() {
var cmd tea.Cmd
m.confirmPrompt, cmd = m.confirmPrompt.Update(msg)
if cmd != nil {
return true, cmd
}
}
switch msg.String() { switch msg.String() {
case "enter": case "enter":
if len(m.conversations) > 0 && m.cursor < len(m.conversations) { if len(m.conversations) > 0 && m.cursor < len(m.conversations) {
@ -89,7 +107,20 @@ func (m *Model) HandleInput(msg tea.KeyMsg) (bool, tea.Cmd) {
case "n": case "n":
// new conversation // new conversation
case "d": case "d":
// show prompt to delete conversation if !m.confirmPrompt.Focused() && len(m.conversations) > 0 && m.cursor < len(m.conversations) {
title := m.conversations[m.cursor].conv.Title
if title == "" {
title = "(untitled)"
}
m.confirmPrompt = bubbles.NewConfirmPrompt(
fmt.Sprintf("Delete '%s'?", title),
deleteConversationPayload(m.conversations[m.cursor].conv),
)
m.confirmPrompt.Style = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("3"))
return true, nil
}
case "c": case "c":
// copy/clone conversation // copy/clone conversation
case "r": case "r":
@ -120,12 +151,23 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.content.SetContent(m.renderConversationList()) m.content.SetContent(m.renderConversationList())
case msgConversationsLoaded: case msgConversationsLoaded:
m.conversations = msg m.conversations = msg
m.cursor = max(0, min(len(m.conversations), m.cursor))
m.content.SetContent(m.renderConversationList()) m.content.SetContent(m.renderConversationList())
case msgConversationSelected: case msgConversationSelected:
m.Values.ConvShortname = msg.ShortName.String m.Values.ConvShortname = msg.ShortName.String
cmds = append(cmds, func() tea.Msg { cmds = append(cmds, func() tea.Msg {
return shared.MsgViewChange(shared.StateChat) return shared.MsgViewChange(shared.StateChat)
}) })
case bubbles.MsgConfirmPromptAnswered:
m.confirmPrompt.Blur()
if msg.Value {
switch payload := msg.Payload.(type) {
case deleteConversationPayload:
cmds = append(cmds, m.deleteConversation(api.Conversation(payload)))
}
}
case msgConversationDeleted:
cmds = append(cmds, m.loadConversations())
} }
var cmd tea.Cmd var cmd tea.Cmd
@ -135,8 +177,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
} }
if m.Width > 0 { if m.Width > 0 {
wrap := lipgloss.NewStyle().Width(m.Width)
m.Header = m.headerView() m.Header = m.headerView()
m.Footer = "" // TODO: show /something/ m.Footer = "" // TODO: "Press ? for help"
if m.confirmPrompt.Focused() {
m.Footer = wrap.Render(m.confirmPrompt.View())
}
m.Error = tuiutil.ErrorBanner(m.Err, m.Width) m.Error = tuiutil.ErrorBanner(m.Err, m.Width)
fixedHeight := tuiutil.Height(m.Header) + tuiutil.Height(m.Error) + tuiutil.Height(m.Footer) fixedHeight := tuiutil.Height(m.Header) + tuiutil.Height(m.Error) + tuiutil.Height(m.Footer)
m.content.Height = m.Height - fixedHeight m.content.Height = m.Height - fixedHeight
@ -162,6 +208,16 @@ func (m *Model) loadConversations() tea.Cmd {
} }
} }
func (m *Model) deleteConversation(conv api.Conversation) tea.Cmd {
return func() tea.Msg {
err := m.Ctx.Store.DeleteConversation(&conv)
if err != nil {
return shared.MsgError(fmt.Errorf("Could not delete conversation: %v", err))
}
return msgConversationDeleted{}
}
}
func (m Model) View() string { func (m Model) View() string {
if m.Width == 0 { if m.Width == 0 {
return "" return ""