From 6a13388cafafcada224b4caabb9f6f193594d757 Mon Sep 17 00:00:00 2001 From: Kyren223 Date: Fri, 24 Jan 2025 21:33:44 +0200 Subject: Added help popups when pressing "?" to show keybinds in the client --- internal/client/ui/core/chat/chat.go | 4 + internal/client/ui/core/core.go | 92 +++++---- internal/client/ui/core/helppopup.go | 245 +++++++++++++++++++++++ internal/client/ui/core/memberlist/memberlist.go | 2 +- 4 files changed, 300 insertions(+), 43 deletions(-) create mode 100644 internal/client/ui/core/helppopup.go diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index c650c04..0675230 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -1102,3 +1102,7 @@ func (m *Model) renderFrequencyName() string { return frequencyNameStyle.Render(frequency.Name) + "\n" } + +func (m *Model) Mode() int { + return m.vi.Mode() +} diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index c099473..43695e7 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -26,6 +26,7 @@ import ( "github.com/kyren223/eko/internal/client/ui/core/state" "github.com/kyren223/eko/internal/client/ui/core/usersettings" "github.com/kyren223/eko/internal/client/ui/loadscreen" + "github.com/kyren223/eko/internal/client/ui/viminput" "github.com/kyren223/eko/internal/data" "github.com/kyren223/eko/internal/packet" "github.com/kyren223/eko/pkg/assert" @@ -63,6 +64,7 @@ type Model struct { timeout time.Duration connected bool + helpPopup *HelpPopup userSettingsPopup *usersettings.Model networkCreationPopup *networkcreation.Model networkUpdatePopup *networkupdate.Model @@ -84,6 +86,7 @@ func New(privKey ed25519.PrivateKey, name string) Model { timer: newTimer(initialTimeout), timeout: initialTimeout, connected: false, + helpPopup: nil, userSettingsPopup: nil, networkCreationPopup: nil, networkUpdatePopup: nil, @@ -123,7 +126,9 @@ func (m Model) View() string { ) var popup string - if m.userSettingsPopup != nil { + if m.helpPopup != nil { + popup = m.helpPopup.View() + } else if m.userSettingsPopup != nil { popup = m.userSettingsPopup.View() } else if m.networkCreationPopup != nil { popup = m.networkCreationPopup.View() @@ -299,11 +304,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { if !m.HasPopup() { popup := networkcreation.New() m.networkCreationPopup = &popup - } else { - cmd := m.updatePopups(msg) - if cmd != nil { - return cmd - } + return nil } case FocusLeftSidebar: if !m.HasPopup() { @@ -317,17 +318,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { } popup := frequencycreation.New(*networkId) m.frequencyCreationPopup = &popup - } else { - cmd := m.updatePopups(msg) - if cmd != nil { - return cmd - } - } - - default: - cmd := m.updatePopups(msg) - if cmd != nil { - return cmd + return nil } } @@ -335,8 +326,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { if m.focus == FocusNetworkList && !m.HasPopup() { popup := networkjoin.New() m.networkJoinPopup = &popup - } else { - m.updatePopups(msg) + return nil } case "i": @@ -347,8 +337,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { if networkId != nil { _ = clipboard.WriteAll(networkId.String()) } - } else { - m.updatePopups(msg) + return nil } case "e": @@ -377,27 +366,41 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { index := m.frequencyList.Index() popup := frequencyupdate.New(*networkId, index) m.frequencyUpdatePopup = &popup - } else { - cmd := m.updatePopups(msg) - if cmd != nil { - return cmd - } + return nil } - // user/profile/options/settings - case "u", "p", "o", "s": - if !m.HasPopup() && m.focus == FocusNetworkList { + // user + case "u": + isChatLocked := m.focus == FocusChat && m.chat.Locked() + if !m.HasPopup() && !isChatLocked { popup := usersettings.New() m.userSettingsPopup = &popup - } else if m.HasPopup() { - cmd := m.updatePopups(msg) - if cmd != nil { - return cmd + return nil + } + + case "?": + normalMode := m.chat.Mode() == viminput.NormalMode + if !m.HasPopup() && (m.focus != FocusChat || !m.chat.Locked() || normalMode) { + switch m.focus { + case FocusNetworkList: + m.helpPopup = NewHelpPopup(HelpNetworkList) + case FocusLeftSidebar: + m.helpPopup = NewHelpPopup(HelpFrequencyList) + case FocusChat: + if m.chat.Locked() { + m.helpPopup = NewHelpPopup(HelpVim) + } else { + m.helpPopup = NewHelpPopup(HelpChat) + } + case FocusRightSidebar: + m.helpPopup = NewHelpPopup(HelpMemberList) } + return nil } case "esc": if m.HasPopup() { + m.helpPopup = nil m.userSettingsPopup = nil m.networkCreationPopup = nil m.networkUpdatePopup = nil @@ -407,7 +410,9 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { } case "enter": - if m.userSettingsPopup != nil { + if m.helpPopup != nil { + m.helpPopup = nil + } else if m.userSettingsPopup != nil { cmd := m.userSettingsPopup.Select() if cmd != nil { m.userSettingsPopup = nil @@ -446,12 +451,9 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { } default: - cmd := m.updatePopups(msg) - if cmd != nil { - return cmd - } + isChatLocked := m.focus == FocusChat && m.chat.Locked() + if !m.HasPopup() && !isChatLocked { - if m.focus != FocusChat || !m.chat.Locked() { left := msg.String() == "H" right := msg.String() == "L" direction := 0 @@ -466,7 +468,8 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { } if m.HasPopup() { - return nil + cmd := m.updatePopups(msg) + return cmd } var cmds []tea.Cmd @@ -531,7 +534,11 @@ func (m *Model) move(direction int) { } func (m *Model) updatePopups(msg tea.Msg) tea.Cmd { - if m.userSettingsPopup != nil { + if m.helpPopup != nil { + popup, cmd := m.helpPopup.Update(msg) + m.helpPopup = &popup + return cmd + } else if m.userSettingsPopup != nil { popup, cmd := m.userSettingsPopup.Update(msg) m.userSettingsPopup = &popup return cmd @@ -560,7 +567,8 @@ func (m *Model) updatePopups(msg tea.Msg) tea.Cmd { } func (m *Model) HasPopup() bool { - return m.userSettingsPopup != nil || + return m.helpPopup != nil || + m.userSettingsPopup != nil || m.networkCreationPopup != nil || m.networkUpdatePopup != nil || m.frequencyCreationPopup != nil || diff --git a/internal/client/ui/core/helppopup.go b/internal/client/ui/core/helppopup.go new file mode 100644 index 0000000..db81a38 --- /dev/null +++ b/internal/client/ui/core/helppopup.go @@ -0,0 +1,245 @@ +package core + +import ( + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/kyren223/eko/internal/client/ui" + "github.com/kyren223/eko/internal/client/ui/colors" +) + +var ( + style = lipgloss.NewStyle(). + MaxWidth(ui.MinWidth). + Border(lipgloss.ThickBorder()). + Padding(1, 2). + Align(lipgloss.Center, lipgloss.Center) + + titleStyle = lipgloss.NewStyle(). + Foreground(colors.Focus). + Border(lipgloss.ThickBorder(), false, false, true). + Padding(0, 4, 1).MarginBottom(1). + Align(lipgloss.Center, lipgloss.Center) + + keyStyle = lipgloss.NewStyle(). + Padding(0, 1). + Background(colors.DarkPurple).Foreground(colors.White) + + descriptionStyle = lipgloss.NewStyle() + + toggleHint = lipgloss.NewStyle().Foreground(colors.Gold). + Render("-- Press space to toggle between global/local keybindings --") +) + +const ( + HelpNetworkList = iota + HelpFrequencyList + HelpChat + HelpMemberList + HelpGlobal + HelpVim + HelpMax +) + +type Keymap struct { + key string + description string +} + +type HelpPopup struct { + help int + global bool +} + +func NewHelpPopup(help int) *HelpPopup { + return &HelpPopup{ + help: help, + global: false, + } +} + +func (m HelpPopup) Init() tea.Cmd { + return nil +} + +func (m HelpPopup) View() string { + if m.global { + m.help = HelpGlobal + } + + title := titleStyle.Render(m.Title()+" Keybindings Cheatsheet") + "\n" + + keymapLists := [][]Keymap{} + switch m.help { + case HelpNetworkList: + keymapLists = m.HelpNetworkList() + case HelpFrequencyList: + keymapLists = m.HelpFrequencyList() + case HelpChat: + keymapLists = m.HelpChat() + case HelpMemberList: + keymapLists = m.HelpMemberList() + case HelpGlobal: + keymapLists = m.HelpGlobal() + case HelpVim: + keymapLists = m.HelpVim() + } + + helps := []string{} + + for i, keymaps := range keymapLists { + var builder strings.Builder + + for _, keymap := range keymaps { + builder.WriteString(keyStyle.Render(keymap.key)) + builder.WriteString(" ") + builder.WriteString(descriptionStyle.Render(keymap.description)) + builder.WriteString("\n\n") + } + + // help := lipgloss.NewStyle().Align(lipgloss.Left).Render() + helps = append(helps, builder.String()) + + if i != len(keymapLists)-1 { + helps = append(helps, " ") // separator + } + } + + content := lipgloss.JoinHorizontal(lipgloss.Top, helps...) + "\n" + + return style.Render(title + content + toggleHint) +} + +func (m HelpPopup) Update(msg tea.Msg) (HelpPopup, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + if msg.Type == tea.KeySpace { + m.global = !m.global + } + } + + return m, nil +} + +func (m HelpPopup) Title() string { + switch m.help { + case HelpNetworkList: + return "Network" + case HelpFrequencyList: + return "Frequency" + case HelpChat: + return "Chat" + case HelpMemberList: + return "Member" + case HelpGlobal: + return "Global" + case HelpVim: + return "VIM" + } + return "Unknown" +} + +func (m HelpPopup) HelpGlobal() [][]Keymap { + return [][]Keymap{{ + {"ctrl+c", "Exit eko"}, + {"H", "Move focus to the right"}, + {"L", "Move focus to the left"}, + {"u", "Edit your user profile"}, + {"?", "Show a help popup"}, + }, { + {"esc", "Close popup"}, + {"tab", "cycle to the next option"}, + {"shift+tab", "cycle to the previous option"}, + {"enter", "confirm the selected option"}, + }} +} + +func (m HelpPopup) HelpNetworkList() [][]Keymap { + return [][]Keymap{{ + {"k", "Move up a network"}, + {"j", "Move down a network"}, + {"K", "Move the selected network up"}, + {"J", "Move the selected network down"}, + {"Q", "Leave the selected network"}, + }, { + {"n", "Create a new network"}, + {"e", "Edit the selected network"}, + {"a", "Join a new network from an invite code"}, + {"i", "Copy the selected network's invite code"}, + {"D", "Delete the selected network"}, + }} +} + +func (m HelpPopup) HelpFrequencyList() [][]Keymap { + return [][]Keymap{{ + {"k", "Move up a frequency"}, + {"j", "Move down a frequency"}, + {"ctrl+u", "Move half a page up"}, + {"ctrl+d", "Move half a page down"}, + {"g", "Move to the first frequency"}, + {"G", "Move to the last frequency"}, + }, { + {"n", "Create a new frequency"}, + {"e", "Edit the selected frequency"}, + {"x", "Delete the selected frequency"}, + {"K", "Move the selected frequency up"}, + {"J", "Move the selected frequency down"}, + {"i", "Copy the network's invite code"}, + }} +} + +func (m HelpPopup) HelpChat() [][]Keymap { + return [][]Keymap{{ + {"k", "Move up a message"}, + {"j", "Move down a message"}, + {"ctrl+u", "Move half a page up"}, + {"ctrl+d", "Move half a page down"}, + {"G", "Snap to the bottom message"}, + {"i", "Start typing a message"}, + {"enter", "Snap to bottom and type"}, + {"ctrl+q", "Exit typing mode"}, + }, { + {"x", "Delete selected message"}, + {"e", "Edit selected message"}, + + {"K", "Kick message sender"}, + {"M", "Mute message sender"}, + {"U", "Unmute message sender"}, + {"B", "Ban message sender"}, // TODO: implement + {"P", "Promote sender to admin"}, + {"D", "Demote sender from admin"}, + }} +} + +func (m HelpPopup) HelpMemberList() [][]Keymap { + return [][]Keymap{{ + {"k", "Move up by one"}, + {"j", "Move down by one"}, + {"ctrl+u", "Move half a page up"}, + {"ctrl+d", "Move half a page down"}, + {"g", "Move to the top"}, + {"G", "Move to the bottom"}, + + {"p", "View member profile"}, + {"T", "Trust/untrust member"}, + }, { + {"K", "Kick selected member"}, + {"M", "Mute selected member"}, + {"U", "Unmute selected member"}, + {"B", "Ban selected member"}, // TODO: implement + {"b", "Switch to banlist view"}, // TODO: implement + {"P", "Promote member to admin"}, + {"D", "Demote member from admin"}, + {"ctrl+t", "Transfer ownership"}, // TODO: implement + }} +} + +func (m HelpPopup) HelpVim() [][]Keymap { + return [][]Keymap{{ + {"esc", "Go into normal mode from insert mode"}, + {"q", "Exit typing from normal mode"}, + {"ctrl+q", "Exit typing from insert mode"}, + {"Other", "The rest of the vim keys work as usual"}, + }, {}} +} diff --git a/internal/client/ui/core/memberlist/memberlist.go b/internal/client/ui/core/memberlist/memberlist.go index 2d589fa..f42a4ac 100644 --- a/internal/client/ui/core/memberlist/memberlist.go +++ b/internal/client/ui/core/memberlist/memberlist.go @@ -191,7 +191,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { }) // Admin - case "x", "K": + case "K": networkId := state.NetworkId(m.networkIndex) network := state.State.Networks[*networkId] member := m.Members()[m.index] -- cgit v1.3.1