|
|
|
@ -1,18 +1,145 @@
|
|
|
|
|
#![feature(async_closure)]
|
|
|
|
|
|
|
|
|
|
mod render;
|
|
|
|
|
pub mod svg;
|
|
|
|
|
|
|
|
|
|
use leptos::*;
|
|
|
|
|
use wasm_bindgen::JsCast;
|
|
|
|
|
use leptos::{*, ev::SubmitEvent, html::Input};
|
|
|
|
|
use wasm_bindgen::{JsCast, JsValue};
|
|
|
|
|
use web_sys::File;
|
|
|
|
|
use strum_macros::IntoStaticStr;
|
|
|
|
|
use derive_more::From;
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
struct ResultMessageData {
|
|
|
|
|
title: String,
|
|
|
|
|
message: View,
|
|
|
|
|
colorway: Colorway,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Default, Clone, Copy, IntoStaticStr)]
|
|
|
|
|
enum Colorway {
|
|
|
|
|
#[default]
|
|
|
|
|
#[strum(serialize = "plain")]
|
|
|
|
|
Plain,
|
|
|
|
|
#[strum(serialize = "info")]
|
|
|
|
|
Info,
|
|
|
|
|
#[strum(serialize = "ok")]
|
|
|
|
|
Ok,
|
|
|
|
|
#[strum(serialize = "warn")]
|
|
|
|
|
Warn,
|
|
|
|
|
#[strum(serialize = "bad")]
|
|
|
|
|
Bad,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[component]
|
|
|
|
|
fn ResultMessage(cx: Scope, message: ReadSignal<Option<ResultMessageData>>) -> impl IntoView {
|
|
|
|
|
view! { cx,
|
|
|
|
|
<Show when=move || message().is_some() fallback=move |_| {}>
|
|
|
|
|
<div class=move || {
|
|
|
|
|
let colorway_str: &str = message().unwrap().colorway.into();
|
|
|
|
|
colorway_str.to_owned() + " box"
|
|
|
|
|
}>
|
|
|
|
|
<h3>{move || message().unwrap().title}</h3>
|
|
|
|
|
<p>{move || message().unwrap().message}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(From, IntoStaticStr)]
|
|
|
|
|
enum ReadKleError {
|
|
|
|
|
#[strum(serialize = "No file chosen")]
|
|
|
|
|
NoFile,
|
|
|
|
|
#[strum(serialize = "Failed to open file")]
|
|
|
|
|
TextAwait(JsValue),
|
|
|
|
|
#[strum(serialize = "Failed to parse file to string")]
|
|
|
|
|
ParseToString,
|
|
|
|
|
#[strum(serialize = "Failed to parse KLE JSON")]
|
|
|
|
|
Serde(serde_json::Error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ToString for ReadKleError {
|
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
|
if let Self::TextAwait(error) = self {
|
|
|
|
|
if let Some(error) = error.as_string() {
|
|
|
|
|
return error;
|
|
|
|
|
}
|
|
|
|
|
} else if let Self::Serde(error) = self {
|
|
|
|
|
return error.to_string();
|
|
|
|
|
}
|
|
|
|
|
"".to_string()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn read_kle_from_file(file: &ReadSignal<Option<File>>) -> Result<kle_serial::Keyboard, ReadKleError> {
|
|
|
|
|
let file = match file() {
|
|
|
|
|
Some(file) => file,
|
|
|
|
|
None => return Err(ReadKleError::NoFile),
|
|
|
|
|
};
|
|
|
|
|
let file_contents = match wasm_bindgen_futures::JsFuture::from(file.text()).await?.as_string() {
|
|
|
|
|
Some(contents) => contents,
|
|
|
|
|
None => return Err(ReadKleError::ParseToString),
|
|
|
|
|
};
|
|
|
|
|
let keyboard = serde_json::from_str(&file_contents)?;
|
|
|
|
|
Ok(keyboard)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[component]
|
|
|
|
|
fn KeyboardFromFile(cx: Scope, set_keyboard: WriteSignal<Option<kle_serial::Keyboard>>) -> impl IntoView {
|
|
|
|
|
let file_input = create_node_ref::<Input>(cx);
|
|
|
|
|
let (file, set_file) = create_signal(cx, Option::<File>::None);
|
|
|
|
|
let (result_message, set_result_message) = create_signal(cx, Option::<ResultMessageData>::None);
|
|
|
|
|
let on_submit = move |e: SubmitEvent| {
|
|
|
|
|
e.prevent_default();
|
|
|
|
|
spawn_local(async move {
|
|
|
|
|
match read_kle_from_file(&file).await {
|
|
|
|
|
Ok(keyboard) => {
|
|
|
|
|
set_result_message(Some(ResultMessageData {
|
|
|
|
|
title: "Success".to_owned(),
|
|
|
|
|
message: view! { cx,
|
|
|
|
|
"Loaded KLE layout "<b>{&keyboard.metadata.name}</b>" successfully!"
|
|
|
|
|
}.into_view(cx),
|
|
|
|
|
colorway: Colorway::Ok,
|
|
|
|
|
}));
|
|
|
|
|
set_keyboard(Some(keyboard));
|
|
|
|
|
},
|
|
|
|
|
Err(err) => set_result_message(Some(ResultMessageData {
|
|
|
|
|
message: view! { cx,
|
|
|
|
|
{
|
|
|
|
|
err.to_string()
|
|
|
|
|
}
|
|
|
|
|
}.into_view(cx),
|
|
|
|
|
title: <ReadKleError as Into<&str>>::into(err).to_string(),
|
|
|
|
|
colorway: Colorway::Bad,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
view! { cx,
|
|
|
|
|
<h3>"Load KLE JSON from file"</h3>
|
|
|
|
|
<ResultMessage message=result_message />
|
|
|
|
|
<form class="f-row align-items:center" on:submit=on_submit>
|
|
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
accept="application/json"
|
|
|
|
|
node_ref=file_input
|
|
|
|
|
on:change=move |_| {
|
|
|
|
|
set_file(file_input().unwrap().files().map(|files| files.get(0)).flatten());
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
<Show when=move || file().is_some() fallback=move |_| {}>
|
|
|
|
|
<input type="submit" value="Load" />
|
|
|
|
|
</Show>
|
|
|
|
|
</form>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[component]
|
|
|
|
|
fn App(cx: Scope) -> impl IntoView {
|
|
|
|
|
let (count, set_count) = create_signal(cx, 0);
|
|
|
|
|
let (keyboard, set_keyboard) = create_signal(cx, None);
|
|
|
|
|
|
|
|
|
|
view! { cx,
|
|
|
|
|
<button on:click=move |_| set_count(count.get() + 1)>
|
|
|
|
|
"Click me: "
|
|
|
|
|
{move || count.get()}
|
|
|
|
|
</button>
|
|
|
|
|
<KeyboardFromFile set_keyboard />
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|