//! 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) -> Vec { 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)> = 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, cx: &mut Context) -> 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(); move |cx: &mut App| { session.update(cx, |session, cx| session.unsettle(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 |_, _, cx| unarchive(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 |_, _, cx| button_unarchive(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); }, )), ) }); }