diff --git a/internal/client/ui/core/chat/chat.go b/internal/client/ui/core/chat/chat.go index 8798977..cbc511e 100644 --- a/internal/client/ui/core/chat/chat.go +++ b/internal/client/ui/core/chat/chat.go @@ -261,7 +261,7 @@ func (m *Model) ResetBeforeSwitch() { network := networks[m.networkIndex] frequencyId := network.Frequencies[m.frequencyIndex].ID log.Println("Saving", frequencyId) - state.State.FrequencyState[frequencyId] = state.Frequency{ + state.State.FrequencyState[frequencyId] = state.FrequencyState{ IncompleteMessage: m.vi.String(), Offset: m.offset, MaxHeight: m.maxMessagesHeight, diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go index 1e2bc6a..5c45f05 100644 --- a/internal/client/ui/core/core.go +++ b/internal/client/ui/core/core.go @@ -3,15 +3,12 @@ package core import ( "crypto/ed25519" "fmt" - "log" - "slices" "time" "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/timer" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/google/btree" "github.com/kyren223/eko/internal/client/gateway" "github.com/kyren223/eko/internal/client/ui" "github.com/kyren223/eko/internal/client/ui/core/chat" @@ -22,7 +19,6 @@ import ( "github.com/kyren223/eko/internal/client/ui/core/networklist" "github.com/kyren223/eko/internal/client/ui/core/state" "github.com/kyren223/eko/internal/client/ui/loadscreen" - "github.com/kyren223/eko/internal/data" "github.com/kyren223/eko/internal/packet" "github.com/kyren223/eko/pkg/assert" "github.com/kyren223/eko/pkg/snowflake" @@ -186,124 +182,16 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { } case *packet.NetworksInfo: - if msg.Set { - state.State.Networks = msg.Networks - } else { - networks := state.State.Networks - - for _, newNetwork := range msg.Networks { - add := true - for i, existingNetwork := range networks { - if existingNetwork.ID == newNetwork.ID { - add = false - if newNetwork.Position == -1 { - newNetwork.Position = existingNetwork.Position - } - networks[i] = newNetwork - break - } - } - if add { - networks = append(networks, newNetwork) - } - } - - networks = slices.DeleteFunc(networks, func(network packet.FullNetwork) bool { - return slices.Contains(msg.RemovedNetworks, network.ID) - }) - slices.SortFunc(networks, func(a, b packet.FullNetwork) int { - return a.Position - b.Position - }) - log.Println(networks) - state.State.Networks = networks - } + state.UpdateNetworks(msg) case *packet.MembersInfo: - var network *packet.FullNetwork - for i, fullNetwork := range state.State.Networks { - if fullNetwork.ID == msg.Network { - network = &state.State.Networks[i] - } - } - - members := network.Members - for _, updatedMember := range msg.Members { - add := true - for i, existingMember := range members { - if existingMember.User.ID == updatedMember.User.ID { - add = false - members[i] = updatedMember - break - } - } - if add { - members = append(members, updatedMember) - } - } - - members = slices.DeleteFunc(members, func(member data.GetNetworkMembersRow) bool { - return slices.Contains(msg.RemovedMembers, member.User.ID) - }) - log.Println(members) - network.Members = members + state.UpdateMembers(msg) case *packet.FrequenciesInfo: - var network *packet.FullNetwork - for i, fullNetwork := range state.State.Networks { - if fullNetwork.ID == msg.Network { - network = &state.State.Networks[i] - } - } - - frequencies := network.Frequencies - for _, newFrequency := range msg.Frequencies { - add := true - for i, existingFrequency := range frequencies { - if existingFrequency.ID == newFrequency.ID { - add = false - if newFrequency.Position == -1 { - newFrequency.Position = existingFrequency.Position - } - frequencies[i] = newFrequency - break - } - } - if add { - frequencies = append(frequencies, newFrequency) - } - } - - frequencies = slices.DeleteFunc(frequencies, func(frequency data.Frequency) bool { - return slices.Contains(msg.RemovedFrequencies, frequency.ID) - }) - slices.SortFunc(frequencies, func(a, b data.Frequency) int { - return int(a.Position - b.Position) - }) - log.Println(frequencies) - network.Frequencies = frequencies + state.UpdateFrequencies(msg) case *packet.MessagesInfo: - for _, id := range msg.RemovedMessages { - for _, btree := range state.State.Messages { - btree.Delete(data.Message{ID: id}) - } - } - - for _, message := range msg.Messages { - msgSource := message.FrequencyID - if msgSource == nil { - msgSource = message.ReceiverID - } - bt := state.State.Messages[*msgSource] - if bt == nil { - bt = btree.NewG(2, func(a, b data.Message) bool { - return a.ID < b.ID - }) - state.State.Messages[*msgSource] = bt - } - bt.ReplaceOrInsert(message) - - } + state.UpdateMessages(msg) case tea.KeyMsg: switch msg.String() { @@ -317,9 +205,11 @@ func (m *Model) updateConnected(msg tea.Msg) tea.Cmd { case FocusFrequencyList: if m.frequencyCreationPopup == nil { index := m.networkList.Index() - network := state.State.Networks[index] - popup := frequencycreation.New(network.ID) - m.frequencyCreationPopup = &popup + networkId := state.NetworkId(index) + if networkId != nil { + popup := frequencycreation.New(*networkId) + m.frequencyCreationPopup = &popup + } } } diff --git a/internal/client/ui/core/frequencylist/frequencylist.go b/internal/client/ui/core/frequencylist/frequencylist.go index 80d350b..8cef2be 100644 --- a/internal/client/ui/core/frequencylist/frequencylist.go +++ b/internal/client/ui/core/frequencylist/frequencylist.go @@ -9,6 +9,7 @@ import ( "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" ) @@ -47,7 +48,7 @@ func (m Model) Init() tea.Cmd { } func (m Model) View() string { - if m.Network() == nil { + if state.NetworkId(m.networkIndex) == nil { return "" } @@ -62,7 +63,7 @@ func (m Model) View() string { builder.WriteString(nameStyle.Render(m.Network().Name)) builder.WriteString("\n") - for i, frequency := range m.Network().Frequencies { + for i, frequency := range m.Frequencies() { frequencyStyle := frequencyStyle.Foreground(lipgloss.Color(frequency.HexColor)) if m.index == i { frequencyStyle = frequencyStyle.Background(colors.BackgroundHighlight) @@ -120,13 +121,13 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.index = min(m.FrequenciesLength()-1, m.index+1) case "x": - frequenciesCount := len(m.Network().Frequencies) + frequenciesCount := len(m.Frequencies()) if frequenciesCount == 1 { // Don't delete the last frequency! return m, nil } // TODO: consider adding a confirmation popup - frequencyId := m.Network().Frequencies[m.index].ID + frequencyId := m.Frequencies()[m.index].ID if m.index == frequenciesCount-1 { m.index-- } @@ -154,15 +155,15 @@ func (m Model) Swap(dir int) (Model, tea.Cmd) { Pos1: m.index, Pos2: m.index + dir, }) - tmp := m.Network().Frequencies[m.index] - m.Network().Frequencies[m.index] = m.Network().Frequencies[m.index+dir] - m.Network().Frequencies[m.index+dir] = tmp + tmp := m.Frequencies()[m.index] + m.Frequencies()[m.index] = m.Frequencies()[m.index+dir] + m.Frequencies()[m.index+dir] = tmp m.index += dir m.history = append(m.history, func(m *Model) { m.index -= dir - tmp := m.Network().Frequencies[m.index] - m.Network().Frequencies[m.index] = m.Network().Frequencies[m.index+dir] - m.Network().Frequencies[m.index+dir] = tmp + tmp := m.Frequencies()[m.index] + m.Frequencies()[m.index] = m.Frequencies()[m.index+dir] + m.Frequencies()[m.index+dir] = tmp }) return m, cmd } @@ -172,11 +173,12 @@ func (m *Model) SetNetworkIndex(networkIndex int) { return } - if 0 <= m.networkIndex && m.networkIndex < len(state.State.Networks) { - network := state.State.Networks[m.networkIndex] - if 0 <= m.index && m.index < len(network.Frequencies) { - frequencyId := network.Frequencies[m.index].ID - state.State.LastFrequency[network.ID] = frequencyId + networkId := state.NetworkId(m.networkIndex) + if networkId != nil { + frequencies := state.State.Frequencies[*networkId] + if 0 <= m.index && m.index < len(frequencies) { + frequencyId := frequencies[m.index].ID + state.State.LastFrequency[*networkId] = frequencyId } } @@ -190,26 +192,46 @@ func (m *Model) SetNetworkIndex(networkIndex int) { m.index = 0 // Try restoring last ID - network := state.State.Networks[m.networkIndex] - if id, ok := state.State.LastFrequency[network.ID]; ok { - for i, frequency := range network.Frequencies { - if frequency.ID == id { - m.index = i - break - } + networkId = state.NetworkId(m.networkIndex) + if networkId == nil { + return + } + id, ok := state.State.LastFrequency[*networkId] + if !ok { + return + } + frequencies := state.State.Frequencies[*networkId] + for i, frequency := range frequencies { + if frequency.ID == id { + m.index = i + break } } } func (m Model) FrequenciesLength() int { - return len(m.Network().Frequencies) + networkId := state.NetworkId(m.networkIndex) + if networkId == nil { + return 0 + } + return len(state.State.Frequencies[*networkId]) } -func (m Model) Network() *packet.FullNetwork { - if m.networkIndex < 0 || m.networkIndex > len(state.State.Networks)-1 { +func (m Model) Network() *data.Network { + networkId := state.NetworkId(m.networkIndex) + if networkId == nil { return nil } - return &state.State.Networks[m.networkIndex] + network := state.State.Networks[*networkId] + return &network +} + +func (m Model) Frequencies() []data.Frequency { + networkId := state.NetworkId(m.networkIndex) + if networkId == nil { + return nil + } + return state.State.Frequencies[*networkId] } func (m *Model) Index() int { diff --git a/internal/client/ui/core/networklist/networklist.go b/internal/client/ui/core/networklist/networklist.go index 23350a3..597a391 100644 --- a/internal/client/ui/core/networklist/networklist.go +++ b/internal/client/ui/core/networklist/networklist.go @@ -1,7 +1,6 @@ package networklist import ( - "slices" "strings" tea "github.com/charmbracelet/bubbletea" @@ -48,18 +47,17 @@ func IconStyle(icon string, fg, bg lipgloss.Color) lipgloss.Style { return lipgloss.NewStyle().SetString(combined) } -const TrustedIndex = -1 +const PeersIndex = -1 type Model struct { - history []func(m *Model) - index int - focus bool + index int + focus bool } func New() Model { return Model{ focus: false, - index: TrustedIndex, + index: PeersIndex, } } @@ -70,7 +68,7 @@ func (m Model) Init() tea.Cmd { func (m Model) View() string { var builder strings.Builder builder.WriteString("\n") - if m.index == TrustedIndex { + if m.index == PeersIndex { builder.WriteString(trustedUsersButtonSelected) } else { builder.WriteString(trustedUsersButton) @@ -81,7 +79,7 @@ func (m Model) View() string { lipgloss.Color(network.FgHexColor), lipgloss.Color(network.BgHexColor), ) - if m.index == i { + if *state.NetworkId(m.index) == i { builder.WriteString(lipgloss.JoinHorizontal( ui.Center, selectedIndicator, @@ -106,18 +104,6 @@ func (m Model) View() string { func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { switch msg := msg.(type) { - case *packet.SwapUserNetworks: - // Pop first by shifting to the left - copy(m.history, m.history[1:]) - m.history = m.history[:len(m.history)-1] - case *packet.Error: - if msg.PktType == packet.PacketSwapUserNetworks { - // Server failed, revert! - undo := m.history[len(m.history)-1] - m.history = m.history[:len(m.history)-1] - undo(&m) - } - case tea.KeyMsg: if !m.focus { return m, nil @@ -139,20 +125,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { m.index = min(len(state.State.Networks)-1, m.index+1) case "Q": - if state.State.UserID == nil || m.index == TrustedIndex { + if state.State.UserID == nil || m.index == PeersIndex { return m, nil } - networks := state.State.Networks - network := networks[m.index] - - // Remove network from the list so it's not visible - state.State.Networks = slices.Delete(networks, m.index, m.index+1) - - // Set back to trusted bcz that's always valid + // Set back to peers icon bcz that's always valid // Where if u were on the network u just left // it'd be an issue (or if u left all networks) - m.index = TrustedIndex + m.index = PeersIndex no := false return m, gateway.Send(&packet.SetMember{ @@ -161,28 +141,22 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { Muted: nil, Banned: nil, BanReason: nil, - Network: network.ID, + Network: *state.NetworkId(m.index), User: *state.State.UserID, }) case "D": - if state.State.UserID == nil || m.index == TrustedIndex { + if state.State.UserID == nil || m.index == PeersIndex { return m, nil } - networks := state.State.Networks - network := networks[m.index] - - // Remove network from the list so it's not visible - state.State.Networks = slices.Delete(networks, m.index, m.index+1) - - // Set back to trusted bcz that's always valid + // Set back to peers icon bcz that's always valid // Where if u were on the network u just left // it'd be an issue (or if u left all networks) - m.index = TrustedIndex + m.index = PeersIndex return m, gateway.Send(&packet.DeleteNetwork{ - Network: network.ID, + Network: *state.NetworkId(m.index), }) } @@ -199,21 +173,13 @@ func (m *Model) Blur() { } func (m Model) Swap(dir int) (Model, tea.Cmd) { - cmd := gateway.Send(&packet.SwapUserNetworks{ - Pos1: m.index, - Pos2: m.index + dir, - }) - tmp := state.State.Networks[m.index] - state.State.Networks[m.index] = state.State.Networks[m.index+dir] - state.State.Networks[m.index+dir] = tmp + tmp := state.Data.Networks[m.index] + state.Data.Networks[m.index] = state.Data.Networks[m.index+dir] + state.Data.Networks[m.index+dir] = tmp m.index += dir - m.history = append(m.history, func(m *Model) { - m.index -= dir - tmp := state.State.Networks[m.index] - state.State.Networks[m.index] = state.State.Networks[m.index+dir] - state.State.Networks[m.index+dir] = tmp + return m, gateway.Send(&packet.SetUserData{ + Data: state.JsonUserData(), }) - return m, cmd } func (m Model) Index() int { diff --git a/internal/client/ui/core/state/state.go b/internal/client/ui/core/state/state.go index 1041d23..db5ab6a 100644 --- a/internal/client/ui/core/state/state.go +++ b/internal/client/ui/core/state/state.go @@ -1,13 +1,17 @@ package state import ( + "encoding/json" + "slices" + "github.com/google/btree" "github.com/kyren223/eko/internal/data" "github.com/kyren223/eko/internal/packet" + "github.com/kyren223/eko/pkg/assert" "github.com/kyren223/eko/pkg/snowflake" ) -type Frequency struct { +type FrequencyState struct { IncompleteMessage string Offset int MaxHeight int @@ -17,17 +21,166 @@ type state struct { UserID *snowflake.ID // Key is either a frequency or receiver - FrequencyState map[snowflake.ID]Frequency - LastFrequency map[snowflake.ID]snowflake.ID // key is network + FrequencyState map[snowflake.ID]FrequencyState // key is frequency id + LastFrequency map[snowflake.ID]snowflake.ID // key is network id - Messages map[snowflake.ID]*btree.BTreeG[data.Message] - Networks []packet.FullNetwork + Messages map[snowflake.ID]*btree.BTreeG[data.Message] // key is frequency id or receiver id + Networks map[snowflake.ID]data.Network // key is network id + Frequencies map[snowflake.ID][]data.Frequency // key is network id + Members map[snowflake.ID]map[snowflake.ID]data.Member // key is network id then user id + Users map[snowflake.ID]data.User // key is user id } var State state = state{ UserID: nil, - FrequencyState: map[snowflake.ID]Frequency{}, + FrequencyState: map[snowflake.ID]FrequencyState{}, LastFrequency: map[snowflake.ID]snowflake.ID{}, Messages: map[snowflake.ID]*btree.BTreeG[data.Message]{}, - Networks: []packet.FullNetwork{}, + Networks: map[snowflake.ID]data.Network{}, + Frequencies: map[snowflake.ID][]data.Frequency{}, + Members: map[snowflake.ID]map[snowflake.ID]data.Member{}, + Users: map[snowflake.ID]data.User{}, +} + +type UserData struct { + Networks []snowflake.ID +} + +var Data UserData = UserData{ + Networks: []snowflake.ID{}, +} + +func UpdateNetworks(info *packet.NetworksInfo) { + networks := State.Networks + + for _, removedNetworkId := range info.RemovedNetworks { + delete(networks, removedNetworkId) + delete(State.Frequencies, removedNetworkId) + delete(State.Members, removedNetworkId) + + for i, network := range Data.Networks { + if network == removedNetworkId { + copy(Data.Networks[i:], Data.Networks[i+1:]) + Data.Networks = Data.Networks[:len(Data.Networks)-1] + break + } + } + } + + for _, network := range info.Networks { + if _, ok := networks[network.ID]; !ok { + Data.Networks = append(Data.Networks, network.ID) + } + + networks[network.ID] = network.Network + State.Frequencies[network.ID] = network.Frequencies + for _, member := range network.Members { + State.Members[network.ID][member.UserID] = member + } + + for _, user := range network.Users { + State.Users[user.ID] = user + } + } +} + +func UpdateFrequencies(info *packet.FrequenciesInfo) { + // TODO: check this + frequencies := State.Frequencies[info.Network] + for _, newFrequency := range info.Frequencies { + add := true + for i, existingFrequency := range frequencies { + if existingFrequency.ID == newFrequency.ID { + add = false + if newFrequency.Position == -1 { + newFrequency.Position = existingFrequency.Position + } + frequencies[i] = newFrequency + break + } + } + if add { + frequencies = append(frequencies, newFrequency) + } + } + + frequencies = slices.DeleteFunc(frequencies, func(frequency data.Frequency) bool { + return slices.Contains(info.RemovedFrequencies, frequency.ID) + }) + slices.SortFunc(frequencies, func(a, b data.Frequency) int { + return int(a.Position - b.Position) + }) +} + +func UpdateMessages(info *packet.MessagesInfo) { + for _, id := range info.RemovedMessages { + for _, btree := range State.Messages { + btree.Delete(data.Message{ID: id}) + } + } + + for _, message := range info.Messages { + msgSource := message.FrequencyID + if msgSource == nil { + msgSource = message.ReceiverID + } + bt := State.Messages[*msgSource] + if bt == nil { + bt = btree.NewG(2, func(a, b data.Message) bool { + return a.ID < b.ID + }) + State.Messages[*msgSource] = bt + } + bt.ReplaceOrInsert(message) + } +} + +func UpdateMembers(info *packet.MembersInfo) { + members := State.Members[info.Network] + for _, member := range info.Members { + members[member.UserID] = member + } + for _, removedMember := range info.RemovedMembers { + delete(members, removedMember) + + if removedMember != *State.UserID { + continue + } + delete(State.Networks, info.Network) + delete(State.Frequencies, info.Network) + delete(State.Members, info.Network) + + for i, network := range Data.Networks { + if network == info.Network { + copy(Data.Networks[i:], Data.Networks[i+1:]) + Data.Networks = Data.Networks[:len(Data.Networks)-1] + break + } + } + } + for _, user := range info.Users { + State.Users[user.ID] = user + } +} + +func NetworkId(index int) *snowflake.ID { + if 0 <= index && index < len(Data.Networks) { + return &Data.Networks[index] + } + return nil +} + +func JsonUserData() string { + bytes, err := json.Marshal(Data) + assert.NoError(err, "marshling should never fail") + return string(bytes) +} + +func FromJsonUserDat(s string) { + var data UserData + err := json.Unmarshal([]byte(s), &data) + if err != nil { + return + } + Data = data }