Showing
14 changed files
with
204 additions
and
26 deletions
| ... | ... | @@ -8,27 +8,23 @@ pub enum Either<L, R> { |
| 8 | 8 | |
| 9 | 9 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 10 | 10 | pub struct MarkdownJson { |
| 11 | - pub name: String, | |
| 12 | - pub content: String, | |
| 13 | - pub number_of_versions: i32, | |
| 14 | - pub date_created: String, | |
| 15 | - pub date_updated: String, | |
| 11 | + pub name :String, | |
| 12 | + pub content :String, | |
| 13 | + pub number_of_versions :i32, | |
| 14 | + pub date_created :String, | |
| 15 | + pub date_updated :String, | |
| 16 | 16 | } |
| 17 | 17 | |
| 18 | 18 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 19 | 19 | pub struct MarkdownDiffJson { |
| 20 | - pub id: i32, | |
| 21 | - pub date_created: String, | |
| 20 | + pub id :i32, | |
| 21 | + pub date_created :String, | |
| 22 | 22 | } |
| 23 | 23 | |
| 24 | 24 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 25 | 25 | pub struct ImageJson { |
| 26 | - pub upload_uuid :Option<Vec<u8>>, | |
| 27 | - pub uuid :Option<Vec<u8>>, | |
| 28 | - pub size :i32, | |
| 29 | - pub dim_x :Option<i32>, | |
| 30 | - pub dim_y :Option<i32>, | |
| 31 | - pub mime_type :String, | |
| 26 | + pub id :i32, | |
| 27 | + pub uuid :Vec<u8>, | |
| 32 | 28 | pub date_created :String, |
| 33 | 29 | pub date_updated :String |
| 34 | 30 | } | ... | ... |
| ... | ... | @@ -57,6 +57,9 @@ async fn main() -> std::io::Result<()> { |
| 57 | 57 | . service( web::resource("/upload") |
| 58 | 58 | . route(web::post().to(upload)) |
| 59 | 59 | ) |
| 60 | + . service( web::resource("/images") | |
| 61 | + . route(web::get().to(get_images)) | |
| 62 | + ) | |
| 60 | 63 | . service( web::resource("/images/{id}") |
| 61 | 64 | . route(web::get().to(get_image)) |
| 62 | 65 | ) | ... | ... |
| ... | ... | @@ -240,6 +240,13 @@ pub(crate) fn finalize( pool :Arc<Pool> |
| 240 | 240 | } |
| 241 | 241 | } |
| 242 | 242 | |
| 243 | +pub(crate) fn get_images(pool: Arc<Pool>) -> Result<Vec<Image>> | |
| 244 | +{ | |
| 245 | + use crate::schema::images::dsl::*; | |
| 246 | + let db_connection = pool.get()?; | |
| 247 | + Ok(images.load::<Image>(&db_connection)?) | |
| 248 | +} | |
| 249 | + | |
| 243 | 250 | pub(crate) fn get_image( pool :Arc<Pool> |
| 244 | 251 | , ident :i32 ) -> Result<Image> |
| 245 | 252 | { | ... | ... |
| ... | ... | @@ -2,7 +2,7 @@ use std::fmt::Display; |
| 2 | 2 | |
| 3 | 3 | use crate::{models::image, AppData, error::Error}; |
| 4 | 4 | |
| 5 | -use actix_web::{Error as ActixError, web, http::StatusCode}; | |
| 5 | +use actix_web::{Error as ActixError, web, HttpResponse, http::StatusCode}; | |
| 6 | 6 | use anyhow::Result; |
| 7 | 7 | use serde::{Deserialize, Serialize}; |
| 8 | 8 | |
| ... | ... | @@ -35,6 +35,17 @@ impl Display for Size { |
| 35 | 35 | } |
| 36 | 36 | } |
| 37 | 37 | |
| 38 | +pub async fn get_images(app_data: web::Data<AppData>) | |
| 39 | + -> Result<HttpResponse, ActixError> | |
| 40 | +{ | |
| 41 | + let pool = app_data.database_pool.clone(); | |
| 42 | + | |
| 43 | + Ok(web::block(move || image::get_images(pool)) | |
| 44 | + . await | |
| 45 | + . map(|images| HttpResponse::Ok().json(images)) | |
| 46 | + . map_err(|_| HttpResponse::InternalServerError())?) | |
| 47 | +} | |
| 48 | + | |
| 38 | 49 | pub async fn get_image( app_data: web::Data<AppData> |
| 39 | 50 | , ident: web::Path<i32> |
| 40 | 51 | , size: web::Query<SizeQuery> | ... | ... |
| ... | ... | @@ -111,17 +111,17 @@ async fn save_resized( original :&DynamicImage |
| 111 | 111 | Err(e) if e.kind() == ErrorKind::NotFound => |
| 112 | 112 | spawn_blocking(move || -> Result<(), Error> { |
| 113 | 113 | let mut scaled = original.resize(width, height, Lanczos3); |
| 114 | - overlay(&mut scaled, CONFIG.copyright_image(), 0_u32, 0_u32); | |
| 115 | 114 | |
| 116 | 115 | if let Size::Thumbnail = size { |
| 117 | - scaled.save_with_format(&path, Jpeg)?; | |
| 118 | 116 | } else { |
| 119 | - let stegonography = CONFIG.copyright_steganography().as_bytes(); | |
| 120 | - let encoder = Encoder::new(stegonography, scaled); | |
| 121 | - let scaled = encoder.encode_alpha(); | |
| 122 | - scaled.save_with_format(&path, Jpeg)?; | |
| 117 | + overlay(&mut scaled, CONFIG.copyright_image(), 0_u32, 0_u32); | |
| 123 | 118 | } |
| 124 | 119 | |
| 120 | + let stegonography = CONFIG.copyright_steganography().as_bytes(); | |
| 121 | + let encoder = Encoder::new(stegonography, scaled); | |
| 122 | + let scaled = encoder.encode_alpha(); | |
| 123 | + scaled.save_with_format(&path, Jpeg)?; | |
| 124 | + | |
| 125 | 125 | let exiv = Metadata::new_from_path(&path)?; |
| 126 | 126 | exiv.set_tag_string("Exif.Image.Copyright", CONFIG.copyright_exiv())?; |
| 127 | 127 | exiv.save_to_file(&path)?; | ... | ... |
| 1 | -.upload { | |
| 1 | +.application > div { | |
| 2 | 2 | float: left; |
| 3 | - width: 400px; | |
| 3 | + width: 30%; | |
| 4 | +} | |
| 5 | + | |
| 6 | +.selector { | |
| 7 | + padding: 1em; | |
| 8 | + border-radius: .5em; | |
| 9 | + border: 1px solid #ddd; | |
| 10 | + background: #f7f7f7; | |
| 11 | +} | |
| 12 | + | |
| 13 | +.selector img { | |
| 14 | + max-width: 75px; | |
| 15 | + max-height: 75px; | |
| 16 | + width: auto; | |
| 17 | + height: auto; | |
| 18 | + vertical-align: middle; | |
| 19 | +} | |
| 20 | + | |
| 21 | +.selector > ul { | |
| 22 | + display: flex; | |
| 23 | + flex-wrap: wrap; | |
| 24 | + gap: 5px; | |
| 25 | + justify-content: space-between; | |
| 26 | + align-items: center; | |
| 27 | + max-height: 15em; | |
| 28 | + width: calc(100% - 15px); | |
| 29 | + overflow-y: auto; | |
| 30 | + overflow-x: auto; | |
| 31 | + background: #ffffff; | |
| 32 | + border-radius: .35em; | |
| 33 | + border: 2px solid #bbb; | |
| 34 | + cursor: default; | |
| 35 | + padding-left: 5px; | |
| 36 | + padding-right: 5px; | |
| 37 | + margin-top: 0; | |
| 38 | + margin-bottom: 0; | |
| 39 | +} | |
| 40 | + | |
| 41 | +.selector > ul > li { | |
| 42 | + display: unset; | |
| 43 | + border: 1px solid black; | |
| 44 | + width: fit-content; | |
| 45 | + height: fit-content; | |
| 46 | + margin-bottom: .15em; | |
| 47 | +} | |
| 48 | + | |
| 49 | +.selector > ul > li:last-child { | |
| 50 | + margin-bottom: 0; | |
| 51 | +} | |
| 52 | + | |
| 53 | +.upload { | |
| 4 | 54 | padding: 1em; |
| 5 | 55 | border-radius: .5em; |
| 6 | 56 | border: 1px solid #ddd; |
| ... | ... | @@ -55,7 +105,6 @@ |
| 55 | 105 | } |
| 56 | 106 | |
| 57 | 107 | .markdown { |
| 58 | - float: left; | |
| 59 | 108 | padding: 1em; |
| 60 | 109 | border-radius: .5em; |
| 61 | 110 | border: 1px solid #ddd; |
| ... | ... | @@ -76,7 +125,6 @@ |
| 76 | 125 | |
| 77 | 126 | .markdown > div:first-child { |
| 78 | 127 | position: fixed; |
| 79 | - width: inherit; | |
| 80 | 128 | z-index: 10; |
| 81 | 129 | } |
| 82 | 130 | ... | ... |
ui/src/api/image.rs
0 → 100644
| 1 | +use std::fmt::Display; | |
| 2 | + | |
| 3 | +use artshop_common::types::ImageJson; | |
| 4 | + | |
| 5 | +use super::super::error::*; | |
| 6 | +use super::super::client::Client; | |
| 7 | + | |
| 8 | +#[derive(Debug, Clone)] | |
| 9 | +pub struct Image { | |
| 10 | + pub json: ImageJson, | |
| 11 | +} | |
| 12 | + | |
| 13 | +pub(crate) async fn images() -> Result<Vec<Image>> { | |
| 14 | + let client = Client::new()?; | |
| 15 | + let (response, data) = client.get("/api/v0/images").await?; | |
| 16 | + | |
| 17 | + match response.status() { | |
| 18 | + 200 => Ok ( serde_json::from_str(data.as_str()) | |
| 19 | + . map(|images :Vec<ImageJson>| { | |
| 20 | + images.into_iter().map(|json| Image { json }).collect() | |
| 21 | + })? ), | |
| 22 | + status => Err(status_error(status)), | |
| 23 | + } | |
| 24 | +} | |
| 25 | + | |
| 26 | +fn status_error<I: Display>(status :I) -> Error { | |
| 27 | + let err_str = format!("Invalid response status: {}", status); | |
| 28 | + Error::from(err_str.as_str()) | |
| 29 | +} | |
| 30 | + | |
| 31 | +// impl Image { | |
| 32 | +// } | ... | ... |
ui/src/component/imageselector/logic.rs
0 → 100644
| 1 | +use mogwai::prelude::*; | |
| 2 | + | |
| 3 | +use crate::api::image::{Image, images}; | |
| 4 | + | |
| 5 | +use super::{PatchSender, view::image_preview_view}; | |
| 6 | + | |
| 7 | +pub(super) async fn image_preview_logic() { | |
| 8 | +} | |
| 9 | + | |
| 10 | +pub(super) async fn selector_logic( mut rx_dom :broadcast::Receiver<Dom> | |
| 11 | + , tx_previews :PatchSender) { | |
| 12 | + let mut previews :ListPatchModel<Image> = ListPatchModel::new(); | |
| 13 | + | |
| 14 | + mogwai::spawn(previews.stream().for_each(move |patch| { | |
| 15 | + let patch :ListPatch<ViewBuilder<Dom>> = patch.map(|i| { | |
| 16 | + let view = image_preview_view( | |
| 17 | + String::from(format!( "/api/v0/images/{}?size=thumbnail" | |
| 18 | + , i.json.id ))); | |
| 19 | + let logic = image_preview_logic(); | |
| 20 | + | |
| 21 | + Component::from(view).with_logic(logic).into() | |
| 22 | + }); | |
| 23 | + let tx_previews = tx_previews.clone(); | |
| 24 | + | |
| 25 | + async move { | |
| 26 | + tx_previews.send(patch).await.unwrap(); | |
| 27 | + } | |
| 28 | + })); | |
| 29 | + | |
| 30 | + if let Some(_) = rx_dom.next().await { | |
| 31 | + for image in images().await.unwrap().into_iter() { | |
| 32 | + previews.list_patch_push(image); | |
| 33 | + } | |
| 34 | + } | |
| 35 | +} | ... | ... |
ui/src/component/imageselector/mod.rs
0 → 100644
| 1 | +pub(crate) mod logic; | |
| 2 | +mod view; | |
| 3 | + | |
| 4 | +use mogwai::prelude::*; | |
| 5 | + | |
| 6 | +use self::{view::selector_view, logic::selector_logic}; | |
| 7 | + | |
| 8 | +type PatchSender = mpmc::Sender<ListPatch<ViewBuilder<Dom>>>; | |
| 9 | +type PatchReceiver = mpmc::Receiver<ListPatch<ViewBuilder<Dom>>>; | |
| 10 | + | |
| 11 | +pub(crate) async fn new() -> Component<Dom> { | |
| 12 | + let (tx_previews, rx_previews) = mpmc::bounded(1); | |
| 13 | + let (tx_dom, rx_dom) = broadcast::bounded(1); | |
| 14 | + | |
| 15 | + let view = selector_view(tx_dom, rx_previews); | |
| 16 | + let logic = selector_logic(rx_dom, tx_previews); | |
| 17 | + | |
| 18 | + Component::from(view).with_logic(logic) | |
| 19 | +} | ... | ... |
ui/src/component/imageselector/view.rs
0 → 100644
| 1 | +use mogwai::prelude::*; | |
| 2 | + | |
| 3 | +use crate::component::imageselector::PatchReceiver; | |
| 4 | + | |
| 5 | +pub(super) fn image_preview_view(image_url :String) -> ViewBuilder<Dom> { | |
| 6 | + builder! { | |
| 7 | + <li><img src=image_url/></li> | |
| 8 | + } | |
| 9 | +} | |
| 10 | + | |
| 11 | +pub(super) fn selector_view( tx_dom :broadcast::Sender<Dom> | |
| 12 | + , rx_previews :PatchReceiver) -> ViewBuilder<Dom> { | |
| 13 | + let post_build = move |dom: &mut Dom| { | |
| 14 | + tx_dom.try_broadcast(dom.clone()).unwrap(); | |
| 15 | + }; | |
| 16 | + | |
| 17 | + builder! { | |
| 18 | + <div class="selector"> | |
| 19 | + <ul patch:children=rx_previews | |
| 20 | + post:build=post_build> | |
| 21 | + </ul> | |
| 22 | + </div> | |
| 23 | + } | |
| 24 | +} | ... | ... |
| ... | ... | @@ -29,11 +29,13 @@ pub async fn main() -> Result<(), JsValue> { |
| 29 | 29 | |
| 30 | 30 | let md = markdown::new().await; |
| 31 | 31 | let comp = upload::new().await; |
| 32 | + let selector = imageselector::new().await; | |
| 32 | 33 | |
| 33 | 34 | let page = Component::from(builder! { |
| 34 | - <div> | |
| 35 | + <div class="application"> | |
| 35 | 36 | {comp} |
| 36 | 37 | {md} |
| 38 | + {selector} | |
| 37 | 39 | </div> |
| 38 | 40 | }); |
| 39 | 41 | page.build()?.run() | ... | ... |
Please
register
or
login
to post a comment