Rename configuration fields and UI widgets

This commit is contained in:
ayn2op 2023-01-09 12:14:30 +05:30
parent 8a279e056d
commit 433da8cd68
11 changed files with 262 additions and 310 deletions

View file

@ -11,25 +11,28 @@ import (
const Name = "discordo"
type MessagesViewKeysConfig struct {
OpenActionsView string `yaml:"open_actions_view"`
type (
MessagesTextKeysConfig struct {
LaunchActions string `yaml:"launch_actions"`
SelectPreviousMessage string `yaml:"select_previous_message"`
SelectNextMessage string `yaml:"select_next_message"`
SelectFirstMessage string `yaml:"select_first_message"`
SelectLastMessage string `yaml:"select_last_message"`
}
SelectPrevious string `yaml:"select_previous"`
SelectNext string `yaml:"select_next"`
SelectFirst string `yaml:"select_first"`
SelectLast string `yaml:"select_last"`
}
type InputViewKeysConfig struct {
SendMessage string `yaml:"send_message"`
OpenExternalEditor string `yaml:"open_external_editor"`
PasteClipboard string `yaml:"paste_clipboard"`
}
MessageInputKeysConfig struct {
Send string `yaml:"send"`
Paste string `yaml:"paste"`
type KeysConfig struct {
MessagesView MessagesViewKeysConfig `yaml:"messages_view"`
InputView InputViewKeysConfig `yaml:"input_view"`
}
LaunchEditor string `yaml:"launch_editor"`
}
KeysConfig struct {
MessagesText MessagesTextKeysConfig `yaml:"messages_text"`
MessageInput MessageInputKeysConfig `yaml:"message_input"`
}
)
type ThemeConfig struct {
Background string `yaml:"background"`
@ -48,34 +51,77 @@ type Config struct {
Timezone string `yaml:"timezone"`
// A textual representation of the time value formatted according to the layout defined by its value. Learn more: https://pkg.go.dev/time#Layout
TimeFormat string `yaml:"time_format"`
// Keybindings
Keys KeysConfig `yaml:"keys"`
// Theme
Keys KeysConfig `yaml:"keys"`
Theme ThemeConfig `yaml:"theme"`
}
func New() *Config {
return &Config{
func New() (*Config, error) {
path, err := os.UserConfigDir()
if err != nil {
return nil, err
}
// Create the configuration directory if it does not exist already.
path = filepath.Join(path, Name)
err = os.MkdirAll(path, os.ModePerm)
if err != nil {
return nil, err
}
c := def()
path = filepath.Join(path, "config.yml")
_, err = os.Stat(path)
// If the configuration file does not exist, create a new one and write the default configuration to it.
if os.IsNotExist(err) {
f, err := os.Create(path)
if err != nil {
return nil, err
}
defer f.Close()
err = yaml.NewEncoder(f).Encode(c)
if err != nil {
return nil, err
}
} else {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
err = yaml.NewDecoder(f).Decode(&c)
if err != nil {
return nil, err
}
}
return &c, nil
}
func def() Config {
return Config{
Mouse: true,
MessagesLimit: 50,
Timestamps: false,
Timezone: "Local",
TimeFormat: time.Kitchen,
Timestamps: false,
Timezone: "Local",
TimeFormat: time.Kitchen,
Keys: KeysConfig{
MessagesView: MessagesViewKeysConfig{
OpenActionsView: "Rune[a]",
MessagesText: MessagesTextKeysConfig{
LaunchActions: "Rune[a]",
SelectPreviousMessage: "Up",
SelectNextMessage: "Down",
SelectFirstMessage: "Home",
SelectLastMessage: "End",
SelectPrevious: "Up",
SelectNext: "Down",
SelectFirst: "Home",
SelectLast: "End",
},
InputView: InputViewKeysConfig{
SendMessage: "Enter",
OpenExternalEditor: "Ctrl+E",
PasteClipboard: "Ctrl+V",
MessageInput: MessageInputKeysConfig{
Send: "Enter",
Paste: "Ctrl+V",
LaunchEditor: "Ctrl+E",
},
},
Theme: ThemeConfig{
@ -86,44 +132,6 @@ func New() *Config {
}
}
func (cfg *Config) Load() error {
path := DirPath()
// Create the configuration directory if it does not exist already.
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return err
}
// Open the configuration file with create and read-write flag.
f, err := os.OpenFile(
filepath.Join(path, "config.yml"),
os.O_CREATE|os.O_RDWR,
os.ModePerm,
)
if err != nil {
return err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
// If the configuration file is empty (the size of the file is zero; a new configuration file was created), write the default configuration to the file.
if fi.Size() == 0 {
return yaml.NewEncoder(f).Encode(cfg)
}
return yaml.NewDecoder(f).Decode(&cfg)
}
// DirPath returns the path of the configuration directory.
func DirPath() string {
path, _ := os.UserConfigDir()
return filepath.Join(path, Name)
}
// LogDirPath returns the path of the log directory.
func LogDirPath() string {
path, _ := os.UserCacheDir()

View file

@ -38,8 +38,8 @@ func init() {
func main() {
flag.Parse()
cfg := config.New()
if err := cfg.Load(); err != nil {
cfg, err := config.New()
if err != nil {
log.Fatal(err)
}

0
out.log Normal file
View file

View file

@ -15,14 +15,14 @@ import (
var linkRegex = regexp.MustCompile("https?://.+")
type ActionsView struct {
type ActionsList struct {
*tview.List
app *Application
message *discord.Message
}
func newActionsView(app *Application, m *discord.Message) *ActionsView {
v := &ActionsView{
func newActionsList(app *Application, m *discord.Message) *ActionsList {
v := &ActionsList{
List: tview.NewList(),
app: app,
message: m,
@ -31,13 +31,13 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView {
v.ShowSecondaryText(false)
v.SetDoneFunc(func() {
app.SetRoot(app.view, true)
app.SetFocus(app.view.MessagesView)
app.SetFocus(app.view.MessagesText)
})
isDM := channelIsInDMCategory(app.view.ChannelsView.selected)
isDM := channelIsInDMCategory(app.view.ChannelsTree.selected)
// If the client user has the `SEND_MESSAGES` permission, add "Reply" and "Mention Reply" actions.
if isDM || !isDM && hasPermission(app.state, app.view.ChannelsView.selected.ID, discord.PermissionSendMessages) {
if isDM || !isDM && hasPermission(app.state, app.view.ChannelsTree.selected.ID, discord.PermissionSendMessages) {
v.AddItem("Reply", "", 'r', v.replyAction)
v.AddItem("Mention Reply", "", 'R', v.mentionReplyAction)
}
@ -56,7 +56,7 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView {
}
app.SetRoot(app.view, true)
app.SetFocus(app.view.MessagesView)
app.SetFocus(app.view.MessagesText)
})
}
@ -69,7 +69,7 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView {
me, _ := app.state.MeStore.Me()
// If the client user has the `MANAGE_MESSAGES` permission, add a new action to delete the message.
if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(app.state, app.view.ChannelsView.selected.ID, discord.PermissionManageMessages)) {
if (isDM && m.Author.ID == me.ID) || (!isDM && hasPermission(app.state, app.view.ChannelsTree.selected.ID, discord.PermissionManageMessages)) {
v.AddItem("Delete", "", 'd', v.deleteAction)
}
@ -85,37 +85,37 @@ func newActionsView(app *Application, m *discord.Message) *ActionsView {
return v
}
func (v *ActionsView) replyAction() {
v.app.view.InputView.SetTitle("Replying to " + v.message.Author.Tag())
func (al *ActionsList) replyAction() {
al.app.view.MessageInput.SetTitle("Replying to " + al.message.Author.Tag())
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.InputView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessageInput)
}
func (v *ActionsView) mentionReplyAction() {
v.app.view.InputView.SetTitle("[@] Replying to " + v.message.Author.Tag())
func (al *ActionsList) mentionReplyAction() {
al.app.view.MessageInput.SetTitle("[@] Replying to " + al.message.Author.Tag())
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.InputView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessageInput)
}
func (v *ActionsView) selectReplyAction() {
ms, err := v.app.state.Cabinet.Messages(v.message.ChannelID)
func (al *ActionsList) selectReplyAction() {
ms, err := al.app.state.Cabinet.Messages(al.message.ChannelID)
if err != nil {
return
}
v.app.view.MessagesView.selected, _ = findMessageByID(ms, v.message.ReferencedMessage.ID)
v.app.view.MessagesView.
Highlight(v.message.ReferencedMessage.ID.String()).
al.app.view.MessagesText.selected, _ = findMessageByID(ms, al.message.ReferencedMessage.ID)
al.app.view.MessagesText.
Highlight(al.message.ReferencedMessage.ID.String()).
ScrollToHighlight()
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}
func (v *ActionsView) openAttachmentAction() {
for _, a := range v.message.Attachments {
func (al *ActionsList) openAttachmentAction() {
for _, a := range al.message.Attachments {
cacheDirPath, _ := os.UserCacheDir()
f, err := os.Create(filepath.Join(cacheDirPath, a.Filename))
if err != nil {
@ -137,12 +137,12 @@ func (v *ActionsView) openAttachmentAction() {
go open.Run(f.Name())
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}
func (v *ActionsView) downloadAttachmentAction() {
for _, a := range v.message.Attachments {
func (al *ActionsList) downloadAttachmentAction() {
for _, a := range al.message.Attachments {
path, err := os.UserHomeDir()
if err != nil {
path = os.TempDir()
@ -168,66 +168,66 @@ func (v *ActionsView) downloadAttachmentAction() {
f.Write(d)
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}
func (v *ActionsView) deleteAction() {
v.app.view.MessagesView.Clear()
func (al *ActionsList) deleteAction() {
al.app.view.MessagesText.Clear()
err := v.app.state.MessageRemove(v.message.ChannelID, v.message.ID)
err := al.app.state.MessageRemove(al.message.ChannelID, al.message.ID)
if err != nil {
return
}
err = v.app.state.DeleteMessage(v.message.ChannelID, v.message.ID, "Unknown")
err = al.app.state.DeleteMessage(al.message.ChannelID, al.message.ID, "Unknown")
if err != nil {
return
}
// The returned slice will be sorted from latest to oldest.
ms, err := v.app.state.Cabinet.Messages(v.message.ChannelID)
ms, err := al.app.state.Cabinet.Messages(al.message.ChannelID)
if err != nil {
return
}
for i := len(ms) - 1; i >= 0; i-- {
_, err = v.app.view.MessagesView.Write(buildMessage(v.app, ms[i]))
_, err = al.app.view.MessagesText.Write(buildMessage(al.app, ms[i]))
if err != nil {
return
}
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}
func (v *ActionsView) copyContentAction() {
err := clipboard.WriteAll(v.message.Content)
func (al *ActionsList) copyContentAction() {
err := clipboard.WriteAll(al.message.Content)
if err != nil {
return
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}
func (v *ActionsView) copyIDAction() {
err := clipboard.WriteAll(v.message.ID.String())
func (al *ActionsList) copyIDAction() {
err := clipboard.WriteAll(al.message.ID.String())
if err != nil {
return
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}
func (v *ActionsView) copyLinkAction() {
err := clipboard.WriteAll(v.message.URL())
func (al *ActionsList) copyLinkAction() {
err := clipboard.WriteAll(al.message.URL())
if err != nil {
return
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.MessagesView)
al.app.SetRoot(al.app.view, true)
al.app.SetFocus(al.app.view.MessagesText)
}

View file

@ -73,9 +73,9 @@ func (app *Application) Run(token string) {
}
app.SetRoot(app.view, true)
app.SetFocus(app.view.GuildsView)
app.SetFocus(app.view.GuildsTree)
} else {
loginView := newLoginView(app)
loginView := newLoginForm(app)
app.SetRoot(loginView, true)
}
@ -103,7 +103,7 @@ func (app *Application) onBeforeDraw(screen tcell.Screen) bool {
}
func (c *Application) onReady(r *gateway.ReadyEvent) {
root := c.view.GuildsView.GetRoot()
root := c.view.GuildsTree.GetRoot()
for _, gf := range r.UserSettings.GuildFolders {
if gf.ID == 0 {
for _, gID := range gf.GuildIDs {
@ -154,24 +154,24 @@ func (c *Application) onReady(r *gateway.ReadyEvent) {
}
c.view.GuildsView.SetCurrentNode(root)
c.SetFocus(c.view.GuildsView)
c.view.GuildsTree.SetCurrentNode(root)
c.SetFocus(c.view.GuildsTree)
}
func (c *Application) onGuildCreate(g *gateway.GuildCreateEvent) {
guildNode := tview.NewTreeNode(g.Name)
guildNode.SetReference(g.ID)
rootNode := c.view.GuildsView.GetRoot()
rootNode := c.view.GuildsTree.GetRoot()
rootNode.AddChild(guildNode)
c.view.GuildsView.SetCurrentNode(rootNode)
c.SetFocus(c.view.GuildsView)
c.view.GuildsTree.SetCurrentNode(rootNode)
c.SetFocus(c.view.GuildsTree)
c.Draw()
}
func (c *Application) onGuildDelete(g *gateway.GuildDeleteEvent) {
rootNode := c.view.GuildsView.GetRoot()
rootNode := c.view.GuildsTree.GetRoot()
var parentNode *tview.TreeNode
rootNode.Walk(func(node, _ *tview.TreeNode) bool {
if node.GetReference() == g.ID {
@ -190,14 +190,14 @@ func (c *Application) onGuildDelete(g *gateway.GuildDeleteEvent) {
}
func (c *Application) onMessageCreate(m *gateway.MessageCreateEvent) {
if c.view.ChannelsView.selected != nil && m.ChannelID == c.view.ChannelsView.selected.ID {
_, err := c.view.MessagesView.Write(buildMessage(c, m.Message))
if c.view.ChannelsTree.selected != nil && m.ChannelID == c.view.ChannelsTree.selected.ID {
_, err := c.view.MessagesText.Write(buildMessage(c, m.Message))
if err != nil {
return
}
if len(c.view.MessagesView.GetHighlights()) == 0 {
c.view.MessagesView.ScrollToEnd()
if len(c.view.MessagesText.GetHighlights()) == 0 {
c.view.MessagesText.ScrollToEnd()
}
}
}

View file

@ -8,15 +8,15 @@ import (
"github.com/rivo/tview"
)
type ChannelsView struct {
type ChannelsTree struct {
*tview.TreeView
app *Application
selected *discord.Channel
}
func newChannelsView(app *Application) *ChannelsView {
v := &ChannelsView{
func newChannelsTree(app *Application) *ChannelsTree {
v := &ChannelsTree{
TreeView: tview.NewTreeView(),
app: app,
@ -34,14 +34,14 @@ func newChannelsView(app *Application) *ChannelsView {
return v
}
func (v *ChannelsView) onSelected(node *tview.TreeNode) {
func (v *ChannelsTree) onSelected(node *tview.TreeNode) {
v.selected = nil
v.app.view.MessagesView.selected = -1
v.app.view.MessagesView.
v.app.view.MessagesText.selected = -1
v.app.view.MessagesText.
Highlight().
Clear().
SetTitle("")
v.app.view.InputView.SetText("")
v.app.view.MessageInput.SetText("")
ref := node.GetReference()
if ref == nil {
@ -64,21 +64,21 @@ func (v *ChannelsView) onSelected(node *tview.TreeNode) {
default:
v.selected = c
v.app.view.MessagesView.setTitle(c)
v.app.SetFocus(v.app.view.InputView)
v.app.view.MessagesText.setTitle(c)
v.app.SetFocus(v.app.view.MessageInput)
go v.app.view.MessagesView.loadMessages(c)
go v.app.view.MessagesText.loadMessages(c)
}
}
func (v *ChannelsView) createChannelNode(c discord.Channel) *tview.TreeNode {
func (v *ChannelsTree) createChannelNode(c discord.Channel) *tview.TreeNode {
channelNode := tview.NewTreeNode(channelToString(c))
channelNode.SetReference(c.ID)
return channelNode
}
func (v *ChannelsView) createPrivateChannelNodes(root *tview.TreeNode) {
func (v *ChannelsTree) createPrivateChannelNodes(root *tview.TreeNode) {
cs, err := v.app.state.Cabinet.PrivateChannels()
if err != nil {
log.Println(err)
@ -102,7 +102,7 @@ func (v *ChannelsView) createPrivateChannelNodes(root *tview.TreeNode) {
}
}
func (v *ChannelsView) createGuildChannelNodes(root *tview.TreeNode, gID discord.GuildID) {
func (v *ChannelsTree) createGuildChannelNodes(root *tview.TreeNode, gID discord.GuildID) {
cs, err := v.app.state.Cabinet.Channels(gID)
if err != nil {
log.Println(err)
@ -118,7 +118,7 @@ func (v *ChannelsView) createGuildChannelNodes(root *tview.TreeNode, gID discord
v.createChildrenChannelNodes(root, cs)
}
func (v *ChannelsView) createOrphanChannelNodes(root *tview.TreeNode, cs []discord.Channel) {
func (v *ChannelsTree) createOrphanChannelNodes(root *tview.TreeNode, cs []discord.Channel) {
for _, c := range cs {
if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && (!c.ParentID.IsValid()) {
root.AddChild(v.createChannelNode(c))
@ -126,7 +126,7 @@ func (v *ChannelsView) createOrphanChannelNodes(root *tview.TreeNode, cs []disco
}
}
func (v *ChannelsView) createCategoryChannelNodes(root *tview.TreeNode, cs []discord.Channel) {
func (v *ChannelsTree) createCategoryChannelNodes(root *tview.TreeNode, cs []discord.Channel) {
CATEGORY:
for _, c := range cs {
if c.Type == discord.GuildCategory {
@ -142,7 +142,7 @@ CATEGORY:
}
}
func (v *ChannelsView) createChildrenChannelNodes(root *tview.TreeNode, cs []discord.Channel) {
func (v *ChannelsTree) createChildrenChannelNodes(root *tview.TreeNode, cs []discord.Channel) {
for _, c := range cs {
if (c.Type == discord.GuildText || c.Type == discord.GuildNews) && (c.ParentID.IsValid()) {
var parentNode *tview.TreeNode

View file

@ -5,14 +5,14 @@ import (
"github.com/rivo/tview"
)
type GuildsView struct {
type GuildsTree struct {
*tview.TreeView
app *Application
}
func newGuildsView(app *Application) *GuildsView {
v := &GuildsView{
func newGuildsTree(app *Application) *GuildsTree {
v := &GuildsTree{
TreeView: tview.NewTreeView(),
app: app,
@ -33,16 +33,16 @@ func newGuildsView(app *Application) *GuildsView {
return v
}
func (v *GuildsView) onSelected(node *tview.TreeNode) {
v.app.view.ChannelsView.selected = nil
v.app.view.MessagesView.selected = -1
rootNode := v.app.view.ChannelsView.GetRoot()
func (v *GuildsTree) onSelected(node *tview.TreeNode) {
v.app.view.ChannelsTree.selected = nil
v.app.view.MessagesText.selected = -1
rootNode := v.app.view.ChannelsTree.GetRoot()
rootNode.ClearChildren()
v.app.view.MessagesView.
v.app.view.MessagesText.
Highlight().
Clear().
SetTitle("")
v.app.view.InputView.SetText("")
v.app.view.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,11 +53,11 @@ func (v *GuildsView) 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 {
v.app.view.ChannelsView.createPrivateChannelNodes(rootNode)
v.app.view.ChannelsTree.createPrivateChannelNodes(rootNode)
} else { // Guild
v.app.view.ChannelsView.createGuildChannelNodes(rootNode, ref.(discord.GuildID))
v.app.view.ChannelsTree.createGuildChannelNodes(rootNode, ref.(discord.GuildID))
}
v.app.view.ChannelsView.SetCurrentNode(rootNode)
v.app.SetFocus(v.app.view.ChannelsView)
v.app.view.ChannelsTree.SetCurrentNode(rootNode)
v.app.SetFocus(v.app.view.ChannelsTree)
}

View file

@ -10,31 +10,31 @@ import (
"github.com/zalando/go-keyring"
)
type LoginView struct {
type LoginForm struct {
*tview.Form
app *Application
}
func newLoginView(app *Application) *LoginView {
v := &LoginView{
func newLoginForm(app *Application) *LoginForm {
lf := &LoginForm{
Form: tview.NewForm(),
app: app,
}
v.AddInputField("Email", "", 0, nil, nil)
v.AddPasswordField("Password", "", 0, 0, nil)
v.AddPasswordField("Code (optional)", "", 0, 0, nil)
v.AddButton("Login", v.onLoginButtonSelected)
lf.AddInputField("Email", "", 0, nil, nil)
lf.AddPasswordField("Password", "", 0, 0, nil)
lf.AddPasswordField("Code (optional)", "", 0, 0, nil)
lf.AddButton("Login", lf.onLoginButtonSelected)
v.SetTitle("Login")
v.SetTitleAlign(tview.AlignLeft)
v.SetBorder(true)
v.SetBorderPadding(1, 1, 1, 1)
lf.SetTitle("Login")
lf.SetTitleAlign(tview.AlignLeft)
lf.SetBorder(true)
lf.SetBorderPadding(1, 1, 1, 1)
return v
return lf
}
func (v *LoginView) onLoginButtonSelected() {
func (v *LoginForm) onLoginButtonSelected() {
email := v.GetFormItem(0).(*tview.InputField).GetText()
password := v.GetFormItem(1).(*tview.InputField).GetText()
if email == "" || password == "" {
@ -69,7 +69,7 @@ func (v *LoginView) onLoginButtonSelected() {
}
v.app.SetRoot(v.app.view, true)
v.app.SetFocus(v.app.view.GuildsView)
v.app.SetFocus(v.app.view.GuildsTree)
go keyring.Set(config.Name, "token", l.Token)
}

View file

@ -15,53 +15,53 @@ import (
"github.com/rivo/tview"
)
type InputView struct {
type MessageInput struct {
*tview.InputField
app *Application
}
func newInputView(app *Application) *InputView {
v := &InputView{
func newMessageInput(app *Application) *MessageInput {
mi := &MessageInput{
InputField: tview.NewInputField(),
app: app,
}
v.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
v.SetPlaceholder("Message...")
v.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor))
v.SetInputCapture(v.inputCapture)
mi.SetFieldBackgroundColor(tview.Styles.PrimitiveBackgroundColor)
mi.SetPlaceholder("Message...")
mi.SetPlaceholderStyle(tcell.StyleDefault.Background(tview.Styles.PrimitiveBackgroundColor))
mi.SetInputCapture(mi.inputCapture)
v.SetTitleAlign(tview.AlignLeft)
v.SetBorder(true)
v.SetBorderPadding(0, 0, 1, 1)
mi.SetTitleAlign(tview.AlignLeft)
mi.SetBorder(true)
mi.SetBorderPadding(0, 0, 1, 1)
return v
return mi
}
func (v *InputView) inputCapture(event *tcell.EventKey) *tcell.EventKey {
func (v *MessageInput) inputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Name() {
case v.app.config.Keys.InputView.SendMessage:
case v.app.config.Keys.MessageInput.Send:
return v.sendMessage()
case v.app.config.Keys.InputView.OpenExternalEditor:
case v.app.config.Keys.MessageInput.LaunchEditor:
return v.openExternalEditor()
case v.app.config.Keys.InputView.PasteClipboard:
case v.app.config.Keys.MessageInput.Paste:
return v.pasteClipboard()
case "Esc":
v.
SetText("").
SetTitle("")
v.app.view.MessagesView.selected = -1
v.app.view.MessagesView.Highlight()
v.app.view.MessagesText.selected = -1
v.app.view.MessagesText.Highlight()
return nil
}
return event
}
func (v *InputView) sendMessage() *tcell.EventKey {
if v.app.view.ChannelsView.selected == nil {
func (v *MessageInput) sendMessage() *tcell.EventKey {
if v.app.view.ChannelsTree.selected == nil {
return nil
}
@ -70,14 +70,14 @@ func (v *InputView) sendMessage() *tcell.EventKey {
return nil
}
ms, err := v.app.state.Messages(v.app.view.ChannelsView.selected.ID, v.app.config.MessagesLimit)
ms, err := v.app.state.Messages(v.app.view.ChannelsTree.selected.ID, v.app.config.MessagesLimit)
if err != nil {
log.Println(err)
return nil
}
if len(v.app.view.MessagesView.GetHighlights()) != 0 {
mID, err := discord.ParseSnowflake(v.app.view.MessagesView.GetHighlights()[0])
if len(v.app.view.MessagesText.GetHighlights()) != 0 {
mID, err := discord.ParseSnowflake(v.app.view.MessagesText.GetHighlights()[0])
if err != nil {
log.Println(err)
return nil
@ -99,19 +99,19 @@ func (v *InputView) sendMessage() *tcell.EventKey {
go v.app.state.SendMessageComplex(m.ChannelID, d)
v.app.view.MessagesView.selected = -1
v.app.view.MessagesView.Highlight()
v.app.view.MessagesText.selected = -1
v.app.view.MessagesText.Highlight()
v.SetTitle("")
} else {
go v.app.state.SendMessage(v.app.view.ChannelsView.selected.ID, t)
go v.app.state.SendMessage(v.app.view.ChannelsTree.selected.ID, t)
}
v.SetText("")
return nil
}
func (v *InputView) pasteClipboard() *tcell.EventKey {
func (v *MessageInput) pasteClipboard() *tcell.EventKey {
text, err := clipboard.ReadAll()
if err != nil {
log.Println(err)
@ -123,7 +123,7 @@ func (v *InputView) pasteClipboard() *tcell.EventKey {
return nil
}
func (v *InputView) openExternalEditor() *tcell.EventKey {
func (v *MessageInput) openExternalEditor() *tcell.EventKey {
e := os.Getenv("EDITOR")
if e == "" {
log.Println("environment variable EDITOR is empty")

View file

@ -9,7 +9,7 @@ import (
"github.com/rivo/tview"
)
type MessagesView struct {
type MessagesText struct {
*tview.TextView
// The index of the currently selected message. A negative index indicates that there is no currently selected message.
@ -17,31 +17,31 @@ type MessagesView struct {
app *Application
}
func newMessagesView(app *Application) *MessagesView {
v := &MessagesView{
func newMessagesText(app *Application) *MessagesText {
mt := &MessagesText{
TextView: tview.NewTextView(),
selected: -1,
app: app,
}
v.SetDynamicColors(true)
v.SetRegions(true)
v.SetWordWrap(true)
v.SetInputCapture(v.onInputCapture)
v.SetChangedFunc(func() {
v.app.Draw()
mt.SetDynamicColors(true)
mt.SetRegions(true)
mt.SetWordWrap(true)
mt.SetInputCapture(mt.onInputCapture)
mt.SetChangedFunc(func() {
mt.app.Draw()
})
v.SetTitle("Messages")
v.SetTitleAlign(tview.AlignLeft)
v.SetBorder(true)
v.SetBorderPadding(0, 0, 1, 1)
mt.SetTitle("Messages")
mt.SetTitleAlign(tview.AlignLeft)
mt.SetBorder(true)
mt.SetBorderPadding(0, 0, 1, 1)
return v
return mt
}
func (v *MessagesView) setTitle(c *discord.Channel) {
func (v *MessagesText) setTitle(c *discord.Channel) {
title := channelToString(*c)
if c.Topic != "" {
title += " - " + discordmd.Parse(c.Topic)
@ -50,7 +50,7 @@ func (v *MessagesView) setTitle(c *discord.Channel) {
v.SetTitle(title)
}
func (v *MessagesView) loadMessages(c *discord.Channel) {
func (v *MessagesText) loadMessages(c *discord.Channel) {
// The returned slice will be sorted from latest to oldest.
ms, err := v.app.state.Messages(c.ID, v.app.config.MessagesLimit)
if err != nil {
@ -59,7 +59,7 @@ func (v *MessagesView) loadMessages(c *discord.Channel) {
}
for i := len(ms) - 1; i >= 0; i-- {
_, err = v.app.view.MessagesView.Write(buildMessage(v.app, ms[i]))
_, err = v.app.view.MessagesText.Write(buildMessage(v.app, ms[i]))
if err != nil {
log.Println(err)
continue
@ -69,32 +69,32 @@ func (v *MessagesView) loadMessages(c *discord.Channel) {
v.ScrollToEnd()
}
func (v *MessagesView) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
if v.app.view.ChannelsView.selected == nil {
func (v *MessagesText) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
if v.app.view.ChannelsTree.selected == nil {
return nil
}
// Messages should return messages ordered from latest to earliest.
ms, err := v.app.state.Cabinet.Messages(v.app.view.ChannelsView.selected.ID)
ms, err := v.app.state.Cabinet.Messages(v.app.view.ChannelsTree.selected.ID)
if err != nil || len(ms) == 0 {
return nil
}
switch e.Name() {
case v.app.config.Keys.MessagesView.OpenActionsView:
case v.app.config.Keys.MessagesText.LaunchActions:
return v.openActionsView(ms)
case v.app.config.Keys.MessagesView.SelectPreviousMessage:
case v.app.config.Keys.MessagesText.SelectPrevious:
return v.selectPreviousMessage(ms)
case v.app.config.Keys.MessagesView.SelectNextMessage:
case v.app.config.Keys.MessagesText.SelectNext:
return v.selectNextMessage(ms)
case v.app.config.Keys.MessagesView.SelectFirstMessage:
case v.app.config.Keys.MessagesText.SelectFirst:
return v.selectFirstMessage(ms)
case v.app.config.Keys.MessagesView.SelectLastMessage:
case v.app.config.Keys.MessagesText.SelectLast:
return v.selectLastMessage(ms)
case "Esc":
v.selected = -1
v.app.view.ChannelsView.selected = nil
v.app.view.ChannelsTree.selected = nil
v.app.SetFocus(v.app.view)
v.
@ -107,7 +107,7 @@ func (v *MessagesView) onInputCapture(e *tcell.EventKey) *tcell.EventKey {
return e
}
func (v *MessagesView) selectPreviousMessage(ms []discord.Message) *tcell.EventKey {
func (v *MessagesText) selectPreviousMessage(ms []discord.Message) *tcell.EventKey {
// If there are no highlighted regions, select the latest (last) message.
if len(v.GetHighlights()) == 0 {
v.selected = 0
@ -125,7 +125,7 @@ func (v *MessagesView) selectPreviousMessage(ms []discord.Message) *tcell.EventK
return nil
}
func (v *MessagesView) selectNextMessage(ms []discord.Message) *tcell.EventKey {
func (v *MessagesText) selectNextMessage(ms []discord.Message) *tcell.EventKey {
// If there are no highlighted regions, select the latest (last) message.
if len(v.GetHighlights()) == 0 {
v.selected = 0
@ -144,7 +144,7 @@ func (v *MessagesView) selectNextMessage(ms []discord.Message) *tcell.EventKey {
return nil
}
func (v *MessagesView) selectFirstMessage(ms []discord.Message) *tcell.EventKey {
func (v *MessagesText) selectFirstMessage(ms []discord.Message) *tcell.EventKey {
v.selected = len(ms) - 1
v.
Highlight(ms[v.selected].ID.String()).
@ -152,7 +152,7 @@ func (v *MessagesView) selectFirstMessage(ms []discord.Message) *tcell.EventKey
return nil
}
func (v *MessagesView) selectLastMessage(ms []discord.Message) *tcell.EventKey {
func (v *MessagesText) selectLastMessage(ms []discord.Message) *tcell.EventKey {
v.selected = 0
v.
Highlight(ms[v.selected].ID.String()).
@ -160,7 +160,7 @@ func (v *MessagesView) selectLastMessage(ms []discord.Message) *tcell.EventKey {
return nil
}
func (v *MessagesView) openActionsView(ms []discord.Message) *tcell.EventKey {
func (v *MessagesText) openActionsView(ms []discord.Message) *tcell.EventKey {
hs := v.GetHighlights()
if len(hs) == 0 {
return nil
@ -176,7 +176,7 @@ func (v *MessagesView) openActionsView(ms []discord.Message) *tcell.EventKey {
return nil
}
actionsView := newActionsView(v.app, m)
actionsView := newActionsList(v.app, m)
v.app.SetRoot(actionsView, true)
return nil
}

View file

@ -1,98 +1,42 @@
package ui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type focusedId int
const (
focusedIdGuildsView focusedId = iota
focusedIdChannelsView
focusedIdMessagesView
focusedIdInputView
)
type View struct {
*tview.Flex
GuildsView *GuildsView
ChannelsView *ChannelsView
MessagesView *MessagesView
InputView *InputView
GuildsTree *GuildsTree
ChannelsTree *ChannelsTree
MessagesText *MessagesText
MessageInput *MessageInput
app *Application
focused focusedId
app *Application
}
func newView(app *Application) *View {
v := &View{
Flex: tview.NewFlex(),
GuildsView: newGuildsView(app),
ChannelsView: newChannelsView(app),
MessagesView: newMessagesView(app),
InputView: newInputView(app),
GuildsTree: newGuildsTree(app),
ChannelsTree: newChannelsTree(app),
MessagesText: newMessagesText(app),
MessageInput: newMessageInput(app),
app: app,
}
left := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(v.GuildsView, 10, 1, false).
AddItem(v.ChannelsView, 0, 1, false)
AddItem(v.GuildsTree, 10, 1, false).
AddItem(v.ChannelsTree, 0, 1, false)
right := tview.NewFlex().
SetDirection(tview.FlexRow).
AddItem(v.MessagesView, 0, 1, false).
AddItem(v.InputView, 3, 1, false)
AddItem(v.MessagesText, 0, 1, false).
AddItem(v.MessageInput, 3, 1, false)
v.AddItem(left, 0, 1, false)
v.AddItem(right, 0, 4, false)
v.SetInputCapture(v.onInputCapture)
return v
}
func (v *View) onInputCapture(event *tcell.EventKey) *tcell.EventKey {
switch event.Key() {
case tcell.KeyEsc:
v.focused = 0
case tcell.KeyBacktab:
// If the currently focused view is the guilds view (first), then focus the input view (last)
if v.focused == 0 {
v.focused = focusedIdInputView
} else {
v.focused--
}
v.setFocus()
case tcell.KeyTab:
// If the currently focused view is the input view (last), then focus the guilds view (first)
if v.focused == focusedIdInputView {
v.focused = focusedIdGuildsView
} else {
v.focused++
}
v.setFocus()
}
return event
}
func (v *View) setFocus() {
var p tview.Primitive
switch v.focused {
case focusedIdGuildsView:
p = v.GuildsView
case focusedIdChannelsView:
p = v.ChannelsView
case focusedIdMessagesView:
p = v.MessagesView
case focusedIdInputView:
p = v.InputView
}
v.app.SetFocus(p)
}