Showing
14 changed files
with
204 additions
and
26 deletions
| @@ -8,27 +8,23 @@ pub enum Either<L, R> { | @@ -8,27 +8,23 @@ pub enum Either<L, R> { | ||
| 8 | 8 | ||
| 9 | #[derive(Clone, Debug, Serialize, Deserialize)] | 9 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 10 | pub struct MarkdownJson { | 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 | #[derive(Clone, Debug, Serialize, Deserialize)] | 18 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 19 | pub struct MarkdownDiffJson { | 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 | #[derive(Clone, Debug, Serialize, Deserialize)] | 24 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 25 | pub struct ImageJson { | 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 | pub date_created :String, | 28 | pub date_created :String, |
| 33 | pub date_updated :String | 29 | pub date_updated :String |
| 34 | } | 30 | } |
| @@ -57,6 +57,9 @@ async fn main() -> std::io::Result<()> { | @@ -57,6 +57,9 @@ async fn main() -> std::io::Result<()> { | ||
| 57 | . service( web::resource("/upload") | 57 | . service( web::resource("/upload") |
| 58 | . route(web::post().to(upload)) | 58 | . route(web::post().to(upload)) |
| 59 | ) | 59 | ) |
| 60 | + . service( web::resource("/images") | ||
| 61 | + . route(web::get().to(get_images)) | ||
| 62 | + ) | ||
| 60 | . service( web::resource("/images/{id}") | 63 | . service( web::resource("/images/{id}") |
| 61 | . route(web::get().to(get_image)) | 64 | . route(web::get().to(get_image)) |
| 62 | ) | 65 | ) |
| @@ -240,6 +240,13 @@ pub(crate) fn finalize( pool :Arc<Pool> | @@ -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 | pub(crate) fn get_image( pool :Arc<Pool> | 250 | pub(crate) fn get_image( pool :Arc<Pool> |
| 244 | , ident :i32 ) -> Result<Image> | 251 | , ident :i32 ) -> Result<Image> |
| 245 | { | 252 | { |
| @@ -2,7 +2,7 @@ use std::fmt::Display; | @@ -2,7 +2,7 @@ use std::fmt::Display; | ||
| 2 | 2 | ||
| 3 | use crate::{models::image, AppData, error::Error}; | 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 | use anyhow::Result; | 6 | use anyhow::Result; |
| 7 | use serde::{Deserialize, Serialize}; | 7 | use serde::{Deserialize, Serialize}; |
| 8 | 8 | ||
| @@ -35,6 +35,17 @@ impl Display for Size { | @@ -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 | pub async fn get_image( app_data: web::Data<AppData> | 49 | pub async fn get_image( app_data: web::Data<AppData> |
| 39 | , ident: web::Path<i32> | 50 | , ident: web::Path<i32> |
| 40 | , size: web::Query<SizeQuery> | 51 | , size: web::Query<SizeQuery> |
| @@ -111,17 +111,17 @@ async fn save_resized( original :&DynamicImage | @@ -111,17 +111,17 @@ async fn save_resized( original :&DynamicImage | ||
| 111 | Err(e) if e.kind() == ErrorKind::NotFound => | 111 | Err(e) if e.kind() == ErrorKind::NotFound => |
| 112 | spawn_blocking(move || -> Result<(), Error> { | 112 | spawn_blocking(move || -> Result<(), Error> { |
| 113 | let mut scaled = original.resize(width, height, Lanczos3); | 113 | let mut scaled = original.resize(width, height, Lanczos3); |
| 114 | - overlay(&mut scaled, CONFIG.copyright_image(), 0_u32, 0_u32); | ||
| 115 | 114 | ||
| 116 | if let Size::Thumbnail = size { | 115 | if let Size::Thumbnail = size { |
| 117 | - scaled.save_with_format(&path, Jpeg)?; | ||
| 118 | } else { | 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 | let exiv = Metadata::new_from_path(&path)?; | 125 | let exiv = Metadata::new_from_path(&path)?; |
| 126 | exiv.set_tag_string("Exif.Image.Copyright", CONFIG.copyright_exiv())?; | 126 | exiv.set_tag_string("Exif.Image.Copyright", CONFIG.copyright_exiv())?; |
| 127 | exiv.save_to_file(&path)?; | 127 | exiv.save_to_file(&path)?; |
| 1 | -.upload { | 1 | +.application > div { |
| 2 | float: left; | 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 | padding: 1em; | 54 | padding: 1em; |
| 5 | border-radius: .5em; | 55 | border-radius: .5em; |
| 6 | border: 1px solid #ddd; | 56 | border: 1px solid #ddd; |
| @@ -55,7 +105,6 @@ | @@ -55,7 +105,6 @@ | ||
| 55 | } | 105 | } |
| 56 | 106 | ||
| 57 | .markdown { | 107 | .markdown { |
| 58 | - float: left; | ||
| 59 | padding: 1em; | 108 | padding: 1em; |
| 60 | border-radius: .5em; | 109 | border-radius: .5em; |
| 61 | border: 1px solid #ddd; | 110 | border: 1px solid #ddd; |
| @@ -76,7 +125,6 @@ | @@ -76,7 +125,6 @@ | ||
| 76 | 125 | ||
| 77 | .markdown > div:first-child { | 126 | .markdown > div:first-child { |
| 78 | position: fixed; | 127 | position: fixed; |
| 79 | - width: inherit; | ||
| 80 | z-index: 10; | 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 | +} |
| @@ -58,7 +58,6 @@ pub(super) fn markdown_view( tx_logic: broadcast::Sender<MarkdownLogic> | @@ -58,7 +58,6 @@ pub(super) fn markdown_view( tx_logic: broadcast::Sender<MarkdownLogic> | ||
| 58 | 58 | ||
| 59 | builder! { | 59 | builder! { |
| 60 | <div class="markdown" | 60 | <div class="markdown" |
| 61 | - style:width="33%" | ||
| 62 | post:build=move |_: &mut Dom| { | 61 | post:build=move |_: &mut Dom| { |
| 63 | tx_logic.try_broadcast(MarkdownLogic::Choose(None)).unwrap(); | 62 | tx_logic.try_broadcast(MarkdownLogic::Choose(None)).unwrap(); |
| 64 | } | 63 | } |
| @@ -29,11 +29,13 @@ pub async fn main() -> Result<(), JsValue> { | @@ -29,11 +29,13 @@ pub async fn main() -> Result<(), JsValue> { | ||
| 29 | 29 | ||
| 30 | let md = markdown::new().await; | 30 | let md = markdown::new().await; |
| 31 | let comp = upload::new().await; | 31 | let comp = upload::new().await; |
| 32 | + let selector = imageselector::new().await; | ||
| 32 | 33 | ||
| 33 | let page = Component::from(builder! { | 34 | let page = Component::from(builder! { |
| 34 | - <div> | 35 | + <div class="application"> |
| 35 | {comp} | 36 | {comp} |
| 36 | {md} | 37 | {md} |
| 38 | + {selector} | ||
| 37 | </div> | 39 | </div> |
| 38 | }); | 40 | }); |
| 39 | page.build()?.run() | 41 | page.build()?.run() |
Please
register
or
login
to post a comment