Add conversation deletion to conversations view
This commit is contained in:
parent
4ef841e945
commit
e59ce973b6
67
pkg/tui/bubbles/confirmprompt.go
Normal file
67
pkg/tui/bubbles/confirmprompt.go
Normal 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
|
||||||
|
}
|
@ -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 ""
|
||||||
|
Loading…
Reference in New Issue
Block a user