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