implement add SVG by URL, prepare for dimensions support

master
Sameer Puri 4 years ago
parent 8a5c068738
commit 2a8ac774fd

3
Cargo.lock generated

@ -669,12 +669,15 @@ dependencies = [
"g-code",
"gloo-file",
"gloo-timers 0.1.0",
"js-sys",
"log",
"paste",
"roxmltree",
"serde",
"svg2gcode",
"svgtypes",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-logger",
"web-sys",
"yew",

@ -18,6 +18,7 @@ codespan = "0.11"
serde = "1"
paste = "1"
log = "0"
svgtypes = "0"
yew = { git = "https://github.com/yewstack/yew.git" }
yewdux-functional = { git = "https://github.com/intendednull/yewdux.git" }
@ -28,3 +29,5 @@ wasm-logger = "0.2"
gloo-file = { version = "0.1", features = ["futures"] }
gloo-timers = "0.1"
base64 = "0.13"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"

@ -2,10 +2,14 @@ use codespan_reporting::term::{emit, termcolor::NoColor, Config};
use g_code::parse::{into_diagnostic, snippet_parser};
use gloo_file::futures::read_as_text;
use gloo_timers::callback::Timeout;
use js_sys::TypeError;
use log::info;
use paste::paste;
use roxmltree::Document;
use std::num::ParseFloatError;
use web_sys::{FileList, HtmlElement};
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{window, FileList, HtmlElement, Response};
use yew::prelude::*;
use yewdux::prelude::{BasicStore, Dispatcher};
use yewdux_functional::use_store;
@ -441,14 +445,13 @@ pub fn settings_form() -> Html {
#[function_component(SvgInput)]
pub fn svg_input() -> Html {
let app = use_store::<AppStore>();
let parsed_state = use_ref(Vec::default);
let parsed_state_cloned = parsed_state.clone();
let onchange = app
.dispatch()
let file_upload_state = use_ref(Vec::default);
let file_upload_state_cloned = file_upload_state.clone();
let file_upload_onchange =
app.dispatch()
.future_callback_with(move |app, file_list: FileList| {
let parsed_state_cloned = parsed_state_cloned.clone();
let file_upload_state_cloned = file_upload_state_cloned.clone();
async move {
let mut results = Vec::with_capacity(file_list.length() as usize);
for file in (0..file_list.length()).filter_map(|i| file_list.item(i)) {
@ -464,6 +467,7 @@ pub fn svg_input() -> Html {
Ok(Svg {
content: text,
filename,
dimensions: [None; 2],
})
}
}),
@ -471,9 +475,9 @@ pub fn svg_input() -> Html {
}
app.reduce(move |app| {
// Clear any errors from previous entry, add new successfully parsed SVGs
(*parsed_state_cloned).borrow_mut().clear();
(*file_upload_state_cloned).borrow_mut().clear();
for result in results.iter() {
(*parsed_state_cloned)
(*file_upload_state_cloned)
.borrow_mut()
.push(result.clone().map(|_| ()));
}
@ -482,27 +486,110 @@ pub fn svg_input() -> Html {
}
});
let errors = parsed_state
let file_upload_errors = file_upload_state
.borrow()
.iter()
.filter_map(|res| res.as_ref().err())
.cloned()
.collect::<Vec<_>>();
let res = if parsed_state.borrow().is_empty() {
let file_upload_res = if file_upload_state.borrow().is_empty() {
None
} else if errors.is_empty() {
} else if file_upload_errors.is_empty() {
Some(Ok(()))
} else {
Some(Err(errors.join("\n")))
Some(Err(file_upload_errors.join("\n")))
};
let url_input_state = use_state(|| Option::<String>::None);
let url_input_parsed = use_state(|| Option::<Result<String, String>>::None);
let url_input_oninput = {
let url_input_state = url_input_state.clone();
let url_input_parsed = url_input_parsed.clone();
Callback::from(move |url: InputData| {
url_input_state.set(Some(url.value));
url_input_parsed.set(None);
})
};
let url_add_loading = use_state(|| false);
let url_add_onclick = {
let url_input_state = url_input_state.clone();
let url_input_parsed = url_input_parsed.clone();
let url_add_loading = url_add_loading.clone();
app.dispatch().future_callback_with(move |app, _| {
let url_input_state = url_input_state.clone();
let url_input_parsed = url_input_parsed.clone();
let url_add_loading = url_add_loading.clone();
url_add_loading.set(true);
let request_url = url_input_state.as_ref().unwrap().clone();
async move {
url_input_parsed.set(None);
let res = JsFuture::from(window().unwrap().fetch_with_str(&request_url))
.await
.map(|res| res.dyn_into::<Response>().unwrap());
url_add_loading.set(false);
match res {
Ok(res) => {
let response_url = res.url();
let text = JsFuture::from(res.text().unwrap())
.await
.unwrap()
.as_string()
.unwrap();
if let Some(err) = Document::parse(&text).err() {
url_input_parsed.set(Some(Err(format!(
"Error parsing {}: {}",
&response_url, err
))));
} else {
app.reduce(move |app| {
app.svgs.push(Svg {
content: text,
filename: response_url,
dimensions: [None; 2],
})
});
};
}
Err(err) => {
url_input_parsed.set(Some(Err(format!(
"Error fetching {}: {:?}",
&request_url,
err.dyn_into::<TypeError>().unwrap().message()
))));
}
}
}
})
};
html! {
<FormGroup success={res.as_ref().map(Result::is_ok)}>
<FormGroup success={file_upload_res.as_ref().map(Result::is_ok).or(url_input_parsed.as_ref().map(Result::is_ok))}>
<FileUpload<(), String>
label="Select SVG files"
accept=".svg"
multiple={true}
parsed={res}
onchange={onchange}
onchange={file_upload_onchange}
/>
<div class="divider text-center" data-content="OR"/>
<Input<String, String>
label="Add an SVG file by URL"
r#type={InputType::Url}
placeholder="https://raw.githubusercontent.com/sameer/svg2gcode/master/examples/Vanderbilt_Commodores_logo.svg"
oninput={url_input_oninput}
button={html_nested!(
<Button
style={ButtonStyle::Primary}
title="Add"
input_group=true
disabled={(*url_input_state).is_none()}
onclick={url_add_onclick}
loading={*url_add_loading}
/>
)}
parsed={(*url_input_parsed).clone()}
/>
</FormGroup>
}

@ -46,7 +46,7 @@ macro_rules! css_class_enum {
#[derive(Properties, PartialEq, Clone)]
pub struct InputProps<T, E>
where
T: Display + Clone + PartialEq,
T: Clone + PartialEq,
E: Display + Clone + PartialEq,
{
pub label: &'static str,
@ -54,8 +54,19 @@ where
pub parsed: Option<Result<T, E>>,
pub placeholder: Option<T>,
pub default: Option<T>,
#[prop_or(InputType::Text)]
pub r#type: InputType,
#[prop_or_default]
pub oninput: Callback<InputData>,
#[prop_or_default]
pub button: Option<VChild<Button>>,
}
css_class_enum! {
InputType {
Text => "text",
Url => "url"
}
}
#[function_component(Input)]
@ -86,9 +97,12 @@ where
{ props.label }
</label>
<div class={classes!(if success || error { Some("has-icon-right") } else { None })}>
<input id={id} class="form-input" type="text" ref={(*node_ref).clone()}
<div class={classes!(if props.button.is_some() { Some("input-group") } else { None })}>
<input id={id} class="form-input" type={props.r#type.to_string()} ref={(*node_ref).clone()}
oninput={props.oninput.clone()} placeholder={ props.placeholder.as_ref().map(ToString::to_string) }
/>
{ props.button.clone().map(Html::from).unwrap_or_default() }
</div>
{
if let Some(parsed) = props.parsed.as_ref() {
match parsed {
@ -380,6 +394,8 @@ pub struct ButtonProps {
pub disabled: bool,
#[prop_or(false)]
pub loading: bool,
#[prop_or(false)]
pub input_group: bool,
pub title: Option<&'static str>,
pub icon: Option<VChild<Icon>>,
#[prop_or_default]
@ -395,6 +411,7 @@ pub fn button(props: &ButtonProps) -> Html {
props.style.to_string(),
if props.disabled { Some("disabled") } else { None },
if props.loading { Some("loading" )} else { None },
if props.input_group { Some("input-group-btn") } else { None }
)}
disabled={props.disabled}
onclick={props.onclick.clone()}

@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use std::num::ParseFloatError;
use svgtypes::Length;
use yewdux::prelude::{BasicStore, Persistent, PersistentStore};
#[derive(Debug, Clone)]
@ -56,10 +57,11 @@ pub struct AppState {
pub svgs: Vec<Svg>,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct Svg {
pub content: String,
pub filename: String,
pub dimensions: [Option<Length>; 2],
}
impl Default for AppState {

Loading…
Cancel
Save