refactor(ui): modularize code (#190)
This commit is contained in:
parent
0689e21870
commit
f3531be04c
6 changed files with 221 additions and 185 deletions
18
ui/app.go
18
ui/app.go
|
|
@ -15,11 +15,11 @@ import (
|
|||
|
||||
type App struct {
|
||||
*tview.Application
|
||||
MainFlex *tview.Flex
|
||||
GuildsTree *GuildsTree
|
||||
ChannelsTree *ChannelsTree
|
||||
MessagesPanel *MessagesPanel
|
||||
MessageInputField *MessageInput
|
||||
MainFlex *tview.Flex
|
||||
GuildsTree *GuildsTree
|
||||
ChannelsTree *ChannelsTree
|
||||
MessagesPanel *MessagesPanel
|
||||
MessageInput *MessageInput
|
||||
|
||||
Config *config.Config
|
||||
State *state.State
|
||||
|
|
@ -46,7 +46,7 @@ func NewApp(token string, c *config.Config) *App {
|
|||
app.GuildsTree = NewGuildsTree(app)
|
||||
app.ChannelsTree = NewChannelsTree(app)
|
||||
app.MessagesPanel = NewMessagesPanel(app)
|
||||
app.MessageInputField = NewMessageInput(app)
|
||||
app.MessageInput = NewMessageInput(app)
|
||||
app.EnableMouse(app.Config.Mouse)
|
||||
app.MainFlex.SetInputCapture(app.onInputCapture)
|
||||
|
||||
|
|
@ -68,7 +68,7 @@ func (app *App) Connect() error {
|
|||
}
|
||||
|
||||
func (app *App) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
|
||||
if app.MessageInputField.HasFocus() {
|
||||
if app.MessageInput.HasFocus() {
|
||||
return e
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ func (app *App) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
|
|||
app.SetFocus(app.MessagesPanel)
|
||||
return nil
|
||||
case app.Config.Keys.ToggleMessageInput:
|
||||
app.SetFocus(app.MessageInputField)
|
||||
app.SetFocus(app.MessageInput)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ func (app *App) DrawMainFlex() {
|
|||
rightFlex := tview.NewFlex().
|
||||
SetDirection(tview.FlexRow).
|
||||
AddItem(app.MessagesPanel, 0, 1, false).
|
||||
AddItem(app.MessageInputField, 3, 1, false)
|
||||
AddItem(app.MessageInput, 3, 1, false)
|
||||
app.MainFlex.
|
||||
AddItem(leftFlex, 0, 1, false).
|
||||
AddItem(rightFlex, 0, 4, false)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
|
@ -37,7 +39,7 @@ func (ct *ChannelsTree) onSelected(node *tview.TreeNode) {
|
|||
Highlight().
|
||||
Clear().
|
||||
SetTitle("")
|
||||
ct.app.MessageInputField.SetText("")
|
||||
ct.app.MessageInput.SetText("")
|
||||
|
||||
ref := node.GetReference()
|
||||
c, err := ct.app.State.Cabinet.Channel(ref.(discord.ChannelID))
|
||||
|
|
@ -52,7 +54,7 @@ func (ct *ChannelsTree) onSelected(node *tview.TreeNode) {
|
|||
}
|
||||
|
||||
ct.SelectedChannel = c
|
||||
ct.app.SetFocus(ct.app.MessageInputField)
|
||||
ct.app.SetFocus(ct.app.MessageInput)
|
||||
|
||||
title := channelToString(*c)
|
||||
if c.Topic != "" {
|
||||
|
|
@ -85,6 +87,36 @@ func (ct *ChannelsTree) createChannelNode(c discord.Channel) *tview.TreeNode {
|
|||
return channelNode
|
||||
}
|
||||
|
||||
func (ct *ChannelsTree) createPrivateChannelNodes(rootNode *tview.TreeNode) {
|
||||
cs, err := ct.app.State.Cabinet.PrivateChannels()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(cs, func(i, j int) bool {
|
||||
return cs[i].LastMessageID > cs[j].LastMessageID
|
||||
})
|
||||
|
||||
for _, c := range cs {
|
||||
rootNode.AddChild(ct.createChannelNode(c))
|
||||
}
|
||||
}
|
||||
|
||||
func (ct *ChannelsTree) createGuildChannelNodes(rootNode *tview.TreeNode, gID discord.GuildID) {
|
||||
cs, err := ct.app.State.Cabinet.Channels(gID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(cs, func(i, j int) bool {
|
||||
return cs[i].Position < cs[j].Position
|
||||
})
|
||||
|
||||
ct.createOrphanChannelNodes(rootNode, cs)
|
||||
ct.createCategoryChannelNodes(rootNode, cs)
|
||||
ct.createChildrenChannelNodes(rootNode, cs)
|
||||
}
|
||||
|
||||
func (ct *ChannelsTree) createOrphanChannelNodes(rootNode *tview.TreeNode, cs []discord.Channel) {
|
||||
for _, c := range cs {
|
||||
if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && (!c.ParentID.IsValid()) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/diamondburned/arikawa/v3/discord"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
|
@ -42,7 +40,7 @@ func (gt *GuildsTree) onSelected(node *tview.TreeNode) {
|
|||
Highlight().
|
||||
Clear().
|
||||
SetTitle("")
|
||||
gt.app.MessageInputField.SetText("")
|
||||
gt.app.MessageInput.SetText("")
|
||||
|
||||
// If the selected node has children (guild folder), expand the selected node if it is collapsed, otherwise collapse.
|
||||
if len(node.GetChildren()) != 0 {
|
||||
|
|
@ -53,31 +51,9 @@ func (gt *GuildsTree) onSelected(node *tview.TreeNode) {
|
|||
ref := node.GetReference()
|
||||
// If the reference of the selected node is nil, it must be the direct messages node.
|
||||
if ref == nil {
|
||||
cs, err := gt.app.State.Cabinet.PrivateChannels()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(cs, func(i, j int) bool {
|
||||
return cs[i].LastMessageID > cs[j].LastMessageID
|
||||
})
|
||||
|
||||
for _, c := range cs {
|
||||
rootNode.AddChild(gt.app.ChannelsTree.createChannelNode(c))
|
||||
}
|
||||
gt.app.ChannelsTree.createPrivateChannelNodes(rootNode)
|
||||
} else { // Guild
|
||||
cs, err := gt.app.State.Cabinet.Channels(ref.(discord.GuildID))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(cs, func(i, j int) bool {
|
||||
return cs[i].Position < cs[j].Position
|
||||
})
|
||||
|
||||
gt.app.ChannelsTree.createOrphanChannelNodes(rootNode, cs)
|
||||
gt.app.ChannelsTree.createCategoryChannelNodes(rootNode, cs)
|
||||
gt.app.ChannelsTree.createChildrenChannelNodes(rootNode, cs)
|
||||
gt.app.ChannelsTree.createGuildChannelNodes(rootNode, ref.(discord.GuildID))
|
||||
}
|
||||
|
||||
gt.app.ChannelsTree.SetCurrentNode(rootNode)
|
||||
|
|
|
|||
|
|
@ -82,19 +82,19 @@ func NewMessageActionsList(app *App, m *discord.Message) *MessageActionsList {
|
|||
}
|
||||
|
||||
func (mal *MessageActionsList) replyAction() {
|
||||
mal.app.MessageInputField.SetTitle("Replying to " + mal.message.Author.Tag())
|
||||
mal.app.MessageInput.SetTitle("Replying to " + mal.message.Author.Tag())
|
||||
|
||||
mal.app.
|
||||
SetRoot(mal.app.MainFlex, true).
|
||||
SetFocus(mal.app.MessageInputField)
|
||||
SetFocus(mal.app.MessageInput)
|
||||
}
|
||||
|
||||
func (mal *MessageActionsList) mentionReplyAction() {
|
||||
mal.app.MessageInputField.SetTitle("[@] Replying to " + mal.message.Author.Tag())
|
||||
mal.app.MessageInput.SetTitle("[@] Replying to " + mal.message.Author.Tag())
|
||||
|
||||
mal.app.
|
||||
SetRoot(mal.app.MainFlex, true).
|
||||
SetFocus(mal.app.MessageInputField)
|
||||
SetFocus(mal.app.MessageInput)
|
||||
}
|
||||
|
||||
func (mal *MessageActionsList) selectReplyAction() {
|
||||
|
|
|
|||
|
|
@ -40,99 +40,107 @@ func NewMessageInput(app *App) *MessageInput {
|
|||
func (mi *MessageInput) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
|
||||
switch e.Name() {
|
||||
case "Enter":
|
||||
if mi.app.ChannelsTree.SelectedChannel == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := strings.TrimSpace(mi.app.MessageInputField.GetText())
|
||||
if t == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ms, err := mi.app.State.Messages(mi.app.ChannelsTree.SelectedChannel.ID, mi.app.Config.MessagesLimit)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(mi.app.MessagesPanel.GetHighlights()) != 0 {
|
||||
mID, err := discord.ParseSnowflake(mi.app.MessagesPanel.GetHighlights()[0])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, m := findMessageByID(ms, discord.MessageID(mID))
|
||||
d := api.SendMessageData{
|
||||
Content: t,
|
||||
Reference: m.Reference,
|
||||
AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
|
||||
}
|
||||
|
||||
// If the title of the message InputField widget has "[@]" as a prefix, send the message as a reply and mention the replied user.
|
||||
if strings.HasPrefix(mi.app.MessageInputField.GetTitle(), "[@]") {
|
||||
d.AllowedMentions.RepliedUser = option.True
|
||||
}
|
||||
|
||||
go mi.app.State.SendMessageComplex(m.ChannelID, d)
|
||||
|
||||
mi.app.MessagesPanel.SelectedMessage = -1
|
||||
mi.app.MessagesPanel.Highlight()
|
||||
|
||||
mi.app.MessageInputField.SetTitle("")
|
||||
} else {
|
||||
go mi.app.State.SendMessage(mi.app.ChannelsTree.SelectedChannel.ID, t)
|
||||
}
|
||||
|
||||
mi.app.MessageInputField.SetText("")
|
||||
|
||||
return nil
|
||||
return mi.sendMessage()
|
||||
case "Ctrl+V":
|
||||
text, _ := clipboard.ReadAll()
|
||||
text = mi.app.MessageInputField.GetText() + text
|
||||
mi.app.MessageInputField.SetText(text)
|
||||
|
||||
return nil
|
||||
return mi.pasteFromClipboard()
|
||||
case "Esc":
|
||||
mi.app.MessageInputField.
|
||||
mi.
|
||||
SetText("").
|
||||
SetTitle("")
|
||||
mi.app.SetFocus(mi.app.MainFlex)
|
||||
|
||||
mi.app.MessagesPanel.SelectedMessage = -1
|
||||
mi.app.MessagesPanel.Highlight()
|
||||
|
||||
return nil
|
||||
case mi.app.Config.Keys.OpenExternalEditor:
|
||||
e := os.Getenv("EDITOR")
|
||||
if e == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
cmd := exec.Command(e, f.Name())
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
|
||||
mi.app.Suspend(func() {
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mi.app.MessageInputField.SetText(string(b))
|
||||
|
||||
return nil
|
||||
return mi.openExternalEditor()
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func (mi *MessageInput) sendMessage() *tcell.EventKey {
|
||||
if mi.app.ChannelsTree.SelectedChannel == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := strings.TrimSpace(mi.GetText())
|
||||
if t == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ms, err := mi.app.State.Messages(mi.app.ChannelsTree.SelectedChannel.ID, mi.app.Config.MessagesLimit)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(mi.app.MessagesPanel.GetHighlights()) != 0 {
|
||||
mID, err := discord.ParseSnowflake(mi.app.MessagesPanel.GetHighlights()[0])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, m := findMessageByID(ms, discord.MessageID(mID))
|
||||
d := api.SendMessageData{
|
||||
Content: t,
|
||||
Reference: m.Reference,
|
||||
AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
|
||||
}
|
||||
|
||||
// If the title of the message InputField widget has "[@]" as a prefix, send the message as a reply and mention the replied user.
|
||||
if strings.HasPrefix(mi.GetTitle(), "[@]") {
|
||||
d.AllowedMentions.RepliedUser = option.True
|
||||
}
|
||||
|
||||
go mi.app.State.SendMessageComplex(m.ChannelID, d)
|
||||
|
||||
mi.app.MessagesPanel.SelectedMessage = -1
|
||||
mi.app.MessagesPanel.Highlight()
|
||||
|
||||
mi.SetTitle("")
|
||||
} else {
|
||||
go mi.app.State.SendMessage(mi.app.ChannelsTree.SelectedChannel.ID, t)
|
||||
}
|
||||
|
||||
mi.SetText("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mi *MessageInput) pasteFromClipboard() *tcell.EventKey {
|
||||
text, _ := clipboard.ReadAll()
|
||||
text = mi.GetText() + text
|
||||
mi.SetText(text)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mi *MessageInput) openExternalEditor() *tcell.EventKey {
|
||||
e := os.Getenv("EDITOR")
|
||||
if e == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
cmd := exec.Command(e, f.Name())
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
|
||||
mi.app.Suspend(func() {
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mi.SetText(string(b))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,74 +50,19 @@ func (mp *MessagesPanel) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
switch e.Name() {
|
||||
case mp.app.Config.Keys.SelectPreviousMessage:
|
||||
// If there are no highlighted regions, select the latest (last) message in the messages panel.
|
||||
if len(mp.app.MessagesPanel.GetHighlights()) == 0 {
|
||||
mp.SelectedMessage = 0
|
||||
} else {
|
||||
// If the selected message is the oldest (first) message, select the latest (last) message in the messages panel.
|
||||
if mp.SelectedMessage == len(ms)-1 {
|
||||
mp.SelectedMessage = 0
|
||||
} else {
|
||||
mp.SelectedMessage++
|
||||
}
|
||||
}
|
||||
|
||||
mp.app.MessagesPanel.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
return mp.selectPreviousMessage(ms)
|
||||
case mp.app.Config.Keys.SelectNextMessage:
|
||||
// If there are no highlighted regions, select the latest (last) message in the messages panel.
|
||||
if len(mp.app.MessagesPanel.GetHighlights()) == 0 {
|
||||
mp.SelectedMessage = 0
|
||||
} else {
|
||||
// If the selected message is the latest (last) message, select the oldest (first) message in the messages panel.
|
||||
if mp.SelectedMessage == 0 {
|
||||
mp.SelectedMessage = len(ms) - 1
|
||||
} else {
|
||||
mp.SelectedMessage--
|
||||
}
|
||||
}
|
||||
|
||||
mp.app.MessagesPanel.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
return mp.selectNextMessage(ms)
|
||||
case mp.app.Config.Keys.SelectFirstMessage:
|
||||
mp.SelectedMessage = len(ms) - 1
|
||||
mp.app.MessagesPanel.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
return mp.selectFirstMessage(ms)
|
||||
case mp.app.Config.Keys.SelectLastMessage:
|
||||
mp.SelectedMessage = 0
|
||||
mp.app.MessagesPanel.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
return mp.selectLastMessage(ms)
|
||||
case mp.app.Config.Keys.OpenMessageActionsList:
|
||||
hs := mp.app.MessagesPanel.GetHighlights()
|
||||
if len(hs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mID, err := discord.ParseSnowflake(hs[0])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, m := findMessageByID(ms, discord.MessageID(mID))
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
actionsList := NewMessageActionsList(mp.app, m)
|
||||
mp.app.SetRoot(actionsList, true)
|
||||
return nil
|
||||
return mp.openMessageActionsList(ms)
|
||||
case "Esc":
|
||||
mp.SelectedMessage = -1
|
||||
mp.app.SetFocus(mp.app.MainFlex)
|
||||
mp.app.MessagesPanel.
|
||||
mp.
|
||||
Clear().
|
||||
Highlight().
|
||||
SetTitle("")
|
||||
|
|
@ -126,3 +71,78 @@ func (mp *MessagesPanel) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
return e
|
||||
}
|
||||
|
||||
func (mp *MessagesPanel) selectPreviousMessage(ms []discord.Message) *tcell.EventKey {
|
||||
// If there are no highlighted regions, select the latest (last) message in the messages panel.
|
||||
if len(mp.GetHighlights()) == 0 {
|
||||
mp.SelectedMessage = 0
|
||||
} else {
|
||||
// If the selected message is the oldest (first) message, select the latest (last) message in the messages panel.
|
||||
if mp.SelectedMessage == len(ms)-1 {
|
||||
mp.SelectedMessage = 0
|
||||
} else {
|
||||
mp.SelectedMessage++
|
||||
}
|
||||
}
|
||||
|
||||
mp.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *MessagesPanel) selectNextMessage(ms []discord.Message) *tcell.EventKey {
|
||||
// If there are no highlighted regions, select the latest (last) message in the messages panel.
|
||||
if len(mp.GetHighlights()) == 0 {
|
||||
mp.SelectedMessage = 0
|
||||
} else {
|
||||
// If the selected message is the latest (last) message, select the oldest (first) message in the messages panel.
|
||||
if mp.SelectedMessage == 0 {
|
||||
mp.SelectedMessage = len(ms) - 1
|
||||
} else {
|
||||
mp.SelectedMessage--
|
||||
}
|
||||
}
|
||||
|
||||
mp.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *MessagesPanel) selectFirstMessage(ms []discord.Message) *tcell.EventKey {
|
||||
mp.SelectedMessage = len(ms) - 1
|
||||
mp.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *MessagesPanel) selectLastMessage(ms []discord.Message) *tcell.EventKey {
|
||||
mp.SelectedMessage = 0
|
||||
mp.
|
||||
Highlight(ms[mp.SelectedMessage].ID.String()).
|
||||
ScrollToHighlight()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *MessagesPanel) openMessageActionsList(ms []discord.Message) *tcell.EventKey {
|
||||
hs := mp.GetHighlights()
|
||||
if len(hs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mID, err := discord.ParseSnowflake(hs[0])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, m := findMessageByID(ms, discord.MessageID(mID))
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
actionsList := NewMessageActionsList(mp.app, m)
|
||||
mp.app.SetRoot(actionsList, true)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue