feat: implemented the dynamic inline keyboards via the panel component.

This commit is contained in:
Andrey Parhomenko 2023-12-19 22:35:57 +03:00
parent fbe0cf1c44
commit cc8f7e82e2
5 changed files with 158 additions and 45 deletions

View file

@ -111,6 +111,8 @@ var beh = tg.NewBehaviour().
tg.NewButton("Mutate messages").Go("/mutate-messages"), tg.NewButton("Mutate messages").Go("/mutate-messages"),
).Row( ).Row(
tg.NewButton("Send location").Go("/send-location"), tg.NewButton("Send location").Go("/send-location"),
).Row(
tg.NewButton("Dynamic panel").Go("panel"),
).Reply(), ).Reply(),
), ),
@ -124,6 +126,53 @@ var beh = tg.NewBehaviour().
} }
}), }),
tg.NewNode(
"panel",
tg.RenderFunc(func(c *tg.Context) tg.UI {
var (
n = 0
ln = 4
panel *tg.PanelCompo
)
panel = tg.NewMessage(
"Some panel",
).Panel(c, tg.RowserFunc(func(c *tg.Context) []tg.ButtonRow{
btns := []tg.ButtonRow{
tg.ButtonRow{tg.NewButton("Static shit")},
}
for i:=0 ; i<ln ; i++ {
num := 1 + n * ln + i
btns = append(btns, tg.ButtonRow{
tg.NewButton("%d", num).WithAction(tg.Func(func(c *tg.Context){
c.Sendf("%d", num*num)
})),
tg.NewButton("%d", num*num),
})
}
btns = append(btns, tg.ButtonRow{
tg.NewButton("Prev").WithAction(tg.ActionFunc(func(c *tg.Context){
n--
panel.Update(c)
})),
tg.NewButton("Next").WithAction(tg.ActionFunc(func(c *tg.Context){
n++
panel.Update(c)
})),
})
return btns
}))
return tg.UI{
panel,
tg.NewMessage("").Reply(
backKeyboard.Reply(),
),
}
}),
),
tg.NewNode( tg.NewNode(
"mutate-messages", tg.RenderFunc(func(c *tg.Context) tg.UI { "mutate-messages", tg.RenderFunc(func(c *tg.Context) tg.UI {
return tg.UI{ return tg.UI{

View file

@ -46,6 +46,10 @@ type context struct {
//path, prevPath Path //path, prevPath Path
} }
type Contexter interface {
GetContext() *Context
}
// Interface to interact with the user. // Interface to interact with the user.
type Context struct { type Context struct {
*context *context
@ -59,6 +63,10 @@ type Context struct {
input *UpdateChan input *UpdateChan
} }
func (c *Context) GetContext() *Context {
return c
}
// General type function to define actions, single component widgets // General type function to define actions, single component widgets
// and components themselves. // and components themselves.
type Func func(*Context) type Func func(*Context)

View file

@ -40,12 +40,16 @@ func (compo *InlineCompo) SendConfig(
sid SessionId, bot *Bot, sid SessionId, bot *Bot,
) (*SendConfig) { ) (*SendConfig) {
sendConfig := compo.MessageCompo.SendConfig(sid, bot) sendConfig := compo.MessageCompo.SendConfig(sid, bot)
if len(compo.Inline.Rows) > 0 {
sendConfig.Message.ReplyMarkup = compo.Inline.ToApi() sendConfig.Message.ReplyMarkup = compo.Inline.ToApi()
}
return sendConfig return sendConfig
} }
// Update the component on the client side.
func (compo *InlineCompo) Update(c *Context) { func (compo *InlineCompo) Update(c *Context) {
if compo.Message != nil {
var edit tgbotapi.Chattable var edit tgbotapi.Chattable
markup := compo.Inline.ToApi() markup := compo.Inline.ToApi()
ln := len(markup.InlineKeyboard) ln := len(markup.InlineKeyboard)
@ -65,6 +69,8 @@ func (compo *InlineCompo) Update(c *Context) {
} }
msg, _ := c.Bot.Api.Send(edit) msg, _ := c.Bot.Api.Send(edit)
compo.Message = &msg compo.Message = &msg
}
compo.buttonMap = compo.MakeButtonMap()
} }
// Implementing the Filterer interface. // Implementing the Filterer interface.
@ -82,10 +88,15 @@ func (compo *InlineCompo) Filter(u *Update) bool {
} }
// Implementing the Server interface. // Implementing the Server interface.
func (widget *InlineCompo) Serve(c *Context) { func (compo *InlineCompo) Serve(c *Context) {
btns := widget.ButtonMap()
for u := range c.Input() { for u := range c.Input() {
compo.OnOneUpdate(c, u)
}
}
func (compo *InlineCompo) OnOneUpdate(c *Context, u *Update) {
var act Action var act Action
btns := compo.ButtonMap()
cb := tgbotapi.NewCallback( cb := tgbotapi.NewCallback(
u.CallbackQuery.ID, u.CallbackQuery.ID,
u.CallbackQuery.Data, u.CallbackQuery.Data,
@ -94,21 +105,18 @@ func (widget *InlineCompo) Serve(c *Context) {
_, err := c.Bot.Api.Request(cb) _, err := c.Bot.Api.Request(cb)
if err != nil { if err != nil {
//return err return
continue
} }
btn, ok := btns[data] btn, ok := btns[data]
if !ok { if !ok {
continue return
} }
if btn != nil { if btn != nil {
act = btn.Action act = btn.Action
} else if widget.Action != nil { } else if compo.Action != nil {
act = widget.Action act = compo.Action
} }
c.WithUpdate(u).Run(act) c.WithUpdate(u).Run(act)
}
} }

View file

@ -79,8 +79,16 @@ func (kbd *Keyboard) ActionFunc(fn ActionFunc) *Keyboard {
return kbd.WithAction(fn) return kbd.WithAction(fn)
} }
// Returns the map of buttons. Used to define the Action. // Returns the map of buttons.
func (kbd Keyboard) ButtonMap() ButtonMap { func (kbd Keyboard) ButtonMap() ButtonMap {
if kbd.buttonMap == nil {
kbd.buttonMap = kbd.MakeButtonMap()
}
return kbd.buttonMap
}
// Returns the map of buttons on the most fresh version of the keyboard.
func (kbd Keyboard) MakeButtonMap() ButtonMap {
ret := make(ButtonMap) ret := make(ButtonMap)
for _, vi := range kbd.Rows { for _, vi := range kbd.Rows {
for _, vj := range vi { for _, vj := range vi {

40
panel.go Normal file
View file

@ -0,0 +1,40 @@
package tg
type Rowser interface {
MakeRows(c *Context) []ButtonRow
}
type RowserFunc func(c *Context) []ButtonRow
func (fn RowserFunc) MakeRows(c *Context) []ButtonRow {
return fn(c)
}
// The type represents the inline panel with
// scrollable via buttons content.
// Can be used for example to show users via SQL and offset
// or something like that.
type PanelCompo struct {
*InlineCompo
Rowser Rowser
}
// Transform to the panel with dynamic rows.
func (compo *MessageCompo) Panel(
c *Context, // The context that all the buttons will get.
rowser Rowser, // The rows generator.
) *PanelCompo {
ret := &PanelCompo{}
ret.InlineCompo = compo.Inline(
NewKeyboard(
rowser.MakeRows(c)...,
).Inline(),
)
ret.Rowser = rowser
return ret
}
func (compo *PanelCompo) Update(c *Context) {
compo.Rows = compo.Rowser.MakeRows(c)
compo.InlineCompo.Update(c)
}