summaryrefslogtreecommitdiff
path: root/internal/client
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2025-01-25 18:26:17 +0200
committerKyren223 <Kyren223@proton.me>2025-01-25 18:26:17 +0200
commit5c96cb4e606298c5a1ef05b5b2dcba099189d079 (patch)
treeec82527b8eeb9ba70a0bcd1b675f855bfe55b9bc /internal/client
parentacf87efce976744d61165e0b0174a1fadccfb779 (diff)
Added banlist on the client and fixed some server side issues with
banning
Diffstat (limited to 'internal/client')
-rw-r--r--internal/client/ui/core/banlist/banlist.go248
-rw-r--r--internal/client/ui/core/core.go12
-rw-r--r--internal/client/ui/core/memberlist/memberlist.go58
3 files changed, 302 insertions, 16 deletions
diff --git a/internal/client/ui/core/banlist/banlist.go b/internal/client/ui/core/banlist/banlist.go
new file mode 100644
index 0000000..5712a67
--- /dev/null
+++ b/internal/client/ui/core/banlist/banlist.go
@@ -0,0 +1,248 @@
+package banlist
+
+import (
+ "bytes"
+ "slices"
+ "strings"
+
+ 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/data"
+ "github.com/kyren223/eko/internal/packet"
+ "github.com/kyren223/eko/pkg/snowflake"
+)
+
+var (
+ sepStyle = lipgloss.NewStyle().Width(0).Background(colors.BackgroundDim).
+ Border(lipgloss.ThickBorder(), false, true, false, false)
+
+ headerStyle = lipgloss.NewStyle().Background(colors.BackgroundDim).
+ Margin(0, 0, 1).Padding(1).Align(lipgloss.Center).
+ Border(lipgloss.ThickBorder(), false, false, true)
+
+ margin = 2
+ padding = 1
+ memberStyle = lipgloss.NewStyle().Background(colors.BackgroundDim).
+ Margin(0, margin).Padding(0, padding).Align(lipgloss.Left)
+
+ symbolWidth = 2
+ widthWithoutMember = ((margin + padding) * 2) + symbolWidth
+
+ ellipsis = "…"
+
+ BackgroundStyle = lipgloss.NewStyle().Background(colors.BackgroundDim)
+)
+
+type Model struct {
+ networkId snowflake.ID
+ base int
+ index int
+ width int
+ height int
+}
+
+func New(networkId snowflake.ID) Model {
+ m := Model{
+ networkId: networkId,
+ base: 0,
+ index: -1,
+ width: -1,
+ height: -1,
+ }
+
+ m.height = ui.Height
+ m.height -= lipgloss.Height(m.renderHeader())
+ m.height -= 1
+
+ return m
+}
+
+func (m Model) Init() tea.Cmd {
+ return gateway.Send(&packet.GetBannedMembers{
+ Network: m.networkId,
+ })
+}
+
+func (m Model) View() string {
+ memberStyle := memberStyle.Width(m.width - (margin * 2))
+ backgroundStyle := BackgroundStyle.Width(m.width)
+ maxMemberWidth := m.width - widthWithoutMember
+ ownerId := state.State.Networks[m.networkId].OwnerID
+
+ var builder strings.Builder
+
+ builder.WriteString(m.renderHeader())
+ builder.WriteString("\n")
+
+ members := m.Members()
+ upper := min(m.base+m.height, len(members))
+ members = members[m.base:upper]
+
+ for i, member := range members {
+ memberStyle := memberStyle
+ if m.index == m.base+i {
+ memberStyle = memberStyle.Background(colors.BackgroundHighlight)
+ }
+
+ user := state.State.Users[member.UserID]
+ trustedPublicKey, isTrusted := state.State.Trusteds[user.ID]
+ keysMatch := bytes.Equal(trustedPublicKey, user.PublicKey)
+
+ var userStyle lipgloss.Style
+ if isTrusted && keysMatch {
+ if ownerId == member.UserID {
+ userStyle = ui.TrustedOwnerMemberStyle
+ } else if member.IsAdmin {
+ userStyle = ui.TrustedAdminMemberStyle
+ } else {
+ userStyle = ui.TrustedNormalMemberStyle
+ }
+ } else {
+ if ownerId == member.UserID {
+ userStyle = ui.OwnerMemberStyle
+ } else if member.IsAdmin {
+ userStyle = ui.AdminMemberStyle
+ } else {
+ userStyle = ui.NormalMemberStyle
+ }
+ }
+ memberName := state.State.Users[member.UserID].Name
+ memberName = userStyle.Render(memberName)
+ if isTrusted && !keysMatch {
+ memberName = ui.UntrustedSymbol + memberName
+ }
+
+ if lipgloss.Width(memberName) <= maxMemberWidth {
+ memberName = lipgloss.NewStyle().
+ MaxWidth(maxMemberWidth).
+ Render(memberName)
+ } else {
+ ellipsisStyle := lipgloss.NewStyle().
+ Background(memberStyle.GetBackground()).Foreground(userStyle.GetForeground())
+ memberName = lipgloss.NewStyle().
+ MaxWidth(maxMemberWidth-1).
+ Render(memberName) + ellipsisStyle.Render(ellipsis)
+ }
+
+ builder.WriteString(backgroundStyle.Render(memberStyle.Render(memberName)))
+ builder.WriteString("\n")
+ }
+
+ sidebar := BackgroundStyle.Height(ui.Height).Render(builder.String())
+ sep := sepStyle.Height(ui.Height)
+ sep = sep.BorderForeground(colors.Focus)
+ result := lipgloss.JoinHorizontal(lipgloss.Top, sep.String(), sidebar)
+
+ return result
+}
+
+func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
+ // Calculate height for banned members
+ m.height = ui.Height
+ m.height -= lipgloss.Height(m.renderHeader())
+ m.height -= 1
+
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ 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(m.MembersLength() - 1)
+ case "ctrl+u":
+ m.SetIndex(m.index - m.height/2)
+ case "ctrl+d":
+ m.SetIndex(m.index + m.height/2)
+
+ case "p":
+ // TODO: profile
+
+ case "U":
+ member := m.Members()[m.index]
+ no := false
+ return m, gateway.Send(&packet.SetMember{
+ Member: nil,
+ Admin: nil,
+ Muted: nil,
+ Banned: &no,
+ BanReason: nil,
+ Network: m.networkId,
+ User: member.UserID,
+ })
+
+ }
+ }
+
+ return m, nil
+}
+
+func (m *Model) MembersLength() int {
+ return len(m.Members())
+}
+
+func (m *Model) Members() []data.Member {
+ ownerId := state.State.Networks[m.networkId].OwnerID
+ membersMap := state.State.Members[m.networkId]
+ members := make([]data.Member, 0, len(membersMap))
+ for _, member := range membersMap {
+ if member.IsBanned {
+ members = append(members, member)
+ }
+ }
+ slices.SortFunc(members, func(a, b data.Member) int {
+ if a.UserID == ownerId {
+ return -1
+ } else if b.UserID == ownerId {
+ return 1
+ }
+
+ if a.IsAdmin && !b.IsAdmin {
+ return -1
+ } else if !a.IsAdmin && b.IsAdmin {
+ return 1
+ }
+
+ aName, bName := state.State.Users[a.UserID].Name, state.State.Users[b.UserID].Name
+ if aName == bName {
+ return int(a.UserID.Time()) - int(b.UserID.Time())
+ } else if aName < bName {
+ return -1
+ } else {
+ return 1
+ }
+ })
+
+ return members
+}
+
+func (m *Model) Index() int {
+ return m.index
+}
+
+func (m *Model) SetIndex(index int) {
+ m.index = min(max(index, 0), m.MembersLength()-1)
+ if m.index < m.base && m.index != -1 {
+ m.base = m.index
+ } else if m.index >= m.base+m.height {
+ m.base = 1 + m.index - m.height
+ }
+}
+
+func (m Model) renderHeader() string {
+ headerStyle := headerStyle.Width(m.width)
+ headerStyle = headerStyle.BorderForeground(colors.Focus)
+ return headerStyle.Render("Ban List")
+}
+
+func (m *Model) SetWidth(width int) {
+ m.width = width
+}
diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go
index df5fbde..c0b23ff 100644
--- a/internal/client/ui/core/core.go
+++ b/internal/client/ui/core/core.go
@@ -117,10 +117,10 @@ func (m Model) View() string {
}
networkList := m.networkList.View()
- frequencyList := m.frequencyList.View()
+ leftSidebar := m.frequencyList.View()
chat := m.chat.View()
- memberList := m.memberList.View()
- result := lipgloss.JoinHorizontal(lipgloss.Top, networkList, frequencyList, chat, memberList)
+ rightSidebar := m.memberList.View()
+ result := lipgloss.JoinHorizontal(lipgloss.Top, networkList, leftSidebar, chat, rightSidebar)
result = lipgloss.Place(
ui.Width, ui.Height,
@@ -228,9 +228,9 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd {
sidebarWidth = max(sidebarWidth, MinSidebarWidth)
chatWidth := totalWidth - (2 * (sidebarWidth + 1))
- log.Println("Widths:", ui.Width)
- log.Println("sidebarWidth:", sidebarWidth)
- log.Println("chatWidth:", chatWidth)
+ // log.Println("Widths:", ui.Width)
+ // log.Println("sidebarWidth:", sidebarWidth)
+ // log.Println("chatWidth:", chatWidth)
m.frequencyList.SetWidth(sidebarWidth)
m.memberList.SetWidth(sidebarWidth)
diff --git a/internal/client/ui/core/memberlist/memberlist.go b/internal/client/ui/core/memberlist/memberlist.go
index beb6807..0e59ab0 100644
--- a/internal/client/ui/core/memberlist/memberlist.go
+++ b/internal/client/ui/core/memberlist/memberlist.go
@@ -10,6 +10,7 @@ import (
"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/banlist"
"github.com/kyren223/eko/internal/client/ui/core/state"
"github.com/kyren223/eko/internal/data"
"github.com/kyren223/eko/internal/packet"
@@ -45,6 +46,8 @@ type Model struct {
focus bool
width int
height int
+
+ banlist *banlist.Model
}
func New() Model {
@@ -56,6 +59,7 @@ func New() Model {
focus: false,
width: -1,
height: -1,
+ banlist: nil,
}
}
@@ -69,6 +73,10 @@ func (m Model) View() string {
return ""
}
+ if m.banlist != nil {
+ return m.banlist.View()
+ }
+
memberStyle := memberStyle.Width(m.width - (margin * 2))
backgroundStyle := BackgroundStyle.Width(m.width)
maxMemberWidth := m.width - widthWithoutMember
@@ -144,13 +152,35 @@ func (m Model) View() string {
}
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
- if state.NetworkId(m.networkIndex) != nil {
- // Calculate height for frequencies
- m.height = ui.Height
- m.height -= lipgloss.Height(m.renderHeader())
- m.height -= 1
+ if state.NetworkId(m.networkIndex) == nil {
+ return m, nil
+ }
+
+ if key, ok := msg.(tea.KeyMsg); ok {
+ if key.String() == "b" {
+ if m.banlist == nil {
+ banlist := banlist.New(m.Network().ID)
+ m.banlist = &banlist
+ m.banlist.SetWidth(m.width)
+ return m, m.banlist.Init()
+ } else {
+ m.banlist = nil
+ return m, nil
+ }
+ }
}
+ if m.banlist != nil {
+ banlist, cmd := m.banlist.Update(msg)
+ m.banlist = &banlist
+ return m, cmd
+ }
+
+ // Calculate height for members
+ m.height = ui.Height
+ m.height -= lipgloss.Height(m.renderHeader())
+ m.height -= 1
+
switch msg := msg.(type) {
case tea.KeyMsg:
if !m.focus {
@@ -281,7 +311,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
User: member.UserID,
})
case "B":
- // TODO: ban
networkId := state.NetworkId(m.networkIndex)
network := state.State.Networks[*networkId]
member := m.Members()[m.index]
@@ -305,8 +334,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
}
}
return m, cmd
- case "b":
- // TODO: switch to ban list
// Owner
case "D":
@@ -375,6 +402,8 @@ func (m *Model) Blur() {
m.focus = false
m.index = -1
m.base = 0
+
+ m.banlist = nil
}
func (m *Model) SetNetworkAndFrequency(networkIndex, frequencyIndex int) {
@@ -408,7 +437,7 @@ func (m *Model) MembersLength() int {
if networkId == nil {
return 0
}
- return len(state.State.Members[*networkId])
+ return len(m.Members())
}
func (m *Model) Network() *data.Network {
@@ -434,7 +463,9 @@ func (m *Model) Members() []data.Member {
membersMap := m.MembersMap()
members := make([]data.Member, 0, len(membersMap))
for _, member := range membersMap {
- members = append(members, member)
+ if member.IsMember {
+ members = append(members, member)
+ }
}
slices.SortFunc(members, func(a, b data.Member) int {
if a.UserID == ownerId {
@@ -501,4 +532,11 @@ func (m Model) renderHeader() string {
func (m *Model) SetWidth(width int) {
m.width = width
+ if m.banlist != nil {
+ m.banlist.SetWidth(width)
+ }
+}
+
+func (m *Model) IsBanList() bool {
+ return m.banlist != nil
}