refactor(ui): modularize code (#190)

This commit is contained in:
ayntgl 2022-08-25 23:38:37 +05:30 • committed by GitHub
parent 0689e21870
commit f3531be04c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 221 additions and 185 deletions

View file

@ -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)

View file

@ -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()) {

View file

@ -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)

View file

@ -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() {

View file

@ -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
}

View file

@ -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
}