use chat::Message; use common::TimestampExt; use gpui::prelude::FluentBuilder; use gpui::{ AnyElement, App, InteractiveElement, IntoElement, ObjectFit, ParentElement, SharedUri, Styled, StyledImage, WeakEntity, div, img, px, }; use nip29::Group; use person::PersonRegistry; use settings::AppSettings; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::menu::{ContextMenu, PopupMenuItem}; use ui::message::MessageRow; use crate::GroupPanel; pub(crate) struct Actions { pub group: WeakEntity, pub panel: WeakEntity, pub admin: bool, pub pinned: bool, } pub(crate) fn render( ix: usize, message: &Message, content: AnyElement, show_author: bool, actions: Actions, cx: &App, ) -> AnyElement { let persons = PersonRegistry::global(cx); let author = persons.read(cx).get(&message.author, cx); let hide_avatar = AppSettings::get_hide_avatar(cx); let row = MessageRow::new(ix) .show_author(show_author) .hide_avatar(hide_avatar) .avatar( Avatar::new(author.avatar()) .seed(author.avatar_seed()) .flex_shrink_0(), ) .author(author.name()) .timestamp(message.created_at.to_human_time()) .child(content) .when(!message.media.is_empty(), |this| { this.child(media(&message.media, cx)) }) .into_any_element(); if !actions.admin { return row; } let id = message.id; let pinned = actions.pinned; let group = actions.group; let panel = actions.panel; ContextMenu::new( format!("message-menu-{}", id.to_hex()), row, move |menu, _window, _cx| { let pin = { let group = group.clone(); let panel = panel.clone(); PopupMenuItem::new(if pinned { "Unpin" } else { "Pin" }).on_click( move |_event, _window, cx| { if let Err(error) = panel.update_in(cx, |panel, window, cx| { panel.toggle_pin(group.clone(), id, window, cx); }) { log::warn!("nip29: pinning a message failed: {error}"); } }, ) }; let delete = { let group = group.clone(); let panel = panel.clone(); PopupMenuItem::new("Delete message").on_click(move |_event, _window, cx| { if let Err(error) = panel.update_in(cx, |panel, window, cx| { panel.delete_message(group.clone(), id, window, cx); }) { log::warn!("nip29: deleting a message failed: {error}"); } }) }; menu.item(pin).item(delete) }, ) .into_any_element() } fn media(media: &[SharedUri], cx: &App) -> AnyElement { if let [only] = media { return div() .child( img(only.clone()) .border_1() .border_color(cx.theme().border_variant) .h(px(250.)) .object_fit(ObjectFit::Cover) .rounded(cx.theme().radius), ) .into_any_element(); } div() .w_full() .flex_1() .flex() .flex_row() .flex_wrap() .gap_2() .children(media.iter().enumerate().map(|(ix, item)| { div().id(format!("media-{ix}")).child( img(item.clone()) .h_32() .border_1() .border_color(cx.theme().border_variant) .rounded(cx.theme().radius), ) })) .into_any_element() }