Commit 5b9ef0e36746ad99245c953001fe9a2d4c7c0233
1 parent
b3ee7df8
Get preview and file info from file selector
Showing
7 changed files
with
215 additions
and
51 deletions
| 1 | .markdown { | 1 | .markdown { |
| 2 | - float: left; | ||
| 3 | - padding: 1em; | ||
| 4 | - border-radius: .5em; | ||
| 5 | - border: 1px solid #ddd; | ||
| 6 | - background: #f7f7f7; | 2 | + float: left; |
| 3 | + padding: 1em; | ||
| 4 | + border-radius: .5em; | ||
| 5 | + border: 1px solid #ddd; | ||
| 6 | + background: #f7f7f7; | ||
| 7 | } | 7 | } |
| 8 | 8 | ||
| 9 | .markdown p { | 9 | .markdown p { |
| 10 | - text-align: justify; | ||
| 11 | - text-indent: .5em; | ||
| 12 | - margin-block: .5em; | 10 | + text-align: justify; |
| 11 | + text-indent: .5em; | ||
| 12 | + margin-block: .5em; | ||
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | .markdown > div:first-child { | 15 | .markdown > div:first-child { |
| 16 | - position: fixed; | 16 | + position: fixed; |
| 17 | width: inherit; | 17 | width: inherit; |
| 18 | z-index: 10; | 18 | z-index: 10; |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | .markdown > div:first-child > div { | 21 | .markdown > div:first-child > div { |
| 22 | position: relative; | 22 | position: relative; |
| 23 | - left: .5em; | 23 | + left: .5em; |
| 24 | width: inherit; | 24 | width: inherit; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | .markdown > div:first-child > pre { | 27 | .markdown > div:first-child > pre { |
| 28 | - white-space: pre-wrap; | ||
| 29 | - height: 15em; | 28 | + white-space: pre-wrap; |
| 29 | + height: 15em; | ||
| 30 | width: calc(100% - 10px); | 30 | width: calc(100% - 10px); |
| 31 | - overflow-y: auto; | ||
| 32 | - overflow-x: hidden; | ||
| 33 | - background: #ffffff; | ||
| 34 | - border-radius: .35em; | ||
| 35 | - border: 2px solid #bbb; | ||
| 36 | - margin: 2px 0px; | 31 | + overflow-y: auto; |
| 32 | + overflow-x: hidden; | ||
| 33 | + background: #ffffff; | ||
| 34 | + border-radius: .35em; | ||
| 35 | + border: 2px solid #bbb; | ||
| 36 | + margin: 2px 0px; | ||
| 37 | opacity: .95; | 37 | opacity: .95; |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | .markdown > div:first-child > div > div { | 40 | .markdown > div:first-child > div > div { |
| 41 | - display: inline; | 41 | + display: inline; |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | .markdown > div:first-child > div ul { | 44 | .markdown > div:first-child > div ul { |
| 45 | - height: 15em; | ||
| 46 | - list-style-type: none; | ||
| 47 | - margin: 2px 0px; | ||
| 48 | - overflow-y: auto; | ||
| 49 | - padding-left: 0px; | ||
| 50 | - position: absolute; | ||
| 51 | - scroll-behavior: auto; | ||
| 52 | - top: 1.8em; | 45 | + height: 15em; |
| 46 | + list-style-type: none; | ||
| 47 | + margin: 2px 0px; | ||
| 48 | + overflow-y: auto; | ||
| 49 | + padding-left: 0px; | ||
| 50 | + position: absolute; | ||
| 51 | + scroll-behavior: auto; | ||
| 52 | + top: 1.8em; | ||
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | .markdown blockquote { | 55 | .markdown blockquote { |
| 56 | - border-left: 5px solid #ccc; | ||
| 57 | - margin: .5em 10px; | ||
| 58 | - padding: .5em 10px; | 56 | + border-left: 5px solid #ccc; |
| 57 | + margin: .5em 10px; | ||
| 58 | + padding: .5em 10px; | ||
| 59 | } | 59 | } |
| 60 | 60 | ||
| 61 | .markdown blockquote p { | 61 | .markdown blockquote p { |
| 62 | - text-align: left; | ||
| 63 | - text-indent: 0; | ||
| 64 | - margin-block: 0; | 62 | + text-align: left; |
| 63 | + text-indent: 0; | ||
| 64 | + margin-block: 0; | ||
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | .markdown .footnote-definition > * { | 67 | .markdown .footnote-definition > * { |
| 68 | - display: inline; | 68 | + display: inline; |
| 69 | +} | ||
| 70 | + | ||
| 71 | +@keyframes spinner { | ||
| 72 | + 0% { | ||
| 73 | + transform: translate3d(-50%, -50%, 0) rotate(0deg); | ||
| 74 | + } | ||
| 75 | + 100% { | ||
| 76 | + transform: translate3d(-50%, -50%, 0) rotate(360deg); | ||
| 77 | + } | ||
| 78 | +} | ||
| 79 | + | ||
| 80 | +.spin::before { | ||
| 81 | + animation: 1.5s linear infinite spinner; | ||
| 82 | + animation-play-state: inherit; | ||
| 83 | + border: solid 5px #cfd0d1; | ||
| 84 | + border-bottom-color: #1c87c9; | ||
| 85 | + border-radius: 50%; | ||
| 86 | + content: ""; | ||
| 87 | + height: 1.5em; | ||
| 88 | + width: 1.5em; | ||
| 89 | + position: absolute; | ||
| 90 | + top: 10%; | ||
| 91 | + left: 1.5em; | ||
| 92 | + transform: translate3d(-50%, -50%, 0); | ||
| 93 | + will-change: transform; | ||
| 69 | } | 94 | } |
| @@ -36,20 +36,28 @@ version = "^0.5" | @@ -36,20 +36,28 @@ version = "^0.5" | ||
| 36 | [dependencies.web-sys] | 36 | [dependencies.web-sys] |
| 37 | version = "^0.3" | 37 | version = "^0.3" |
| 38 | features = [ | 38 | features = [ |
| 39 | - "Document", | ||
| 40 | - "DomParser", | ||
| 41 | - "Headers", | ||
| 42 | - "HtmlElement", | ||
| 43 | - "HtmlInputElement", | ||
| 44 | - "MouseEvent", | ||
| 45 | - "Node", | ||
| 46 | - "Request", | ||
| 47 | - "RequestInit", | ||
| 48 | - "RequestMode", | ||
| 49 | - "Response", | ||
| 50 | - "SupportedType", | ||
| 51 | - "Window", | ||
| 52 | -] | 39 | + "Blob", |
| 40 | + "CanvasRenderingContext2d", | ||
| 41 | + "Document", | ||
| 42 | + "DomParser", | ||
| 43 | + "File", | ||
| 44 | + "FileList", | ||
| 45 | + "FileReader", | ||
| 46 | + "ImageBitmap", | ||
| 47 | + "Headers", | ||
| 48 | + "HtmlCanvasElement", | ||
| 49 | + "HtmlElement", | ||
| 50 | + "HtmlInputElement", | ||
| 51 | + "MouseEvent", | ||
| 52 | + "Node", | ||
| 53 | + "ReadableStream", | ||
| 54 | + "Request", | ||
| 55 | + "RequestInit", | ||
| 56 | + "RequestMode", | ||
| 57 | + "Response", | ||
| 58 | + "SupportedType", | ||
| 59 | + "Window", | ||
| 60 | + ] | ||
| 53 | 61 | ||
| 54 | [dev-dependencies] | 62 | [dev-dependencies] |
| 55 | wasm-bindgen-test = "0.2" | 63 | wasm-bindgen-test = "0.2" |
ui/src/component/upload/logic.rs
0 → 100644
| 1 | +use mogwai::prelude::*; | ||
| 2 | +use web_sys::{HtmlInputElement, window, ImageBitmap, HtmlCanvasElement, CanvasRenderingContext2d}; | ||
| 3 | + | ||
| 4 | +use crate::component::upload::view::upload_preview_view; | ||
| 5 | + | ||
| 6 | +async fn upload_preview_logic( mut rx_canvas :broadcast::Receiver<Dom> | ||
| 7 | + , bitmap :ImageBitmap ) { | ||
| 8 | + while let Some(dom) = rx_canvas.next().await { | ||
| 9 | + match dom.inner_read() { | ||
| 10 | + Either::Left(c) => { | ||
| 11 | + let canvas = c.to_owned().dyn_into::<HtmlCanvasElement>().unwrap(); | ||
| 12 | + let context = canvas | ||
| 13 | + . get_context("2d").unwrap().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(); | ||
| 19 | + }, | ||
| 20 | + _ => (), | ||
| 21 | + } | ||
| 22 | + } | ||
| 23 | +} | ||
| 24 | + | ||
| 25 | +pub(super) async fn upload_logic( mut rx_logic: broadcast::Receiver<DomEvent> | ||
| 26 | + , tx_previews: mpmc::Sender<ListPatch<ViewBuilder<Dom>>> | ||
| 27 | + ) { | ||
| 28 | + while let Some(msg) = rx_logic.next().await { | ||
| 29 | + match msg.clone_inner() { | ||
| 30 | + 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(); | ||
| 35 | + | ||
| 36 | + let mut previews = vec![]; | ||
| 37 | + for index in 0..filelist.length() { | ||
| 38 | + let (tx_canvas, rx_canvas) = broadcast::bounded(1); | ||
| 39 | + 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()); | ||
| 55 | + } | ||
| 56 | + let previews = ListPatch::splice(.., previews.into_iter()); | ||
| 57 | + tx_previews.send(previews).await.unwrap(); | ||
| 58 | + }, | ||
| 59 | + _ => (), | ||
| 60 | + } | ||
| 61 | + } | ||
| 62 | +} |
ui/src/component/upload/mod.rs
0 → 100644
| 1 | +mod view; | ||
| 2 | +mod logic; | ||
| 3 | + | ||
| 4 | +use mogwai::prelude::*; | ||
| 5 | + | ||
| 6 | +use self::{view::upload_view, logic::upload_logic}; | ||
| 7 | + | ||
| 8 | +pub(crate) async fn new() -> Component<Dom> { | ||
| 9 | + let (tx_logic, rx_logic) = broadcast::bounded(1); | ||
| 10 | + let (tx_previews, rx_previews) = mpmc::bounded(1); | ||
| 11 | + | ||
| 12 | + let view = upload_view(tx_logic, rx_previews); | ||
| 13 | + let logic = upload_logic(rx_logic, tx_previews); | ||
| 14 | + | ||
| 15 | + Component::from(view).with_logic(logic) | ||
| 16 | +} |
ui/src/component/upload/view.rs
0 → 100644
| 1 | +use mogwai::prelude::*; | ||
| 2 | + | ||
| 3 | +pub(super) fn upload_preview_view( tx_canvas :broadcast::Sender<Dom> | ||
| 4 | + , filename :String | ||
| 5 | + , size :f64 | ||
| 6 | + , mime_type :String | ||
| 7 | + , mtime :f64 ) -> ViewBuilder<Dom> { | ||
| 8 | + let post_build = move |dom: &mut Dom| { | ||
| 9 | + tx_canvas.try_broadcast(dom.clone()).unwrap(); | ||
| 10 | + }; | ||
| 11 | + | ||
| 12 | + builder! { | ||
| 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> | ||
| 26 | + </li> | ||
| 27 | + } | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +pub(super) fn upload_view( tx_logic: broadcast::Sender<DomEvent> | ||
| 31 | + , rx_previews: mpmc::Receiver<ListPatch<ViewBuilder<Dom>>> | ||
| 32 | + ) -> ViewBuilder<Dom> { | ||
| 33 | + builder! { | ||
| 34 | + <div class="upload"> | ||
| 35 | + <div class="spin"></div> | ||
| 36 | + <div> | ||
| 37 | + <input type="file" | ||
| 38 | + multiple="multiple" | ||
| 39 | + accept="image/*" | ||
| 40 | + on:change=tx_logic.sink()/> | ||
| 41 | + </div> | ||
| 42 | + <ul patch:children=rx_previews> | ||
| 43 | + </ul> | ||
| 44 | + </div> | ||
| 45 | + } | ||
| 46 | +} |
| @@ -6,7 +6,7 @@ mod component; | @@ -6,7 +6,7 @@ mod component; | ||
| 6 | 6 | ||
| 7 | use std::panic; | 7 | use std::panic; |
| 8 | 8 | ||
| 9 | -use crate::component::markdown; | 9 | +use crate::component::*; |
| 10 | use log::Level; | 10 | use log::Level; |
| 11 | use mogwai::prelude::*; | 11 | use mogwai::prelude::*; |
| 12 | use wasm_bindgen::prelude::*; | 12 | use wasm_bindgen::prelude::*; |
| @@ -16,8 +16,14 @@ pub async fn main() -> Result<(), JsValue> { | @@ -16,8 +16,14 @@ pub async fn main() -> Result<(), JsValue> { | ||
| 16 | panic::set_hook(Box::new(console_error_panic_hook::hook)); | 16 | panic::set_hook(Box::new(console_error_panic_hook::hook)); |
| 17 | console_log::init_with_level(Level::Trace).unwrap(); | 17 | console_log::init_with_level(Level::Trace).unwrap(); |
| 18 | 18 | ||
| 19 | - let comp = markdown::new().await; | 19 | + let md = markdown::new().await; |
| 20 | + let comp = upload::new().await; | ||
| 20 | 21 | ||
| 21 | - let page = Component::from(builder! {{comp}}); | 22 | + let page = Component::from(builder! { |
| 23 | + <div> | ||
| 24 | + {comp} | ||
| 25 | + {md} | ||
| 26 | + </div> | ||
| 27 | + }); | ||
| 22 | page.build()?.run() | 28 | page.build()?.run() |
| 23 | } | 29 | } |
Please
register
or
login
to post a comment