From d170057b371287e6b456fefd54fe339592f4067c Mon Sep 17 00:00:00 2001 From: erroneousboat Date: Sun, 17 Dec 2017 12:22:04 +0100 Subject: [PATCH] Implement theming for several components --- components/channels.go | 77 ++++++++++++++++++++++++++++++ components/chat.go | 14 ++++-- components/mode.go | 19 ++++++-- config/config.go | 94 +++++++++++++++++++----------------- config/theme.go | 20 ++++++-- handlers/event.go | 24 ++-------- service/channel.go | 76 ----------------------------- service/slack.go | 105 +++++++++++++++++++++-------------------- views/view.go | 9 +--- 9 files changed, 226 insertions(+), 212 deletions(-) delete mode 100644 service/channel.go diff --git a/components/channels.go b/components/channels.go index 73f5413..df089b0 100644 --- a/components/channels.go +++ b/components/channels.go @@ -1,6 +1,8 @@ package components import ( + "fmt" + "html" "strings" "github.com/erroneousboat/termui" @@ -13,8 +15,82 @@ const ( IconGroup = "☰" IconIM = "●" IconNotification = "*" + + PresenceAway = "away" + PresenceActive = "active" + + ChannelTypeChannel = "channel" + ChannelTypeGroup = "group" + ChannelTypeIM = "im" ) +type ChannelItem struct { + ID string + Name string + Topic string + Type string + UserID string + Presence string + Notification bool + + StylePrefix string + StyleIcon string + StyleText string +} + +// ToString will set the label of the channel, how it will be +// displayed on screen. Based on the type, different icons are +// shown, as well as an optional notification icon. +func (c ChannelItem) ToString() string { + var prefix string + if c.Notification { + prefix = IconNotification + } else { + prefix = " " + } + + var icon string + switch c.Type { + case ChannelTypeChannel: + icon = IconChannel + case ChannelTypeGroup: + icon = IconGroup + case ChannelTypeIM: + switch c.Presence { + case PresenceActive: + icon = IconOnline + case PresenceAway: + icon = IconOffline + default: + icon = IconIM + } + } + + label := fmt.Sprintf( + "[%s](%s) [%s](%s) [%s](%s)", + prefix, c.StylePrefix, + icon, c.StyleIcon, + c.Name, c.StyleText, + ) + + return label +} + +// GetChannelName will return a formatted representation of the +// name of the channel +func (c ChannelItem) GetChannelName() string { + var channelName string + if c.Topic != "" { + channelName = fmt.Sprintf("%s - %s", + html.UnescapeString(c.Name), + html.UnescapeString(c.Topic), + ) + } else { + channelName = c.Name + } + return channelName +} + // Channels is the definition of a Channels component type Channels struct { List *termui.List @@ -51,6 +127,7 @@ func (c *Channels) Buffer() termui.Buffer { break } + // Set the visible cursor var cells []termui.Cell if y == c.CursorPosition { cells = termui.DefaultTxBuilder.Build( diff --git a/components/chat.go b/components/chat.go index c1f4b24..3fbafec 100644 --- a/components/chat.go +++ b/components/chat.go @@ -16,25 +16,29 @@ type Message struct { Time time.Time Name string Content string + + StyleTime string + StyleName string + StyleText string } -func (m Message) ToString(stlTime string, stlName string, stlContent string) string { +func (m Message) ToString() string { if (m.Time != time.Time{} && m.Name != "") { return html.UnescapeString( fmt.Sprintf( "[[%s]](%s) [<%s>](%s) [%s](%s)", m.Time.Format("15:04"), - stlTime, + m.StyleTime, m.Name, - stlName, + m.StyleName, m.Content, - stlContent, + m.StyleText, ), ) } else { return html.UnescapeString( - fmt.Sprintf("[%s](%s)", m.Content, stlContent), + fmt.Sprintf("[%s](%s)", m.Content, m.StyleText), ) } } diff --git a/components/mode.go b/components/mode.go index 799a5eb..36cf8ca 100644 --- a/components/mode.go +++ b/components/mode.go @@ -1,6 +1,14 @@ package components -import "github.com/erroneousboat/termui" +import ( + "github.com/erroneousboat/termui" +) + +const ( + CommandMode = "NORMAL" + InsertMode = "INSERT" + SearchMode = "SEARCH" +) // Mode is the definition of Mode component type Mode struct { @@ -10,10 +18,11 @@ type Mode struct { // CreateMode is the constructor of the Mode struct func CreateModeComponent() *Mode { mode := &Mode{ - Par: termui.NewPar("NORMAL"), + Par: termui.NewPar(CommandMode), } mode.Par.Height = 3 + mode.SetCommandMode() return mode } @@ -82,16 +91,16 @@ func (m *Mode) SetY(y int) { } func (m *Mode) SetInsertMode() { - m.Par.Text = "INSERT" + m.Par.Text = InsertMode termui.Render(m) } func (m *Mode) SetCommandMode() { - m.Par.Text = "NORMAL" + m.Par.Text = CommandMode termui.Render(m) } func (m *Mode) SetSearchMode() { - m.Par.Text = "SEARCH" + m.Par.Text = SearchMode termui.Render(m) } diff --git a/config/config.go b/config/config.go index bb9f01a..b9b430a 100644 --- a/config/config.go +++ b/config/config.go @@ -4,12 +4,13 @@ import ( "encoding/json" "errors" "os" + + "github.com/erroneousboat/termui" ) // Config is the definition of a Config struct type Config struct { - SlackToken string `json:"slack_token"` - // Theme string `json:"theme"` + SlackToken string `json:"slack_token"` SidebarWidth int `json:"sidebar_width"` MainWidth int `json:"-"` KeyMap map[string]keyMapping `json:"key_map"` @@ -20,8 +21,41 @@ type keyMapping map[string]string // NewConfig loads the config file and returns a Config struct func NewConfig(filepath string) (*Config, error) { - cfg := Config{ - // Theme: "dark", + cfg := getDefaultConfig() + + file, err := os.Open(filepath) + if err != nil { + return &cfg, err + } + + if err := json.NewDecoder(file).Decode(&cfg); err != nil { + return &cfg, err + } + + if cfg.SlackToken == "" { + return &cfg, errors.New("couldn't find 'slack_token' parameter") + } + + if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 { + return &cfg, errors.New("please specify the 'sidebar_width' between 1 and 11") + } + + cfg.MainWidth = 12 - cfg.SidebarWidth + + termui.ColorMap = map[string]termui.Attribute{ + "fg": termui.StringToAttribute(cfg.Theme.View.Fg), + "bg": termui.StringToAttribute(cfg.Theme.View.Bg), + "border.fg": termui.StringToAttribute(cfg.Theme.View.BorderFg), + "label.fg": termui.StringToAttribute(cfg.Theme.View.LabelFg), + "par.fg": termui.StringToAttribute(cfg.Theme.View.ParFg), + "par.label.bg": termui.StringToAttribute(cfg.Theme.View.ParLabelFg), + } + + return &cfg, nil +} + +func getDefaultConfig() Config { + return Config{ SidebarWidth: 1, MainWidth: 11, KeyMap: map[string]keyMapping{ @@ -63,48 +97,24 @@ func NewConfig(filepath string) (*Config, error) { }, }, Theme: Theme{ - Message: Message{ - Time: "fg-red,fg-bold", - Name: "fg-blue,fg-bold", - Content: "", + View: View{ + Fg: "white", + Bg: "default", + BorderFg: "white", + LabelFg: "green,bold", + ParFg: "white", + ParLabelFg: "white", }, Channel: Channel{ Prefix: "", - Icon: "fg-red", - Name: "", + Icon: "fg-green,fg-bold", + Text: "fg-blue,fg-bold", + }, + Message: Message{ + Time: "fg-red,fg-bold", + Name: "fg-blue,fg-bold", + Text: "", }, }, } - - file, err := os.Open(filepath) - if err != nil { - return &cfg, err - } - - if err := json.NewDecoder(file).Decode(&cfg); err != nil { - return &cfg, err - } - - if cfg.SlackToken == "" { - return &cfg, errors.New("couldn't find 'slack_token' parameter") - } - - if cfg.SidebarWidth < 1 || cfg.SidebarWidth > 11 { - return &cfg, errors.New("please specify the 'sidebar_width' between 1 and 11") - } - - cfg.MainWidth = 12 - cfg.SidebarWidth - - // if cfg.Theme == "light" { - // termui.ColorMap = map[string]termui.Attribute{ - // "fg": termui.ColorBlack, - // "bg": termui.ColorWhite, - // "border.fg": termui.ColorBlack, - // "label.fg": termui.ColorBlue, - // "par.fg": termui.ColorYellow, - // "par.label.bg": termui.ColorWhite, - // } - // } - - return &cfg, nil } diff --git a/config/theme.go b/config/theme.go index 7068ce1..0b113d9 100644 --- a/config/theme.go +++ b/config/theme.go @@ -1,18 +1,28 @@ package config type Theme struct { - Message Message `json:"message"` + View View `json:"view"` Channel Channel `json:"channel"` + Message Message `json:"message"` +} + +type View struct { + Fg string `json:"fg"` + Bg string `json:"bg"` + BorderFg string `json:"border_fg"` + LabelFg string `json:"border_fg"` + ParFg string `json:"par_fg"` + ParLabelFg string `json:"par_label_fg"` } type Message struct { - Time string `json:"time"` - Name string `json:"name"` - Content string `json:"content"` + Time string `json:"time"` + Name string `json:"name"` + Text string `json:"text"` } type Channel struct { Prefix string `json:"prefix"` Icon string `json:"icon"` - Name string `json:"name"` + Text string `json:"text"` } diff --git a/handlers/event.go b/handlers/event.go index d93a012..e49eb8d 100644 --- a/handlers/event.go +++ b/handlers/event.go @@ -110,11 +110,7 @@ func messageHandler(ctx *context.AppContext) { // when attachments are added to message for i := len(msg) - 1; i >= 0; i-- { ctx.View.Chat.AddMessage( - msg[i].ToString( - ctx.Config.Theme.Message.Time, - ctx.Config.Theme.Message.Name, - ctx.Config.Theme.Message.Content, - ), + msg[i].ToString(), ) } @@ -272,14 +268,7 @@ func actionGetMessages(ctx *context.AppContext) { var strMsgs []string for _, msg := range msgs { - strMsgs = append( - strMsgs, - msg.ToString( - ctx.Config.Theme.Message.Time, - ctx.Config.Theme.Message.Name, - ctx.Config.Theme.Message.Content, - ), - ) + strMsgs = append(strMsgs, msg.ToString()) } ctx.View.Chat.SetMessages(strMsgs) @@ -350,14 +339,7 @@ func actionChangeChannel(ctx *context.AppContext) { var strMsgs []string for _, msg := range msgs { - strMsgs = append( - strMsgs, - msg.ToString( - ctx.Config.Theme.Message.Time, - ctx.Config.Theme.Message.Name, - ctx.Config.Theme.Message.Content, - ), - ) + strMsgs = append(strMsgs, msg.ToString()) } // Set messages for the channel diff --git a/service/channel.go b/service/channel.go deleted file mode 100644 index 756076d..0000000 --- a/service/channel.go +++ /dev/null @@ -1,76 +0,0 @@ -package service - -import ( - "fmt" - "html" - - "github.com/erroneousboat/slack-term/components" -) - -const ( - PresenceAway = "away" - PresenceActive = "active" -) - -type Channel struct { - ID string - Name string - Topic string - Type string - UserID string - Presence string - Notification bool -} - -// ToString will set the label of the channel, how it will be -// displayed on screen. Based on the type, different icons are -// shown, as well as an optional notification icon. -func (c Channel) ToString(stlPrefix string, stlIcon string, stlName string) string { - var prefix string - if c.Notification { - prefix = components.IconNotification - } else { - prefix = " " - } - - var icon string - switch c.Type { - case ChannelTypeChannel: - icon = components.IconChannel - case ChannelTypeGroup: - icon = components.IconGroup - case ChannelTypeIM: - switch c.Presence { - case PresenceActive: - icon = components.IconOnline - case PresenceAway: - icon = components.IconOffline - default: - icon = components.IconIM - } - } - - label := fmt.Sprintf( - "[%s](%s) [%s](%s) [%s](%s)", - prefix, stlPrefix, - icon, stlIcon, - c.Name, stlName, - ) - - return label -} - -// GetChannelName will return a formatted representation of the -// name of the channel -func (c Channel) GetChannelName() string { - var channelName string - if c.Topic != "" { - channelName = fmt.Sprintf("%s - %s", - html.UnescapeString(c.Name), - html.UnescapeString(c.Topic), - ) - } else { - channelName = c.Name - } - return channelName -} diff --git a/service/slack.go b/service/slack.go index 6f84a69..56e6383 100644 --- a/service/slack.go +++ b/service/slack.go @@ -26,7 +26,7 @@ type SlackService struct { Client *slack.Client RTM *slack.RTM SlackChannels []interface{} - Channels []Channel + Channels []components.ChannelItem UserCache map[string]string CurrentUserID string CurrentUsername string @@ -79,23 +79,27 @@ func NewSlackService(config *config.Config) (*SlackService, error) { // an []interface as well as to a []Channel which will give us easy access // to the id and name of the Channel. func (s *SlackService) GetChannels() []string { - var chans []Channel + var chans []components.ChannelItem // Channel slackChans, err := s.Client.GetChannels(true) if err != nil { - chans = append(chans, Channel{}) + chans = append(chans, components.ChannelItem{}) } + for _, chn := range slackChans { if chn.IsMember { s.SlackChannels = append(s.SlackChannels, chn) chans = append( - chans, Channel{ - ID: chn.ID, - Name: chn.Name, - Topic: chn.Topic.Value, - Type: ChannelTypeChannel, - UserID: "", + chans, components.ChannelItem{ + ID: chn.ID, + Name: chn.Name, + Topic: chn.Topic.Value, + Type: components.ChannelTypeChannel, + UserID: "", + StylePrefix: s.Config.Theme.Channel.Prefix, + StyleIcon: s.Config.Theme.Channel.Icon, + StyleText: s.Config.Theme.Channel.Text, }, ) } @@ -104,17 +108,20 @@ func (s *SlackService) GetChannels() []string { // Groups slackGroups, err := s.Client.GetGroups(true) if err != nil { - chans = append(chans, Channel{}) + chans = append(chans, components.ChannelItem{}) } for _, grp := range slackGroups { s.SlackChannels = append(s.SlackChannels, grp) chans = append( - chans, Channel{ - ID: grp.ID, - Name: grp.Name, - Topic: grp.Topic.Value, - Type: ChannelTypeGroup, - UserID: "", + chans, components.ChannelItem{ + ID: grp.ID, + Name: grp.Name, + Topic: grp.Topic.Value, + Type: components.ChannelTypeGroup, + UserID: "", + StylePrefix: s.Config.Theme.Channel.Prefix, + StyleIcon: s.Config.Theme.Channel.Icon, + StyleText: s.Config.Theme.Channel.Text, }, ) } @@ -122,7 +129,7 @@ func (s *SlackService) GetChannels() []string { // IM slackIM, err := s.Client.GetIMChannels() if err != nil { - chans = append(chans, Channel{}) + chans = append(chans, components.ChannelItem{}) } for _, im := range slackIM { @@ -138,13 +145,16 @@ func (s *SlackService) GetChannels() []string { if ok { chans = append( chans, - Channel{ - ID: im.ID, - Name: name, - Topic: "", - Type: ChannelTypeIM, - UserID: im.User, - Presence: presence, + components.ChannelItem{ + ID: im.ID, + Name: name, + Topic: "", + Type: components.ChannelTypeIM, + UserID: im.User, + Presence: presence, + StylePrefix: s.Config.Theme.Channel.Prefix, + StyleIcon: s.Config.Theme.Channel.Icon, + StyleText: s.Config.Theme.Channel.Text, }, ) s.SlackChannels = append(s.SlackChannels, im) @@ -155,14 +165,7 @@ func (s *SlackService) GetChannels() []string { var channels []string for _, chn := range s.Channels { - channels = append( - channels, - chn.ToString( - s.Config.Theme.Channel.Prefix, - s.Config.Theme.Channel.Icon, - s.Config.Theme.Channel.Name, - ), - ) + channels = append(channels, chn.ToString()) } return channels } @@ -171,14 +174,7 @@ func (s *SlackService) GetChannels() []string { func (s *SlackService) ChannelsToString() []string { var channels []string for _, chn := range s.Channels { - channels = append( - channels, - chn.ToString( - s.Config.Theme.Channel.Prefix, - s.Config.Theme.Channel.Icon, - s.Config.Theme.Channel.Name, - ), - ) + channels = append(channels, chn.ToString()) } return channels } @@ -376,7 +372,7 @@ func (s *SlackService) CreateMessage(message slack.Message) []components.Message // When there are attachments append them if len(message.Attachments) > 0 { - msgs = append(msgs, createMessageFromAttachments(message.Attachments)...) + msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...) } // Parse time @@ -388,9 +384,12 @@ func (s *SlackService) CreateMessage(message slack.Message) []components.Message // Format message msg := components.Message{ - Time: time.Unix(intTime, 0), - Name: name, - Content: parseMessage(s, message.Text), + Time: time.Unix(intTime, 0), + Name: name, + Content: parseMessage(s, message.Text), + StyleTime: s.Config.Theme.Message.Time, + StyleName: s.Config.Theme.Message.Name, + StyleText: s.Config.Theme.Message.Text, } msgs = append(msgs, msg) @@ -441,7 +440,7 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent // When there are attachments append them if len(message.Attachments) > 0 { - msgs = append(msgs, createMessageFromAttachments(message.Attachments)...) + msgs = append(msgs, s.CreateMessageFromAttachments(message.Attachments)...) } // Parse time @@ -453,9 +452,12 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent // Format message msg := components.Message{ - Time: time.Unix(intTime, 0), - Name: name, - Content: parseMessage(s, message.Text), + Time: time.Unix(intTime, 0), + Name: name, + Content: parseMessage(s, message.Text), + StyleTime: s.Config.Theme.Message.Time, + StyleName: s.Config.Theme.Message.Name, + StyleText: s.Config.Theme.Message.Text, } msgs = append(msgs, msg) @@ -538,9 +540,9 @@ func parseEmoji(msg string) string { ) } -// createMessageFromAttachments will construct a array of string of the Field +// CreateMessageFromAttachments will construct a array of string of the Field // values of Attachments from a Message. -func createMessageFromAttachments(atts []slack.Attachment) []components.Message { +func (s *SlackService) CreateMessageFromAttachments(atts []slack.Attachment) []components.Message { var msgs []components.Message for _, att := range atts { for i := len(att.Fields) - 1; i >= 0; i-- { @@ -550,6 +552,9 @@ func createMessageFromAttachments(atts []slack.Attachment) []components.Message att.Fields[i].Title, att.Fields[i].Value, ), + StyleTime: s.Config.Theme.Message.Time, + StyleName: s.Config.Theme.Message.Name, + StyleText: s.Config.Theme.Message.Text, }, ) } diff --git a/views/view.go b/views/view.go index 811066e..3361616 100644 --- a/views/view.go +++ b/views/view.go @@ -39,14 +39,7 @@ func CreateView(config *config.Config, svc *service.SlackService) *View { var strMsgs []string for _, msg := range msgs { - strMsgs = append( - strMsgs, - msg.ToString( - config.Theme.Message.Time, - config.Theme.Message.Name, - config.Theme.Message.Content, - ), - ) + strMsgs = append(strMsgs, msg.ToString()) } chat.SetMessages(strMsgs)