From 0b30cc210f0b58c2b80eca9a5eed2186e6255914 Mon Sep 17 00:00:00 2001 From: Vika Date: Thu, 22 Aug 2024 17:01:30 +0300 Subject: Display toasts on submitted posts and errors Success toasts also display a button to open the post in your browser of choice. --- src/lib.rs | 335 ++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 178 insertions(+), 157 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 015f230..320bf80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -94,178 +94,181 @@ impl AsyncComponent for PostComposerModel { bar }, - #[name = "content_wrapper"] - adw::BreakpointBin { - set_width_request: 360, - set_height_request: 480, - - #[name = "content"] - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, - set_margin_all: 5, - - #[name = "name_label"] - gtk::Label { - set_markup: "Name", - set_margin_vertical: 10, - set_margin_horizontal: 10, - set_halign: gtk::Align::Start, - set_valign: gtk::Align::Start, - }, - #[name = "name_field"] - gtk::Entry { - set_hexpand: true, - set_buffer: &model.name_buffer, - #[track = "model.changed(Self::submit_busy_guard())"] - set_sensitive: model.submit_busy_guard.is_none(), - }, - - #[name = "summary_label"] - gtk::Label { - set_markup: "Summary", - set_margin_vertical: 10, - set_margin_horizontal: 10, - set_halign: gtk::Align::Start, - set_valign: gtk::Align::Start, - }, - #[name = "summary_field"] + #[name = "toast_overlay"] + adw::ToastOverlay { + #[name = "content_wrapper"] + adw::BreakpointBin { + set_width_request: 360, + set_height_request: 480, + + #[name = "content"] gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_css_classes: &["linked"], - + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_margin_all: 5, + + #[name = "name_label"] + gtk::Label { + set_markup: "Name", + set_margin_vertical: 10, + set_margin_horizontal: 10, + set_halign: gtk::Align::Start, + set_valign: gtk::Align::Start, + }, + #[name = "name_field"] gtk::Entry { set_hexpand: true, - set_buffer: &model.summary_buffer, - #[track = "model.changed(Self::ai_summary_busy_guard() | Self::submit_busy_guard())"] - set_sensitive: model.ai_summary_busy_guard.is_none() && model.submit_busy_guard.is_none(), + set_buffer: &model.name_buffer, + #[track = "model.changed(Self::submit_busy_guard())"] + set_sensitive: model.submit_busy_guard.is_none(), }, - #[name = "ai_summary_button"] - gtk::Button { - connect_clicked => Self::Input::AiGenSummaryBegin, - - #[track = "model.changed(Self::ai_summary_busy_guard() | Self::submit_busy_guard())"] - set_sensitive: model.ai_summary_busy_guard.is_none() && model.submit_busy_guard.is_none(), - set_tooltip: "Smart Summary\nAsk a language model to summarize the content of your post in a single sentence.", - - gtk::Stack { - gtk::Label { - #[track = "model.changed(Self::ai_summary_busy_guard())"] - set_visible: model.ai_summary_busy_guard.is_none(), - set_markup: "✨" - }, - gtk::Spinner { - #[track = "model.changed(Self::ai_summary_busy_guard())"] - set_visible: model.ai_summary_busy_guard.is_some(), - set_spinning: true, - } - } + #[name = "summary_label"] + gtk::Label { + set_markup: "Summary", + set_margin_vertical: 10, + set_margin_horizontal: 10, + set_halign: gtk::Align::Start, + set_valign: gtk::Align::Start, }, - }, - - #[name = "tag_label"] - gtk::Label { - set_markup: "Tags", - set_margin_vertical: 10, - set_margin_horizontal: 10, - set_halign: gtk::Align::Start, - set_valign: gtk::Align::Start, - }, - #[name = "tag_holder"] - // TODO: tag component (because of complex logic) - gtk::Box { - set_css_classes: &["frame"], - set_hexpand: true, - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - set_height_request: 36, - }, - - - #[name = "content_label"] - gtk::Label { - set_markup: "Content", - set_halign: gtk::Align::Start, - set_valign: gtk::Align::Start, - set_margin_vertical: 10, - set_margin_horizontal: 10, - }, + #[name = "summary_field"] + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + add_css_class: "linked", - #[name = "content_textarea"] - gtk::ScrolledWindow { - set_vexpand: true, + gtk::Entry { + set_hexpand: true, + set_buffer: &model.summary_buffer, + #[track = "model.changed(Self::ai_summary_busy_guard() | Self::submit_busy_guard())"] + set_sensitive: model.ai_summary_busy_guard.is_none() && model.submit_busy_guard.is_none(), + }, + #[name = "ai_summary_button"] + gtk::Button { + connect_clicked => Self::Input::AiGenSummaryBegin, + #[track = "model.changed(Self::ai_summary_busy_guard() | Self::submit_busy_guard())"] + set_sensitive: model.ai_summary_busy_guard.is_none() && model.submit_busy_guard.is_none(), + set_tooltip_markup: Some("Smart Summary\nAsk a language model to summarize the content of your post in a single sentence."), + + gtk::Stack { + gtk::Label { + #[track = "model.changed(Self::ai_summary_busy_guard())"] + set_visible: model.ai_summary_busy_guard.is_none(), + set_markup: "✨" + }, + + gtk::Spinner { + #[track = "model.changed(Self::ai_summary_busy_guard())"] + set_visible: model.ai_summary_busy_guard.is_some(), + set_spinning: true, + } + } + }, + }, - gtk::TextView { - set_buffer: Some(&model.content_buffer), + #[name = "tag_label"] + gtk::Label { + set_markup: "Tags", + set_margin_vertical: 10, + set_margin_horizontal: 10, + set_halign: gtk::Align::Start, + set_valign: gtk::Align::Start, + }, + #[name = "tag_holder"] + // TODO: tag component (because of complex logic) + gtk::Box { + add_css_class: "frame", set_hexpand: true, - set_css_classes: &["frame", "view"], + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 5, + set_height_request: 36, + }, - set_monospace: true, - set_wrap_mode: gtk::WrapMode::Word, - set_vscroll_policy: gtk::ScrollablePolicy::Natural, - set_left_margin: 8, - set_right_margin: 8, - set_top_margin: 8, - set_bottom_margin: 8, + #[name = "content_label"] + gtk::Label { + set_markup: "Content", + set_halign: gtk::Align::Start, + set_valign: gtk::Align::Start, + set_margin_vertical: 10, + set_margin_horizontal: 10, + }, - #[track = "model.changed(Self::submit_busy_guard())"] - set_sensitive: model.submit_busy_guard.is_none(), - } - }, + #[name = "content_textarea"] + gtk::ScrolledWindow { + set_vexpand: true, - #[name = "misc_prop_wrapper"] - gtk::Box { - set_hexpand: true, + gtk::TextView { + set_buffer: Some(&model.content_buffer), + set_hexpand: true, + #[iterate] + add_css_class: &["frame", "view"], - gtk::FlowBox { - set_hexpand: false, - set_orientation: gtk::Orientation::Horizontal, - set_homogeneous: false, - set_column_spacing: 0, - set_min_children_per_line: 2, - set_max_children_per_line: 6, - set_selection_mode: gtk::SelectionMode::None, - - append = >k::Box { - set_spacing: 5, - - #[name = "visibility_label"] - gtk::Label { - set_markup: "Visibility", - set_halign: gtk::Align::Start, - set_valign: gtk::Align::Start, - set_margin_vertical: 10, - set_margin_horizontal: 10, - }, - #[name = "visibility_selector"] - gtk::DropDown { - set_model: Some(>k::StringList::new(&VISIBILITY)), - set_hexpand: false, + set_monospace: true, + set_wrap_mode: gtk::WrapMode::Word, + set_vscroll_policy: gtk::ScrollablePolicy::Natural, + + set_left_margin: 8, + set_right_margin: 8, + set_top_margin: 8, + set_bottom_margin: 8, #[track = "model.changed(Self::submit_busy_guard())"] set_sensitive: model.submit_busy_guard.is_none(), + } + }, + + #[name = "misc_prop_wrapper"] + gtk::Box { + set_hexpand: true, + + gtk::FlowBox { + set_hexpand: false, + set_orientation: gtk::Orientation::Horizontal, + set_homogeneous: false, + set_column_spacing: 0, + set_min_children_per_line: 2, + set_max_children_per_line: 6, + set_selection_mode: gtk::SelectionMode::None, + + append = >k::Box { + set_spacing: 5, + + #[name = "visibility_label"] + gtk::Label { + set_markup: "Visibility", + set_halign: gtk::Align::Start, + set_valign: gtk::Align::Start, + set_margin_vertical: 10, + set_margin_horizontal: 10, + }, + #[name = "visibility_selector"] + gtk::DropDown { + set_model: Some(>k::StringList::new(&VISIBILITY)), + set_hexpand: false, + + #[track = "model.changed(Self::submit_busy_guard())"] + set_sensitive: model.submit_busy_guard.is_none(), + }, }, }, }, }, - }, - add_breakpoint = adw::Breakpoint::new( - adw::BreakpointCondition::new_length( - adw::BreakpointConditionLengthType::MinWidth, - 512.0, - adw::LengthUnit::Px - ) - ) { - add_setter: (&content, "layout_manager", Some(&model.wide_layout.to_value())), - add_setter: (&name_label, "halign", Some(>k::Align::End.to_value())), - add_setter: (&summary_label, "halign", Some(>k::Align::End.to_value())), - add_setter: (&tag_label, "halign", Some(>k::Align::End.to_value())), - add_setter: (&content_label, "halign", Some(>k::Align::End.to_value())), - }, + add_breakpoint = adw::Breakpoint::new( + adw::BreakpointCondition::new_length( + adw::BreakpointConditionLengthType::MinWidth, + 512.0, + adw::LengthUnit::Px + ) + ) { + add_setter: (&content, "layout_manager", Some(&model.wide_layout.to_value())), + add_setter: (&name_label, "halign", Some(>k::Align::End.to_value())), + add_setter: (&summary_label, "halign", Some(>k::Align::End.to_value())), + add_setter: (&tag_label, "halign", Some(>k::Align::End.to_value())), + add_setter: (&content_label, "halign", Some(>k::Align::End.to_value())), + }, + } } } } @@ -292,7 +295,10 @@ impl AsyncComponent for PostComposerModel { tracker: Default::default() }; - let widgets = view_output!(); + let widgets = view_output!(); + + #[cfg(debug_assertions)] + window.add_css_class("devel"); let layout = &model.wide_layout; widgets.content.set_layout_manager(Some(layout.clone())); @@ -345,7 +351,7 @@ impl AsyncComponent for PostComposerModel { widgets: &mut Self::Widgets, message: Self::Input, sender: AsyncComponentSender, - _root: &Self::Root + root: &Self::Root ) { self.reset(); // Reset the tracker @@ -410,20 +416,35 @@ impl AsyncComponent for PostComposerModel { proplist.push(PropertyValue::Plain(selected.to_owned())); } - log::warn!("sending post: {:#}", serde_json::to_value(&mf2).unwrap()); - match self.micropub.send_post(mf2).await { Ok(location) => { self.name_buffer.set_text(""); self.summary_buffer.set_text(""); // TODO: tags self.content_buffer.set_text(""); - // TODO: display toast! - log::warn!("post submitted: {}", location); + let toast = adw::Toast::new("Post submitted"); + toast.set_button_label(Some("Open")); + toast.connect_button_clicked(glib::clone!(#[strong] root, move |toast| { + gtk::UriLauncher::new(&location.to_string()).launch( + Some(root.upcast_ref::()), + None::<&gio::Cancellable>, + glib::clone!(#[weak] toast, move |result| { + if let Err(err) = result { + log::warn!("Error opening post URI: {}", err); + } else { + toast.dismiss() + } + }) + ); + })); + widgets.toast_overlay.add_toast(toast); }, Err(err) => { - // TODO: display error dialog - log::warn!("error sending the post: {}", err); + log::warn!("Error sending post: {}", err); + let toast = adw::Toast::new(&format!("Error sending post: {}", err)); + toast.set_timeout(0); + toast.set_priority(adw::ToastPriority::High); + widgets.toast_overlay.add_toast(toast); } } self.set_submit_busy_guard(None); -- cgit 1.4.1