Commit a0ed54d6ab9a03fbccd9d15b4d802267d5175b33
1 parent
5b9ef0e3
Use a ListPatchModel to keep track of the uploads list
Showing
8 changed files
with
175 additions
and
49 deletions
1 | +.upload { | |
2 | + float: left; | |
3 | + width: 400px; | |
4 | + padding: 1em; | |
5 | + border-radius: .5em; | |
6 | + border: 1px solid #ddd; | |
7 | + background: #f7f7f7; | |
8 | +} | |
9 | + | |
10 | +.upload ul { | |
11 | + padding-left: 5px; | |
12 | +} | |
13 | + | |
14 | +.upload > ul { | |
15 | + max-height: 15em; | |
16 | + width: calc(100% - 10px); | |
17 | + overflow-y: auto; | |
18 | + overflow-x: hidden; | |
19 | + background: #ffffff; | |
20 | + border-radius: .35em; | |
21 | + border: 2px solid #bbb; | |
22 | +} | |
23 | + | |
24 | +.upload > ul > li { | |
25 | + display: flex; | |
26 | + border: 1px solid black; | |
27 | + width: fit-content; | |
28 | +} | |
29 | + | |
30 | +.upload > ul > li > ul > li { | |
31 | + display: block; | |
32 | +} | |
33 | + | |
1 | 34 | .markdown { |
2 | 35 | float: left; |
3 | 36 | padding: 1em; | ... | ... |
ui/src/api/upload.rs
0 → 100644
1 | +use std::fmt::Display; | |
2 | + | |
3 | +use super::super::error::*; | |
4 | +use super::super::client::Client; | |
5 | + | |
6 | +#[derive(Debug, Clone)] | |
7 | +pub struct Upload { | |
8 | + client: Client, | |
9 | +} | |
10 | + | |
11 | +impl Upload { | |
12 | + pub(crate) async fn new(name :&str) -> Result<()> { | |
13 | + Ok(()) | |
14 | + /* | |
15 | + match response.status() { | |
16 | + 200 => Ok(self), | |
17 | + status => Err(Self::status_error(status)), | |
18 | + } | |
19 | + */ | |
20 | + } | |
21 | +} | ... | ... |
1 | 1 | use js_sys::JsString; |
2 | 2 | use mogwai::prelude::*; |
3 | 3 | use wasm_bindgen::prelude::*; |
4 | -use web_sys::{Window, window, Response, Request, RequestInit, RequestMode}; | |
4 | +use web_sys::{Window, window, Response, Request, RequestInit, RequestMode, ReadableStream}; | |
5 | 5 | use super::error::*; |
6 | 6 | |
7 | 7 | use std::result::Result as StdResult; |
... | ... | @@ -25,7 +25,7 @@ impl Client { |
25 | 25 | |
26 | 26 | pub async fn get(&self, url :&str) -> Result<(Response, String)> { |
27 | 27 | let mut init = RequestInit::new(); |
28 | - let request = REQUEST( &url | |
28 | + let request = REQUEST( url | |
29 | 29 | , init . method("GET") |
30 | 30 | . mode(RequestMode::Cors) )?; |
31 | 31 | |
... | ... | @@ -46,7 +46,7 @@ impl Client { |
46 | 46 | |
47 | 47 | pub async fn put(&self, url :&str, data :&str) -> Result<Response> { |
48 | 48 | let mut init = RequestInit::new(); |
49 | - let request = REQUEST( &url | |
49 | + let request = REQUEST( url | |
50 | 50 | , init . method("PUT") |
51 | 51 | . mode(RequestMode::Cors) |
52 | 52 | . body(Some(&data.into())) )?; |
... | ... | @@ -61,4 +61,25 @@ impl Client { |
61 | 61 | |
62 | 62 | Ok(response) |
63 | 63 | } |
64 | + | |
65 | + pub async fn post_stream( &self | |
66 | + , url :&str | |
67 | + , mime_type :&str | |
68 | + , data :ReadableStream) -> Result<Response> { | |
69 | + let mut init = RequestInit::new(); | |
70 | + let request = REQUEST( url | |
71 | + , init . method("POST") | |
72 | + . mode(RequestMode::Cors) | |
73 | + . body(Some(&data.into())) )?; | |
74 | + | |
75 | + request . headers() | |
76 | + . set("Content-Type", mime_type)?; | |
77 | + | |
78 | + let response = JsFuture::from( self.window | |
79 | + . fetch_with_request(&request)) | |
80 | + . await? | |
81 | + . dyn_into::<Response>()?; | |
82 | + | |
83 | + Ok(response) | |
84 | + } | |
64 | 85 | } | ... | ... |
1 | 1 | use mogwai::prelude::*; |
2 | -use web_sys::{HtmlInputElement, window, ImageBitmap, HtmlCanvasElement, CanvasRenderingContext2d}; | |
2 | +use web_sys::{HtmlInputElement, ImageBitmap, HtmlCanvasElement, CanvasRenderingContext2d}; | |
3 | 3 | |
4 | -use crate::component::upload::view::upload_preview_view; | |
4 | +use super::upload::Upload; | |
5 | 5 | |
6 | -async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> | |
7 | - , bitmap :ImageBitmap ) { | |
6 | +pub(super) async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> | |
7 | + , upload :Upload ) { | |
8 | 8 | while let Some(dom) = rx_canvas.next().await { |
9 | 9 | match dom.inner_read() { |
10 | 10 | Either::Left(c) => { |
... | ... | @@ -12,10 +12,12 @@ async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> |
12 | 12 | let context = canvas |
13 | 13 | . get_context("2d").unwrap().unwrap() |
14 | 14 | . dyn_into::<CanvasRenderingContext2d>().unwrap(); |
15 | - context . draw_image_with_image_bitmap_and_dw_and_dh( &bitmap | |
16 | - , 0.0, 0.0 | |
17 | - , 100.0, 100.0 ) | |
18 | - . unwrap(); | |
15 | + context | |
16 | + . draw_image_with_image_bitmap_and_dw_and_dh( | |
17 | + &upload.bitmap() | |
18 | + , 0.0, 0.0 | |
19 | + , canvas.width() as f64, canvas.height() as f64 ) | |
20 | + . unwrap(); | |
19 | 21 | }, |
20 | 22 | _ => (), |
21 | 23 | } |
... | ... | @@ -25,36 +27,28 @@ async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> |
25 | 27 | pub(super) async fn upload_logic( mut rx_logic: broadcast::Receiver<DomEvent> |
26 | 28 | , tx_previews: mpmc::Sender<ListPatch<ViewBuilder<Dom>>> |
27 | 29 | ) { |
30 | + let mut uploads: ListPatchModel<Upload> = ListPatchModel::new(); | |
31 | + | |
32 | + mogwai::spawn(uploads.stream().for_each(move |patch| { | |
33 | + let patch = patch.map(|u| u.into()); | |
34 | + let tx_previews = tx_previews.clone(); | |
35 | + async move { | |
36 | + tx_previews.send(patch).await.unwrap(); | |
37 | + } | |
38 | + })); | |
39 | + | |
28 | 40 | while let Some(msg) = rx_logic.next().await { |
29 | 41 | match msg.clone_inner() { |
30 | 42 | Either::Left(val) => { |
31 | - let event = val.dyn_into::<Event>().unwrap(); | |
32 | - let target = event.target().unwrap(); | |
33 | - let element = target.dyn_into::<HtmlInputElement>().unwrap(); | |
34 | - let filelist = element.files().unwrap(); | |
43 | + let filelist = val.dyn_into::<Event>().unwrap() | |
44 | + . target().unwrap() | |
45 | + . dyn_into::<HtmlInputElement>().unwrap() | |
46 | + . files().unwrap(); | |
35 | 47 | |
36 | - let mut previews = vec![]; | |
37 | 48 | for index in 0..filelist.length() { |
38 | - let (tx_canvas, rx_canvas) = broadcast::bounded(1); | |
39 | 49 | let file = filelist.item(index).unwrap(); |
40 | - let bitmap = | |
41 | - JsFuture::from( window().unwrap() | |
42 | - . create_image_bitmap_with_blob(&file.clone().into()).unwrap()); | |
43 | - let bitmap = bitmap | |
44 | - . await.unwrap() | |
45 | - . dyn_into::<ImageBitmap>().unwrap(); | |
46 | - | |
47 | - let view = upload_preview_view( tx_canvas | |
48 | - , file.name() | |
49 | - , file.size() | |
50 | - , file.type_() | |
51 | - , file.last_modified() ); | |
52 | - let logic = upload_preview_logic(rx_canvas, bitmap); | |
53 | - | |
54 | - previews.push(Component::from(view).with_logic(logic).into()); | |
50 | + uploads.list_patch_push(Upload::new(file).await); | |
55 | 51 | } |
56 | - let previews = ListPatch::splice(.., previews.into_iter()); | |
57 | - tx_previews.send(previews).await.unwrap(); | |
58 | 52 | }, |
59 | 53 | _ => (), |
60 | 54 | } | ... | ... |
ui/src/component/upload/upload.rs
0 → 100644
1 | +use mogwai::{prelude::*, utils::window}; | |
2 | +use web_sys::{File, ImageBitmap, ReadableStream}; | |
3 | + | |
4 | +use super::{view::upload_preview_view, logic::upload_preview_logic}; | |
5 | + | |
6 | +#[derive(Clone, Debug)] | |
7 | +pub(super) struct Upload { | |
8 | + file :File, | |
9 | + bitmap :ImageBitmap, | |
10 | +} | |
11 | + | |
12 | +impl Upload { | |
13 | + pub(super) async fn new(file :File) -> Upload { | |
14 | + let bitmap = window() | |
15 | + . create_image_bitmap_with_blob(&file.clone().into()) | |
16 | + . unwrap(); | |
17 | + let bitmap = JsFuture::from(bitmap) | |
18 | + . await.unwrap() | |
19 | + . dyn_into::<ImageBitmap>().unwrap(); | |
20 | + | |
21 | + Self { file, bitmap } | |
22 | + } | |
23 | + | |
24 | + pub(super) fn mime_type(&self) -> String { | |
25 | + self.file.type_() | |
26 | + } | |
27 | + | |
28 | + pub(super) fn data(&self) -> ReadableStream { | |
29 | + self.file.stream() | |
30 | + } | |
31 | + | |
32 | + pub(super) fn bitmap(&self) -> ImageBitmap { | |
33 | + self.to_owned().bitmap | |
34 | + } | |
35 | +} | |
36 | + | |
37 | +impl From<Upload> for Component<Dom> { | |
38 | + fn from(upload: Upload) -> Self { | |
39 | + let (tx_canvas, rx_canvas) = broadcast::bounded(1); | |
40 | + | |
41 | + let view = upload_preview_view( tx_canvas | |
42 | + , upload.file.name() | |
43 | + , upload.file.size() | |
44 | + , upload.file.type_() | |
45 | + , upload.file.last_modified() ); | |
46 | + let logic = upload_preview_logic(rx_canvas, upload); | |
47 | + | |
48 | + Component::from(view).with_logic(logic) | |
49 | + } | |
50 | +} | |
51 | + | |
52 | +impl From<Upload> for ViewBuilder<Dom> { | |
53 | + fn from(upload: Upload) -> Self { | |
54 | + let component :Component<Dom> = upload.into(); | |
55 | + component.into() | |
56 | + } | |
57 | +} | ... | ... |
... | ... | @@ -11,18 +11,15 @@ pub(super) fn upload_preview_view( tx_canvas :broadcast::Sender<Dom> |
11 | 11 | |
12 | 12 | builder! { |
13 | 13 | <li style:display="flex"> |
14 | - <div style:width="100px" | |
15 | - style:height="100px"> | |
16 | - <canvas post:build=post_build /> | |
17 | - </div> | |
18 | - <div style:width="fit-content"> | |
19 | - <ul> | |
20 | - <li>{format!("filename: {}", filename)}</li> | |
21 | - <li>{format!("size: {}", size)}</li> | |
22 | - <li>{format!("mime type: {}", mime_type)}</li> | |
23 | - <li>{format!("modification time: {}", mtime)}</li> | |
24 | - </ul> | |
25 | - </div> | |
14 | + <canvas width="75px" | |
15 | + height="75px" | |
16 | + post:build=post_build /> | |
17 | + <ul> | |
18 | + <li>{format!("filename: {}", filename)}</li> | |
19 | + <li>{format!("size: {}", size)}</li> | |
20 | + <li>{format!("mime type: {}", mime_type)}</li> | |
21 | + <li>{format!("modification time: {}", mtime)}</li> | |
22 | + </ul> | |
26 | 23 | </li> |
27 | 24 | } |
28 | 25 | } |
... | ... | @@ -30,14 +27,15 @@ pub(super) fn upload_preview_view( tx_canvas :broadcast::Sender<Dom> |
30 | 27 | pub(super) fn upload_view( tx_logic: broadcast::Sender<DomEvent> |
31 | 28 | , rx_previews: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>> |
32 | 29 | ) -> ViewBuilder<Dom> { |
30 | +// <div class="spin"></div> | |
33 | 31 | builder! { |
34 | 32 | <div class="upload"> |
35 | - <div class="spin"></div> | |
36 | 33 | <div> |
37 | 34 | <input type="file" |
38 | 35 | multiple="multiple" |
39 | 36 | accept="image/*" |
40 | - on:change=tx_logic.sink()/> | |
37 | + on:change=tx_logic.sink() /> | |
38 | + <button>"Upload"</button> | |
41 | 39 | </div> |
42 | 40 | <ul patch:children=rx_previews> |
43 | 41 | </ul> | ... | ... |
Please
register
or
login
to post a comment