From c549fdd2740e1e8fe08809bcc96270c6fa77bf20 Mon Sep 17 00:00:00 2001 From: trisua Date: Wed, 30 Apr 2025 16:45:31 -0400 Subject: [PATCH] add: better mobile chats state add: move channels list to turbo-frame fix: don't spam _render (socket) --- crates/app/src/assets.rs | 2 + crates/app/src/public/html/chats/app.html | 300 +++++++++--------- .../app/src/public/html/chats/channels.html | 53 ++++ crates/app/src/routes/pages/chats.rs | 68 +++- crates/app/src/routes/pages/mod.rs | 12 + 5 files changed, 265 insertions(+), 170 deletions(-) create mode 100644 crates/app/src/public/html/chats/channels.html diff --git a/crates/app/src/assets.rs b/crates/app/src/assets.rs index c3a8d69..8dd050f 100644 --- a/crates/app/src/assets.rs +++ b/crates/app/src/assets.rs @@ -93,6 +93,7 @@ pub const MOD_WARNINGS: &str = include_str!("./public/html/mod/warnings.html"); pub const CHATS_APP: &str = include_str!("./public/html/chats/app.html"); pub const CHATS_STREAM: &str = include_str!("./public/html/chats/stream.html"); pub const CHATS_MESSAGE: &str = include_str!("./public/html/chats/message.html"); +pub const CHATS_CHANNELS: &str = include_str!("./public/html/chats/channels.html"); // langs pub const LANG_EN_US: &str = include_str!("./langs/en-US.toml"); @@ -260,6 +261,7 @@ pub(crate) async fn write_assets(config: &Config) -> PathBufD { write_template!(html_path->"chats/app.html"(crate::assets::CHATS_APP) -d "chats" --config=config); write_template!(html_path->"chats/stream.html"(crate::assets::CHATS_STREAM) --config=config); write_template!(html_path->"chats/message.html"(crate::assets::CHATS_MESSAGE) --config=config); + write_template!(html_path->"chats/channels.html"(crate::assets::CHATS_CHANNELS) --config=config); html_path } diff --git a/crates/app/src/public/html/chats/app.html b/crates/app/src/public/html/chats/app.html index fef59cb..e9e0f94 100644 --- a/crates/app/src/public/html/chats/app.html +++ b/crates/app/src/public/html/chats/app.html @@ -90,58 +90,13 @@ {{ icon "plus" }} {{ text "communities:action.create_channel" }} - {% endif %} {% for channel in channels %} {% if selected_community == 0 - %} -
- - {{ icon "rss" }} - {{ channel.title }} - + {% endif %} - -
- {% else %} - - {{ icon "rss" }} - {{ channel.title }} - - {% endif %} {% endfor %} + {% if channel %} @@ -149,6 +104,7 @@
{ @@ -477,6 +452,117 @@ }), ); }); + + setTimeout(() => { + window.socket.addEventListener("message", async (event) => { + if (event.data === "Ping") { + return socket.send("Pong"); + } + + const msg = JSON.parse(event.data); + const [channel_id, data] = JSON.parse(msg.data); + + if (msg.method === "Message" && window.CURRENT_PAGE === 0) { + if (channel_id !== window.CHAT_PROPS.selected_channel) { + // message not for us... maybe send notification later + // something like /api/v1/messages/{id}/mark_unread + return; + } + + if (document.getElementById("stream_body")) { + const element = document.createElement("div"); + element.style.display = "contents"; + element.innerHTML = await ( + await fetch( + `/chats/${window.CHAT_PROPS.selected_community}/${window.CHAT_PROPS.selected_channel}/_render`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + data: msg.data, + }), + }, + ) + ).text(); + + document + .getElementById("stream_body") + .prepend(element); + clean_text(); + } else { + console.log("abandoned remote"); + socket.close(); + } + } else if (msg.method === "Delete") { + if (document.getElementById(`message-${data.id}`)) { + document + .getElementById(`message-${data.id}`) + .remove(); + } + } + }); + + globalThis.create_message_from_form = async (e) => { + e.preventDefault(); + await trigger("atto::debounce", ["messages::create"]); + + fetch("/api/v1/messages", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + content: e.target.content.value, + channel: window.CHAT_PROPS.selected_channel, + }), + }) + .then((res) => res.json()) + .then((res) => { + if (!res.ok) { + trigger("atto::toast", ["error", res.message]); + } + + e.target.reset(); + }); + }; + + globalThis.delete_message = async (id) => { + if ( + !(await trigger("atto::confirm", [ + "Are you sure you would like to do this?", + ])) + ) { + return; + } + + fetch(`/api/v1/messages/${id}`, { + method: "DELETE", + }) + .then((res) => res.json()) + .then((res) => { + trigger("atto::toast", [ + res.ok ? "success" : "error", + res.message, + ]); + }); + }; + + const clean_text = () => { + trigger("atto::clean_date_codes"); + trigger("atto::hooks::online_indicator"); + }; + + document.addEventListener( + "turbo:before-frame-render", + (event) => { + setTimeout(clean_text, 50); + }, + ); + + setTimeout(clean_text, 150); + }, 250); }; @@ -497,108 +583,6 @@ } } }, 100); - - setTimeout(() => { - window.socket.addEventListener("message", async (event) => { - if (event.data === "Ping") { - return socket.send("Pong"); - } - - const msg = JSON.parse(event.data); - const [channel_id, data] = JSON.parse(msg.data); - - if (msg.method === "Message" && window.CURRENT_PAGE === 0) { - if (channel_id !== "{{ selected_channel }}") { - // message not for us... maybe send notification later - // something like /api/v1/messages/{id}/mark_unread - return; - } - - if (document.getElementById("stream_body")) { - const element = document.createElement("div"); - element.style.display = "contents"; - element.innerHTML = await ( - await fetch( - "/chats/{{ selected_community }}/{{ selected_channel }}/_render", - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ data: msg.data }), - }, - ) - ).text(); - - document.getElementById("stream_body").prepend(element); - clean_text(); - } else { - console.log("abandoned remote"); - socket.close(); - } - } else if (msg.method === "Delete") { - if (document.getElementById(`message-${data.id}`)) { - document.getElementById(`message-${data.id}`).remove(); - } - } - }); - - globalThis.create_message_from_form = async (e) => { - e.preventDefault(); - await trigger("atto::debounce", ["messages::create"]); - - fetch("/api/v1/messages", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - content: e.target.content.value, - channel: "{{ selected_channel }}", - }), - }) - .then((res) => res.json()) - .then((res) => { - if (!res.ok) { - trigger("atto::toast", ["error", res.message]); - } - - e.target.reset(); - }); - }; - - globalThis.delete_message = async (id) => { - if ( - !(await trigger("atto::confirm", [ - "Are you sure you would like to do this?", - ])) - ) { - return; - } - - fetch(`/api/v1/messages/${id}`, { - method: "DELETE", - }) - .then((res) => res.json()) - .then((res) => { - trigger("atto::toast", [ - res.ok ? "success" : "error", - res.message, - ]); - }); - }; - - const clean_text = () => { - trigger("atto::clean_date_codes"); - trigger("atto::hooks::online_indicator"); - }; - - document.addEventListener("turbo:before-frame-render", (event) => { - setTimeout(clean_text, 50); - }); - - setTimeout(clean_text, 150); - }, 250); {% endif %} diff --git a/crates/app/src/public/html/chats/channels.html b/crates/app/src/public/html/chats/channels.html new file mode 100644 index 0000000..01d1dd0 --- /dev/null +++ b/crates/app/src/public/html/chats/channels.html @@ -0,0 +1,53 @@ + + {% for channel in channels %} {% if selected_community == 0 %} +
+ + {{ icon "rss" }} + {{ channel.title }} + + + +
+ {% else %} + + {{ icon "rss" }} + {{ channel.title }} + + {% endif %} {% endfor %} +
diff --git a/crates/app/src/routes/pages/chats.rs b/crates/app/src/routes/pages/chats.rs index 0f03f8c..7401fd8 100644 --- a/crates/app/src/routes/pages/chats.rs +++ b/crates/app/src/routes/pages/chats.rs @@ -1,4 +1,4 @@ -use super::{render_error, PaginatedQuery}; +use super::{render_error, ChatsAppQuery, PaginatedQuery}; use crate::{State, assets::initial_context, get_lang, get_user_from_token}; use axum::{ extract::{Path, Query}, @@ -28,7 +28,7 @@ pub async fn app_request( jar: CookieJar, Extension(data): Extension, Path((selected_community, selected_channel)): Path<(usize, usize)>, - Query(props): Query, + Query(props): Query, ) -> impl IntoResponse { let data = data.read().await; let user = match get_user_from_token!(jar, data.0) { @@ -60,17 +60,19 @@ pub async fn app_request( Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), }; - let channels = if selected_community == 0 { - match data.0.get_channels_by_user(user.id).await { + if selected_community != 0 && selected_channel == 0 { + let channels = match data.0.get_channels_by_community(selected_community).await { Ok(p) => p, Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + }; + + if let Some(ref channel) = channels.get(0) { + return Ok(Html(format!( + "", + selected_community, channel.id, props.nav + ))); } - } else { - match data.0.get_channels_by_community(selected_community).await { - Ok(p) => p, - Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), - } - }; + } let community = if selected_community != 0 { match data.0.get_community_by_id(selected_community).await { @@ -130,9 +132,7 @@ pub async fn app_request( context.insert("community", &community); context.insert("channel", &channel); - context.insert("communities", &communities); - context.insert("channels", &channels); // return Ok(Html(data.1.render("chats/app.html", &context).unwrap())) @@ -249,3 +249,47 @@ pub async fn message_request( // return Ok(Html(data.1.render("chats/message.html", &context).unwrap())) } + +/// `/chats/{community}/{channel/_channels` +pub async fn channels_request( + jar: CookieJar, + Extension(data): Extension, + Path((community, channel)): Path<(usize, usize)>, + Query(props): Query, +) -> impl IntoResponse { + let data = data.read().await; + let user = match get_user_from_token!(jar, data.0) { + Some(ua) => ua, + None => { + return Err(Html( + render_error(Error::NotAllowed, &jar, &data, &None).await, + )); + } + }; + + let channels = if community == 0 { + match data.0.get_channels_by_user(user.id).await { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + } + } else { + match data.0.get_channels_by_community(community).await { + Ok(p) => p, + Err(e) => return Err(Html(render_error(e, &jar, &data, &Some(user)).await)), + } + }; + + let lang = get_lang!(jar, data.0); + let mut context = initial_context(&data.0.0, lang, &Some(user)).await; + + context.insert("channels", &channels); + context.insert("page", &props.page); + + context.insert("selected_community", &community); + context.insert("selected_channel", &channel); + + // return + Ok(Html( + data.1.render("chats/channels.html", &context).unwrap(), + )) +} diff --git a/crates/app/src/routes/pages/mod.rs b/crates/app/src/routes/pages/mod.rs index c2dec85..9986e7c 100644 --- a/crates/app/src/routes/pages/mod.rs +++ b/crates/app/src/routes/pages/mod.rs @@ -99,6 +99,10 @@ pub fn routes() -> Router { "/chats/{community}/{channel}/_render", post(chats::message_request), ) + .route( + "/chats/{community}/{channel}/_channels", + get(chats::channels_request), + ) } pub async fn render_error( @@ -119,6 +123,14 @@ pub struct PaginatedQuery { pub page: usize, } +#[derive(Deserialize)] +pub struct ChatsAppQuery { + #[serde(default)] + pub page: usize, + #[serde(default)] + pub nav: bool, +} + #[derive(Deserialize)] pub struct ProfileQuery { #[serde(default)]