Jelajahi Sumber

refactor(ui): modularize code (#190)

ayntgl 3 tahun lalu
induk
melakukan
f3531be04c
6 mengubah file dengan 210 tambahan dan 174 penghapusan
  1. 9 9
      ui/app.go
  2. 34 2
      ui/channels_tree.go
  3. 3 27
      ui/guilds_tree.go
  4. 4 4
      ui/message_actions_list.go
  5. 79 71
      ui/message_input.go
  6. 81 61
      ui/messages_panel.go

+ 9 - 9
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)

+ 34 - 2
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()) {

+ 3 - 27
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)

+ 4 - 4
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() {

+ 79 - 71
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
-		}
+		return mi.sendMessage()
+	case "Ctrl+V":
+		return mi.pasteFromClipboard()
+	case "Esc":
+		mi.
+			SetText("").
+			SetTitle("")
+		mi.app.SetFocus(mi.app.MainFlex)
 
-		if len(mi.app.MessagesPanel.GetHighlights()) != 0 {
-			mID, err := discord.ParseSnowflake(mi.app.MessagesPanel.GetHighlights()[0])
-			if err != nil {
-				return nil
-			}
+		mi.app.MessagesPanel.SelectedMessage = -1
+		mi.app.MessagesPanel.Highlight()
+		return nil
+	case mi.app.Config.Keys.OpenExternalEditor:
+		return mi.openExternalEditor()
+	}
 
-			_, m := findMessageByID(ms, discord.MessageID(mID))
-			d := api.SendMessageData{
-				Content:         t,
-				Reference:       m.Reference,
-				AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
-			}
+	return e
+}
 
-			// 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
-			}
+func (mi *MessageInput) sendMessage() *tcell.EventKey {
+	if mi.app.ChannelsTree.SelectedChannel == nil {
+		return nil
+	}
 
-			go mi.app.State.SendMessageComplex(m.ChannelID, d)
+	t := strings.TrimSpace(mi.GetText())
+	if t == "" {
+		return nil
+	}
 
-			mi.app.MessagesPanel.SelectedMessage = -1
-			mi.app.MessagesPanel.Highlight()
+	ms, err := mi.app.State.Messages(mi.app.ChannelsTree.SelectedChannel.ID, mi.app.Config.MessagesLimit)
+	if err != nil {
+		return nil
+	}
 
-			mi.app.MessageInputField.SetTitle("")
-		} else {
-			go mi.app.State.SendMessage(mi.app.ChannelsTree.SelectedChannel.ID, t)
+	if len(mi.app.MessagesPanel.GetHighlights()) != 0 {
+		mID, err := discord.ParseSnowflake(mi.app.MessagesPanel.GetHighlights()[0])
+		if err != nil {
+			return nil
 		}
 
-		mi.app.MessageInputField.SetText("")
+		_, m := findMessageByID(ms, discord.MessageID(mID))
+		d := api.SendMessageData{
+			Content:         t,
+			Reference:       m.Reference,
+			AllowedMentions: &api.AllowedMentions{RepliedUser: option.False},
+		}
 
-		return nil
-	case "Ctrl+V":
-		text, _ := clipboard.ReadAll()
-		text = mi.app.MessageInputField.GetText() + text
-		mi.app.MessageInputField.SetText(text)
+		// 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
+		}
 
-		return nil
-	case "Esc":
-		mi.app.MessageInputField.
-			SetText("").
-			SetTitle("")
-		mi.app.SetFocus(mi.app.MainFlex)
+		go mi.app.State.SendMessageComplex(m.ChannelID, d)
 
 		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
-		}
+		mi.SetTitle("")
+	} else {
+		go mi.app.State.SendMessage(mi.app.ChannelsTree.SelectedChannel.ID, t)
+	}
 
-		f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
-		if err != nil {
-			return nil
-		}
-		defer os.Remove(f.Name())
+	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
+	}
 
-		cmd := exec.Command(e, f.Name())
-		cmd.Stdin = os.Stdin
-		cmd.Stdout = os.Stdout
+	f, err := os.CreateTemp(os.TempDir(), "discordo-*.md")
+	if err != nil {
+		return nil
+	}
+	defer os.Remove(f.Name())
 
-		mi.app.Suspend(func() {
-			err = cmd.Run()
-			if err != nil {
-				return
-			}
-		})
+	cmd := exec.Command(e, f.Name())
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
 
-		b, err := io.ReadAll(f)
+	mi.app.Suspend(func() {
+		err = cmd.Run()
 		if err != nil {
-			return nil
+			return
 		}
+	})
 
-		mi.app.MessageInputField.SetText(string(b))
-
+	b, err := io.ReadAll(f)
+	if err != nil {
 		return nil
 	}
 
-	return e
+	mi.SetText(string(b))
+	return nil
 }

+ 81 - 61
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
+}