//! The Archive page, laid out like T3 Code's: settled threads by project,
//! newest first, each with when it was archived and created, an Unarchive
//! button, and Unarchive and Delete on right-click.
use gpui_kit::component::{
Icon, WindowExt as _, h_flex,
menu::{ContextMenuExt as _, PopupMenuItem},
};
use gpui_kit::*;
use super::SettingsView;
use super::widgets::{Section, SettingRow, ago};
use crate::settings;
use crate::ui::icons::Lucide;
use crate::ui::metal::{self, MetalButton};
use crate::ui::pickers;
use crate::ui::workspace::{SettledThread, Workspace};
impl SettingsView {
pub(super) fn render_archive(&mut self, cx: &mut Context<Self>) -> Vec<AnyElement> {
let p = metal::palette(cx);
let threads = self
.workspace
.upgrade()
.map(|workspace| workspace.read(cx).settled_threads(cx))
.unwrap_or_default();
if threads.is_empty() {
return vec![
Section::new("Archived threads")
.id("archive")
.row(
SettingRow::new("No archived threads")
.icon(Icon::new(Lucide::Archive).size(px(14.)).text_color(p.ink3))
.description("Archived threads will appear here."),
)
.render(cx),
];
}
// One section per project, in the order their newest thread was archived.
let mut projects: Vec<(SharedString, SharedString, usize, Vec<AnyElement>)> = Vec::new();
for (ix, thread) in threads.iter().enumerate() {
let row = archive_row(ix, thread, self.workspace.clone(), cx);
match projects.iter_mut().find(|(name, ..)| *name == thread.project) {
Some((.., rows)) => rows.push(row),
None => projects.push((thread.project.clone(), thread.initials.clone(), thread.tint, vec![row])),
}
}
projects
.into_iter()
.enumerate()
.map(|(ix, (name, initials, tint, rows))| {
let section = Section::new(name).icon(metal::badge(initials, tint, p)).rows(rows);
if ix == 0 { section.id("archive") } else { section }.render(cx)
})
.collect()
}
}
fn archive_row(ix: usize, thread: &SettledThread, workspace: WeakEntity<Workspace>, cx: &mut Context<SettingsView>) -> AnyElement {
let p = metal::palette(cx);
let created = thread.session.read(cx).created;
let archived = match thread.age.as_str() {
"now" => "just now".to_string(),
age => format!("{age} ago"),
};
let session = thread.session.clone();
let view = cx.weak_entity();
let unarchive = {
let session = session.clone();
let view = view.clone();
let workspace = workspace.clone();
move |window: &mut Window, cx: &mut App| {
let _ = workspace.update(cx, |workspace, cx| workspace.restore_thread(session.clone(), window, cx));
let _ = view.update(cx, |_, cx| cx.notify());
}
};
let delete = {
let session = session.clone();
let view = view.clone();
move |window: &mut Window, cx: &mut App| {
let session = session.clone();
let workspace = workspace.clone();
let view = view.clone();
let forget = move |window: &mut Window, cx: &mut App| {
let _ = workspace.update(cx, |workspace, cx| workspace.forget_thread(session.clone(), window, cx));
let _ = view.update(cx, |_, cx| cx.notify());
};
if settings::get(|settings| settings.confirm_delete) {
confirm_delete(forget, window, cx);
} else {
forget(window, cx);
}
}
};
let button_unarchive = unarchive.clone();
let (menu_unarchive, menu_delete) = (unarchive.clone(), delete.clone());
div()
.id(("archived-thread", ix))
.context_menu(move |menu, _, _| {
let (unarchive, delete) = (menu_unarchive.clone(), menu_delete.clone());
menu.item(PopupMenuItem::new("Unarchive").on_click(move |_, window, cx| unarchive(window, cx)))
.item(PopupMenuItem::new("Delete").on_click(move |_, window, cx| delete(window, cx)))
})
.child(
SettingRow::new(thread.title.clone())
.icon(pickers::provider_icon(thread.provider, px(12.), p))
.description(format!("Archived {archived} · Created {}", ago(created)))
.control(
MetalButton::new(("unarchive-thread", ix))
.compact()
.icon(Lucide::ArchiveX)
.label("Unarchive")
.on_click(move |_, window, cx| button_unarchive(window, cx)),
),
)
.into_any_element()
}
fn confirm_delete(forget: impl Fn(&mut Window, &mut App) + Clone + 'static, window: &mut Window, cx: &mut App) {
window.open_dialog(cx, move |dialog, _, cx| {
let p = metal::palette(cx);
let forget = forget.clone();
dialog
.title("Delete thread?")
.w(px(420.))
.child(div().text_size(px(13.)).text_color(p.ink2).child("This deletes the thread and its chat history."))
.footer(
h_flex()
.w_full()
.justify_end()
.gap(px(8.))
.child(
MetalButton::new("delete-cancel")
.ghost()
.compact()
.label("Cancel")
.on_click(|_, window, cx| window.close_dialog(cx)),
)
.child(MetalButton::new("delete-confirm").danger().compact().label("Delete").on_click(
move |_, window, cx| {
window.close_dialog(cx);
forget(window, cx);
},
)),
)
});
}