Files
coop/crates/chat_ui/src/trash.rs
T
2026-11-14 01:27:25 +00:00

180 lines
5.6 KiB
Rust

use std::rc::Rc;
use chat::{ChatRegistry, TrashedMessage};
use common::TimestampExt;
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, AppContext, Context, EventEmitter, FocusHandle, Focusable, IntoElement,
ParentElement, Render, Styled, Window, div, uniform_list,
};
use gpui_kit::component::button::{Button, ButtonVariants as _};
use gpui_kit::component::dock::{Panel, PanelEvent};
use gpui_kit::component::{ActiveTheme, Disableable, Icon, Sizable, h_flex, v_flex};
use ui::IconName;
/// Initialize the trash panel.
pub fn init(window: &mut Window, cx: &mut App) -> gpui::Entity<TrashPanel> {
cx.new(|cx| TrashPanel::new(window, cx))
}
/// The messages that could not be decrypted, shown like a chat room.
pub struct TrashPanel {
focus_handle: FocusHandle,
}
impl TrashPanel {
fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
}
}
fn render_entry(&self, message: &TrashedMessage, cx: &Context<Self>) -> AnyElement {
h_flex()
.w_full()
.gap_2()
.items_center()
.px_2()
.py_1p5()
.rounded(cx.theme().radius)
.bg(cx.theme().secondary.opacity(0.5))
.child(
Icon::new(IconName::Warning)
.small()
.text_color(cx.theme().warning),
)
.child(
v_flex()
.flex_1()
.min_w_0()
.child(div().text_sm().child("Could not be decrypted"))
.child(
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.truncate()
.child(message.reason.clone()),
),
)
.child(
div()
.text_xs()
.text_color(cx.theme().muted_foreground)
.child(message.created_at.to_ago()),
)
.into_any_element()
}
fn render_empty(&self, cx: &Context<Self>) -> AnyElement {
div()
.text_color(cx.theme().muted_foreground)
.child("Trash is empty")
.into_any_element()
}
}
impl gpui_kit::component::dock::BasePanel for TrashPanel {
fn panel_name(&self) -> &'static str {
"TrashPanel"
}
}
impl Panel for TrashPanel {
fn title(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.gap_1p5()
.child(
Icon::new(IconName::Warning)
.small()
.text_color(cx.theme().muted_foreground),
)
.child("Trash")
.into_any_element()
}
}
impl EventEmitter<PanelEvent> for TrashPanel {}
impl Focusable for TrashPanel {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for TrashPanel {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let messages = Rc::new(ChatRegistry::global(cx).read(cx).trashed(cx));
let count = messages.len();
v_flex()
.size_full()
.p_3()
.gap_3()
.text_xs()
.child(
h_flex()
.w_full()
.gap_2()
.items_center()
.justify_between()
.child(
div()
.flex_1()
.text_color(cx.theme().muted_foreground)
.child("Messages that could not be decrypted. New encryption keys may make them readable."),
)
.child(
Button::new("trash-retry")
.label("Try again")
.ghost()
.small()
.disabled(count == 0)
.on_click(|_, _window, cx| {
ChatRegistry::global(cx).update(cx, |registry, cx| {
registry.reprocess_trash(cx);
});
}),
),
)
.when(count == 0, |this| this.child(self.render_empty(cx)))
.when(count > 0, |this| {
this.child(
uniform_list(
"trash-list",
count,
cx.processor(move |this, range, _window, cx| {
this.render_entries(range, messages.as_slice(), cx)
}),
)
.min_h_0()
.flex_1(),
)
})
}
}
impl TrashPanel {
fn render_entries(
&self,
range: std::ops::Range<usize>,
messages: &[TrashedMessage],
cx: &mut Context<Self>,
) -> Vec<AnyElement> {
messages
.get(range)
.map(|slice| {
slice
.iter()
.map(|message| {
// Keep a small gap between entries so they read as separate rows
div()
.pb_1p5()
.child(self.render_entry(message, cx))
.into_any_element()
})
.collect()
})
.unwrap_or_default()
}
}