Skip to content

Commit

Permalink
[wip] adding plugin window and plugin widget
Browse files Browse the repository at this point in the history
  • Loading branch information
vctt94 committed Aug 19, 2024
1 parent e4f7957 commit bad64ee
Show file tree
Hide file tree
Showing 7 changed files with 518 additions and 13 deletions.
56 changes: 53 additions & 3 deletions brclient/appstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ type appState struct {
subRate uint64 // milliatoms / byte
expirationDays uint64

pluginsClient map[clientintf.PluginID]*client.PluginClient
pluginsClient map[clientintf.PluginID]*client.PluginClient
pluginWindowsMtx sync.Mutex
pluginWindows []*pluginWindow

// When written, this makes the next wallet check be skipped.
skipWalletCheckChan chan struct{}
Expand Down Expand Up @@ -1184,6 +1186,48 @@ func (as *appState) findOrNewGCWindow(gcID zkidentity.ShortID) *chatWindow {
return cw
}

func (as *appState) findOrNewPluginWindow(id clientintf.UserID, alias string) *pluginWindow {
as.pluginWindowsMtx.Lock()
for _, pw := range as.pluginWindows {
if pw.uid == id {
as.pluginWindowsMtx.Unlock()
return pw
}
}

if alias == "" {
alias, _ = as.c.UserNick(id)
if alias == "" {
alias = id.ShortLogID()
}
}

styles := as.styles.Load()
t := newTextAreaModel(styles)
t.Placeholder = "Plugin"
t.CharLimit = 0
t.FocusedStyle.Prompt = styles.focused
t.FocusedStyle.Text = styles.focused
t.BlurredStyle.Prompt = styles.noStyle
t.BlurredStyle.Text = styles.noStyle
t.Focus()

pw := &pluginWindow{
uid: id,
alias: strescape.Nick(alias),
me: as.c.LocalNick(),
textArea: t,
embedContent: make(map[string]string),
}
as.pluginWindows = append(as.pluginWindows, pw)
as.updatedCW[len(as.chatWindows)-1] = false
as.pluginWindowsMtx.Unlock()

as.footerInvalidate()
as.sendMsg(showPluginWindow{})
return pw
}

// findOrNewChatWindow finds the existing chat window for the given user or
// creates a new one with the given alias.
func (as *appState) findOrNewChatWindow(id clientintf.UserID, alias string) *chatWindow {
Expand Down Expand Up @@ -2745,7 +2789,7 @@ func (as *appState) pluginVersion(cw *chatWindow, pid clientintf.PluginID) {
as.cwHelpMsg("plugin version: %+v\n", version)
}

func (as *appState) pluginAction(cw *chatWindow, pid clientintf.PluginID, action string, data []byte) {
func (as *appState) pluginAction(pw *pluginWindow, pid clientintf.PluginID, action string, data []byte) {
if as.pluginsClient[pid] == nil {
as.cwHelpMsg("plugin not found")
return
Expand Down Expand Up @@ -2812,7 +2856,13 @@ func (as *appState) startReadingUpdatesAndNtfn(id zkidentity.ShortID) {
as.log.Error("error rendering app update %s: %v", as.pluginsClient[id].Name, err)
return
}
as.cwHelpMsg(res.Data)
as.pluginWindowsMtx.Lock()
for _, pw := range as.pluginWindows {
if pw.uid == id {
pw.renderPluginString(id.String(), res.Data)
}
}
as.pluginWindowsMtx.Unlock()
}
}()
go func() {
Expand Down
14 changes: 6 additions & 8 deletions brclient/chatwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,14 +451,12 @@ type chatMsg struct {

type chatWindow struct {
sync.Mutex
uid clientintf.UserID
isGC bool
// XXX create plugin window able to receive inputs and render data on screen.
isPlugin bool
msgs []*chatMsg
alias string
me string // nick of the local user
gc zkidentity.ShortID
uid clientintf.UserID
isGC bool
msgs []*chatMsg
alias string
me string // nick of the local user
gc zkidentity.ShortID

initTime time.Time // When the cw was created and history read.

Expand Down
4 changes: 2 additions & 2 deletions brclient/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3616,8 +3616,8 @@ var pluginCmds = []tuicmd{
data = []byte(args[2])
}

cw := as.findOrNewChatWindow(ru.ID(), ru.Nick())
as.pluginAction(cw, ru.ID(), action, data)
pw := as.findOrNewPluginWindow(ru.ID(), ru.Nick())
go as.pluginAction(pw, ru.ID(), action, data)
return nil
},
},
Expand Down
4 changes: 4 additions & 0 deletions brclient/mainwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,10 @@ func (mws mainWindowState) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
mws.as.workingCmd = ""
return newNewPostWindow(mws.as)

case showPluginWindow:
mws.as.workingCmd = ""
return newPluginWindow(mws.as)

case showFeedWindow:
mws.as.workingCmd = ""
return newFeedWindow(mws.as, -1, -1, msg.author)
Expand Down
3 changes: 3 additions & 0 deletions brclient/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ type currentTimeChanged struct{}
// showNewPostWindow shows the create post window.
type showNewPostWindow struct{}

// showPluginWindow shows the specific plugin window.
type showPluginWindow struct{}

// showFeedWindow shows the feed window.
type showFeedWindow struct {
author *clientintf.UserID
Expand Down
233 changes: 233 additions & 0 deletions brclient/pluginwidget.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package main

import (
"fmt"
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/companyzero/bisonrelay/client/clientdb"
"github.com/erikgeiser/promptkit/selection"
)

// embedWidget is used to display the new embed screen in new posts and other
// places that allow adding an embed.
type pluginWidget struct {
initless
as *appState

embedding bool
formEmbed formHelper
embedErr error

sharing bool
sharedFiles []clientdb.SharedFileAndShares
selSharedFiles *selection.Model[string]
idxSharedFile int

addEmbedCB func(id string, data string) error
}

func (ew *pluginWidget) active() bool {
return ew.sharing || ew.embedding
}

func (ew *pluginWidget) activate() []tea.Cmd {
ew.idxSharedFile = -1
ew.embedding = true
ew.embedErr = nil
return ew.formEmbed.setFocus(0)
}

// tryEmbed tries to create the embed and calls the addEmbedCB.
// func (ew *pluginWidget) tryEmbed() error {
// var args mdembeds.EmbeddedArgs

// args.Alt = url.PathEscape(ew.formEmbed.inputs[1].(*textInputHelper).Value())

// filename, err := homedir.Expand(ew.formEmbed.inputs[0].(*textInputHelper).Value())
// if err != nil {
// return err
// }

// var data []byte
// var id string

// if filename != "" {
// data, err = os.ReadFile(filename)
// if err != nil {
// return err
// }

// if uint64(len(data)) > rpc.MaxChunkSize {
// return fmt.Errorf("file too big to embed")
// }

// args.Typ = mime.TypeByExtension(filepath.Ext(filename))
// id = chainhash.HashH(data).String()[:8]
// pseudoData := fmt.Sprintf("[content %s]", id)
// args.Data = []byte(pseudoData)
// }

// if ew.idxSharedFile > -1 && ew.idxSharedFile < len(ew.sharedFiles) {
// sf := ew.sharedFiles[ew.idxSharedFile]
// args.Download = sf.SF.FID
// args.Cost = sf.Cost
// args.Size = sf.Size
// }

// embedStr := args.String()
// return ew.addEmbedCB(id, data, embedStr)
// }

func (ew *pluginWidget) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)

if ew.sharing {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case msg.Type == tea.KeyEnter:
ew.sharing = false
choice, err := ew.selSharedFiles.ValueAsChoice()
if err == nil {
ew.idxSharedFile = choice.Index()
}
return ew, nil

case msg.Type == tea.KeyEsc:
ew.sharing = false
return ew, nil
}
}

_, cmd = ew.selSharedFiles.Update(msg)

return ew, cmd
}

if ew.embedding {
switch msg := msg.(type) {
case msgCancelForm:
ew.embedding = false
ew.formEmbed.clear()
cmds = ew.formEmbed.setFocus(-1)
return ew, batchCmds(cmds)

case msgSubmitForm:
// err := ew.tryEmbed()
// if err == nil {
// return ew, emitMsg(msgCancelForm{})
// }
// ew.embedErr = err

case msgShowSharedFilesForLink:
// ew.sharing = true
// cmd = ew.listSharedFiles()
// return ew, cmd

case tea.KeyMsg:
switch {
case msg.Type == tea.KeyF2 || msg.Type == tea.KeyEsc:
// Simulate canceling the form.
return ew, emitMsg(msgCancelForm{})
}
}

ew.formEmbed, cmd = ew.formEmbed.Update(msg)
return ew, cmd
}

return ew, cmd
}

func (ew *pluginWidget) embeddingView() string {
var b strings.Builder

nbLines := 2 + 1 + ew.formEmbed.lineCount() + 2

b.WriteString(ew.formEmbed.View())
b.WriteString("\n")
if ew.embedErr != nil {
b.WriteString(ew.as.styles.Load().err.Render(ew.embedErr.Error()))
}
b.WriteString("\n")

if ew.idxSharedFile > -1 && ew.idxSharedFile < len(ew.sharedFiles) {
b.WriteString(fmt.Sprintf("Linking to shared file %s",
ew.sharedFiles[ew.idxSharedFile].SF.Filename))
}

for i := 0; i < ew.as.winH-nbLines-1; i++ {
b.WriteString("\n")
}

return b.String()
}

func (ew *pluginWidget) sharingView() string {
var b strings.Builder

b.WriteString(ew.selSharedFiles.View())

nbLines := 2 + 2 + 5
for i := 0; i < ew.as.winH-nbLines-1; i++ {
b.WriteString("\n")
}

return b.String()
}

func (ew *pluginWidget) View() string {
if ew.sharing {
return ew.sharingView()
} else if ew.embedding {
return ew.embeddingView()
}
return ""
}

func newPluginWidget(as *appState, addEmbedCB func(string, string) error) *pluginWidget {
styles := as.styles.Load()

formEmbed := newFormHelper(styles,
newTextInputHelper(styles,
tihWithPrompt("Plugin: "),
),
newTextInputHelper(styles,
tihWithPrompt("Alt Text: "),
),
newButtonHelper(styles,
btnWithLabel("[ Link to Shared File ]"),
btnWithTrailing("\n\n"),
btnWithFixedMsgAction(msgShowSharedFilesForLink{}),
),
newButtonHelper(styles,
btnWithLabel("[ Cancel ]"),
btnWithTrailing(" "),
btnWithFixedMsgAction(msgCancelForm{}),
),
newButtonHelper(styles,
btnWithLabel(" [ Add Embed ]"),
btnWithTrailing("\n"),
btnWithFixedMsgAction(msgSubmitForm{}),
),
)

sel := selection.New("Select shared file", []string{""})
selSharedFiles := selection.NewModel(sel)
selSharedFiles.Filter = nil
//selSharedFiles.Update(tea.WindowSizeMsg{Width: as.winW, Height: 10})
//selSharedFiles.Selection.PageSize = 10

ew := &pluginWidget{
as: as,
formEmbed: formEmbed,
selSharedFiles: selSharedFiles,
addEmbedCB: addEmbedCB,
}

return ew
}
Loading

0 comments on commit bad64ee

Please sign in to comment.