summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2024-08-22 17:01:30 +0300
committerVika <vika@fireburn.ru>2024-08-22 17:01:30 +0300
commit0b30cc210f0b58c2b80eca9a5eed2186e6255914 (patch)
tree86d4fdd8a3876231792f3617d44b6b9658796c88
parentbd01af416055cffed8a36bf2cba0073c9cb289f3 (diff)
downloadbowl-0b30cc210f0b58c2b80eca9a5eed2186e6255914.tar.zst
Display toasts on submitted posts and errors
Success toasts also display a button to open the post in your browser
of choice.
-rw-r--r--src/lib.rs335
1 files 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("<b>Smart Summary</b>\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 = &gtk::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(&gtk::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 = &gtk::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(&gtk::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(&gtk::Align::End.to_value())),
-                        add_setter: (&summary_label, "halign", Some(&gtk::Align::End.to_value())),
-                        add_setter: (&tag_label, "halign", Some(&gtk::Align::End.to_value())),
-                        add_setter: (&content_label, "halign", Some(&gtk::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(&gtk::Align::End.to_value())),
+                            add_setter: (&summary_label, "halign", Some(&gtk::Align::End.to_value())),
+                            add_setter: (&tag_label, "halign", Some(&gtk::Align::End.to_value())),
+                            add_setter: (&content_label, "halign", Some(&gtk::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<Self>,
-        _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::<gtk::ApplicationWindow>()),
+                                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);