refactor(ui/chat): inline state close command
This commit is contained in:
parent
8d31a80d0c
commit
764d4b7a0a
4 changed files with 214 additions and 219 deletions
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
type LogoutEvent struct{ tcell.EventTime }
|
||||
|
||||
func (v *Model) logout() tview.Command {
|
||||
func (m *Model) logout() tview.Command {
|
||||
return func() tcell.Event {
|
||||
return &LogoutEvent{}
|
||||
}
|
||||
|
|
@ -17,11 +17,13 @@ func (v *Model) logout() tview.Command {
|
|||
|
||||
type QuitEvent struct{ tcell.EventTime }
|
||||
|
||||
func (v *Model) closeState() tview.Command {
|
||||
func (m *Model) closeState() tview.Command {
|
||||
return func() tcell.Event {
|
||||
if err := v.CloseState(); err != nil {
|
||||
slog.Error("failed to close the session", "err", err)
|
||||
return tcell.NewEventError(err)
|
||||
if m.state != nil {
|
||||
if err := m.state.Close(); err != nil {
|
||||
slog.Error("failed to close the session", "err", err)
|
||||
return tcell.NewEventError(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,59 +7,59 @@ import (
|
|||
|
||||
var _ help.KeyMap = (*Model)(nil)
|
||||
|
||||
func (v *Model) ShortHelp() []keybind.Keybind {
|
||||
func (m *Model) ShortHelp() []keybind.Keybind {
|
||||
short := make([]keybind.Keybind, 0, 16)
|
||||
if active := v.activeKeyMap(); active != nil {
|
||||
if active := m.activeKeyMap(); active != nil {
|
||||
short = append(short, active.ShortHelp()...)
|
||||
}
|
||||
short = append(short, v.baseShortHelp()...)
|
||||
short = append(short, m.baseShortHelp()...)
|
||||
return short
|
||||
}
|
||||
|
||||
func (v *Model) FullHelp() [][]keybind.Keybind {
|
||||
func (m *Model) FullHelp() [][]keybind.Keybind {
|
||||
full := make([][]keybind.Keybind, 0, 8)
|
||||
if active := v.activeKeyMap(); active != nil {
|
||||
if active := m.activeKeyMap(); active != nil {
|
||||
full = append(full, active.FullHelp()...)
|
||||
}
|
||||
full = append(full, v.baseFullHelp()...)
|
||||
full = append(full, m.baseFullHelp()...)
|
||||
return full
|
||||
}
|
||||
|
||||
func (v *Model) activeKeyMap() help.KeyMap {
|
||||
if v.GetVisible(channelsPickerLayerName) {
|
||||
return v.channelsPicker
|
||||
func (m *Model) activeKeyMap() help.KeyMap {
|
||||
if m.GetVisible(channelsPickerLayerName) {
|
||||
return m.channelsPicker
|
||||
}
|
||||
|
||||
if v.app == nil {
|
||||
if m.app == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v.app.Focused() {
|
||||
case v.guildsTree:
|
||||
return v.guildsTree
|
||||
case v.messagesList:
|
||||
return v.messagesList
|
||||
case v.messageInput:
|
||||
return v.messageInput
|
||||
switch m.app.Focused() {
|
||||
case m.guildsTree:
|
||||
return m.guildsTree
|
||||
case m.messagesList:
|
||||
return m.messagesList
|
||||
case m.messageInput:
|
||||
return m.messageInput
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) baseShortHelp() []keybind.Keybind {
|
||||
cfg := v.cfg.Keybinds
|
||||
func (m *Model) baseShortHelp() []keybind.Keybind {
|
||||
cfg := m.cfg.Keybinds
|
||||
short := []keybind.Keybind{cfg.FocusGuildsTree.Keybind, cfg.FocusMessagesList.Keybind}
|
||||
if !v.messageInput.GetDisabled() {
|
||||
if !m.messageInput.GetDisabled() {
|
||||
short = append(short, cfg.FocusMessageInput.Keybind)
|
||||
}
|
||||
short = append(short, cfg.ToggleGuildsTree.Keybind, cfg.ToggleChannelsPicker.Keybind)
|
||||
return short
|
||||
}
|
||||
|
||||
func (v *Model) baseFullHelp() [][]keybind.Keybind {
|
||||
cfg := v.cfg.Keybinds
|
||||
func (m *Model) baseFullHelp() [][]keybind.Keybind {
|
||||
cfg := m.cfg.Keybinds
|
||||
focus := []keybind.Keybind{cfg.FocusGuildsTree.Keybind, cfg.FocusMessagesList.Keybind}
|
||||
if !v.messageInput.GetDisabled() {
|
||||
if !m.messageInput.GetDisabled() {
|
||||
focus = append(focus, cfg.FocusMessageInput.Keybind)
|
||||
}
|
||||
return [][]keybind.Keybind{
|
||||
|
|
|
|||
|
|
@ -81,131 +81,131 @@ func NewView(app *tview.Application, cfg *config.Config, token string) *Model {
|
|||
return v
|
||||
}
|
||||
|
||||
func (v *Model) SelectedChannel() *discord.Channel {
|
||||
v.selectedChannelMu.RLock()
|
||||
defer v.selectedChannelMu.RUnlock()
|
||||
return v.selectedChannel
|
||||
func (m *Model) SelectedChannel() *discord.Channel {
|
||||
m.selectedChannelMu.RLock()
|
||||
defer m.selectedChannelMu.RUnlock()
|
||||
return m.selectedChannel
|
||||
}
|
||||
|
||||
func (v *Model) SetSelectedChannel(channel *discord.Channel) {
|
||||
v.selectedChannelMu.Lock()
|
||||
v.selectedChannel = channel
|
||||
v.selectedChannelMu.Unlock()
|
||||
func (m *Model) SetSelectedChannel(channel *discord.Channel) {
|
||||
m.selectedChannelMu.Lock()
|
||||
m.selectedChannel = channel
|
||||
m.selectedChannelMu.Unlock()
|
||||
}
|
||||
|
||||
func (v *Model) buildLayout() {
|
||||
v.Clear()
|
||||
v.rightFlex.Clear()
|
||||
v.mainFlex.Clear()
|
||||
func (m *Model) buildLayout() {
|
||||
m.Clear()
|
||||
m.rightFlex.Clear()
|
||||
m.mainFlex.Clear()
|
||||
|
||||
v.rightFlex.
|
||||
m.rightFlex.
|
||||
SetDirection(flex.DirectionRow).
|
||||
AddItem(v.messagesList, 0, 1, false).
|
||||
AddItem(v.messageInput, 3, 1, false)
|
||||
AddItem(m.messagesList, 0, 1, false).
|
||||
AddItem(m.messageInput, 3, 1, false)
|
||||
// The guilds tree is always focused first at start-up.
|
||||
v.mainFlex.
|
||||
AddItem(v.guildsTree, 0, 1, true).
|
||||
AddItem(v.rightFlex, 0, 4, false)
|
||||
m.mainFlex.
|
||||
AddItem(m.guildsTree, 0, 1, true).
|
||||
AddItem(m.rightFlex, 0, 4, false)
|
||||
|
||||
v.AddLayer(v.mainFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true))
|
||||
v.AddLayer(v.messageInput.mentionsList, layers.WithName(mentionsListLayerName), layers.WithResize(false), layers.WithVisible(false))
|
||||
m.AddLayer(m.mainFlex, layers.WithName(flexLayerName), layers.WithResize(true), layers.WithVisible(true))
|
||||
m.AddLayer(m.messageInput.mentionsList, layers.WithName(mentionsListLayerName), layers.WithResize(false), layers.WithVisible(false))
|
||||
}
|
||||
|
||||
func (v *Model) togglePicker() {
|
||||
if v.HasLayer(channelsPickerLayerName) {
|
||||
v.closePicker()
|
||||
func (m *Model) togglePicker() {
|
||||
if m.HasLayer(channelsPickerLayerName) {
|
||||
m.closePicker()
|
||||
} else {
|
||||
v.openPicker()
|
||||
m.openPicker()
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) openPicker() {
|
||||
v.AddLayer(
|
||||
ui.Centered(v.channelsPicker, v.cfg.Picker.Width, v.cfg.Picker.Height),
|
||||
func (m *Model) openPicker() {
|
||||
m.AddLayer(
|
||||
ui.Centered(m.channelsPicker, m.cfg.Picker.Width, m.cfg.Picker.Height),
|
||||
layers.WithName(channelsPickerLayerName),
|
||||
layers.WithResize(true),
|
||||
layers.WithVisible(true),
|
||||
layers.WithOverlay(),
|
||||
).SendToFront(channelsPickerLayerName)
|
||||
v.channelsPicker.update()
|
||||
m.channelsPicker.update()
|
||||
}
|
||||
|
||||
func (v *Model) closePicker() {
|
||||
v.RemoveLayer(channelsPickerLayerName)
|
||||
v.channelsPicker.Update()
|
||||
func (m *Model) closePicker() {
|
||||
m.RemoveLayer(channelsPickerLayerName)
|
||||
m.channelsPicker.Update()
|
||||
}
|
||||
|
||||
func (v *Model) toggleGuildsTree() {
|
||||
func (m *Model) toggleGuildsTree() {
|
||||
// The guilds tree is visible if the number of items is two.
|
||||
if v.mainFlex.GetItemCount() == 2 {
|
||||
v.mainFlex.RemoveItem(v.guildsTree)
|
||||
if v.guildsTree.HasFocus() {
|
||||
v.app.SetFocus(v.mainFlex)
|
||||
if m.mainFlex.GetItemCount() == 2 {
|
||||
m.mainFlex.RemoveItem(m.guildsTree)
|
||||
if m.guildsTree.HasFocus() {
|
||||
m.app.SetFocus(m.mainFlex)
|
||||
}
|
||||
} else {
|
||||
v.buildLayout()
|
||||
v.app.SetFocus(v.guildsTree)
|
||||
m.buildLayout()
|
||||
m.app.SetFocus(m.guildsTree)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) focusGuildsTree() bool {
|
||||
func (m *Model) focusGuildsTree() bool {
|
||||
// The guilds tree is not hidden if the number of items is two.
|
||||
if v.mainFlex.GetItemCount() == 2 {
|
||||
v.app.SetFocus(v.guildsTree)
|
||||
if m.mainFlex.GetItemCount() == 2 {
|
||||
m.app.SetFocus(m.guildsTree)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *Model) focusMessageInput() bool {
|
||||
if !v.messageInput.GetDisabled() {
|
||||
v.app.SetFocus(v.messageInput)
|
||||
func (m *Model) focusMessageInput() bool {
|
||||
if !m.messageInput.GetDisabled() {
|
||||
m.app.SetFocus(m.messageInput)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *Model) focusPrevious() {
|
||||
switch v.app.Focused() {
|
||||
case v.messagesList: // Handle both a.messagesList and a.flex as well as other edge cases (if there is).
|
||||
if v.focusGuildsTree() {
|
||||
func (m *Model) focusPrevious() {
|
||||
switch m.app.Focused() {
|
||||
case m.messagesList: // Handle both a.messagesList and a.flex as well as other edge cases (if there is).
|
||||
if m.focusGuildsTree() {
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case v.guildsTree:
|
||||
if v.focusMessageInput() {
|
||||
case m.guildsTree:
|
||||
if m.focusMessageInput() {
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case v.messageInput:
|
||||
v.app.SetFocus(v.messagesList)
|
||||
case m.messageInput:
|
||||
m.app.SetFocus(m.messagesList)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) focusNext() {
|
||||
switch v.app.Focused() {
|
||||
case v.messagesList:
|
||||
if v.focusMessageInput() {
|
||||
func (m *Model) focusNext() {
|
||||
switch m.app.Focused() {
|
||||
case m.messagesList:
|
||||
if m.focusMessageInput() {
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case v.messageInput: // Handle both a.messageInput and a.flex as well as other edge cases (if there is).
|
||||
if v.focusGuildsTree() {
|
||||
case m.messageInput: // Handle both a.messageInput and a.flex as well as other edge cases (if there is).
|
||||
if m.focusGuildsTree() {
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case v.guildsTree:
|
||||
v.app.SetFocus(v.messagesList)
|
||||
case m.guildsTree:
|
||||
m.app.SetFocus(m.messagesList)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) HandleEvent(event tcell.Event) tview.Command {
|
||||
func (m *Model) HandleEvent(event tcell.Event) tview.Command {
|
||||
switch event := event.(type) {
|
||||
case *tview.InitEvent:
|
||||
return func() tcell.Event {
|
||||
if err := v.OpenState(v.token); err != nil {
|
||||
if err := m.OpenState(m.token); err != nil {
|
||||
slog.Error("failed to open chat state", "err", err)
|
||||
return tcell.NewEventError(err)
|
||||
}
|
||||
|
|
@ -213,18 +213,18 @@ func (v *Model) HandleEvent(event tcell.Event) tview.Command {
|
|||
}
|
||||
case *QuitEvent:
|
||||
return tview.Batch(
|
||||
v.closeState(),
|
||||
m.closeState(),
|
||||
tview.Quit(),
|
||||
)
|
||||
case *tview.ModalDoneEvent:
|
||||
if v.HasLayer(confirmModalLayerName) {
|
||||
v.RemoveLayer(confirmModalLayerName)
|
||||
if v.confirmModalPreviousFocus != nil {
|
||||
v.app.SetFocus(v.confirmModalPreviousFocus)
|
||||
if m.HasLayer(confirmModalLayerName) {
|
||||
m.RemoveLayer(confirmModalLayerName)
|
||||
if m.confirmModalPreviousFocus != nil {
|
||||
m.app.SetFocus(m.confirmModalPreviousFocus)
|
||||
}
|
||||
onDone := v.confirmModalDone
|
||||
v.confirmModalDone = nil
|
||||
v.confirmModalPreviousFocus = nil
|
||||
onDone := m.confirmModalDone
|
||||
m.confirmModalDone = nil
|
||||
m.confirmModalPreviousFocus = nil
|
||||
if onDone != nil {
|
||||
onDone(event.ButtonLabel)
|
||||
}
|
||||
|
|
@ -232,49 +232,49 @@ func (v *Model) HandleEvent(event tcell.Event) tview.Command {
|
|||
}
|
||||
case *tview.KeyEvent:
|
||||
switch {
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusGuildsTree.Keybind):
|
||||
v.messageInput.removeMentionsList()
|
||||
v.focusGuildsTree()
|
||||
case keybind.Matches(event, m.cfg.Keybinds.FocusGuildsTree.Keybind):
|
||||
m.messageInput.removeMentionsList()
|
||||
m.focusGuildsTree()
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusMessagesList.Keybind):
|
||||
v.messageInput.removeMentionsList()
|
||||
v.app.SetFocus(v.messagesList)
|
||||
case keybind.Matches(event, m.cfg.Keybinds.FocusMessagesList.Keybind):
|
||||
m.messageInput.removeMentionsList()
|
||||
m.app.SetFocus(m.messagesList)
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusMessageInput.Keybind):
|
||||
v.focusMessageInput()
|
||||
case keybind.Matches(event, m.cfg.Keybinds.FocusMessageInput.Keybind):
|
||||
m.focusMessageInput()
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusPrevious.Keybind):
|
||||
v.focusPrevious()
|
||||
case keybind.Matches(event, m.cfg.Keybinds.FocusPrevious.Keybind):
|
||||
m.focusPrevious()
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.FocusNext.Keybind):
|
||||
v.focusNext()
|
||||
case keybind.Matches(event, m.cfg.Keybinds.FocusNext.Keybind):
|
||||
m.focusNext()
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.Logout.Keybind):
|
||||
return tview.Batch(v.closeState(), v.logout())
|
||||
case keybind.Matches(event, v.cfg.Keybinds.ToggleGuildsTree.Keybind):
|
||||
v.toggleGuildsTree()
|
||||
case keybind.Matches(event, m.cfg.Keybinds.Logout.Keybind):
|
||||
return tview.Batch(m.closeState(), m.logout())
|
||||
case keybind.Matches(event, m.cfg.Keybinds.ToggleGuildsTree.Keybind):
|
||||
m.toggleGuildsTree()
|
||||
return nil
|
||||
case keybind.Matches(event, v.cfg.Keybinds.ToggleChannelsPicker.Keybind):
|
||||
v.togglePicker()
|
||||
case keybind.Matches(event, m.cfg.Keybinds.ToggleChannelsPicker.Keybind):
|
||||
m.togglePicker()
|
||||
return nil
|
||||
}
|
||||
case *closeLayerEvent:
|
||||
if v.HasLayer(event.name) {
|
||||
v.HideLayer(event.name)
|
||||
if m.HasLayer(event.name) {
|
||||
m.HideLayer(event.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return v.Layers.HandleEvent(event)
|
||||
return m.Layers.HandleEvent(event)
|
||||
}
|
||||
|
||||
func (v *Model) showConfirmModal(prompt string, buttons []string, onDone func(label string)) {
|
||||
v.confirmModalPreviousFocus = v.app.Focused()
|
||||
v.confirmModalDone = onDone
|
||||
func (m *Model) showConfirmModal(prompt string, buttons []string, onDone func(label string)) {
|
||||
m.confirmModalPreviousFocus = m.app.Focused()
|
||||
m.confirmModalDone = onDone
|
||||
|
||||
modal := tview.NewModal().
|
||||
SetText(prompt).
|
||||
AddButtons(buttons)
|
||||
v.
|
||||
m.
|
||||
AddLayer(
|
||||
ui.Centered(modal, 0, 0),
|
||||
layers.WithName(confirmModalLayerName),
|
||||
|
|
@ -285,75 +285,75 @@ func (v *Model) showConfirmModal(prompt string, buttons []string, onDone func(la
|
|||
SendToFront(confirmModalLayerName)
|
||||
}
|
||||
|
||||
func (v *Model) onReadUpdate(event *read.UpdateEvent) {
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
func (m *Model) onReadUpdate(event *read.UpdateEvent) {
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
// Use indexed node lookup to avoid walking the whole tree on every read
|
||||
// event. This runs frequently while reading/typing across channels.
|
||||
if event.GuildID.IsValid() {
|
||||
if guildNode := v.guildsTree.findNodeByReference(event.GuildID); guildNode != nil {
|
||||
v.guildsTree.setNodeLineStyle(guildNode, v.guildsTree.getGuildNodeStyle(event.GuildID))
|
||||
if guildNode := m.guildsTree.findNodeByReference(event.GuildID); guildNode != nil {
|
||||
m.guildsTree.setNodeLineStyle(guildNode, m.guildsTree.getGuildNodeStyle(event.GuildID))
|
||||
}
|
||||
}
|
||||
|
||||
// Channel style is always updated for the target channel regardless of
|
||||
// whether it's in a guild or DM.
|
||||
if channelNode := v.guildsTree.findNodeByReference(event.ChannelID); channelNode != nil {
|
||||
v.guildsTree.setNodeLineStyle(channelNode, v.guildsTree.getChannelNodeStyle(event.ChannelID))
|
||||
if channelNode := m.guildsTree.findNodeByReference(event.ChannelID); channelNode != nil {
|
||||
m.guildsTree.setNodeLineStyle(channelNode, m.guildsTree.getChannelNodeStyle(event.ChannelID))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (v *Model) clearTypers() {
|
||||
v.typersMu.Lock()
|
||||
for _, timer := range v.typers {
|
||||
func (m *Model) clearTypers() {
|
||||
m.typersMu.Lock()
|
||||
for _, timer := range m.typers {
|
||||
timer.Stop()
|
||||
}
|
||||
clear(v.typers)
|
||||
v.typersMu.Unlock()
|
||||
v.updateFooter()
|
||||
clear(m.typers)
|
||||
m.typersMu.Unlock()
|
||||
m.updateFooter()
|
||||
}
|
||||
|
||||
func (v *Model) addTyper(userID discord.UserID) {
|
||||
v.typersMu.Lock()
|
||||
typer, ok := v.typers[userID]
|
||||
func (m *Model) addTyper(userID discord.UserID) {
|
||||
m.typersMu.Lock()
|
||||
typer, ok := m.typers[userID]
|
||||
if ok {
|
||||
typer.Reset(typingDuration)
|
||||
} else {
|
||||
v.typers[userID] = time.AfterFunc(typingDuration, func() {
|
||||
v.removeTyper(userID)
|
||||
m.typers[userID] = time.AfterFunc(typingDuration, func() {
|
||||
m.removeTyper(userID)
|
||||
})
|
||||
}
|
||||
v.typersMu.Unlock()
|
||||
v.updateFooter()
|
||||
m.typersMu.Unlock()
|
||||
m.updateFooter()
|
||||
}
|
||||
|
||||
func (v *Model) removeTyper(userID discord.UserID) {
|
||||
v.typersMu.Lock()
|
||||
if typer, ok := v.typers[userID]; ok {
|
||||
func (m *Model) removeTyper(userID discord.UserID) {
|
||||
m.typersMu.Lock()
|
||||
if typer, ok := m.typers[userID]; ok {
|
||||
typer.Stop()
|
||||
delete(v.typers, userID)
|
||||
delete(m.typers, userID)
|
||||
}
|
||||
v.typersMu.Unlock()
|
||||
v.updateFooter()
|
||||
m.typersMu.Unlock()
|
||||
m.updateFooter()
|
||||
}
|
||||
|
||||
func (v *Model) updateFooter() {
|
||||
selectedChannel := v.SelectedChannel()
|
||||
func (m *Model) updateFooter() {
|
||||
selectedChannel := m.SelectedChannel()
|
||||
if selectedChannel == nil {
|
||||
return
|
||||
}
|
||||
guildID := selectedChannel.GuildID
|
||||
|
||||
v.typersMu.RLock()
|
||||
defer v.typersMu.RUnlock()
|
||||
m.typersMu.RLock()
|
||||
defer m.typersMu.RUnlock()
|
||||
|
||||
var footer string
|
||||
if len(v.typers) > 0 {
|
||||
if len(m.typers) > 0 {
|
||||
var names []string
|
||||
for userID := range v.typers {
|
||||
for userID := range m.typers {
|
||||
var name string
|
||||
if guildID.IsValid() {
|
||||
member, err := v.state.Cabinet.Member(guildID, userID)
|
||||
member, err := m.state.Cabinet.Member(guildID, userID)
|
||||
if err != nil {
|
||||
slog.Error("failed to get member from state", "err", err, "guild_id", guildID, "user_id", userID)
|
||||
continue
|
||||
|
|
@ -390,5 +390,5 @@ func (v *Model) updateFooter() {
|
|||
}
|
||||
}
|
||||
|
||||
go v.app.QueueUpdateDraw(func() { v.messagesList.SetFooter(footer) })
|
||||
go m.app.QueueUpdateDraw(func() { m.messagesList.SetFooter(footer) })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ import (
|
|||
"github.com/diamondburned/ningen/v3"
|
||||
)
|
||||
|
||||
func (v *Model) OpenState(token string) error {
|
||||
func (m *Model) OpenState(token string) error {
|
||||
identifyProps := http.IdentifyProperties()
|
||||
gateway.DefaultIdentity = identifyProps
|
||||
gateway.DefaultPresence = &gateway.UpdatePresenceCommand{
|
||||
Status: v.cfg.Status,
|
||||
Status: m.cfg.Status,
|
||||
}
|
||||
|
||||
id := gateway.DefaultIdentifier(token)
|
||||
|
|
@ -32,38 +32,31 @@ func (v *Model) OpenState(token string) error {
|
|||
|
||||
session := session.NewCustom(id, http.NewClient(token), handler.New())
|
||||
state := state.NewFromSession(session, defaultstore.New())
|
||||
v.state = ningen.FromState(state)
|
||||
m.state = ningen.FromState(state)
|
||||
|
||||
// Handlers
|
||||
v.state.AddHandler(v.onRaw)
|
||||
v.state.AddHandler(v.onReady)
|
||||
v.state.AddHandler(v.onMessageCreate)
|
||||
v.state.AddHandler(v.onMessageUpdate)
|
||||
v.state.AddHandler(v.onMessageDelete)
|
||||
v.state.AddHandler(v.onReadUpdate)
|
||||
v.state.AddHandler(v.onGuildMembersChunk)
|
||||
v.state.AddHandler(v.onGuildMemberRemove)
|
||||
m.state.AddHandler(m.onRaw)
|
||||
m.state.AddHandler(m.onReady)
|
||||
m.state.AddHandler(m.onMessageCreate)
|
||||
m.state.AddHandler(m.onMessageUpdate)
|
||||
m.state.AddHandler(m.onMessageDelete)
|
||||
m.state.AddHandler(m.onReadUpdate)
|
||||
m.state.AddHandler(m.onGuildMembersChunk)
|
||||
m.state.AddHandler(m.onGuildMemberRemove)
|
||||
|
||||
if v.cfg.TypingIndicator.Receive {
|
||||
v.state.AddHandler(v.onTypingStart)
|
||||
if m.cfg.TypingIndicator.Receive {
|
||||
m.state.AddHandler(m.onTypingStart)
|
||||
}
|
||||
|
||||
v.state.StateLog = func(err error) {
|
||||
m.state.StateLog = func(err error) {
|
||||
slog.Error("state log", "err", err)
|
||||
}
|
||||
|
||||
v.state.OnRequest = append(v.state.OnRequest, httputil.WithHeaders(http.Headers()), v.onRequest)
|
||||
return v.state.Open(context.Background())
|
||||
m.state.OnRequest = append(m.state.OnRequest, httputil.WithHeaders(http.Headers()), m.onRequest)
|
||||
return m.state.Open(context.Background())
|
||||
}
|
||||
|
||||
func (v *Model) CloseState() error {
|
||||
if v.state == nil {
|
||||
return nil
|
||||
}
|
||||
return v.state.Close()
|
||||
}
|
||||
|
||||
func (v *Model) onRequest(r httpdriver.Request) error {
|
||||
func (m *Model) onRequest(r httpdriver.Request) error {
|
||||
if req, ok := r.(*httpdriver.DefaultRequest); ok {
|
||||
slog.Debug("new HTTP request", "method", req.Method, "url", req.URL)
|
||||
}
|
||||
|
|
@ -71,7 +64,7 @@ func (v *Model) onRequest(r httpdriver.Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (v *Model) onRaw(event *ws.RawEvent) {
|
||||
func (m *Model) onRaw(event *ws.RawEvent) {
|
||||
slog.Debug(
|
||||
"new raw event",
|
||||
"code", event.OriginalCode,
|
||||
|
|
@ -80,16 +73,16 @@ func (v *Model) onRaw(event *ws.RawEvent) {
|
|||
)
|
||||
}
|
||||
|
||||
func (v *Model) onReady(event *gateway.ReadyEvent) {
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
func (m *Model) onReady(event *gateway.ReadyEvent) {
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
// Rebuild indexes from scratch so reconnects and account switches do not
|
||||
// retain pointers to detached tree nodes.
|
||||
v.guildsTree.resetNodeIndex()
|
||||
m.guildsTree.resetNodeIndex()
|
||||
|
||||
dmNode := tview.NewTreeNode("Direct Messages").SetReference(dmNode{}).SetExpandable(true).SetExpanded(false)
|
||||
v.guildsTree.dmRootNode = dmNode
|
||||
m.guildsTree.dmRootNode = dmNode
|
||||
|
||||
root := v.guildsTree.
|
||||
root := m.guildsTree.
|
||||
GetRoot().
|
||||
ClearChildren().
|
||||
AddChild(dmNode)
|
||||
|
|
@ -127,7 +120,7 @@ func (v *Model) onReady(event *gateway.ReadyEvent) {
|
|||
|
||||
// Orphan guild - add directly to root in order
|
||||
if guildEvent, ok := guildsByID[guildID]; ok {
|
||||
v.guildsTree.createGuildNode(root, guildEvent.Guild)
|
||||
m.guildsTree.createGuildNode(root, guildEvent.Guild)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,59 +128,59 @@ func (v *Model) onReady(event *gateway.ReadyEvent) {
|
|||
for _, folder := range event.UserSettings.GuildFolders {
|
||||
if folder.ID == 0 && len(folder.GuildIDs) == 1 {
|
||||
if guild, ok := guildsByID[folder.GuildIDs[0]]; ok {
|
||||
v.guildsTree.createGuildNode(root, guild.Guild)
|
||||
m.guildsTree.createGuildNode(root, guild.Guild)
|
||||
}
|
||||
} else {
|
||||
v.guildsTree.createFolderNode(folder, guildsByID)
|
||||
m.guildsTree.createFolderNode(folder, guildsByID)
|
||||
}
|
||||
}
|
||||
|
||||
v.guildsTree.SetCurrentNode(root)
|
||||
v.app.SetFocus(v.guildsTree)
|
||||
m.guildsTree.SetCurrentNode(root)
|
||||
m.app.SetFocus(m.guildsTree)
|
||||
})
|
||||
}
|
||||
|
||||
func (v *Model) onMessageCreate(message *gateway.MessageCreateEvent) {
|
||||
selectedChannel := v.SelectedChannel()
|
||||
func (m *Model) onMessageCreate(message *gateway.MessageCreateEvent) {
|
||||
selectedChannel := m.SelectedChannel()
|
||||
if selectedChannel != nil && selectedChannel.ID == message.ChannelID {
|
||||
v.removeTyper(message.Author.ID)
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.addMessage(message.Message)
|
||||
m.removeTyper(message.Author.ID)
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
m.messagesList.addMessage(message.Message)
|
||||
})
|
||||
} else {
|
||||
if err := notifications.Notify(v.state, message, v.cfg); err != nil {
|
||||
if err := notifications.Notify(m.state, message, m.cfg); err != nil {
|
||||
slog.Error("failed to notify", "err", err, "channel_id", message.ChannelID, "message_id", message.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) onMessageUpdate(message *gateway.MessageUpdateEvent) {
|
||||
if selected := v.SelectedChannel(); selected != nil && selected.ID == message.ChannelID {
|
||||
index := slices.IndexFunc(v.messagesList.messages, func(m discord.Message) bool {
|
||||
func (m *Model) onMessageUpdate(message *gateway.MessageUpdateEvent) {
|
||||
if selected := m.SelectedChannel(); selected != nil && selected.ID == message.ChannelID {
|
||||
index := slices.IndexFunc(m.messagesList.messages, func(m discord.Message) bool {
|
||||
return m.ID == message.ID
|
||||
})
|
||||
if index < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.setMessage(index, message.Message)
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
m.messagesList.setMessage(index, message.Message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) onMessageDelete(message *gateway.MessageDeleteEvent) {
|
||||
if selected := v.SelectedChannel(); selected != nil && selected.ID == message.ChannelID {
|
||||
prevCursor := v.messagesList.Cursor()
|
||||
deletedIndex := slices.IndexFunc(v.messagesList.messages, func(m discord.Message) bool {
|
||||
func (m *Model) onMessageDelete(message *gateway.MessageDeleteEvent) {
|
||||
if selected := m.SelectedChannel(); selected != nil && selected.ID == message.ChannelID {
|
||||
prevCursor := m.messagesList.Cursor()
|
||||
deletedIndex := slices.IndexFunc(m.messagesList.messages, func(m discord.Message) bool {
|
||||
return m.ID == message.ID
|
||||
})
|
||||
if deletedIndex < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.deleteMessage(deletedIndex)
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
m.messagesList.deleteMessage(deletedIndex)
|
||||
})
|
||||
|
||||
// Keep cursor stable when possible after removal.
|
||||
|
|
@ -196,7 +189,7 @@ func (v *Model) onMessageDelete(message *gateway.MessageDeleteEvent) {
|
|||
// Prefer previous item; fall forward if we deleted the first.
|
||||
newCursor = deletedIndex - 1
|
||||
if newCursor < 0 {
|
||||
if deletedIndex < len(v.messagesList.messages) {
|
||||
if deletedIndex < len(m.messagesList.messages) {
|
||||
newCursor = deletedIndex
|
||||
} else {
|
||||
newCursor = -1
|
||||
|
|
@ -208,23 +201,23 @@ func (v *Model) onMessageDelete(message *gateway.MessageDeleteEvent) {
|
|||
}
|
||||
if newCursor != prevCursor {
|
||||
// Avoid redundant cursor updates if nothing changed.
|
||||
v.app.QueueUpdateDraw(func() {
|
||||
v.messagesList.SetCursor(newCursor)
|
||||
m.app.QueueUpdateDraw(func() {
|
||||
m.messagesList.SetCursor(newCursor)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Model) onGuildMembersChunk(event *gateway.GuildMembersChunkEvent) {
|
||||
v.messagesList.setFetchingChunk(false, uint(len(event.Members)))
|
||||
func (m *Model) onGuildMembersChunk(event *gateway.GuildMembersChunkEvent) {
|
||||
m.messagesList.setFetchingChunk(false, uint(len(event.Members)))
|
||||
}
|
||||
|
||||
func (v *Model) onGuildMemberRemove(event *gateway.GuildMemberRemoveEvent) {
|
||||
v.messageInput.cache.Invalidate(event.GuildID.String()+" "+event.User.Username, v.state.MemberState.SearchLimit)
|
||||
func (m *Model) onGuildMemberRemove(event *gateway.GuildMemberRemoveEvent) {
|
||||
m.messageInput.cache.Invalidate(event.GuildID.String()+" "+event.User.Username, m.state.MemberState.SearchLimit)
|
||||
}
|
||||
|
||||
func (v *Model) onTypingStart(event *gateway.TypingStartEvent) {
|
||||
selectedChannel := v.SelectedChannel()
|
||||
func (m *Model) onTypingStart(event *gateway.TypingStartEvent) {
|
||||
selectedChannel := m.SelectedChannel()
|
||||
if selectedChannel == nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -233,10 +226,10 @@ func (v *Model) onTypingStart(event *gateway.TypingStartEvent) {
|
|||
return
|
||||
}
|
||||
|
||||
me, _ := v.state.Cabinet.Me()
|
||||
me, _ := m.state.Cabinet.Me()
|
||||
if event.UserID == me.ID {
|
||||
return
|
||||
}
|
||||
|
||||
v.addTyper(event.UserID)
|
||||
m.addTyper(event.UserID)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue