use dioxus::prelude::*;
use crate::components::modal::Modal;
use crate::state::app_state::AppState;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HistoryVisibility {
WorldReadable,
Shared,
Invited,
Joined,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GuestAccess {
CanJoin,
Forbidden,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum JoinRule {
Public,
Invite,
Knock,
Restricted,
}
#[derive(Clone, Debug, PartialEq)]
pub struct PermissionAction {
pub name: String,
pub description: String,
pub level: i64,
}
#[component]
pub fn RoomSettingsDialog(room_id: String, on_close: EventHandler<()>) -> Element {
let state = use_context::<Signal<AppState>>();
let mut active_tab = use_signal(|| SettingsTab::General);
let mut room_name = use_signal(|| String::new());
let mut room_topic = use_signal(|| String::new());
let mut history_visibility = use_signal(|| HistoryVisibility::Shared);
let mut guest_access = use_signal(|| GuestAccess::Forbidden);
let mut join_rule = use_signal(|| JoinRule::Invite);
let mut room_addresses = use_signal(Vec::<String>::new);
let mut new_address = use_signal(|| String::new());
let mut permissions = use_signal(Vec::<PermissionAction>::new);
let mut loading = use_signal(|| true);
let mut saving = use_signal(|| false);
let mut error = use_signal(|| Option::<String>::None);
let mut success = use_signal(|| Option::<String>::None);
let rid = room_id.clone();
use_effect(move || {
let rid = rid.clone();
spawn(async move {
let client = { state.read().client.clone() };
if let Some(client) = client {
if let Ok(room_id) = matrix_sdk::ruma::OwnedRoomId::try_from(rid.as_str()) {
if let Some(room) = client.get_room(&room_id) {
if let Ok(name) = room.display_name().await {
room_name.set(name.to_string());
}
if let Some(topic) = room.topic() {
room_topic.set(topic);
}
permissions.set(vec![
PermissionAction { name: "Send messages".into(), description: "Send text messages".into(), level: 0 },
PermissionAction { name: "Send reactions".into(), description: "React to messages".into(), level: 0 },
PermissionAction { name: "Send media".into(), description: "Upload images and files".into(), level: 0 },
PermissionAction { name: "Invite users".into(), description: "Invite people to this room".into(), level: 0 },
PermissionAction { name: "Kick users".into(), description: "Remove users from the room".into(), level: 50 },
PermissionAction { name: "Ban users".into(), description: "Permanently ban users".into(), level: 50 },
PermissionAction { name: "Redact messages".into(), description: "Delete others' messages".into(), level: 50 },
PermissionAction { name: "Send state events".into(), description: "Modify room state".into(), level: 50 },
PermissionAction { name: "Change room name".into(), description: "Set the room name".into(), level: 50 },
PermissionAction { name: "Change room topic".into(), description: "Set the room topic".into(), level: 50 },
PermissionAction { name: "Change room avatar".into(), description: "Set the room avatar".into(), level: 50 },
PermissionAction { name: "Change history visibility".into(), description: "Who can see history".into(), level: 100 },
PermissionAction { name: "Enable encryption".into(), description: "Enable E2E encryption".into(), level: 100 },
PermissionAction { name: "Modify widgets".into(), description: "Add or remove widgets".into(), level: 50 },
PermissionAction { name: "Pin messages".into(), description: "Pin/unpin messages".into(), level: 50 },
PermissionAction { name: "Upgrade room".into(), description: "Upgrade to new room version".into(), level: 100 },
PermissionAction { name: "Change permissions".into(), description: "Modify power levels".into(), level: 100 },
PermissionAction { name: "Change join rules".into(), description: "Who can join the room".into(), level: 100 },
PermissionAction { name: "Change guest access".into(), description: "Allow or deny guests".into(), level: 100 },
PermissionAction { name: "Change server ACLs".into(), description: "Server access control".into(), level: 100 },
PermissionAction { name: "Send @room notifications".into(), description: "Notify all members".into(), level: 50 },
PermissionAction { name: "Change room address".into(), description: "Set room aliases".into(), level: 50 },
PermissionAction { name: "Manage integrations".into(), description: "Add bots and integrations".into(), level: 50 },
PermissionAction { name: "Set space children".into(), description: "Add rooms to space".into(), level: 50 },
]);
}
}
}
loading.set(false);
});
});
let on_save = {
let rid = room_id.clone();
move |_| {
saving.set(true);
error.set(None);
success.set(None);
let rid = rid.clone();
let name = room_name.read().clone();
let topic = room_topic.read().clone();
spawn(async move {
let client = { state.read().client.clone() };
if let Some(client) = client {
if let Ok(room_id) = matrix_sdk::ruma::OwnedRoomId::try_from(rid.as_str()) {
if let Some(room) = client.get_room(&room_id) {
let name_content = matrix_sdk::ruma::events::room::name::RoomNameEventContent::new(name);
if let Err(e) = room.send_state_event(name_content).await {
error.set(Some(format!("Failed to save name: {e}")));
saving.set(false);
return;
}
let topic_content = matrix_sdk::ruma::events::room::topic::RoomTopicEventContent::new(topic);
if let Err(e) = room.send_state_event(topic_content).await {
error.set(Some(format!("Failed to save topic: {e}")));
saving.set(false);
return;
}
success.set(Some("Settings saved successfully".to_string()));
}
}
}
saving.set(false);
});
}
};
rsx! {
Modal {
title: "Room Settings".to_string(),
on_close: move |_| on_close.call(()),
wide: true,
div {
class: "room-settings",
div {
class: "room-settings__tabs",
button {
class: if *active_tab.read() == SettingsTab::General { "room-settings__tab room-settings__tab--active" } else { "room-settings__tab" },
onclick: move |_| active_tab.set(SettingsTab::General),
"General"
}
button {
class: if *active_tab.read() == SettingsTab::Security { "room-settings__tab room-settings__tab--active" } else { "room-settings__tab" },
onclick: move |_| active_tab.set(SettingsTab::Security),
"Security"
}
button {
class: if *active_tab.read() == SettingsTab::Roles { "room-settings__tab room-settings__tab--active" } else { "room-settings__tab" },
onclick: move |_| active_tab.set(SettingsTab::Roles),
"Roles & Permissions"
}
button {
class: if *active_tab.read() == SettingsTab::Advanced { "room-settings__tab room-settings__tab--active" } else { "room-settings__tab" },
onclick: move |_| active_tab.set(SettingsTab::Advanced),
"Advanced"
}
}
if let Some(ref err) = *error.read() {
div { class: "room-settings__error", "{err}" }
}
if let Some(ref msg) = *success.read() {
div { class: "room-settings__success", "{msg}" }
}
if *active_tab.read() == SettingsTab::General {
div {
class: "room-settings__section",
div {
class: "room-settings__field",
label { "Room Name" }
input {
r#type: "text",
class: "settings-input",
value: "{room_name}",
oninput: move |evt| room_name.set(evt.value()),
}
}
div {
class: "room-settings__field",
label { "Room Topic" }
textarea {
class: "settings-textarea",
rows: "3",
value: "{room_topic}",
oninput: move |evt| room_topic.set(evt.value()),
}
}
h4 { "Room Addresses" }
div {
class: "room-settings__addresses",
for addr in room_addresses.read().iter() {
{
let a = addr.clone();
rsx! {
div {
class: "room-settings__address",
span { "{a}" }
button {
class: "btn btn--sm btn--danger",
onclick: move |_| {
room_addresses.write().retain(|x| x != &a);
},
"Remove"
}
}
}
}
}
div {
class: "room-settings__address-add",
input {
r#type: "text",
class: "settings-input",
placeholder: "#alias:server.com",
value: "{new_address}",
oninput: move |evt| new_address.set(evt.value()),
}
button {
class: "btn btn--secondary btn--sm",
onclick: move |_| {
let addr = new_address.read().trim().to_string();
if !addr.is_empty() {
room_addresses.write().push(addr);
new_address.set(String::new());
}
},
"Add"
}
}
}
div {
class: "room-settings__actions",
button {
class: "btn btn--primary",
disabled: *saving.read(),
onclick: on_save,
if *saving.read() { "Saving..." } else { "Save Changes" }
}
}
}
}
if *active_tab.read() == SettingsTab::Security {
div {
class: "room-settings__section",
h4 { "Join Rules" }
div {
class: "room-settings__radio-group",
label {
input { r#type: "radio", name: "join_rule", checked: *join_rule.read() == JoinRule::Invite, oninput: move |_| join_rule.set(JoinRule::Invite) }
div {
strong { "Private (Invite Only)" }
p { "Only invited users can join" }
}
}
label {
input { r#type: "radio", name: "join_rule", checked: *join_rule.read() == JoinRule::Public, oninput: move |_| join_rule.set(JoinRule::Public) }
div {
strong { "Public" }
p { "Anyone can find and join this room" }
}
}
label {
input { r#type: "radio", name: "join_rule", checked: *join_rule.read() == JoinRule::Knock, oninput: move |_| join_rule.set(JoinRule::Knock) }
div {
strong { "Knock (Ask to Join)" }
p { "Users can request to join and must be approved" }
}
}
label {
input { r#type: "radio", name: "join_rule", checked: *join_rule.read() == JoinRule::Restricted, oninput: move |_| join_rule.set(JoinRule::Restricted) }
div {
strong { "Space Members" }
p { "Only members of a parent space can join" }
}
}
}
h4 { "Guest Access" }
div {
class: "room-settings__radio-group",
label {
input { r#type: "radio", name: "guest", checked: *guest_access.read() == GuestAccess::Forbidden, oninput: move |_| guest_access.set(GuestAccess::Forbidden) }
div {
strong { "Forbidden" }
p { "Guests cannot join this room" }
}
}
label {
input { r#type: "radio", name: "guest", checked: *guest_access.read() == GuestAccess::CanJoin, oninput: move |_| guest_access.set(GuestAccess::CanJoin) }
div {
strong { "Can Join" }
p { "Guest accounts can join (note: guests cannot decrypt E2EE)" }
}
}
}
h4 { "Who can read history?" }
div {
class: "room-settings__radio-group",
label {
input { r#type: "radio", name: "history", checked: *history_visibility.read() == HistoryVisibility::WorldReadable, oninput: move |_| history_visibility.set(HistoryVisibility::WorldReadable) }
div {
strong { "Anyone" }
p { "Anyone, including guests, can read the full history" }
}
}
label {
input { r#type: "radio", name: "history", checked: *history_visibility.read() == HistoryVisibility::Shared, oninput: move |_| history_visibility.set(HistoryVisibility::Shared) }
div {
strong { "Members (full history)" }
p { "New members can see all past messages" }
}
}
label {
input { r#type: "radio", name: "history", checked: *history_visibility.read() == HistoryVisibility::Invited, oninput: move |_| history_visibility.set(HistoryVisibility::Invited) }
div {
strong { "Members (since invited)" }
p { "New members see messages from their invite onwards" }
}
}
label {
input { r#type: "radio", name: "history", checked: *history_visibility.read() == HistoryVisibility::Joined, oninput: move |_| history_visibility.set(HistoryVisibility::Joined) }
div {
strong { "Members (since joined)" }
p { "New members see messages only from when they joined" }
}
}
}
}
}
if *active_tab.read() == SettingsTab::Roles {
div {
class: "room-settings__section",
h4 { "Power Levels" }
p { class: "room-settings__desc", "Admin = 100, Moderator = 50, Default = 0" }
div {
class: "room-settings__permissions",
for perm in permissions.read().iter() {
{
let name = perm.name.clone();
let desc = perm.description.clone();
let level = perm.level;
let role = match level {
100 => "Admin",
50 => "Moderator",
_ => "Default",
};
rsx! {
div {
class: "room-settings__permission",
div {
class: "room-settings__permission-info",
span { class: "room-settings__permission-name", "{name}" }
span { class: "room-settings__permission-desc", "{desc}" }
}
span { class: "room-settings__permission-level", "{role} ({level})" }
}
}
}
}
}
}
}
if *active_tab.read() == SettingsTab::Advanced {
div {
class: "room-settings__section",
h4 { "Room Information" }
div {
class: "room-settings__info-row",
span { class: "room-settings__info-label", "Internal Room ID:" }
code { class: "room-settings__info-value", "{room_id}" }
}
h4 { "Room Upgrade" }
p { class: "room-settings__desc", "Upgrade this room to a newer room version. This creates a new room and archives the old one." }
button {
class: "btn btn--secondary",
onclick: move |_| {
tracing::info!("Room upgrade requested for {}", room_id);
},
"Upgrade Room"
}
}
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum SettingsTab {
General,
Security,
Roles,
Advanced,
}