diff options
| -rw-r--r-- | internal/client/ui/core/banreason/banreason.go | 155 | ||||
| -rw-r--r-- | internal/client/ui/core/core.go | 24 | ||||
| -rw-r--r-- | internal/client/ui/core/memberlist/memberlist.go | 25 | ||||
| -rw-r--r-- | internal/client/ui/ui.go | 6 | ||||
| -rw-r--r-- | internal/packet/models.go | 1 | ||||
| -rw-r--r-- | internal/server/api/api.go | 8 |
6 files changed, 216 insertions, 3 deletions
diff --git a/internal/client/ui/core/banreason/banreason.go b/internal/client/ui/core/banreason/banreason.go new file mode 100644 index 0000000..4e62514 --- /dev/null +++ b/internal/client/ui/core/banreason/banreason.go @@ -0,0 +1,155 @@ +package banreason + +import ( + 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()) + + blurredBanStyle = lipgloss.NewStyle(). + Background(colors.Gray).Padding(0, 1) + focusedBanStyle = lipgloss.NewStyle(). + Background(colors.Blue).Padding(0, 1) +) + +const ( + BanReasonField = iota + BanField + FieldCount +) + +type Model struct { + networkId snowflake.ID + userId snowflake.ID + banReason field.Model + banStyle lipgloss.Style + + selected int + nameWidth int +} + +func New(userId, networkId snowflake.ID) Model { + banReason := field.New(width) + banReason.Header = "Ban Reason" + banReason.HeaderStyle = headerStyle + banReason.FocusedStyle = fieldFocusedStyle + banReason.BlurredStyle = fieldBlurredStyle + banReason.Input.CharLimit = packet.MaxBanReasonBytes + banReason.Focus() + nameWidth := lipgloss.Width(banReason.View()) + + return Model{ + networkId: networkId, + userId: userId, + banReason: banReason, + banStyle: blurredBanStyle, + selected: 0, + nameWidth: nameWidth, + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + name := m.banReason.View() + ban := lipgloss.NewStyle(). + Width(m.nameWidth).Align(lipgloss.Center). + Render(m.banStyle.Render("Ban", state.State.Users[m.userId].Name)) + + content := flex.NewVertical(name, ban).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 BanReasonField: + m.banReason, cmd = m.banReason.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.banReason.Blur() + m.banStyle = blurredBanStyle + switch m.selected { + case BanReasonField: + return m.banReason.Focus() + case BanField: + m.banStyle = focusedBanStyle + return nil + default: + assert.Never("missing switch statement field in update focus", "selected", m.selected) + return nil + } +} + +func (m *Model) Select() tea.Cmd { + if m.selected != BanField { + return nil + } + + banReason := m.banReason.Input.Value() + + yes := true + return gateway.Send(&packet.SetMember{ + Member: nil, + Admin: nil, + Muted: nil, + Banned: &yes, + BanReason: &banReason, + Network: m.networkId, + User: m.userId, + }) +} diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index 43695e7..df5fbde 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -14,6 +14,7 @@ import ( "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/core/banreason" "github.com/kyren223/eko/internal/client/ui/core/chat" "github.com/kyren223/eko/internal/client/ui/core/frequencycreation" "github.com/kyren223/eko/internal/client/ui/core/frequencylist" @@ -71,6 +72,7 @@ type Model struct { networkJoinPopup *networkjoin.Model frequencyCreationPopup *frequencycreation.Model frequencyUpdatePopup *frequencyupdate.Model + banReasonPopup *banreason.Model networkList networklist.Model frequencyList frequencylist.Model memberList memberlist.Model @@ -93,6 +95,7 @@ func New(privKey ed25519.PrivateKey, name string) Model { networkJoinPopup: nil, frequencyCreationPopup: nil, frequencyUpdatePopup: nil, + banReasonPopup: nil, networkList: networklist.New(), frequencyList: frequencylist.New(), memberList: memberlist.New(), @@ -140,6 +143,8 @@ func (m Model) View() string { popup = m.frequencyUpdatePopup.View() } else if m.networkJoinPopup != nil { popup = m.networkJoinPopup.View() + } else if m.banReasonPopup != nil { + popup = m.banReasonPopup.View() } if popup != "" { x := (ui.Width - lipgloss.Width(popup)) / 2 @@ -296,6 +301,10 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { case *packet.TrustInfo: state.UpdateTrusteds(msg) + case ui.BanPopupMsg: + popup := banreason.New(msg.User, msg.Network) + m.banReasonPopup = &popup + case tea.KeyMsg: switch msg.String() { case "n": @@ -407,6 +416,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { m.frequencyCreationPopup = nil m.frequencyUpdatePopup = nil m.networkJoinPopup = nil + m.banReasonPopup = nil } case "enter": @@ -448,12 +458,17 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { m.networkJoinPopup = nil } return cmd + } else if m.banReasonPopup != nil { + cmd := m.banReasonPopup.Select() + if cmd != nil { + m.banReasonPopup = nil + } + return cmd } default: isChatLocked := m.focus == FocusChat && m.chat.Locked() if !m.HasPopup() && !isChatLocked { - left := msg.String() == "H" right := msg.String() == "L" direction := 0 @@ -562,6 +577,10 @@ func (m *Model) updatePopups(msg tea.Msg) tea.Cmd { popup, cmd := m.networkJoinPopup.Update(msg) m.networkJoinPopup = &popup return cmd + } else if m.banReasonPopup != nil { + popup, cmd := m.banReasonPopup.Update(msg) + m.banReasonPopup = &popup + return cmd } return nil } @@ -573,5 +592,6 @@ func (m *Model) HasPopup() bool { m.networkUpdatePopup != nil || m.frequencyCreationPopup != nil || m.frequencyUpdatePopup != nil || - m.networkJoinPopup != nil + m.networkJoinPopup != nil || + m.banReasonPopup != nil } diff --git a/internal/client/ui/core/memberlist/memberlist.go b/internal/client/ui/core/memberlist/memberlist.go index f42a4ac..beb6807 100644 --- a/internal/client/ui/core/memberlist/memberlist.go +++ b/internal/client/ui/core/memberlist/memberlist.go @@ -282,6 +282,31 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { }) case "B": // TODO: ban + networkId := state.NetworkId(m.networkIndex) + network := state.State.Networks[*networkId] + member := m.Members()[m.index] + + if !m.MembersMap()[*state.UserID].IsAdmin { + return m, nil + } + + if member.UserID == *state.UserID { + return m, nil + } + + if member.IsAdmin && network.OwnerID != *state.UserID { + return m, nil + } + + cmd := func() tea.Msg { + return ui.BanPopupMsg{ + Network: *state.NetworkId(m.networkIndex), + User: member.UserID, + } + } + return m, cmd + case "b": + // TODO: switch to ban list // Owner case "D": diff --git a/internal/client/ui/ui.go b/internal/client/ui/ui.go index d199fa1..746fdd5 100644 --- a/internal/client/ui/ui.go +++ b/internal/client/ui/ui.go @@ -14,6 +14,7 @@ import ( "github.com/kyren223/eko/internal/client/ui/colors" "github.com/kyren223/eko/pkg/assert" + "github.com/kyren223/eko/pkg/snowflake" ) const ( @@ -51,6 +52,11 @@ func Transition(model tea.Model) tea.Cmd { type QuitMsg struct{} +type BanPopupMsg struct { + Network snowflake.ID + User snowflake.ID +} + func AddBorderHeader(header string, headerOffset int, style lipgloss.Style, render string) string { b := style.GetBorderStyle() body := style.UnsetBorderTop().Render(render) diff --git a/internal/packet/models.go b/internal/packet/models.go index adf875d..a61c61d 100644 --- a/internal/packet/models.go +++ b/internal/packet/models.go @@ -10,6 +10,7 @@ const ( MaxMessageBytes = 2000 MaxUsernameBytes = 32 MaxUserDescriptionBytes = 200 + MaxBanReasonBytes = 64 ) const ( diff --git a/internal/server/api/api.go b/internal/server/api/api.go index 1c18ea9..007cce5 100644 --- a/internal/server/api/api.go +++ b/internal/server/api/api.go @@ -487,6 +487,12 @@ func DeleteNetwork(ctx context.Context, sess *session.Session, request *packet.D } func SetMember(ctx context.Context, sess *session.Session, request *packet.SetMember) packet.Payload { + if request.BanReason != nil && len(*request.BanReason) > packet.MaxBanReasonBytes { + return &packet.Error{Error: fmt.Sprintf( + "Ban reason may not exceed %v bytes", packet.MaxBanReasonBytes, + )} + } + queries := data.New(db) network, err := queries.GetNetworkById(ctx, request.Network) @@ -584,7 +590,7 @@ func SetMember(ctx context.Context, sess *session.Session, request *packet.SetMe isMember = true } } else if request.Admin != nil { - if isSessOwner && request.User != sess.ID() { + if isSessOwner && request.User != sess.ID() && isMember { isAdmin = *request.Admin } } else if request.Muted != nil { |
