Vectrum
Sign In

Give the custom snooze dialog a calendar, and keep list order on undo

The Date field opens a month calendar with past days disabled, as in T3
Code, and clicking in it no longer closes the dialog. Taking back a
settle or snooze leaves the thread where it was in the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JjohannSocommitted 1 parent dc694f2commit966fac2

Showing 1 changed file with 77 additions and 29 deletions.

+77−29src/ui/workspace/actions.rsView File
@@ -11,6 +11,7 @@ use gpui_kit::component::{
1111 input::{Input, InputState},
1212 menu::{DropdownMenu as _, PopupMenu, PopupMenuItem},
1313 notification::Notification,
14+ calendar::{Calendar, CalendarEvent, CalendarState, Matcher},
1415 v_flex,
1516 };
1617 use gpui_kit::prelude::FluentBuilder as _;
@@ -233,6 +234,8 @@ impl Workspace {
233234 let keep = self.undo.iter().rposition(|entry| entry.kind != latest).map_or(0, |ix| ix + 1);
234235 let group = self.undo.split_off(keep);
235236 for entry in group {
237+ // Taking an action back keeps the thread where it was in the list.
238+ let updated = entry.session.read(cx).updated;
236239 match entry.kind {
237240 UndoKind::Settled => entry.session.update(cx, |session, cx| session.unsettle(cx)),
238241 UndoKind::Snoozed => entry.session.update(cx, |session, cx| session.snooze(None, cx)),
@@ -240,10 +243,11 @@ impl Workspace {
240243 UndoKind::Archived => entry.session.update(cx, |session, cx| session.set_archived(false, cx)),
241244 UndoKind::Discarded => {
242245 if let Some((key, chat)) = entry.draft {
243- self.reattach_thread(&key, entry.session, chat, window, cx);
246+ self.reattach_thread(&key, entry.session.clone(), chat, window, cx);
244247 }
245248 }
246249 }
250+ entry.session.update(cx, |session, _| session.updated = updated);
247251 }
248252 cx.notify();
249253 true
@@ -884,6 +888,8 @@ impl Workspace {
884888 let submit = submit.clone();
885889 dialog
886890 .w(px(384.))
891+ // The calendar pops out past the dialog; a click there isn't a click away.
892+ .overlay_closable(false)
887893 .title(
888894 v_flex()
889895 .gap(px(4.))
@@ -967,6 +973,10 @@ impl SnoozeUnit {
967973 struct CustomSnooze {
968974 mode: SnoozeMode,
969975 date: NaiveDate,
976+ /// The date's calendar, past days disabled, and whether it's open.
977+ calendar: Entity<CalendarState>,
978+ calendar_open: bool,
979+ _calendar_events: Subscription,
970980 time: Entity<InputState>,
971981 amount: Entity<InputState>,
972982 unit: SnoozeUnit,
@@ -978,7 +988,32 @@ impl CustomSnooze {
978988 let initial: chrono::DateTime<Local> = (SystemTime::now() + HOUR).into();
979989 let time = cx.new(|cx| InputState::new(window, cx).placeholder("HH:MM").default_value(initial.format("%H:%M").to_string()));
980990 let amount = cx.new(|cx| InputState::new(window, cx).default_value("2"));
981- Self { mode: SnoozeMode::Date, date: initial.date_naive(), time, amount, unit: SnoozeUnit::Hours, error: None }
991+ let today = Local::now().date_naive();
992+ let calendar = cx.new(|cx| {
993+ let mut calendar = CalendarState::new(window, cx).disabled_matcher(Matcher::interval(Some(today), None));
994+ calendar.set_date(initial.date_naive(), window, cx);
995+ calendar
996+ });
997+ let calendar_events = cx.subscribe(&calendar, |this, _, event: &CalendarEvent, cx| {
998+ let CalendarEvent::Selected(date) = event;
999+ if let Some(date) = date.start() {
1000+ this.date = date;
1001+ this.calendar_open = false;
1002+ this.error = None;
1003+ cx.notify();
1004+ }
1005+ });
1006+ Self {
1007+ mode: SnoozeMode::Date,
1008+ date: initial.date_naive(),
1009+ calendar,
1010+ calendar_open: false,
1011+ _calendar_events: calendar_events,
1012+ time,
1013+ amount,
1014+ unit: SnoozeUnit::Hours,
1015+ error: None,
1016+ }
9821017 }
9831018
9841019 /// The wake time the form describes, or `None` (showing why) when it
@@ -1047,36 +1082,48 @@ impl Render for CustomSnooze {
10471082 };
10481083 let fields = match self.mode {
10491084 SnoozeMode::Date => {
1050- let today = Local::now().date_naive();
10511085 let date = self.date;
1052- let entity = entity.clone();
1053- let date_button = MetalButton::new("snooze-date")
1054- .compact()
1055- .w_full()
1056- .justify_between()
1057- .label(date.format("%b %-d, %Y").to_string())
1058- .stretch()
1059- .icon(IconName::Calendar)
1060- .dropdown_menu(move |mut menu, _, _| {
1061- // The next five weeks, past days left out.
1062- for offset in 0..35 {
1063- let day = today + chrono::Duration::days(offset);
1064- let entity = entity.clone();
1065- menu = menu.item(
1066- PopupMenuItem::new(day.format("%a, %b %-d, %Y").to_string()).checked(day == date).on_click(
1067- move |_, _, cx| {
1068- let _ = entity.update(cx, |this, cx| {
1069- this.date = day;
1070- this.error = None;
1071- cx.notify();
1072- });
1073- },
1074- ),
1075- );
1076- }
1077- menu.max_h(px(320.)).scrollable(true)
1086+ let toggle = entity.clone();
1087+ let close = entity.clone();
1088+ let date_button = div()
1089+ .relative()
1090+ .child(
1091+ MetalButton::new("snooze-date")
1092+ .compact()
1093+ .w_full()
1094+ .label(date.format("%b %-d, %Y").to_string())
1095+ .stretch()
1096+ .icon(IconName::Calendar)
1097+ .on_click(move |_, _, cx| {
1098+ let _ = toggle.update(cx, |this, cx| {
1099+ this.calendar_open = !this.calendar_open;
1100+ cx.notify();
1101+ });
1102+ }),
1103+ )
1104+ .when(self.calendar_open, |this| {
1105+ this.child(
1106+ div().absolute().top_full().left_0().child(
1107+ deferred(
1108+ anchored().snap_to_window_with_margin(px(8.)).child(
1109+ crate::ui::pickers::panel(p)
1110+ .mt(px(4.))
1111+ .p(px(8.))
1112+ .on_mouse_down_out(move |_, _, cx| {
1113+ let _ = close.update(cx, |this, cx| {
1114+ this.calendar_open = false;
1115+ cx.notify();
1116+ });
1117+ })
1118+ .child(Calendar::new(&self.calendar)),
1119+ ),
1120+ )
1121+ .with_priority(100),
1122+ ),
1123+ )
10781124 });
10791125 h_flex()
1126+ .items_start()
10801127 .gap(px(12.))
10811128 .child(v_flex().flex_1().min_w_0().gap(px(6.)).child(label("Date")).child(date_button))
10821129 .child(
@@ -1095,6 +1142,7 @@ impl Render for CustomSnooze {
10951142 let dec = entity.clone();
10961143 let inc = entity.clone();
10971144 h_flex()
1145+ .items_start()
10981146 .gap(px(12.))
10991147 .child(
11001148 v_flex().flex_1().min_w_0().gap(px(6.)).child(label("Snooze for")).child(