Support for uploading images in posts/comments
This commit is contained in:
parent
bc488e7cd3
commit
7a293458b9
|
@ -1258,6 +1258,8 @@ dependencies = [
|
||||||
"html2pango",
|
"html2pango",
|
||||||
"lemmy_api_common",
|
"lemmy_api_common",
|
||||||
"markdown",
|
"markdown",
|
||||||
|
"mime_guess",
|
||||||
|
"rand",
|
||||||
"relm4",
|
"relm4",
|
||||||
"relm4-components",
|
"relm4-components",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -1423,6 +1425,16 @@ version = "0.3.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.8"
|
version = "0.8.8"
|
||||||
|
@ -1861,6 +1873,7 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
"mime",
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
@ -2482,6 +2495,15 @@ version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||||
|
dependencies = [
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
|
|
|
@ -6,9 +6,11 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
relm4 = "0.6.0"
|
relm4 = "0.6.0"
|
||||||
relm4-components = { version = "0.6.0", features = ["web"] }
|
relm4-components = { version = "0.6.0", features = ["web"] }
|
||||||
reqwest = { version = "0.11.16", features = ["json", "blocking"] }
|
reqwest = { version = "0.11.16", features = ["json", "blocking", "multipart"] }
|
||||||
serde = { version = "1.0.160", features = ["derive"] }
|
serde = { version = "1.0.160", features = ["derive"] }
|
||||||
serde_json = "1.0.96"
|
serde_json = "1.0.96"
|
||||||
lemmy_api_common = { git = "https://github.com/Bnyro/lemmy.git", rev = "ff69cd151988fa4cff5a6f3789c5675a2e3257f8" }
|
lemmy_api_common = { git = "https://github.com/Bnyro/lemmy.git", rev = "ff69cd151988fa4cff5a6f3789c5675a2e3257f8" }
|
||||||
markdown = "0.3.0"
|
markdown = "0.3.0"
|
||||||
html2pango = "0.5.0"
|
html2pango = "0.5.0"
|
||||||
|
rand = "0.8.5"
|
||||||
|
mime_guess = "2.0.4"
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
use reqwest::blocking::multipart::Part;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::fs::File;
|
||||||
|
use crate::settings;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
|
|
||||||
|
use super::CLIENT;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UploadImageResponse {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
msg: String,
|
||||||
|
files: Vec<UploadImageFile>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct UploadImageFile {
|
||||||
|
pub file: String,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub delete_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upload_image(
|
||||||
|
image: std::path::PathBuf,
|
||||||
|
) -> Result<String, reqwest::Error> {
|
||||||
|
let mime_type = mime_guess::from_path(image.clone()).first();
|
||||||
|
let file_name = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
|
||||||
|
|
||||||
|
let mut file = File::open(image).unwrap();
|
||||||
|
let mut data = Vec::new();
|
||||||
|
file.read_to_end(&mut data).unwrap();
|
||||||
|
|
||||||
|
let part = Part::bytes(data).file_name(file_name).mime_str(&mime_type.unwrap().essence_str())?;
|
||||||
|
let form = reqwest::blocking::multipart::Form::new().part("images[]", part);
|
||||||
|
let account = settings::get_current_account();
|
||||||
|
let base_url = account.instance_url;
|
||||||
|
let path = format!("{}/pictrs/image", base_url);
|
||||||
|
let res: UploadImageResponse = CLIENT
|
||||||
|
.post(&path)
|
||||||
|
.header("cookie", format!("jwt={}", account.jwt.unwrap().to_string()))
|
||||||
|
.multipart(form)
|
||||||
|
.send()?
|
||||||
|
.json()?;
|
||||||
|
|
||||||
|
Ok(format!("{}/pictrs/image/{}", base_url, res.files[0].file))
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ pub mod auth;
|
||||||
pub mod moderation;
|
pub mod moderation;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
pub mod image;
|
||||||
|
|
||||||
static API_VERSION: &str = "v3";
|
static API_VERSION: &str = "v3";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use relm4::prelude::*;
|
use relm4::{prelude::*, gtk::{ResponseType, FileFilter}};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
|
|
||||||
|
use crate::api;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct EditorData {
|
pub struct EditorData {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -17,7 +19,8 @@ pub struct EditorDialog {
|
||||||
url_buffer: gtk::EntryBuffer,
|
url_buffer: gtk::EntryBuffer,
|
||||||
body_buffer: gtk::TextBuffer,
|
body_buffer: gtk::TextBuffer,
|
||||||
// Optional field to temporarily store the post or comment id
|
// Optional field to temporarily store the post or comment id
|
||||||
id: Option<i32>
|
id: Option<i32>,
|
||||||
|
window: gtk::Window
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -32,7 +35,10 @@ pub enum DialogMsg {
|
||||||
Hide,
|
Hide,
|
||||||
UpdateType(EditorType, bool),
|
UpdateType(EditorType, bool),
|
||||||
UpdateData(EditorData),
|
UpdateData(EditorData),
|
||||||
Okay
|
Okay,
|
||||||
|
ChooseImage,
|
||||||
|
UploadImage(std::path::PathBuf),
|
||||||
|
AppendBody(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -108,7 +114,15 @@ impl SimpleComponent for EditorDialog {
|
||||||
},
|
},
|
||||||
gtk::Box {
|
gtk::Box {
|
||||||
set_orientation: gtk::Orientation::Horizontal,
|
set_orientation: gtk::Orientation::Horizontal,
|
||||||
set_halign: gtk::Align::End,
|
set_hexpand: true,
|
||||||
|
gtk::Button {
|
||||||
|
set_label: "Upload image",
|
||||||
|
set_margin_end: 10,
|
||||||
|
connect_clicked => DialogMsg::ChooseImage,
|
||||||
|
},
|
||||||
|
gtk::Box {
|
||||||
|
set_hexpand: true,
|
||||||
|
},
|
||||||
gtk::Button {
|
gtk::Button {
|
||||||
set_label: "Cancel",
|
set_label: "Cancel",
|
||||||
set_margin_end: 10,
|
set_margin_end: 10,
|
||||||
|
@ -136,7 +150,8 @@ impl SimpleComponent for EditorDialog {
|
||||||
let name_buffer = gtk::EntryBuffer::builder().build();
|
let name_buffer = gtk::EntryBuffer::builder().build();
|
||||||
let url_buffer = gtk::EntryBuffer::builder().build();
|
let url_buffer = gtk::EntryBuffer::builder().build();
|
||||||
let body_buffer = gtk::TextBuffer::builder().build();
|
let body_buffer = gtk::TextBuffer::builder().build();
|
||||||
let model = EditorDialog { type_: init, visible: false, is_new: true, name_buffer, url_buffer, body_buffer, id: None };
|
let window = root.toplevel_window().unwrap();
|
||||||
|
let model = EditorDialog { type_: init, visible: false, is_new: true, name_buffer, url_buffer, body_buffer, id: None, window };
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
ComponentParts { model, widgets }
|
ComponentParts { model, widgets }
|
||||||
}
|
}
|
||||||
|
@ -173,6 +188,37 @@ impl SimpleComponent for EditorDialog {
|
||||||
if let Some(url) = data.url { self.url_buffer.set_text(url.to_string()); }
|
if let Some(url) = data.url { self.url_buffer.set_text(url.to_string()); }
|
||||||
self.body_buffer.set_text(&data.body.clone());
|
self.body_buffer.set_text(&data.body.clone());
|
||||||
}
|
}
|
||||||
|
DialogMsg::ChooseImage => {
|
||||||
|
let buttons = [("_Cancel", ResponseType::Cancel), ("_Okay", ResponseType::Accept)];
|
||||||
|
let dialog = gtk::FileChooserDialog::new(Some("Upload image"), None::<>k::ApplicationWindow>, gtk::FileChooserAction::Open, &buttons);
|
||||||
|
dialog.set_transient_for(Some(&self.window));
|
||||||
|
let image_filter = FileFilter::new();
|
||||||
|
image_filter.add_pattern("image/*");
|
||||||
|
dialog.add_filter(&image_filter);
|
||||||
|
dialog.run_async(move |dialog, result | {
|
||||||
|
match result {
|
||||||
|
ResponseType::Accept => {
|
||||||
|
let path = dialog.file().unwrap().path();
|
||||||
|
sender.input(DialogMsg::UploadImage(path.unwrap()))
|
||||||
|
},
|
||||||
|
_ => dialog.hide(),
|
||||||
|
}
|
||||||
|
dialog.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DialogMsg::UploadImage(path) => {
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
if let Ok(image_path) = api::image::upload_image(path) {
|
||||||
|
let new_text = format!("", image_path);
|
||||||
|
sender.input(DialogMsg::AppendBody(new_text));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DialogMsg::AppendBody(new_text) => {
|
||||||
|
let (start, end) = &self.body_buffer.bounds();
|
||||||
|
let body = self.body_buffer.text(start, end, true).to_string();
|
||||||
|
self.body_buffer.set_text(&format!("{}\n{}", body, new_text));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue