Skip to content

Commit

Permalink
feat: implement upload progress indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
msyfls123 committed Apr 23, 2024
1 parent efb0437 commit ba45b5e
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 23 deletions.
111 changes: 89 additions & 22 deletions client/src/component/uploader.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use serde_wasm_bindgen::from_value;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use web_sys::{console, Event, HtmlInputElement};
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
use yew::{ html, prelude::function_component, use_callback, use_context, use_state, Callback, Html, Properties};
use yew::{ html, prelude::function_component, use_callback, use_context, use_memo, use_state, Callback, Html, Properties};
use js_sys::{Promise, JSON::stringify_with_replacer_and_space};
use serde_json::{Value};
use serde::{Deserialize};

use crate::constants::app::AppContext;

#[derive(Deserialize, PartialEq, Clone)]
pub struct Progress {
percent: Value,
total: Value
}

#[derive(Properties, PartialEq)]
pub struct UploaderProps {
pub on_upload_start: Callback<()>,
Expand All @@ -20,6 +29,15 @@ pub fn uploader(props: &UploaderProps) -> Html {
// state
let upload_result = use_state(|| None);
let is_uploading = use_state(|| false);
let progress = use_state(|| 0f64);
let percentage = use_memo(progress.clone(), |progress| {
let progress = *progress.clone();
format!("{:.1}%", progress * 100.0f64)
});
let circle_style = use_memo(progress.clone(), |progress| {
let progress = *progress.clone();
format!("--percentage: {:.1};", progress * 100.0f64)
});
// callbacks
let upload_start = use_callback(
(props.on_upload_start.clone(), is_uploading.clone()),
Expand All @@ -36,37 +54,86 @@ pub fn uploader(props: &UploaderProps) -> Html {
}
);

let on_upload = use_callback(upload_end.clone(), move |e: Event, upload_cb| {
upload_start.emit(());
let upload_cb = upload_cb.clone();
let result = cos_upload.call1(&JsValue::NULL, e.as_ref()).unwrap();
let promise = Promise::from(result);
let closure = Closure::once(Box::new(move |value: JsValue| {
console::log_1(&value);
upload_cb.emit(Some(value));
}) as Box<dyn FnMut(JsValue)>);
let promise = promise.then(&closure);
closure.forget();
let future = JsFuture::from(promise);
spawn_local(async {
match future.await {
Ok(_res) => {},
Err(err) => console::error_1(&err),
};
});
});
let on_upload = use_callback(
(upload_end.clone(), progress.clone()),
move |e: Event, (upload_cb, progress_state)| {
upload_start.emit(());

let progress_state = progress_state.clone();
let closure = Closure::wrap(Box::new(move |payload: JsValue| {
console::log_2(&JsValue::from_str("received"), &payload);
let payload_value: Progress = from_value(payload).unwrap();
let percent = payload_value.percent.as_f64().unwrap();
progress_state.set(percent)
}) as Box<dyn FnMut(JsValue)>);

// invoke upload
let result = cos_upload.call2(
&JsValue::NULL,
e.as_ref(),
closure.as_ref().unchecked_ref()
).unwrap();

closure.forget();

// promise.then
let promise = Promise::from(result);
let upload_cb = upload_cb.clone();
let closure = Closure::once(Box::new(move |value: JsValue| {
console::log_1(&value);
upload_cb.emit(Some(value));
}) as Box<dyn FnMut(JsValue)>);
let promise = promise.then(&closure);
closure.forget();

// spawn promise future
let future = JsFuture::from(promise);
spawn_local(async {
match future.await {
Ok(_res) => {},
Err(err) => console::error_1(&err),
};
});
},
);

html! {
<div>
<div class="upload-item">


{if let Some(data) = upload_result.as_ref() {
let upload_text = stringify_with_replacer_and_space(&data, &JsValue::NULL, &JsValue::from_f64(4.0)).unwrap();
html! { <div>
{ "Upload success" }
<pre>{upload_text.as_string().unwrap_or_default()}</pre>
</div> }
} else if *is_uploading {
html! { <div>{ "Uploading..." }</div> }
html! {
<div class="progress">
<svg
class="progress-circle"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
viewBox="0 0 100 100"
width="100px"
height="100px"
>
<circle
cx="50"
cy="50"
r="45"
stroke-linecap="round"
stroke="#9733EE"
style={<std::string::String as Clone>::clone(&*circle_style.clone())}
/>
</svg>
<div class="outer">
<div class="inner">
<div class="num"> {{ percentage }} </div>
</div>
</div>
</div>
}
} else {
html! {
<input type="file" onchange={move |e: Event| {
Expand Down
50 changes: 50 additions & 0 deletions client/src/css/index.styl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

nav
a
margin: 20px
Expand Down Expand Up @@ -32,3 +33,52 @@ body
.time
.size
font-variant: all-small-caps

.upload-item
margin: 10px 0
padding: 10px
border-width: thin
border-style: dashed
border-color: var(--grey-2_5, rgba(20,30,41,0.12))
.progress
--circle-radius: 50px
--circle-border: 10px
position: relative
.outer
width: calc(2 * var(--circle-radius))
height: calc(2 * var(--circle-radius))
padding: var(--circle-border)
box-shadow: 6px 6px 10px -1px rgba(#000, 0.15),
-6px -6px 10px -1px rgba(#fff, 0.7)
border-radius: 50%
box-sizing: border-box
.inner
height: 100%
display: flex
align-items: center
justify-content: center
border-radius: 50%
box-shadow: inset 4px 4px 6px -1px rgba(#000, 0.2),
inset -4px -4px 6px -1px rgba(#fff, 0.7),
-0.5px -0.5px 0 rgba(#fff, 1),
0.5px 0.5px 0 rgba(#000, 0.15),
0 12px 10px -10px rgba(#000, 0.05)
.num
font-family: 'Courier New', Courier, monospace
color: #555
font-size: 18px

.progress-circle
position: absolute
top: 0
left: 0
circle
--circle: 283
--percentage: 0
fill: none
transform: rotate(-90deg)
transform-origin: center
stroke-width: 10px
stroke-dasharray: var(--circle)
stroke-dashoffset: calc(var(--circle) * (100 - var(--percentage)) / 100)
transition: all 0.5s ease-in-out
5 changes: 4 additions & 1 deletion client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const cos = new COS({
}
});

function handleFileChange(e) {
function handleFileChange(e, progressCb) {
const file = e.target.files[0];
if (!file) {
return Promise.reject('no file');
Expand All @@ -40,6 +40,9 @@ function handleFileChange(e) {
Body: file,
onProgress: function(progressData) {
console.log('percentage', JSON.stringify(progressData));
if (progressCb) {
progressCb(progressData)
}
}
},
function(err, data) {
Expand Down

0 comments on commit ba45b5e

Please sign in to comment.