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 | .markdown { | 34 | .markdown { |
2 | float: left; | 35 | float: left; |
3 | padding: 1em; | 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 | use js_sys::JsString; | 1 | use js_sys::JsString; |
2 | use mogwai::prelude::*; | 2 | use mogwai::prelude::*; |
3 | use wasm_bindgen::prelude::*; | 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 | use super::error::*; | 5 | use super::error::*; |
6 | 6 | ||
7 | use std::result::Result as StdResult; | 7 | use std::result::Result as StdResult; |
@@ -25,7 +25,7 @@ impl Client { | @@ -25,7 +25,7 @@ impl Client { | ||
25 | 25 | ||
26 | pub async fn get(&self, url :&str) -> Result<(Response, String)> { | 26 | pub async fn get(&self, url :&str) -> Result<(Response, String)> { |
27 | let mut init = RequestInit::new(); | 27 | let mut init = RequestInit::new(); |
28 | - let request = REQUEST( &url | 28 | + let request = REQUEST( url |
29 | , init . method("GET") | 29 | , init . method("GET") |
30 | . mode(RequestMode::Cors) )?; | 30 | . mode(RequestMode::Cors) )?; |
31 | 31 | ||
@@ -46,7 +46,7 @@ impl Client { | @@ -46,7 +46,7 @@ impl Client { | ||
46 | 46 | ||
47 | pub async fn put(&self, url :&str, data :&str) -> Result<Response> { | 47 | pub async fn put(&self, url :&str, data :&str) -> Result<Response> { |
48 | let mut init = RequestInit::new(); | 48 | let mut init = RequestInit::new(); |
49 | - let request = REQUEST( &url | 49 | + let request = REQUEST( url |
50 | , init . method("PUT") | 50 | , init . method("PUT") |
51 | . mode(RequestMode::Cors) | 51 | . mode(RequestMode::Cors) |
52 | . body(Some(&data.into())) )?; | 52 | . body(Some(&data.into())) )?; |
@@ -61,4 +61,25 @@ impl Client { | @@ -61,4 +61,25 @@ impl Client { | ||
61 | 61 | ||
62 | Ok(response) | 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 | use mogwai::prelude::*; | 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 | while let Some(dom) = rx_canvas.next().await { | 8 | while let Some(dom) = rx_canvas.next().await { |
9 | match dom.inner_read() { | 9 | match dom.inner_read() { |
10 | Either::Left(c) => { | 10 | Either::Left(c) => { |
@@ -12,10 +12,12 @@ async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> | @@ -12,10 +12,12 @@ async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> | ||
12 | let context = canvas | 12 | let context = canvas |
13 | . get_context("2d").unwrap().unwrap() | 13 | . get_context("2d").unwrap().unwrap() |
14 | . dyn_into::<CanvasRenderingContext2d>().unwrap(); | 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,36 +27,28 @@ async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> | ||
25 | pub(super) async fn upload_logic( mut rx_logic: broadcast::Receiver<DomEvent> | 27 | pub(super) async fn upload_logic( mut rx_logic: broadcast::Receiver<DomEvent> |
26 | , tx_previews: mpmc::Sender<ListPatch<ViewBuilder<Dom>>> | 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 | while let Some(msg) = rx_logic.next().await { | 40 | while let Some(msg) = rx_logic.next().await { |
29 | match msg.clone_inner() { | 41 | match msg.clone_inner() { |
30 | Either::Left(val) => { | 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 | for index in 0..filelist.length() { | 48 | for index in 0..filelist.length() { |
38 | - let (tx_canvas, rx_canvas) = broadcast::bounded(1); | ||
39 | let file = filelist.item(index).unwrap(); | 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,18 +11,15 @@ pub(super) fn upload_preview_view( tx_canvas :broadcast::Sender<Dom> | ||
11 | 11 | ||
12 | builder! { | 12 | builder! { |
13 | <li style:display="flex"> | 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 | </li> | 23 | </li> |
27 | } | 24 | } |
28 | } | 25 | } |
@@ -30,14 +27,15 @@ pub(super) fn upload_preview_view( tx_canvas :broadcast::Sender<Dom> | @@ -30,14 +27,15 @@ pub(super) fn upload_preview_view( tx_canvas :broadcast::Sender<Dom> | ||
30 | pub(super) fn upload_view( tx_logic: broadcast::Sender<DomEvent> | 27 | pub(super) fn upload_view( tx_logic: broadcast::Sender<DomEvent> |
31 | , rx_previews: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>> | 28 | , rx_previews: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>> |
32 | ) -> ViewBuilder<Dom> { | 29 | ) -> ViewBuilder<Dom> { |
30 | +// <div class="spin"></div> | ||
33 | builder! { | 31 | builder! { |
34 | <div class="upload"> | 32 | <div class="upload"> |
35 | - <div class="spin"></div> | ||
36 | <div> | 33 | <div> |
37 | <input type="file" | 34 | <input type="file" |
38 | multiple="multiple" | 35 | multiple="multiple" |
39 | accept="image/*" | 36 | accept="image/*" |
40 | - on:change=tx_logic.sink()/> | 37 | + on:change=tx_logic.sink() /> |
38 | + <button>"Upload"</button> | ||
41 | </div> | 39 | </div> |
42 | <ul patch:children=rx_previews> | 40 | <ul patch:children=rx_previews> |
43 | </ul> | 41 | </ul> |
Please
register
or
login
to post a comment