Split into modules, use resolver v2, initial web loader implementation

main
Elnu 1 year ago
parent 33e42c17bb
commit b995846e12

11
Cargo.lock generated

@ -1448,9 +1448,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.18" version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" checksum = "20b9b67e2ca7dd9e9f9285b759de30ff538aab981abaaf7bc9bd90b84a0126c3"
dependencies = [ dependencies = [
"base64 0.21.2", "base64 0.21.2",
"bytes", "bytes",
@ -2224,11 +2224,12 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [ dependencies = [
"winapi", "cfg-if",
"windows-sys",
] ]
[[package]] [[package]]

@ -1,2 +1,3 @@
[workspace] [workspace]
members = ["dyesub-tool"] members = ["dyesub-tool"]
resolver = "2"

@ -16,4 +16,11 @@ strum = "0.25.0"
strum_macros = "0.25.2" strum_macros = "0.25.2"
wasm-bindgen = "0.2.87" wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37" wasm-bindgen-futures = "0.4.37"
web-sys = { version = "0.3.64", features = ["FileList", "Blob"] }
[dependencies.web-sys]
version = "0.3.64"
features = [
"File",
"FileList",
"Request"
]

@ -0,0 +1,64 @@
use leptos::{*, html::Input};
use web_sys::File;
use crate::{components::ResultMessageData, utils::read_file, error::ReadKleError, models::Colorway};
use super::ResultMessage;
#[component]
pub 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 || {
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,
}));
file_input().unwrap().set_value("");
}
}
});
};
view! { cx,
<h3>"Load KLE JSON from file"</h3>
<ResultMessage message=result_message />
<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());
on_submit();
}
/>
}
}
async fn read_kle_from_file(
file: ReadSignal<Option<File>>,
) -> Result<kle_serial::Keyboard, ReadKleError> {
Ok(serde_json::from_str(&read_file(file).await?)?)
}

@ -0,0 +1,59 @@
use leptos::{*, html::Input};
use web_sys::SubmitEvent;
use crate::{error::FetchKleError, utils::fetch_file, models::Colorway};
use super::{ResultMessage, ResultMessageData};
async fn fetch_layout(url: &str) -> Result<kle_serial::Keyboard, FetchKleError> {
let layout_string = fetch_file(url).await?;
Ok(serde_json::from_str(&layout_string)?)
}
#[component]
pub fn KeyboardFromWeb(
cx: Scope,
set_keyboard: WriteSignal<Option<kle_serial::Keyboard>>,
) -> impl IntoView {
let (result_message, set_result_message) = create_signal(cx, Option::<ResultMessageData>::None);
let input_element: NodeRef<Input> = create_node_ref(cx);
let on_submit = move |e: SubmitEvent| {
e.prevent_default();
spawn_local(async move {
let url = input_element.get().unwrap().value();
match fetch_layout(&url).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: <FetchKleError as Into<&str>>::into(err).to_string(),
colorway: Colorway::Bad,
}));
input_element().unwrap().set_value("");
}
}
})
};
view! { cx,
<h3>"Load KLE JSON from web"</h3>
<ResultMessage message=result_message />
<form class="f-row align-items:center" on:submit=on_submit>
<input class="width:100%" type="text" placeholder="KLE layout, gist URL, gist ID, or direct URL to JSON file" required node_ref=input_element />
<input type="submit" value="Load" />
</form>
}
}

@ -0,0 +1,8 @@
mod resultmessage;
pub use resultmessage::{ResultMessage, ResultMessageData};
mod keyboardfromfile;
pub use keyboardfromfile::KeyboardFromFile;
mod keyboardfromweb;
pub use keyboardfromweb::KeyboardFromWeb;

@ -0,0 +1,31 @@
use leptos::*;
use class_list::class_list;
use crate::models::Colorway;
#[derive(Clone)]
pub struct ResultMessageData {
pub title: String,
pub message: View,
pub colorway: Colorway,
}
#[component]
pub fn ResultMessage(cx: Scope, message: ReadSignal<Option<ResultMessageData>>) -> impl IntoView {
let (open, set_open) = create_signal(cx, true);
create_effect(cx, move |_| {
message.track();
set_open(true);
});
move || view! { cx,
<Show when=move || open() fallback=|_| ()>
{move || message().map(|ResultMessageData { title, message, colorway }| view! { cx,
<div class=class_list!["box", colorway] style="position: relative">
<strong class="block titlebar">{title}</strong>
<button class="iconbutton" on:click=move |_| set_open(false) style="position: absolute; bottom: 0.5em; right: 0.5em">"×"</button>
<p>{message}</p>
</div>
})}
</Show>
}
}

@ -0,0 +1,21 @@
use derive_more::From;
use strum_macros::IntoStaticStr;
use wasm_bindgen::JsValue;
#[derive(From, IntoStaticStr)]
pub enum FetchFileError {
#[strum(serialize = "Failed to fetch file")]
Request(JsValue),
#[strum(serialize = "Failed to read fetched file to string")]
ReadToString,
}
impl ToString for FetchFileError {
fn to_string(&self) -> String {
use FetchFileError::*;
match self {
Request(error) => error.as_string(),
_ => None,
}.unwrap_or_else(|| "".to_string())
}
}

@ -0,0 +1,35 @@
use derive_more::From;
use strum_macros::IntoStaticStr;
use super::FetchFileError;
#[derive(From, IntoStaticStr)]
pub enum FetchKleError {
#[strum(serialize = "Invalid source. Must be KLE layout, gist URL, gist ID, or direct URL to JSON file")]
InvalidSource,
#[strum(serialize = "Failed to fetch file")]
FetchFile(FetchFileError),
#[strum(serialize = "Failed to parse KLE JSON")]
Serde(serde_json::Error),
}
impl ToString for FetchKleError {
fn to_string(&self) -> String {
use FetchKleError::*;
match self {
FetchFile(error) => Some(match error {
FetchFileError::Request(error_info) => {
let mut full_error = <&FetchFileError as Into<&str>>::into(error).to_string();
if let Some(error_info) = error_info.as_string() {
full_error.push_str(": ");
full_error.push_str(&error_info);
}
full_error
},
FetchFileError::ReadToString => error.to_string(),
}),
Serde(error) => Some(error.to_string()),
_ => None,
}.unwrap_or_else(|| "".to_string())
}
}

@ -0,0 +1,11 @@
mod fetchfile;
pub use fetchfile::FetchFileError;
mod readfile;
pub use readfile::ReadFileError;
mod fetchkle;
pub use fetchkle::FetchKleError;
mod readkle;
pub use readkle::ReadKleError;

@ -0,0 +1,23 @@
use derive_more::From;
use strum_macros::IntoStaticStr;
use wasm_bindgen::JsValue;
#[derive(From, IntoStaticStr)]
pub enum ReadFileError {
#[strum(serialize = "No file chosen")]
NoFile,
#[strum(serialize = "Failed to open file")]
TextAwait(JsValue),
#[strum(serialize = "Failed to parse file to string")]
ParseToString,
}
impl ToString for ReadFileError {
fn to_string(&self) -> String {
use ReadFileError::*;
match self {
TextAwait(error) => error.as_string(),
_ => None
}.unwrap_or_else(|| "".to_string())
}
}

@ -0,0 +1,21 @@
use derive_more::From;
use strum_macros::IntoStaticStr;
use super::ReadFileError;
#[derive(From, IntoStaticStr)]
pub enum ReadKleError {
ReadFile(ReadFileError),
#[strum(serialize = "Failed to parse KLE JSON")]
Serde(serde_json::Error),
}
impl ToString for ReadKleError {
fn to_string(&self) -> String {
use ReadKleError::*;
match self {
ReadFile(error) => error.to_string(),
Serde(error) => error.to_string(),
}
}
}

@ -1,166 +1,30 @@
#![feature(async_closure)] #![feature(async_closure)]
#[allow(dead_code)]
mod render; mod render;
pub mod svg; mod error;
mod components;
use class_list::class_list; mod utils;
use derive_more::From; mod models;
use leptos::{ev::SubmitEvent, html::Input, *};
use strum_macros::IntoStaticStr;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::File;
#[derive(Clone)]
struct ResultMessageData {
title: String,
message: View,
colorway: Colorway,
}
#[derive(Default, Clone, Copy, IntoStaticStr)]
#[allow(dead_code)] #[allow(dead_code)]
enum Colorway { pub mod svg;
#[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 {
let (open, set_open) = create_signal(cx, true);
create_effect(cx, move |_| {
message.track();
set_open(true);
});
move || view! { cx,
<Show when=move || open() fallback=|_| ()>
{move || message().map(|ResultMessageData { title, message, colorway }| view! { cx,
<div class=class_list!["box", <Colorway as Into<&str>>::into(colorway)] style="position: relative">
<strong class="block titlebar">{title}</strong>
<button class="iconbutton" on:click=move |_| set_open(false) style="position: absolute; bottom: 0.5em; right: 0.5em">"×"</button>
<p>{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 { use leptos::*;
fn to_string(&self) -> String { use wasm_bindgen::JsCast;
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( use components::*;
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 || {
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,
}));
file_input().unwrap().set_value("");
}
}
});
};
view! { cx,
<h3>"Load KLE JSON from file"</h3>
<ResultMessage message=result_message />
<form class="f-row align-items:center">
<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());
on_submit();
}
/>
</form>
}
}
#[component] #[component]
fn App(cx: Scope) -> impl IntoView { fn App(cx: Scope) -> impl IntoView {
let (keyboard, set_keyboard) = create_signal(cx, None); let (_keyboard, set_keyboard) = create_signal(cx, None);
view! { cx, view! { cx,
<KeyboardFromFile set_keyboard /> <KeyboardFromFile set_keyboard />
<KeyboardFromWeb set_keyboard />
} }
} }
pub fn main() { fn main() {
let root = document().query_selector("main").unwrap().unwrap(); let root = document().query_selector("main").unwrap().unwrap();
mount_to(root.unchecked_into(), |cx| view! { cx, <App/> }); mount_to(root.unchecked_into(), |cx| view! { cx, <App/> });
} }

@ -0,0 +1,25 @@
use class_list::traits::ClassList;
use strum_macros::IntoStaticStr;
#[derive(Default, Clone, Copy, IntoStaticStr)]
#[allow(dead_code)]
pub enum Colorway {
#[default]
#[strum(serialize = "plain")]
Plain,
#[strum(serialize = "info")]
Info,
#[strum(serialize = "ok")]
Ok,
#[strum(serialize = "warn")]
Warn,
#[strum(serialize = "bad")]
Bad,
}
impl ClassList for Colorway {
fn to_class_list(&self, _normalize: bool) -> String {
let class: &str = self.into();
class.to_string()
}
}

@ -0,0 +1,2 @@
mod colorway;
pub use colorway::Colorway;

@ -0,0 +1,34 @@
use leptos::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{File, Window, Request};
use crate::error::{ReadFileError, FetchFileError};
pub fn window() -> Window {
web_sys::window().unwrap()
}
pub async fn read_file(
file: ReadSignal<Option<File>>,
) -> Result<String, ReadFileError> {
let file = match file() {
Some(file) => file,
None => return Err(ReadFileError::NoFile),
};
match JsFuture::from(file.text())
.await?
.as_string()
{
Some(contents) => Ok(contents),
None => Err(ReadFileError::ParseToString),
}
}
pub async fn fetch_file(url: &str) -> Result<String, FetchFileError> {
let request = Request::new_with_str(url)?;
let response = JsFuture::from(window().fetch_with_request(&request)).await?;
match response.as_string() {
Some(string) => Ok(string),
None => Err(FetchFileError::ReadToString),
}
}
Loading…
Cancel
Save