Refactor to get service reference out of components

This commit is contained in:
erroneousboat 2017-12-03 20:40:46 +01:00
parent 6d8d25cf1f
commit 23388070de
7 changed files with 187 additions and 189 deletions

View File

@ -1,13 +1,9 @@
package components
import (
"fmt"
"strings"
"sync"
"github.com/erroneousboat/termui"
"github.com/erroneousboat/slack-term/service"
)
const (
@ -17,9 +13,6 @@ const (
IconGroup = "☰"
IconIM = "●"
IconNotification = "*"
PresenceAway = "away"
PresenceActive = "active"
)
// Channels is the definition of a Channels component
@ -123,67 +116,8 @@ func (c *Channels) SetY(y int) {
c.List.SetY(y)
}
// SetChannels will set the channels from the service, to the
// Items field
func (c *Channels) SetChannels(channels []service.Channel) {
c.List.Items = make([]string, len(channels))
// WaitGroup needed, else SetPresenceChannels will start
// too early
var wg sync.WaitGroup
for i, slackChan := range channels {
wg.Add(1)
go func(i int, slackChan service.Channel) {
label := setChannelLabel(slackChan, false)
c.List.Items[i] = label
wg.Done()
}(i, slackChan)
}
wg.Wait()
}
// SetPresenceChannels will set the icon for all the IM channels
func (c *Channels) SetPresenceChannels(channels []service.Channel) {
for i, slackChan := range channels {
go func(i int, slackChan service.Channel) {
if slackChan.Type == service.ChannelTypeIM {
c.SetPresenceChannel(i, slackChan.Presence)
}
}(i, slackChan)
}
}
// SetPresenceChannel will set the correct icon for one IM channel, on
// a Presence change event
func (c *Channels) SetPresenceChannelEvent(channels []service.Channel, userID string, presence string) {
// Get the correct Channel from svc.Channels
var index int
for i, channel := range channels {
if userID == channel.UserID {
index = i
break
}
}
c.SetPresenceChannel(index, presence)
}
// SetPresenceChannel will set the correct icon for one IM channel
func (c *Channels) SetPresenceChannel(i int, presence string) {
switch presence {
case PresenceActive:
c.List.Items[i] = strings.Replace(
c.List.Items[i], IconOffline, IconOnline, 1,
)
case PresenceAway:
c.List.Items[i] = strings.Replace(
c.List.Items[i], IconOnline, IconOffline, 1,
)
default:
c.List.Items[i] = strings.Replace(
c.List.Items[i], IconOnline, IconOffline, 1,
)
}
func (c *Channels) SetChannels(channels []string) {
c.List.Items = channels
}
// SetSelectedChannel sets the SelectedChannel given the index
@ -201,7 +135,6 @@ func (c *Channels) MoveCursorUp() {
if c.SelectedChannel > 0 {
c.SetSelectedChannel(c.SelectedChannel - 1)
c.ScrollUp()
c.MarkAsRead()
}
}
@ -210,7 +143,6 @@ func (c *Channels) MoveCursorDown() {
if c.SelectedChannel < len(c.List.Items)-1 {
c.SetSelectedChannel(c.SelectedChannel + 1)
c.ScrollDown()
c.MarkAsRead()
}
}
@ -298,67 +230,3 @@ func (c *Channels) Search(term string) {
}
}
}
// MarkAsUnread will be called when a new message arrives and will
// render an notification icon in front of the channel name
func (c *Channels) MarkAsUnread(channels []service.Channel, channelID string) {
// Get the correct Channel from svc.Channels
var index int
for i, channel := range channels {
if channelID == channel.ID {
index = i
break
}
}
if !strings.Contains(c.List.Items[index], IconNotification) {
// The order of svc.Channels relates to the order of
// List.Items, index will be the index of the channel
c.List.Items[index] = fmt.Sprintf(
"%s %s", IconNotification, strings.TrimSpace(c.List.Items[index]),
)
}
// Play terminal bell sound
fmt.Print("\a")
}
// MarkAsRead will remove the notification icon in front of a channel that
// received a new message. This will happen as one will move up or down the
// cursor for Channels
func (c *Channels) MarkAsRead() {
channelName := strings.Split(
c.List.Items[c.SelectedChannel],
fmt.Sprintf("%s ", IconNotification),
)
if len(channelName) > 1 {
c.List.Items[c.SelectedChannel] = fmt.Sprintf(" %s", channelName[1])
} else {
c.List.Items[c.SelectedChannel] = channelName[0]
}
}
// setChannelLabel will set the label of the channel, meaning, how it
// is displayed on screen. Based on the type, different icons are
// shown, as well as an optional notification icon.
func setChannelLabel(channel service.Channel, notification bool) string {
var prefix string
if notification {
prefix = IconNotification
} else {
prefix = " "
}
var label string
switch channel.Type {
case service.ChannelTypeChannel:
label = fmt.Sprintf("%s %s %s", prefix, IconChannel, channel.Name)
case service.ChannelTypeGroup:
label = fmt.Sprintf("%s %s %s", prefix, IconGroup, channel.Name)
case service.ChannelTypeIM:
label = fmt.Sprintf("%s %s %s", prefix, IconIM, channel.Name)
}
return label
}

View File

@ -5,13 +5,30 @@ import (
"html"
"sort"
"strings"
"time"
"github.com/erroneousboat/termui"
"github.com/erroneousboat/slack-term/config"
"github.com/erroneousboat/slack-term/service"
)
type Message struct {
Time time.Time
Name string
Content string
}
func (m Message) ToString() string {
return html.UnescapeString(
fmt.Sprintf(
"[%s] <%s> %s",
m.Time.Format("15:04"),
m.Name,
m.Content,
),
)
}
// Chat is the definition of a Chat component
type Chat struct {
List *termui.List
@ -207,16 +224,7 @@ func (c *Chat) ScrollDown() {
}
// SetBorderLabel will set Label of the Chat pane to the specified string
func (c *Chat) SetBorderLabel(channel service.Channel) {
var channelName string
if channel.Topic != "" {
channelName = fmt.Sprintf("%s - %s",
html.UnescapeString(channel.Name),
html.UnescapeString(channel.Topic),
)
} else {
channelName = channel.Name
}
func (c *Chat) SetBorderLabel(channelName string) {
c.List.BorderLabel = channelName
}

View File

@ -3,8 +3,6 @@ package components
import (
"github.com/erroneousboat/termui"
runewidth "github.com/mattn/go-runewidth"
"github.com/erroneousboat/slack-term/service"
)
// Input is the definition of an Input component
@ -71,11 +69,6 @@ func (i *Input) SetY(y int) {
i.Par.SetY(y)
}
// SendMessage send the input text through the SlackService
func (i *Input) SendMessage(svc *service.SlackService, channel string, message string) {
svc.SendMessage(channel, message)
}
// Insert will insert a given key at the place of the current CursorPositionText
func (i *Input) Insert(key rune) {
// Append key to the left side

View File

@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"os"
"strconv"
"time"
@ -59,7 +60,7 @@ func eventHandler(ctx *context.AppContext) {
// Place your debugging statements here
if ctx.Debug {
ctx.View.Debug.Println("hello world")
ctx.View.Debug.Println("event received")
}
}
}()
@ -207,9 +208,8 @@ func actionSend(ctx *context.AppContext) {
ctx.View.Input.Clear()
ctx.View.Refresh()
ctx.View.Input.SendMessage(
ctx.Service,
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID,
ctx.Service.SendMessage(
ctx.View.Channels.SelectedChannel,
message,
)
}
@ -330,25 +330,30 @@ func actionChangeChannel(ctx *context.AppContext) {
// Set messages for the channel
ctx.View.Chat.SetMessages(messages)
// FIXME
// Set channel name for the Chat pane
ctx.View.Chat.SetBorderLabel(
ctx.Service.Channels[ctx.View.Channels.SelectedChannel],
ctx.Service.Channels[ctx.View.Channels.SelectedChannel].GetChannelName(),
)
// Clear notification icon if there is any
ctx.View.Channels.MarkAsRead()
ctx.Service.MarkAsRead(ctx.View.Channels.SelectedChannel)
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
termui.Render(ctx.View.Channels)
termui.Render(ctx.View.Chat)
}
func actionNewMessage(ctx *context.AppContext, channelID string) {
ctx.View.Channels.MarkAsUnread(ctx.Service.Channels, channelID)
ctx.Service.MarkAsUnread(ctx.View.Channels.SelectedChannel)
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
termui.Render(ctx.View.Channels)
fmt.Print("\a")
}
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
ctx.View.Channels.SetPresenceChannelEvent(ctx.Service.Channels, channelID, presence)
ctx.Service.SetPresenceChannelEvent(channelID, presence)
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
termui.Render(ctx.View.Channels)
}

71
service/channel.go Normal file
View File

@ -0,0 +1,71 @@
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() string {
var prefix string
if c.Notification {
prefix = components.IconNotification
} else {
prefix = " "
}
var label string
switch c.Type {
case ChannelTypeChannel:
label = fmt.Sprintf("%s %s %s", prefix, components.IconChannel, c.Name)
case ChannelTypeGroup:
label = fmt.Sprintf("%s %s %s", prefix, components.IconGroup, c.Name)
case ChannelTypeIM:
var icon string
switch c.Presence {
case PresenceActive:
icon = components.IconOnline
case PresenceAway:
icon = components.IconOffline
default:
icon = components.IconIM
}
label = fmt.Sprintf("%s %s %s", prefix, icon, c.Name)
}
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
}

View File

@ -11,6 +11,7 @@ import (
"github.com/nlopes/slack"
"github.com/erroneousboat/slack-term/components"
"github.com/erroneousboat/slack-term/config"
)
@ -30,15 +31,6 @@ type SlackService struct {
CurrentUsername string
}
type Channel struct {
ID string
Name string
Topic string
Type string
UserID string
Presence string
}
// NewSlackService is the constructor for the SlackService and will initialize
// the RTM and a Client
func NewSlackService(token string) (*SlackService, error) {
@ -84,7 +76,7 @@ func NewSlackService(token string) (*SlackService, error) {
// Because the channels are of different types, we will append them to
// 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() []Channel {
func (s *SlackService) GetChannels() []string {
var chans []Channel
// Channel
@ -159,7 +151,33 @@ func (s *SlackService) GetChannels() []Channel {
s.Channels = chans
return chans
var channels []string
for _, chn := range s.Channels {
channels = append(channels, chn.ToString())
}
return channels
}
// ChannelsToString will relay the string representation for a channel
func (s *SlackService) ChannelsToString() []string {
var channels []string
for _, chn := range s.Channels {
channels = append(channels, chn.ToString())
}
return channels
}
// SetPresenceChannelEvent will set the presence of a IM channel
func (s *SlackService) SetPresenceChannelEvent(userID string, presence string) {
// Get the correct Channel from svc.Channels
var index int
for i, channel := range s.Channels {
if userID == channel.UserID {
index = i
break
}
}
s.Channels[index].Presence = presence
}
// GetSlackChannel returns the representation of a slack channel
@ -199,8 +217,45 @@ func (s *SlackService) SetChannelReadMark(channel interface{}) {
}
}
// MarkAsRead will set the channel as read
func (s *SlackService) MarkAsRead(channelID int) {
channel := s.Channels[channelID]
if channel.Notification {
channel.Notification = false
switch channel.Type {
case ChannelTypeChannel:
s.Client.SetChannelReadMark(
channel.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
case ChannelTypeGroup:
s.Client.SetGroupReadMark(
channel.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
case ChannelTypeIM:
s.Client.MarkIMChannel(
channel.ID, fmt.Sprintf("%f",
float64(time.Now().Unix())),
)
}
}
}
// MarkAsUnread will set the channel as unread
func (s *SlackService) MarkAsUnread(channelID int) {
channel := s.Channels[channelID]
if !channel.Notification {
channel.Notification = true
}
}
// SendMessage will send a message to a particular channel
func (s *SlackService) SendMessage(channel string, message string) {
func (s *SlackService) SendMessage(channelID int, message string) {
// https://godoc.org/github.com/nlopes/slack#PostMessageParameters
postParams := slack.PostMessageParameters{
AsUser: true,
@ -208,7 +263,7 @@ func (s *SlackService) SendMessage(channel string, message string) {
}
// https://godoc.org/github.com/nlopes/slack#Client.PostMessage
s.Client.PostMessage(channel, message, postParams)
s.Client.PostMessage(s.Channels[channelID].ID, message, postParams)
}
// GetMessages will get messages for a channel, group or im channel delimited
@ -313,14 +368,13 @@ func (s *SlackService) CreateMessage(message slack.Message) []string {
intTime := int64(floatTime)
// Format message
msg := fmt.Sprintf(
"[%s] <%s> %s",
time.Unix(intTime, 0).Format("15:04"),
name,
parseMessage(s, message.Text),
)
msg := components.Message{
Time: time.Unix(intTime, 0),
Name: name,
Content: parseMessage(s, message.Text),
}
msgs = append(msgs, msg)
msgs = append(msgs, msg.ToString())
return msgs
}
@ -379,14 +433,13 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
intTime := int64(floatTime)
// Format message
msg := fmt.Sprintf(
"[%s] <%s> %s",
time.Unix(intTime, 0).Format("15:04"),
name,
parseMessage(s, message.Text),
)
msg := components.Message{
Time: time.Unix(intTime, 0),
Name: name,
Content: parseMessage(s, message.Text),
}
msgs = append(msgs, msg)
msgs = append(msgs, msg.ToString())
return msgs
}

View File

@ -25,7 +25,7 @@ func CreateView(svc *service.SlackService) *View {
// Channels: fill the component
slackChans := svc.GetChannels()
channels.SetChannels(slackChans)
channels.SetPresenceChannels(slackChans)
// channels.SetPresenceChannels(slackChans)
// Chat: create the component
chat := components.CreateChatComponent(input.Par.Height)
@ -36,7 +36,7 @@ func CreateView(svc *service.SlackService) *View {
chat.GetMaxItems(),
)
chat.SetMessages(slackMsgs)
chat.SetBorderLabel(svc.Channels[channels.SelectedChannel])
chat.SetBorderLabel(svc.Channels[channels.SelectedChannel].GetChannelName())
// Debug: create the component
debug := components.CreateDebugComponent(input.Par.Height)