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