feat: show toast after upload is finished
This commit is contained in:
parent
3b75134572
commit
1d93482209
4 changed files with 336 additions and 59 deletions
120
src/main.rs
120
src/main.rs
|
@ -6,7 +6,7 @@ use std::{borrow::Cow, path::PathBuf, time::Duration};
|
|||
|
||||
use color_eyre::eyre::{OptionExt, Result, bail};
|
||||
use gobject::GtkZiplineFolder;
|
||||
use relm::{Dialog, DialogInput};
|
||||
use relm::{Dialog, DialogInput, Toast, ToastInput};
|
||||
use relm4::{
|
||||
Component, ComponentController, Controller, RelmApp, Sender,
|
||||
adw::{self, prelude::*},
|
||||
|
@ -51,7 +51,7 @@ enum Message {
|
|||
enum ProgressMessage {
|
||||
SetTotal(usize),
|
||||
Progress(usize),
|
||||
Finish,
|
||||
Finish(String),
|
||||
Error(String),
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,7 @@ struct Tyrolienne {
|
|||
video_path: Option<PathBuf>,
|
||||
folder: Option<ZiplineFolder>,
|
||||
dialog: Controller<Dialog>,
|
||||
toast: Controller<Toast>,
|
||||
}
|
||||
|
||||
impl Tyrolienne {
|
||||
|
@ -100,63 +101,68 @@ impl AsyncComponent for Tyrolienne {
|
|||
set_title: Some("Tyrolienne"),
|
||||
set_default_width: 500,
|
||||
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 0,
|
||||
|
||||
adw::HeaderBar {},
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 32,
|
||||
set_margin_top: 32,
|
||||
set_margin_bottom: 32,
|
||||
set_margin_start: 32,
|
||||
set_margin_end: 32,
|
||||
|
||||
gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
set_css_classes: &["boxed-list"],
|
||||
|
||||
adw::ActionRow {
|
||||
set_activatable: true,
|
||||
set_css_classes: &["property"],
|
||||
set_title: "Video file",
|
||||
#[watch]
|
||||
set_subtitle: &model.display_video_path(),
|
||||
connect_activated => Message::OpenFilePicker,
|
||||
},
|
||||
|
||||
adw::ComboRow {
|
||||
set_title: "Folder",
|
||||
set_model: Some(&folder_store),
|
||||
set_expression: Some(&folder_expression),
|
||||
connect_activated[sender] => move |r| {
|
||||
if let Some(item) = r
|
||||
.selected_item()
|
||||
.and_then(|i| i.downcast::<GtkZiplineFolder>().ok())
|
||||
{
|
||||
sender.input(Message::SetFolder(item.as_folder()));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
#[local_ref]
|
||||
toast_overlay -> adw::ToastOverlay {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
set_spacing: 32,
|
||||
set_margin_top: 32,
|
||||
set_margin_bottom: 32,
|
||||
set_margin_start: 32,
|
||||
set_margin_end: 32,
|
||||
|
||||
gtk::ProgressBar {
|
||||
#[watch]
|
||||
set_fraction: model.progress as f64 / model.total as f64,
|
||||
gtk::ListBox {
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
set_css_classes: &["boxed-list"],
|
||||
|
||||
adw::ActionRow {
|
||||
set_activatable: true,
|
||||
set_css_classes: &["property"],
|
||||
set_title: "Video file",
|
||||
#[watch]
|
||||
set_subtitle: &model.display_video_path(),
|
||||
connect_activated => Message::OpenFilePicker,
|
||||
},
|
||||
|
||||
adw::ComboRow {
|
||||
set_title: "Folder",
|
||||
set_model: Some(&folder_store),
|
||||
set_expression: Some(&folder_expression),
|
||||
connect_activated[sender] => move |r| {
|
||||
if let Some(item) = r
|
||||
.selected_item()
|
||||
.and_then(|i| i.downcast::<GtkZiplineFolder>().ok())
|
||||
{
|
||||
sender.input(Message::SetFolder(item.as_folder()));
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
#[watch]
|
||||
set_label: if model.locked { "Uploading..." } else { "Send" },
|
||||
#[watch]
|
||||
set_sensitive: !model.locked && model.video_path.is_some(),
|
||||
connect_clicked => Message::StartTheProcess,
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 10,
|
||||
|
||||
gtk::ProgressBar {
|
||||
#[watch]
|
||||
set_fraction: model.progress as f64 / model.total as f64,
|
||||
},
|
||||
|
||||
gtk::Button {
|
||||
#[watch]
|
||||
set_label: if model.locked { "Uploading..." } else { "Send" },
|
||||
#[watch]
|
||||
set_sensitive: !model.locked && model.video_path.is_some(),
|
||||
connect_clicked => Message::StartTheProcess,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -169,6 +175,8 @@ impl AsyncComponent for Tyrolienne {
|
|||
root: Self::Root,
|
||||
sender: relm4::AsyncComponentSender<Self>,
|
||||
) -> AsyncComponentParts<Self> {
|
||||
let toast_overlay = adw::ToastOverlay::new();
|
||||
|
||||
let model = Tyrolienne {
|
||||
config,
|
||||
locked: false,
|
||||
|
@ -177,7 +185,10 @@ impl AsyncComponent for Tyrolienne {
|
|||
video_path: None,
|
||||
folder: None,
|
||||
dialog: Dialog::builder()
|
||||
.launch(root.clone())
|
||||
.launch(toast_overlay.clone())
|
||||
.forward(sender.input_sender(), |_| Message::Nothing),
|
||||
toast: Toast::builder()
|
||||
.launch(toast_overlay.clone())
|
||||
.forward(sender.input_sender(), |_| Message::Nothing),
|
||||
};
|
||||
|
||||
|
@ -242,10 +253,7 @@ impl AsyncComponent for Tyrolienne {
|
|||
shutdown
|
||||
.register(async move {
|
||||
match the_process(info, &out).await {
|
||||
Ok(url) => {
|
||||
tracing::info!("{url}");
|
||||
out.emit(ProgressMessage::Finish);
|
||||
}
|
||||
Ok(url) => out.emit(ProgressMessage::Finish(url)),
|
||||
Err(e) => out.emit(ProgressMessage::Error(e.to_string())),
|
||||
}
|
||||
})
|
||||
|
@ -265,12 +273,14 @@ impl AsyncComponent for Tyrolienne {
|
|||
match message {
|
||||
ProgressMessage::SetTotal(total) => self.total = total,
|
||||
ProgressMessage::Progress(prog) => self.progress += prog,
|
||||
ProgressMessage::Finish | ProgressMessage::Error(_) => {
|
||||
ProgressMessage::Finish(_) | ProgressMessage::Error(_) => {
|
||||
self.locked = false;
|
||||
self.progress = 0;
|
||||
self.total = 1;
|
||||
|
||||
if let ProgressMessage::Error(e) = message {
|
||||
if let ProgressMessage::Finish(url) = message {
|
||||
self.toast.emit(ToastInput::Show(url));
|
||||
} else if let ProgressMessage::Error(e) = message {
|
||||
self.dialog.emit(DialogInput::Show {
|
||||
heading: "An error occurred".into(),
|
||||
body: e.to_string(),
|
||||
|
|
78
src/relm.rs
78
src/relm.rs
|
@ -5,7 +5,9 @@ use relm4::{
|
|||
};
|
||||
|
||||
pub struct Dialog {
|
||||
window: adw::ApplicationWindow,
|
||||
// toast overlay looks like an odd choice but i think it's fitting considering it acts as a
|
||||
// "box" for the main content
|
||||
window: adw::ToastOverlay,
|
||||
visible: bool,
|
||||
heading: String,
|
||||
body: String,
|
||||
|
@ -22,7 +24,7 @@ pub struct DialogWidgets {
|
|||
}
|
||||
|
||||
impl SimpleComponent for Dialog {
|
||||
type Init = adw::ApplicationWindow;
|
||||
type Init = adw::ToastOverlay;
|
||||
type Root = adw::AlertDialog;
|
||||
type Widgets = DialogWidgets;
|
||||
type Input = DialogInput;
|
||||
|
@ -80,3 +82,75 @@ impl SimpleComponent for Dialog {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Toast {
|
||||
root: adw::ToastOverlay,
|
||||
visible: bool,
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ToastInput {
|
||||
Show(String),
|
||||
Copy,
|
||||
Dismiss,
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl SimpleComponent for Toast {
|
||||
type Input = ToastInput;
|
||||
type Output = ();
|
||||
type Init = adw::ToastOverlay;
|
||||
|
||||
view! {
|
||||
#[name = "toast"]
|
||||
adw::Toast {
|
||||
#[watch]
|
||||
set_title: &model.text,
|
||||
set_button_label: Some("Copy"),
|
||||
connect_button_clicked => ToastInput::Copy,
|
||||
connect_dismissed => ToastInput::Dismiss,
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
_sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
let model = Self {
|
||||
root: init,
|
||||
visible: false,
|
||||
text: String::new(),
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
relm4::ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
|
||||
match message {
|
||||
ToastInput::Show(text) => {
|
||||
self.visible = true;
|
||||
self.text = text;
|
||||
}
|
||||
ToastInput::Copy => {
|
||||
if let Err(e) =
|
||||
arboard::Clipboard::new().and_then(|mut c| c.set_text(self.text.clone()))
|
||||
{
|
||||
tracing::error!("could not copy url to clipboard: {e}");
|
||||
self.text = "Could not copy".into();
|
||||
} else {
|
||||
self.visible = false;
|
||||
}
|
||||
}
|
||||
ToastInput::Dismiss => self.visible = false,
|
||||
}
|
||||
}
|
||||
|
||||
fn post_view() {
|
||||
if model.visible {
|
||||
model.root.add_toast(widgets.toast.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue