summaryrefslogtreecommitdiff
path: root/internal/client/ui
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2025-01-26 18:17:50 +0200
committerKyren223 <Kyren223@proton.me>2025-01-26 18:17:50 +0200
commit1d76c3f8316fe09c0ade190d94adcab02e28a1e9 (patch)
treeabbc8477baa1d49b8a801cd507f7a7dedb9e48ac /internal/client/ui
parentbcd13d0afba8fae352b87556b31b1cf5d1a6e7c3 (diff)
Added adding/removing user signals (direct messages)
Diffstat (limited to 'internal/client/ui')
-rw-r--r--internal/client/ui/core/core.go35
-rw-r--r--internal/client/ui/core/helppopup.go4
-rw-r--r--internal/client/ui/core/networkjoin/networkjoin.go2
-rw-r--r--internal/client/ui/core/peeradd/peeradd.go190
-rw-r--r--internal/client/ui/core/peerlist/peerlist.go47
5 files changed, 252 insertions, 26 deletions
diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go
index b5cd8ba..6e53fbe 100644
--- a/internal/client/ui/core/core.go
+++ b/internal/client/ui/core/core.go
@@ -25,6 +25,7 @@ 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/state"
"github.com/kyren223/eko/internal/client/ui/core/usersettings"
@@ -76,6 +77,7 @@ type Model struct {
frequencyUpdatePopup *frequencyupdate.Model
banReasonPopup *banreason.Model
banViewPopup *banview.Model
+ peerAddPopup *peeradd.Model
networkList networklist.Model
peerList peerlist.Model
frequencyList frequencylist.Model
@@ -101,6 +103,7 @@ func New(privKey ed25519.PrivateKey, name string) Model {
frequencyUpdatePopup: nil,
banReasonPopup: nil,
banViewPopup: nil,
+ peerAddPopup: nil,
networkList: networklist.New(),
peerList: peerlist.New(),
frequencyList: frequencylist.New(),
@@ -158,6 +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()
}
if popup != "" {
@@ -355,10 +360,17 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd {
}
case "a":
- if m.focus == FocusNetworkList && !m.HasPopup() {
- popup := networkjoin.New()
- m.networkJoinPopup = &popup
- return nil
+ if !m.HasPopup() {
+ if m.focus == FocusNetworkList {
+ popup := networkjoin.New()
+ m.networkJoinPopup = &popup
+ return nil
+ }
+ if m.focus == FocusLeftSidebar && m.networkList.Index() == networklist.PeersIndex {
+ popup := peeradd.New()
+ m.peerAddPopup = &popup
+ return nil
+ }
}
case "i":
@@ -449,6 +461,7 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd {
m.networkJoinPopup = nil
m.banReasonPopup = nil
m.banViewPopup = nil
+ m.peerAddPopup = nil
}
case "enter":
@@ -498,6 +511,13 @@ 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()
+ if i != -1 {
+ m.peerAddPopup = nil
+ m.peerList.SetIndex(i)
+ }
+ return cmd
}
default:
@@ -633,6 +653,10 @@ 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
+ return cmd
}
return nil
}
@@ -646,5 +670,6 @@ func (m *Model) HasPopup() bool {
m.frequencyUpdatePopup != nil ||
m.networkJoinPopup != nil ||
m.banReasonPopup != nil ||
- m.banViewPopup != nil
+ m.banViewPopup != nil ||
+ m.peerAddPopup != nil
}
diff --git a/internal/client/ui/core/helppopup.go b/internal/client/ui/core/helppopup.go
index 0e77607..ad22fba 100644
--- a/internal/client/ui/core/helppopup.go
+++ b/internal/client/ui/core/helppopup.go
@@ -208,8 +208,8 @@ func (m HelpPopup) HelpPeerList() [][]Keymap {
{"g", "Move to the top"},
{"G", "Move to the bottom"},
}, {
- {"n", "New user conversation"},
- {"c", "Close user conversation"},
+ {"a", "Add new user signal"},
+ {"c", "Close user signal"},
{"T", "Trust/untrust user"},
{"B", "Block user"},
{"U", "Unblock user"},
diff --git a/internal/client/ui/core/networkjoin/networkjoin.go b/internal/client/ui/core/networkjoin/networkjoin.go
index 1efd5e8..a0b8039 100644
--- a/internal/client/ui/core/networkjoin/networkjoin.go
+++ b/internal/client/ui/core/networkjoin/networkjoin.go
@@ -74,7 +74,7 @@ func New() Model {
return errors.New("cannot be empty")
}
if _, err := strconv.ParseInt(s, 10, 64); err != nil {
- return err
+ return errors.New("invalid invite code")
}
return nil
diff --git a/internal/client/ui/core/peeradd/peeradd.go b/internal/client/ui/core/peeradd/peeradd.go
new file mode 100644
index 0000000..a80e78f
--- /dev/null
+++ b/internal/client/ui/core/peeradd/peeradd.go
@@ -0,0 +1,190 @@
+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")
+
+ i := -1
+ for i, peer := range state.Data.Peers {
+ if peer == id {
+ i = i
+ break
+ }
+ }
+
+ if i != -1 {
+ return nil, i
+ }
+
+ 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
index b062cb2..477f791 100644
--- a/internal/client/ui/core/peerlist/peerlist.go
+++ b/internal/client/ui/core/peerlist/peerlist.go
@@ -2,13 +2,16 @@ package peerlist
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/packet"
"github.com/kyren223/eko/pkg/snowflake"
)
@@ -17,10 +20,7 @@ var (
Border(lipgloss.ThickBorder(), false, true, false, false)
nameStyle = lipgloss.NewStyle().
- Padding(1).Align(lipgloss.Center).
- Border(lipgloss.ThickBorder(), false, false, true)
- networkIdStyle = lipgloss.NewStyle().
- MarginBottom(1).Padding(1, 2).Align(lipgloss.Center).
+ MarginBottom(1).Padding(1).Align(lipgloss.Center).
Border(lipgloss.ThickBorder(), false, false, true)
margin = 2
@@ -28,13 +28,7 @@ var (
peerStyle = lipgloss.NewStyle().
Margin(0, margin).Padding(0, padding).Align(lipgloss.Left)
- symbolReadWrite = "󰖩 "
- symbolReadOnly = "󱛂 "
- symbolReadOnlyAdmin = "󰖩 "
- symbolNoAccess = "󱚿 "
- symbolNoAccessAdmin = "󱛀 "
- symbolWidth = 2
-
+ symbolWidth = 2
widthWithoutUser = ((margin + padding) * 2) + symbolWidth
ellipsis = "…"
@@ -148,12 +142,34 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
case "g":
m.SetIndex(0)
case "G":
- m.SetIndex(m.FrequenciesLength() - 1)
+ 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 "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)
+ }
+
+ 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,
+ })
+
}
}
@@ -168,11 +184,6 @@ func (m *Model) Blur() {
m.focus = false
}
-func (m Model) FrequenciesLength() int {
- // TODO:
- return len(state.State.Frequencies[0])
-}
-
func (m Model) Peers() []snowflake.ID {
return state.Data.Peers
}
@@ -182,7 +193,7 @@ func (m *Model) Index() int {
}
func (m *Model) SetIndex(index int) {
- m.index = min(max(index, 0), m.FrequenciesLength()-1)
+ 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 {