diff options
| author | Kyren223 <Kyren223@proton.me> | 2025-01-25 18:26:17 +0200 |
|---|---|---|
| committer | Kyren223 <Kyren223@proton.me> | 2025-01-25 18:26:17 +0200 |
| commit | 5c96cb4e606298c5a1ef05b5b2dcba099189d079 (patch) | |
| tree | ec82527b8eeb9ba70a0bcd1b675f855bfe55b9bc /internal/client/ui | |
| parent | acf87efce976744d61165e0b0174a1fadccfb779 (diff) | |
Added banlist on the client and fixed some server side issues with
banning
Diffstat (limited to 'internal/client/ui')
| -rw-r--r-- | internal/client/ui/core/banlist/banlist.go | 248 | ||||
| -rw-r--r-- | internal/client/ui/core/core.go | 12 | ||||
| -rw-r--r-- | internal/client/ui/core/memberlist/memberlist.go | 58 |
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 } |
