gui: fix channel sort order (#17) #20

Merged
restitux merged 4 commits from fix-channel-order into main 2026-01-25 19:10:18 +00:00
2 changed files with 123 additions and 47 deletions
+118 -13
View File
@@ -4,7 +4,7 @@ use dioxus::prelude::*;
use mime_guess::Mime;
use mumble_web2_common::{ClientConfig, ServerStatus};
use ordermap::OrderSet;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use crate::imp;
@@ -54,14 +54,6 @@ pub enum Command {
use Command::*;
use ConnectionState::*;
#[derive(Default)]
pub struct ChannelState {
pub name: String,
pub children: OrderSet<ChannelId>,
pub users: OrderSet<UserId>,
pub parent: Option<ChannelId>,
}
#[derive(Default)]
pub struct UserState {
pub name: String,
@@ -94,8 +86,121 @@ pub struct Chat {
}
#[derive(Default)]
restitux marked this conversation as resolved
Review

Note unrelated to this PR: We should consider adding derive(Debug) to a lot of our structs to make logging easier in the future.

Note unrelated to this PR: We should consider adding `derive(Debug)` to a lot of our structs to make logging easier in the future.
pub struct ServerState {
pub struct ChannelState {
pub name: String,
pub children: OrderSet<ChannelId>,
pub users: OrderSet<UserId>,
pub parent: Option<ChannelId>,
pub position: i32,
}
impl ChannelState {
pub fn update_from_channel_state(
restitux marked this conversation as resolved Outdated
Outdated
Review

Nit: not snake case

Nit: not snake case
&mut self,
channel_state: &mumble_protocol::control::msgs::ChannelState,
) {
if channel_state.has_position() {
self.position = channel_state.get_position();
}
if channel_state.has_parent() {
self.parent = Some(channel_state.get_parent());
}
if channel_state.has_name() {
self.name = channel_state.get_name().to_string();
}
}
}
#[derive(Default)]
pub struct ChannelsState {
pub channels: HashMap<ChannelId, ChannelState>,
}
impl ChannelsState {
pub fn update_from_channel_state(
&mut self,
channel_state: &mumble_protocol::control::msgs::ChannelState,
) {
self.channels
.entry(channel_state.get_channel_id())
.or_default()
.update_from_channel_state(channel_state);
self.update_channel_parents();
}
pub fn update_from_channel_remove(
&mut self,
channel_remove: &mumble_protocol::control::msgs::ChannelRemove,
) {
self.channels.remove(&channel_remove.get_channel_id());
self.update_channel_parents();
}
pub fn update_channel_parents(&mut self) {
// Zero out existing children
for state in self.channels.values_mut() {
state.children.clear();
}
let mut to_sort: Vec<(ChannelId, Option<ChannelId>, i32, String)> = Vec::new();
for (id, state) in self.channels.iter() {
// Handle channels with no parent (the root channel)
restitux marked this conversation as resolved Outdated
Outdated
Review

Nit: Hnadle -> Handle

Nit: Hnadle -> Handle
let Some(parent_id) = state.parent else {
to_sort.push((*id, None, 0, state.name.clone()));
continue;
};
// If a channel has a parent that we haven't gotten a channel
// state packet for, ignore it
if !self.channels.contains_key(&parent_id) {
continue;
}
to_sort.push((*id, Some(parent_id), state.position, state.name.clone()));
}
let pos_name: HashMap<ChannelId, (i32, String)> = self
.channels
.iter()
.map(|(&id, state)| (id, (state.position, state.name.clone())))
.collect();
let mut updated: HashSet<ChannelId> = HashSet::new();
while updated.len() < to_sort.len() {
for &(id, ref parent_id, position, ref name) in &to_sort {
let Some(parent_id) = parent_id else {
updated.insert(id);
continue;
};
if updated.contains(&id) || !updated.contains(&parent_id) {
continue;
}
// Unwrap should never fail here since we pre filter
let parent = self.channels.get_mut(&parent_id).unwrap();
let mut insert_index = parent.children.len();
for (i, &child) in parent.children.iter().enumerate() {
let (p, ref n) = pos_name[&child];
if (position == p && name < n) || p > position {
insert_index = i;
break;
}
}
parent.children.insert_before(insert_index, id);
updated.insert(id);
}
}
}
}
#[derive(Default)]
pub struct ServerState {
pub channels_state: ChannelsState,
pub users: HashMap<UserId, UserState>,
pub chat: Vec<Chat>,
pub session: Option<UserId>,
@@ -182,7 +287,7 @@ pub fn Channel(id: ChannelId) -> Element {
let net: Coroutine<Command> = use_coroutine_handle();
let server = STATE.server.read();
let user = server.session.unwrap();
let Some(state) = server.channels.get(&id) else {
let Some(state) = server.channels_state.channels.get(&id) else {
return rsx!("missing channel {id}");
};
@@ -336,7 +441,7 @@ pub fn ControlView(config: Resource<ClientConfig>) -> Element {
return rsx!();
};
let current_channel_name = server.channels[&channel].name.clone();
let current_channel_name = server.channels_state.channels[&channel].name.clone();
let proxy_url = config
.read_unchecked()
@@ -528,7 +633,7 @@ pub fn ServerView(config: Resource<ClientConfig>) -> Element {
class: "server_grid",
div {
class: "server_channel_box",
for (id, state) in server.channels.iter() {
for (id, state) in server.channels_state.channels.iter() {
if state.parent.is_none() {
Channel { id: *id }
}
+5 -34
View File
@@ -335,41 +335,11 @@ fn accept_packet(
}
ControlPacket::ChannelState(u) => {
let mut server = STATE.server.write();
let id = u.get_channel_id();
let state = server.channels.entry(id).or_default();
let new_parent = if u.has_parent() {
if let Some(parent) = state.parent.and_then(|p| server.channels.get_mut(&p)) {
parent.children.remove(&id);
}
let parent_id = u.get_parent();
let parent = server.channels.entry(parent_id).or_default();
if u.has_position() && u.get_position() as usize <= parent.children.len() {
// TODO: what if positions are received out of order? we need to sort afterwards?
parent.children.insert_before(u.get_position() as usize, id);
} else {
parent.children.insert(id);
}
Some(parent_id)
} else {
None
};
let state = server.channels.entry(id).or_default();
state.parent = new_parent;
if u.has_name() {
state.name = u.get_name().to_string();
}
server.channels_state.update_from_channel_state(&u);
}
ControlPacket::ChannelRemove(u) => {
let mut server = STATE.server.write();
let id = u.get_channel_id();
if let Some(channel) = server.channels.remove(&id) {
if let Some(parent) = channel.parent.and_then(|p| server.channels.get_mut(&p)) {
parent.children.remove(&id);
}
}
server.channels_state.update_from_channel_remove(&u);
}
ControlPacket::UserState(u) => {
let mut server = STATE.server.write();
@@ -381,12 +351,13 @@ fn accept_packet(
let state = state_entry.or_default();
// the server might now send a channel_id if the user is in channel=0
if u.has_channel_id() || new {
if let Some(parent) = server.channels.get_mut(&state.channel) {
if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) {
parent.users.remove(&id);
}
let channel_id = u.get_channel_id();
server
.channels_state
.channels
.entry(channel_id)
.or_default()
@@ -418,7 +389,7 @@ fn accept_packet(
let mut server = STATE.server.write();
let id = u.get_session();
if let Some(state) = server.users.remove(&id) {
if let Some(parent) = server.channels.get_mut(&state.channel) {
if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) {
parent.users.remove(&id);
}
}