From f3531be04c434087c8dff921a03bfc0b117d78ad Mon Sep 17 00:00:00 2001 From: ayntgl <90600421+ayntgl@users.noreply.github.com> Date: Thu, 25 Aug 2022 23:38:37 +0530 Subject: [PATCH] refactor(ui): modularize code (#190) --- ui/app.go | 18 ++-- ui/channels_tree.go | 36 +++++++- ui/guilds_tree.go | 30 +------ ui/message_actions_list.go | 8 +- ui/message_input.go | 172 +++++++++++++++++++------------------ ui/messages_panel.go | 142 +++++++++++++++++------------- 6 files changed, 221 insertions(+), 185 deletions(-) diff --git a/ui/app.go b/ui/app.go index 70f74f4..cefba5d 100644 --- a/ui/app.go +++ b/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) diff --git a/ui/channels_tree.go b/ui/channels_tree.go index b9a6fb2..4398cdf 100644 --- a/ui/channels_tree.go +++ b/ui/channels_tree.go @@ -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()) { diff --git a/ui/guilds_tree.go b/ui/guilds_tree.go index 7e489df..1d42b9d 100644 --- a/ui/guilds_tree.go +++ b/ui/guilds_tree.go @@ -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) diff --git a/ui/message_actions_list.go b/ui/message_actions_list.go index d70871a..712f83b 100644 --- a/ui/message_actions_list.go +++ b/ui/message_actions_list.go @@ -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() { diff --git a/ui/message_input.go b/ui/message_input.go index d92859b..e9189e9 100644 --- a/ui/message_input.go +++ b/ui/message_input.go @@ -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 +} diff --git a/ui/messages_panel.go b/ui/messages_panel.go index 902eaab..d2adec3 100644 --- a/ui/messages_panel.go +++ b/ui/messages_panel.go @@ -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 +}