From 8a8c4e9abc1006da92969e0234bb2d42f587c149 Mon Sep 17 00:00:00 2001 From: Kyren223 Date: Wed, 5 Feb 2025 16:54:09 +0200 Subject: Refactored all instances of the word "peer" and it's variants to "signal" and it's variants as this is the new terminology for "user signals" which are direct messages between 2 users --- internal/client/ui/core/chat/chat.go | 10 +- internal/client/ui/core/core.go | 76 +++---- internal/client/ui/core/helppopup.go | 10 +- internal/client/ui/core/networklist/networklist.go | 32 +-- internal/client/ui/core/peeradd/peeradd.go | 190 ---------------- internal/client/ui/core/peerlist/peerlist.go | 239 --------------------- internal/client/ui/core/signaladd/signaladd.go | 190 ++++++++++++++++ internal/client/ui/core/signallist/signallist.go | 239 +++++++++++++++++++++ internal/client/ui/core/state/state.go | 6 +- 9 files changed, 496 insertions(+), 496 deletions(-) delete mode 100644 internal/client/ui/core/peeradd/peeradd.go delete mode 100644 internal/client/ui/core/peerlist/peerlist.go create mode 100644 internal/client/ui/core/signaladd/signaladd.go create mode 100644 internal/client/ui/core/signallist/signallist.go diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index 6a2e102..aba65a2 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -259,7 +259,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.vi.SetInactive(false) } } else if m.receiverIndex != -1 { - receiverId := state.Data.Peers[m.receiverIndex] + receiverId := state.Data.Signals[m.receiverIndex] if s, ok := state.State.ChatState[receiverId]; ok { m.maxMessagesHeight = s.MaxHeight @@ -637,7 +637,7 @@ func (m *Model) sendMessage() tea.Cmd { var receiverId *snowflake.ID = nil if m.receiverIndex != -1 { - receiverId = &state.Data.Peers[m.receiverIndex] + receiverId = &state.Data.Signals[m.receiverIndex] } var frequencyId *snowflake.ID = nil @@ -737,7 +737,7 @@ func (m *Model) ResetBeforeSwitch() { state.SendUserDatUpdate() } else if m.receiverIndex != -1 { - receiverId := state.Data.Peers[m.receiverIndex] + receiverId := state.Data.Signals[m.receiverIndex] log.Println("Saving signal:", receiverId) state.State.ChatState[receiverId] = state.ChatState{ IncompleteMessage: m.vi.String(), @@ -777,7 +777,7 @@ func (m *Model) RestoreAfterSwitch() tea.Cmd { FrequencyID: &frequency.ID, }) } else if m.receiverIndex != -1 { - receiverId := state.Data.Peers[m.receiverIndex] + receiverId := state.Data.Signals[m.receiverIndex] log.Println("Restoring signal:", receiverId) m.lastReadMsg = state.State.LastReadMessages[receiverId] @@ -878,7 +878,7 @@ func (m *Model) renderMessages(screenHeight int) string { id = &frequencyId btree = state.State.Messages[frequencyId] } else if m.receiverIndex != -1 { - receiverId := state.Data.Peers[m.receiverIndex] + receiverId := state.Data.Signals[m.receiverIndex] id = &receiverId btree = state.State.Messages[receiverId] } diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index 70c871f..b87600d 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -25,8 +25,8 @@ import ( "github.com/kyren223/eko/internal/client/ui/core/networkjoin" "github.com/kyren223/eko/internal/client/ui/core/networklist" "github.com/kyren223/eko/internal/client/ui/core/networkupdate" - "github.com/kyren223/eko/internal/client/ui/core/peeradd" - "github.com/kyren223/eko/internal/client/ui/core/peerlist" + "github.com/kyren223/eko/internal/client/ui/core/signaladd" + "github.com/kyren223/eko/internal/client/ui/core/signallist" "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" @@ -77,9 +77,9 @@ type Model struct { frequencyUpdatePopup *frequencyupdate.Model banReasonPopup *banreason.Model banViewPopup *banview.Model - peerAddPopup *peeradd.Model + signalAddPopup *signaladd.Model networkList networklist.Model - peerList peerlist.Model + signalList signallist.Model frequencyList frequencylist.Model memberList memberlist.Model chat chat.Model @@ -103,9 +103,9 @@ func New(privKey ed25519.PrivateKey, name string) Model { frequencyUpdatePopup: nil, banReasonPopup: nil, banViewPopup: nil, - peerAddPopup: nil, + signalAddPopup: nil, networkList: networklist.New(), - peerList: peerlist.New(), + signalList: signallist.New(), frequencyList: frequencylist.New(), memberList: memberlist.New(), chat: chat.New(), @@ -127,8 +127,8 @@ func (m Model) View() string { networkList := m.networkList.View() var leftSidebar string - if m.networkList.Index() == networklist.PeersIndex { - leftSidebar = m.peerList.View() + if m.networkList.Index() == networklist.SignalsIndex { + leftSidebar = m.signalList.View() } else { leftSidebar = m.frequencyList.View() } @@ -161,8 +161,8 @@ func (m Model) View() string { popup = m.banReasonPopup.View() } else if m.banViewPopup != nil { popup = m.banViewPopup.View() - } else if m.peerAddPopup != nil { - popup = m.peerAddPopup.View() + } else if m.signalAddPopup != nil { + popup = m.signalAddPopup.View() } if popup != "" { @@ -251,7 +251,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { // log.Println("sidebarWidth:", sidebarWidth) // log.Println("chatWidth:", chatWidth) - m.peerList.SetWidth(sidebarWidth) + m.signalList.SetWidth(sidebarWidth) m.frequencyList.SetWidth(sidebarWidth) m.memberList.SetWidth(sidebarWidth) m.chat.SetWidth(chatWidth) @@ -284,7 +284,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { case *packet.NetworksInfo: state.UpdateNetworks(msg) networkId := state.NetworkId(m.networkList.Index()) - if networkId == nil && m.networkList.Index() != networklist.PeersIndex { + if networkId == nil && m.networkList.Index() != networklist.SignalsIndex { m.networkList.SetIndex(m.networkList.Index() - 1) m.frequencyList.SetNetworkIndex(m.networkList.Index()) m.memberList.SetNetworkAndFrequency(m.networkList.Index(), m.frequencyList.Index()) @@ -344,7 +344,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { return nil } case FocusLeftSidebar: - if !m.HasPopup() && m.networkList.Index() != networklist.PeersIndex { + if !m.HasPopup() && m.networkList.Index() != networklist.SignalsIndex { networkId := state.NetworkId(m.networkList.Index()) if networkId == nil { return nil @@ -366,9 +366,9 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { m.networkJoinPopup = &popup return nil } - if m.focus == FocusLeftSidebar && m.networkList.Index() == networklist.PeersIndex { - popup := peeradd.New() - m.peerAddPopup = &popup + if m.focus == FocusLeftSidebar && m.networkList.Index() == networklist.SignalsIndex { + popup := signaladd.New() + m.signalAddPopup = &popup return nil } } @@ -376,7 +376,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { case "i": index := m.networkList.Index() networkFocus := m.focus == FocusNetworkList - if !m.HasPopup() && networkFocus && index != networklist.PeersIndex { + if !m.HasPopup() && networkFocus && index != networklist.SignalsIndex { networkId := state.NetworkId(index) if networkId != nil { _ = clipboard.WriteAll(networkId.String()) @@ -388,7 +388,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { index := m.networkList.Index() networkFocus := m.focus == FocusNetworkList frequencyFocus := m.focus == FocusLeftSidebar - if !m.HasPopup() && networkFocus && index != networklist.PeersIndex { + if !m.HasPopup() && networkFocus && index != networklist.SignalsIndex { networkId := state.NetworkId(index) if networkId == nil { return nil @@ -429,8 +429,8 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { case FocusNetworkList: m.helpPopup = NewHelpPopup(HelpNetworkList) case FocusLeftSidebar: - if m.networkList.Index() == networklist.PeersIndex { - m.helpPopup = NewHelpPopup(HelpPeerList) + if m.networkList.Index() == networklist.SignalsIndex { + m.helpPopup = NewHelpPopup(HelpSignalList) } else { m.helpPopup = NewHelpPopup(HelpFrequencyList) } @@ -461,7 +461,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { m.networkJoinPopup = nil m.banReasonPopup = nil m.banViewPopup = nil - m.peerAddPopup = nil + m.signalAddPopup = nil } case "enter": @@ -511,11 +511,11 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { return cmd } else if m.banViewPopup != nil { m.banViewPopup = nil - } else if m.peerAddPopup != nil { - cmd, i := m.peerAddPopup.Select() + } else if m.signalAddPopup != nil { + cmd, i := m.signalAddPopup.Select() if i != -1 { - m.peerAddPopup = nil - m.peerList.SetIndex(i) + m.signalAddPopup = nil + m.signalList.SetIndex(i) } return cmd } @@ -547,11 +547,11 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { m.networkList, cmd = m.networkList.Update(msg) cmds = append(cmds, cmd) - if m.networkList.Index() == networklist.PeersIndex { - m.peerList, cmd = m.peerList.Update(msg) + if m.networkList.Index() == networklist.SignalsIndex { + m.signalList, cmd = m.signalList.Update(msg) cmds = append(cmds, cmd) - cmd = m.chat.SetReceiver(m.peerList.Index()) + cmd = m.chat.SetReceiver(m.signalList.Index()) cmds = append(cmds, cmd) } else { m.frequencyList.SetNetworkIndex(m.networkList.Index()) @@ -589,7 +589,7 @@ func (m *Model) move(direction int) { switch m.focus { case FocusNetworkList: - m.peerList.Blur() + m.signalList.Blur() m.frequencyList.Blur() m.memberList.Blur() m.chat.Blur() @@ -598,21 +598,21 @@ func (m *Model) move(direction int) { m.networkList.Blur() m.memberList.Blur() m.chat.Blur() - if m.networkList.Index() == networklist.PeersIndex { + if m.networkList.Index() == networklist.SignalsIndex { m.frequencyList.Blur() - m.peerList.Focus() + m.signalList.Focus() } else { - m.peerList.Blur() + m.signalList.Blur() m.frequencyList.Focus() } case FocusChat: - m.peerList.Blur() + m.signalList.Blur() m.networkList.Blur() m.memberList.Blur() m.frequencyList.Blur() m.chat.Focus() case FocusRightSidebar: - m.peerList.Blur() + m.signalList.Blur() m.networkList.Blur() m.frequencyList.Blur() m.chat.Blur() @@ -659,9 +659,9 @@ func (m *Model) updatePopups(msg tea.Msg) tea.Cmd { popup, cmd := m.banViewPopup.Update(msg) m.banViewPopup = &popup return cmd - } else if m.peerAddPopup != nil { - popup, cmd := m.peerAddPopup.Update(msg) - m.peerAddPopup = &popup + } else if m.signalAddPopup != nil { + popup, cmd := m.signalAddPopup.Update(msg) + m.signalAddPopup = &popup return cmd } return nil @@ -677,7 +677,7 @@ func (m *Model) HasPopup() bool { m.networkJoinPopup != nil || m.banReasonPopup != nil || m.banViewPopup != nil || - m.peerAddPopup != nil + m.signalAddPopup != nil } func calculateNotifications() { diff --git a/internal/client/ui/core/helppopup.go b/internal/client/ui/core/helppopup.go index b401373..42b73fb 100644 --- a/internal/client/ui/core/helppopup.go +++ b/internal/client/ui/core/helppopup.go @@ -34,7 +34,7 @@ var ( const ( HelpNetworkList = iota - HelpPeerList + HelpSignalList HelpFrequencyList HelpChat HelpMemberList @@ -76,8 +76,8 @@ func (m HelpPopup) View() string { switch m.help { case HelpNetworkList: keymapLists = m.HelpNetworkList() - case HelpPeerList: - keymapLists = m.HelpPeerList() + case HelpSignalList: + keymapLists = m.HelpSignalList() case HelpFrequencyList: keymapLists = m.HelpFrequencyList() case HelpChat: @@ -132,7 +132,7 @@ func (m HelpPopup) Title() string { switch m.help { case HelpNetworkList: return "Network" - case HelpPeerList: + case HelpSignalList: return "Signals" case HelpFrequencyList: return "Frequency" @@ -199,7 +199,7 @@ func (m HelpPopup) HelpFrequencyList() [][]Keymap { }} } -func (m HelpPopup) HelpPeerList() [][]Keymap { +func (m HelpPopup) HelpSignalList() [][]Keymap { return [][]Keymap{{ {"k", "Move up a user"}, {"j", "Move down a user"}, diff --git a/internal/client/ui/core/networklist/networklist.go b/internal/client/ui/core/networklist/networklist.go index 52c2240..a7713b5 100644 --- a/internal/client/ui/core/networklist/networklist.go +++ b/internal/client/ui/core/networklist/networklist.go @@ -16,20 +16,20 @@ var ( sepStyle = lipgloss.NewStyle().Width(0).BorderBackground(colors.BackgroundDimmer). Border(lipgloss.ThickBorder(), false, true, false, false) - selectedIndicator = "šŸ­€\nā–Œ\nšŸ­›" - notification = " \nā——\n " - peersIcon = ui.IconStyle("ī· ", colors.Turquoise, colors.DarkerCyan, colors.BackgroundDimmer) - peersButton = peersIcon.Background(colors.BackgroundDimmer).Padding(0, 1, 1).String() - peersButtonSelected = lipgloss.JoinHorizontal( + selectedIndicator = "šŸ­€\nā–Œ\nšŸ­›" + notification = " \nā——\n " + signalsIcon = ui.IconStyle("ī· ", colors.Turquoise, colors.DarkerCyan, colors.BackgroundDimmer) + signalsButton = signalsIcon.Background(colors.BackgroundDimmer).Padding(0, 1, 1).String() + signalsButtonSelected = lipgloss.JoinHorizontal( ui.Center, selectedIndicator, - peersIcon.Background(colors.BackgroundDimmer).Padding(0, 1, 1, 0).String(), + signalsIcon.Background(colors.BackgroundDimmer).Padding(0, 1, 1, 0).String(), ) backgroundStyle = lipgloss.NewStyle().Background(colors.BackgroundDimmer) ) -const PeersIndex = -1 +const SignalsIndex = -1 type Model struct { base int @@ -41,7 +41,7 @@ type Model struct { func New() Model { return Model{ base: 0, - index: PeersIndex, + index: SignalsIndex, height: 1, focus: false, } @@ -54,10 +54,10 @@ func (m Model) Init() tea.Cmd { func (m Model) View() string { var builder strings.Builder builder.WriteString("\n") - if m.index == PeersIndex { - builder.WriteString(peersButtonSelected) + if m.index == SignalsIndex { + builder.WriteString(signalsButtonSelected) } else { - builder.WriteString(peersButton) + builder.WriteString(signalsButton) } builder.WriteString("\n") @@ -127,7 +127,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.height = ui.Height m.height -= 1 // Inital top margin m.height /= 4 // 4 per icon - m.height -= 1 // For peers icon + m.height -= 1 // For signals icon m.SetIndex(m.index) switch msg := msg.(type) { @@ -148,7 +148,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.SetIndex(m.index + 1) case "Q": - if state.UserID == nil || m.index == PeersIndex { + if state.UserID == nil || m.index == SignalsIndex { return m, nil } no := false @@ -163,7 +163,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { }) case "D": - if state.UserID == nil || m.index == PeersIndex { + if state.UserID == nil || m.index == SignalsIndex { return m, nil } return m, gateway.Send(&packet.DeleteNetwork{ @@ -201,8 +201,8 @@ func (m Model) Index() int { } func (m *Model) SetIndex(index int) { - m.index = min(max(index, PeersIndex), len(state.State.Networks)-1) - if m.index < m.base && m.index != PeersIndex { + m.index = min(max(index, SignalsIndex), len(state.State.Networks)-1) + if m.index < m.base && m.index != SignalsIndex { m.base = m.index } else if m.index >= m.base+m.height { m.base = 1 + m.index - m.height diff --git a/internal/client/ui/core/peeradd/peeradd.go b/internal/client/ui/core/peeradd/peeradd.go deleted file mode 100644 index 5306c12..0000000 --- a/internal/client/ui/core/peeradd/peeradd.go +++ /dev/null @@ -1,190 +0,0 @@ -package peeradd - -import ( - "errors" - "log" - "slices" - "strconv" - "strings" - - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/kyren223/eko/internal/client/gateway" - "github.com/kyren223/eko/internal/client/ui/colors" - "github.com/kyren223/eko/internal/client/ui/core/state" - "github.com/kyren223/eko/internal/client/ui/field" - "github.com/kyren223/eko/internal/client/ui/layouts/flex" - "github.com/kyren223/eko/internal/packet" - "github.com/kyren223/eko/pkg/assert" - "github.com/kyren223/eko/pkg/snowflake" -) - -var ( - width = 48 - - style = lipgloss.NewStyle(). - Border(lipgloss.ThickBorder()). - Padding(1, 4). - Align(lipgloss.Center, lipgloss.Center) - - headerStyle = lipgloss.NewStyle().Foreground(colors.Turquoise) - - fieldBlurredStyle = lipgloss.NewStyle(). - PaddingLeft(1). - Border(lipgloss.RoundedBorder()). - BorderForeground(colors.DarkCyan) - fieldFocusedStyle = fieldBlurredStyle. - BorderForeground(colors.Focus). - Border(lipgloss.ThickBorder()) - - blurredCreate = lipgloss.NewStyle(). - Background(colors.Gray).Padding(0, 1).Render("Add user signal") - focusedCreate = lipgloss.NewStyle(). - Background(colors.Blue).Padding(0, 1).Render("Add user signal") -) - -const ( - MaxIconLength = 2 - MaxHexDigits = 6 -) - -const ( - NameField = iota - CreateField - FieldCount -) - -type Model struct { - name field.Model - create string - - selected int - nameWidth int -} - -func New() Model { - name := field.New(width) - name.Header = "User ID" - name.HeaderStyle = headerStyle - name.FocusedStyle = fieldFocusedStyle - name.BlurredStyle = fieldBlurredStyle - name.ErrorStyle = lipgloss.NewStyle().Foreground(colors.Error) - name.Input.CharLimit = width - name.Focus() - name.Input.Validate = func(s string) error { - if strings.TrimSpace(s) == "" { - return errors.New("cannot be empty") - } - if _, err := strconv.ParseInt(s, 10, 64); err != nil { - return errors.New("invalid user id") - } - - return nil - } - - nameWidth := lipgloss.Width(name.View()) - - return Model{ - name: name, - create: blurredCreate, - nameWidth: nameWidth, - } -} - -func (m Model) Init() tea.Cmd { - return nil -} - -func (m Model) View() string { - name := m.name.View() - create := lipgloss.NewStyle(). - Width(m.nameWidth).Align(lipgloss.Center). - Render(m.create) - - content := flex.NewVertical(name, create).WithGap(1).View() - return style.Render(content) -} - -func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - key := msg.Type - switch key { - case tea.KeyTab: - return m, m.cycle(1) - case tea.KeyShiftTab: - return m, m.cycle(-1) - - default: - var cmd tea.Cmd - switch m.selected { - case NameField: - m.name, cmd = m.name.Update(msg) - } - return m, cmd - } - } - - return m, nil -} - -func (m *Model) cycle(step int) tea.Cmd { - m.selected += step - if m.selected < 0 { - m.selected = FieldCount - 1 - } else { - m.selected %= FieldCount - } - return m.updateFocus() -} - -func (m *Model) updateFocus() tea.Cmd { - m.name.Blur() - m.create = blurredCreate - switch m.selected { - case NameField: - return m.name.Focus() - case CreateField: - m.create = focusedCreate - return nil - default: - assert.Never("missing switch statement field in update focus", "selected", m.selected) - return nil - } -} - -func (m *Model) Select() (tea.Cmd, int) { - if m.selected != CreateField { - return nil, -1 - } - - m.name.Input.Err = m.name.Input.Validate(m.name.Input.Value()) - if m.name.Input.Err != nil { - return nil, -1 - } - name := m.name.Input.Value() - num, err := strconv.ParseInt(name, 10, 64) - id := snowflake.ID(num) - assert.NoError(err, "input is already validated to be valid") - - index := -1 - for i, peer := range state.Data.Peers { - if peer == id { - index = i - break - } - } - - if index != -1 { - return nil, index - } - - state.Data.Peers = slices.Insert(state.Data.Peers, 0, id) - log.Println("Peers:", state.Data.Peers) - - data := state.JsonUserData() - return gateway.Send(&packet.SetUserData{ - Data: &data, - User: nil, - }), 0 -} diff --git a/internal/client/ui/core/peerlist/peerlist.go b/internal/client/ui/core/peerlist/peerlist.go deleted file mode 100644 index 2a521c7..0000000 --- a/internal/client/ui/core/peerlist/peerlist.go +++ /dev/null @@ -1,239 +0,0 @@ -package peerlist - -import ( - "bytes" - "slices" - "strconv" - "strings" - - "github.com/atotto/clipboard" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/kyren223/eko/internal/client/gateway" - "github.com/kyren223/eko/internal/client/ui" - "github.com/kyren223/eko/internal/client/ui/colors" - "github.com/kyren223/eko/internal/client/ui/core/state" - "github.com/kyren223/eko/internal/packet" - "github.com/kyren223/eko/pkg/snowflake" -) - -var ( - sepStyle = lipgloss.NewStyle().Width(0).BorderBackground(colors.BackgroundDim). - Border(lipgloss.ThickBorder(), false, true, false, false) - - nameStyle = lipgloss.NewStyle(). - Padding(1).Align(lipgloss.Center). - Border(lipgloss.ThickBorder(), false, false, true) - userIdStyle = lipgloss.NewStyle(). - MarginBottom(1).Padding(1, 2).Align(lipgloss.Center). - Border(lipgloss.ThickBorder(), false, false, true) - - margin = 2 - padding = 1 - peerStyle = lipgloss.NewStyle(). - Margin(0, margin).Padding(0, padding).Align(lipgloss.Left) - - symbolWidth = 2 - widthWithoutUser = ((margin + padding) * 2) + symbolWidth - - ellipsis = "…" - - BackgroundStyle = lipgloss.NewStyle().Background(colors.BackgroundDim) -) - -type Model struct { - base int - index int - focus bool - width int - height int -} - -func New() Model { - return Model{ - base: 0, - index: -1, - focus: false, - width: -1, - height: 1, - } -} - -func (m Model) Init() tea.Cmd { - return nil -} - -func (m Model) View() string { - peerStyle := peerStyle.Width(m.width - (margin * 2)) - backgroundStyle := BackgroundStyle.Width(m.width) - maxUserWidth := m.width - widthWithoutUser - - var builder strings.Builder - - builder.WriteString(m.renderHeader()) - - builder.WriteString("\n") - peers := m.Peers() - upper := min(m.base+m.height, len(peers)) - peers = peers[m.base:upper] - for i, peer := range peers { - peerStyle := peerStyle - - user := state.State.Users[peer] - trustedPublicKey, isTrusted := state.State.Trusteds[user.ID] - keysMatch := bytes.Equal(trustedPublicKey, user.PublicKey) - - var userStyle lipgloss.Style - if isTrusted && keysMatch { - userStyle = ui.TrustedUserStyle.Background(colors.BackgroundDim) - } else { - userStyle = ui.UserStyle.Background(colors.BackgroundDim) - } - - if m.index == m.base+i { - peerStyle = peerStyle.Background(colors.BackgroundHighlight) - userStyle = userStyle.Background(colors.BackgroundHighlight) - } - - username := user.Name - username = userStyle.Render(username) - if isTrusted && !keysMatch { - username = ui.UntrustedSymbol + username - } - - if lipgloss.Width(username) <= maxUserWidth { - username = lipgloss.NewStyle(). - MaxWidth(maxUserWidth). - Render(username) - } else { - ellipsisStyle := lipgloss.NewStyle(). - Background(userStyle.GetBackground()). - Foreground(userStyle.GetForeground()) - username = lipgloss.NewStyle(). - MaxWidth(maxUserWidth-1). - Render(username) + ellipsisStyle.Render(ellipsis) - } - - builder.WriteString(backgroundStyle.Render(peerStyle.Render(username))) - builder.WriteString("\n") - } - - sidebar := builder.String() - sep := sepStyle.Height(ui.Height) - if m.focus { - sep = sep.BorderForeground(colors.Focus) - } - result := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, sep.String()) - - return BackgroundStyle.Render(result) -} - -func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { - // Calculate height for frequencies - m.height = ui.Height - m.height -= lipgloss.Height(m.renderHeader()) - m.height -= 1 - m.SetIndex(m.index) - - switch msg := msg.(type) { - case tea.KeyMsg: - if !m.focus { - return m, nil - } - - key := msg.String() - switch key { - case "k": - m.SetIndex(m.index - 1) - case "j": - m.SetIndex(m.index + 1) - case "g": - m.SetIndex(0) - case "G": - m.SetIndex(len(m.Peers()) - 1) - case "ctrl+u": - m.SetIndex(m.index - m.height/2) - case "ctrl+d": - m.SetIndex(m.index + m.height/2) - - case "i": - _ = clipboard.WriteAll(strconv.FormatInt(int64(*state.UserID), 10)) - - case "c": - if m.index == -1 { - return m, nil - } - state.Data.Peers = slices.Delete(state.Data.Peers, m.index, m.index+1) - if m.index == len(state.Data.Peers) { - m.SetIndex(m.index - 1) - } - data := state.JsonUserData() - return m, gateway.Send(&packet.SetUserData{ - Data: &data, - User: nil, - }) - - case "T": - if m.index == -1 { - return m, nil - } - userId := state.Data.Peers[m.index] - - _, isTrusting := state.State.Trusteds[userId] - - return m, gateway.Send(&packet.TrustUser{ - User: userId, - Trust: !isTrusting, - }) - - } - } - - return m, nil -} - -func (m *Model) Focus() { - m.focus = true -} - -func (m *Model) Blur() { - m.focus = false -} - -func (m Model) Peers() []snowflake.ID { - return state.Data.Peers -} - -func (m *Model) Index() int { - return m.index -} - -func (m *Model) SetIndex(index int) { - m.index = min(max(index, 0), len(m.Peers())-1) - if m.index < m.base { - m.base = max(m.index, 0) - } else if m.index >= m.base+m.height { - m.base = 1 + m.index - m.height - } -} - -func (m Model) renderHeader() string { - nameStyle := nameStyle.Background(colors.DarkerCyan).Foreground(colors.Turquoise).Width(m.width) - if m.focus { - nameStyle = nameStyle.BorderForeground(colors.Focus) - } - peersName := nameStyle.Render("User Signals") - - userIdStyle := userIdStyle.Width(m.width) - if m.focus { - userIdStyle = userIdStyle.BorderForeground(colors.Focus) - } - id := "Your User ID\n" + strconv.FormatInt(int64(*state.UserID), 10) - userId := userIdStyle.Render(id) - - return lipgloss.JoinVertical(0, peersName, userId) -} - -func (m *Model) SetWidth(width int) { - m.width = width -} diff --git a/internal/client/ui/core/signaladd/signaladd.go b/internal/client/ui/core/signaladd/signaladd.go new file mode 100644 index 0000000..781b16a --- /dev/null +++ b/internal/client/ui/core/signaladd/signaladd.go @@ -0,0 +1,190 @@ +package signaladd + +import ( + "errors" + "log" + "slices" + "strconv" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/kyren223/eko/internal/client/gateway" + "github.com/kyren223/eko/internal/client/ui/colors" + "github.com/kyren223/eko/internal/client/ui/core/state" + "github.com/kyren223/eko/internal/client/ui/field" + "github.com/kyren223/eko/internal/client/ui/layouts/flex" + "github.com/kyren223/eko/internal/packet" + "github.com/kyren223/eko/pkg/assert" + "github.com/kyren223/eko/pkg/snowflake" +) + +var ( + width = 48 + + style = lipgloss.NewStyle(). + Border(lipgloss.ThickBorder()). + Padding(1, 4). + Align(lipgloss.Center, lipgloss.Center) + + headerStyle = lipgloss.NewStyle().Foreground(colors.Turquoise) + + fieldBlurredStyle = lipgloss.NewStyle(). + PaddingLeft(1). + Border(lipgloss.RoundedBorder()). + BorderForeground(colors.DarkCyan) + fieldFocusedStyle = fieldBlurredStyle. + BorderForeground(colors.Focus). + Border(lipgloss.ThickBorder()) + + blurredCreate = lipgloss.NewStyle(). + Background(colors.Gray).Padding(0, 1).Render("Add user signal") + focusedCreate = lipgloss.NewStyle(). + Background(colors.Blue).Padding(0, 1).Render("Add user signal") +) + +const ( + MaxIconLength = 2 + MaxHexDigits = 6 +) + +const ( + NameField = iota + CreateField + FieldCount +) + +type Model struct { + name field.Model + create string + + selected int + nameWidth int +} + +func New() Model { + name := field.New(width) + name.Header = "User ID" + name.HeaderStyle = headerStyle + name.FocusedStyle = fieldFocusedStyle + name.BlurredStyle = fieldBlurredStyle + name.ErrorStyle = lipgloss.NewStyle().Foreground(colors.Error) + name.Input.CharLimit = width + name.Focus() + name.Input.Validate = func(s string) error { + if strings.TrimSpace(s) == "" { + return errors.New("cannot be empty") + } + if _, err := strconv.ParseInt(s, 10, 64); err != nil { + return errors.New("invalid user id") + } + + return nil + } + + nameWidth := lipgloss.Width(name.View()) + + return Model{ + name: name, + create: blurredCreate, + nameWidth: nameWidth, + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + name := m.name.View() + create := lipgloss.NewStyle(). + Width(m.nameWidth).Align(lipgloss.Center). + Render(m.create) + + content := flex.NewVertical(name, create).WithGap(1).View() + return style.Render(content) +} + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + key := msg.Type + switch key { + case tea.KeyTab: + return m, m.cycle(1) + case tea.KeyShiftTab: + return m, m.cycle(-1) + + default: + var cmd tea.Cmd + switch m.selected { + case NameField: + m.name, cmd = m.name.Update(msg) + } + return m, cmd + } + } + + return m, nil +} + +func (m *Model) cycle(step int) tea.Cmd { + m.selected += step + if m.selected < 0 { + m.selected = FieldCount - 1 + } else { + m.selected %= FieldCount + } + return m.updateFocus() +} + +func (m *Model) updateFocus() tea.Cmd { + m.name.Blur() + m.create = blurredCreate + switch m.selected { + case NameField: + return m.name.Focus() + case CreateField: + m.create = focusedCreate + return nil + default: + assert.Never("missing switch statement field in update focus", "selected", m.selected) + return nil + } +} + +func (m *Model) Select() (tea.Cmd, int) { + if m.selected != CreateField { + return nil, -1 + } + + m.name.Input.Err = m.name.Input.Validate(m.name.Input.Value()) + if m.name.Input.Err != nil { + return nil, -1 + } + name := m.name.Input.Value() + num, err := strconv.ParseInt(name, 10, 64) + id := snowflake.ID(num) + assert.NoError(err, "input is already validated to be valid") + + index := -1 + for i, signal := range state.Data.Signals { + if signal == id { + index = i + break + } + } + + if index != -1 { + return nil, index + } + + state.Data.Signals = slices.Insert(state.Data.Signals, 0, id) + log.Println("Signals:", state.Data.Signals) + + data := state.JsonUserData() + return gateway.Send(&packet.SetUserData{ + Data: &data, + User: nil, + }), 0 +} diff --git a/internal/client/ui/core/signallist/signallist.go b/internal/client/ui/core/signallist/signallist.go new file mode 100644 index 0000000..e6fccb9 --- /dev/null +++ b/internal/client/ui/core/signallist/signallist.go @@ -0,0 +1,239 @@ +package signallist + +import ( + "bytes" + "slices" + "strconv" + "strings" + + "github.com/atotto/clipboard" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/kyren223/eko/internal/client/gateway" + "github.com/kyren223/eko/internal/client/ui" + "github.com/kyren223/eko/internal/client/ui/colors" + "github.com/kyren223/eko/internal/client/ui/core/state" + "github.com/kyren223/eko/internal/packet" + "github.com/kyren223/eko/pkg/snowflake" +) + +var ( + sepStyle = lipgloss.NewStyle().Width(0).BorderBackground(colors.BackgroundDim). + Border(lipgloss.ThickBorder(), false, true, false, false) + + nameStyle = lipgloss.NewStyle(). + Padding(1).Align(lipgloss.Center). + Border(lipgloss.ThickBorder(), false, false, true) + userIdStyle = lipgloss.NewStyle(). + MarginBottom(1).Padding(1, 2).Align(lipgloss.Center). + Border(lipgloss.ThickBorder(), false, false, true) + + margin = 2 + padding = 1 + signalStyle = lipgloss.NewStyle(). + Margin(0, margin).Padding(0, padding).Align(lipgloss.Left) + + symbolWidth = 2 + widthWithoutUser = ((margin + padding) * 2) + symbolWidth + + ellipsis = "…" + + BackgroundStyle = lipgloss.NewStyle().Background(colors.BackgroundDim) +) + +type Model struct { + base int + index int + focus bool + width int + height int +} + +func New() Model { + return Model{ + base: 0, + index: -1, + focus: false, + width: -1, + height: 1, + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + signalStyle := signalStyle.Width(m.width - (margin * 2)) + backgroundStyle := BackgroundStyle.Width(m.width) + maxUserWidth := m.width - widthWithoutUser + + var builder strings.Builder + + builder.WriteString(m.renderHeader()) + + builder.WriteString("\n") + signals := m.Signals() + upper := min(m.base+m.height, len(signals)) + signals = signals[m.base:upper] + for i, signal := range signals { + signalStyle := signalStyle + + user := state.State.Users[signal] + trustedPublicKey, isTrusted := state.State.Trusteds[user.ID] + keysMatch := bytes.Equal(trustedPublicKey, user.PublicKey) + + var userStyle lipgloss.Style + if isTrusted && keysMatch { + userStyle = ui.TrustedUserStyle.Background(colors.BackgroundDim) + } else { + userStyle = ui.UserStyle.Background(colors.BackgroundDim) + } + + if m.index == m.base+i { + signalStyle = signalStyle.Background(colors.BackgroundHighlight) + userStyle = userStyle.Background(colors.BackgroundHighlight) + } + + username := user.Name + username = userStyle.Render(username) + if isTrusted && !keysMatch { + username = ui.UntrustedSymbol + username + } + + if lipgloss.Width(username) <= maxUserWidth { + username = lipgloss.NewStyle(). + MaxWidth(maxUserWidth). + Render(username) + } else { + ellipsisStyle := lipgloss.NewStyle(). + Background(userStyle.GetBackground()). + Foreground(userStyle.GetForeground()) + username = lipgloss.NewStyle(). + MaxWidth(maxUserWidth-1). + Render(username) + ellipsisStyle.Render(ellipsis) + } + + builder.WriteString(backgroundStyle.Render(signalStyle.Render(username))) + builder.WriteString("\n") + } + + sidebar := builder.String() + sep := sepStyle.Height(ui.Height) + if m.focus { + sep = sep.BorderForeground(colors.Focus) + } + result := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, sep.String()) + + return BackgroundStyle.Render(result) +} + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + // Calculate height for frequencies + m.height = ui.Height + m.height -= lipgloss.Height(m.renderHeader()) + m.height -= 1 + m.SetIndex(m.index) + + switch msg := msg.(type) { + case tea.KeyMsg: + if !m.focus { + return m, nil + } + + key := msg.String() + switch key { + case "k": + m.SetIndex(m.index - 1) + case "j": + m.SetIndex(m.index + 1) + case "g": + m.SetIndex(0) + case "G": + m.SetIndex(len(m.Signals()) - 1) + case "ctrl+u": + m.SetIndex(m.index - m.height/2) + case "ctrl+d": + m.SetIndex(m.index + m.height/2) + + case "i": + _ = clipboard.WriteAll(strconv.FormatInt(int64(*state.UserID), 10)) + + case "c": + if m.index == -1 { + return m, nil + } + state.Data.Signals = slices.Delete(state.Data.Signals, m.index, m.index+1) + if m.index == len(state.Data.Signals) { + m.SetIndex(m.index - 1) + } + data := state.JsonUserData() + return m, gateway.Send(&packet.SetUserData{ + Data: &data, + User: nil, + }) + + case "T": + if m.index == -1 { + return m, nil + } + userId := state.Data.Signals[m.index] + + _, isTrusting := state.State.Trusteds[userId] + + return m, gateway.Send(&packet.TrustUser{ + User: userId, + Trust: !isTrusting, + }) + + } + } + + return m, nil +} + +func (m *Model) Focus() { + m.focus = true +} + +func (m *Model) Blur() { + m.focus = false +} + +func (m Model) Signals() []snowflake.ID { + return state.Data.Signals +} + +func (m *Model) Index() int { + return m.index +} + +func (m *Model) SetIndex(index int) { + m.index = min(max(index, 0), len(m.Signals())-1) + if m.index < m.base { + m.base = max(m.index, 0) + } else if m.index >= m.base+m.height { + m.base = 1 + m.index - m.height + } +} + +func (m Model) renderHeader() string { + nameStyle := nameStyle.Background(colors.DarkerCyan).Foreground(colors.Turquoise).Width(m.width) + if m.focus { + nameStyle = nameStyle.BorderForeground(colors.Focus) + } + signalsName := nameStyle.Render("User Signals") + + userIdStyle := userIdStyle.Width(m.width) + if m.focus { + userIdStyle = userIdStyle.BorderForeground(colors.Focus) + } + id := "Your User ID\n" + strconv.FormatInt(int64(*state.UserID), 10) + userId := userIdStyle.Render(id) + + return lipgloss.JoinVertical(0, signalsName, userId) +} + +func (m *Model) SetWidth(width int) { + m.width = width +} diff --git a/internal/client/ui/core/state/state.go b/internal/client/ui/core/state/state.go index 6a865d2..658c218 100644 --- a/internal/client/ui/core/state/state.go +++ b/internal/client/ui/core/state/state.go @@ -51,12 +51,12 @@ var State state = state{ type UserData struct { Networks []snowflake.ID - Peers []snowflake.ID + Signals []snowflake.ID } var Data UserData = UserData{ Networks: []snowflake.ID{}, - Peers: []snowflake.ID{}, + Signals: []snowflake.ID{}, } var UserID *snowflake.ID = nil @@ -281,7 +281,7 @@ func SendFinalData() { sources := []snowflake.ID{} - sources = append(sources, Data.Peers...) + sources = append(sources, Data.Signals...) for _, frequencies := range State.Frequencies { for _, frequency := range frequencies { sources = append(sources, frequency.ID) -- cgit v1.3.1