diff --git a/Cargo.lock b/Cargo.lock index 9b8063cb..0ed3fabf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,44 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "crypto-helper" +version = "0.10.0" +dependencies = [ + "asn1-parser", + "base64 0.22.0", + "bcrypt", + "flate2", + "gloo-timers 0.3.0", + "hex", + "hmac-sha256", + "hmac-sha512", + "js-sys", + "log", + "md5", + "oid", + "paste", + "picky", + "picky-krb", + "rand 0.9.0-alpha.0", + "rand_chacha 0.9.0-alpha.0", + "rsa", + "serde", + "serde_json", + "serde_qs", + "sha1 0.11.0-pre.3", + "similar", + "time", + "wasm-bindgen", + "wasm-logger", + "web-sys", + "yew", + "yew-agent", + "yew-hooks", + "yew-notifications", + "yew-router", +] + [[package]] name = "curve25519-dalek" version = "4.1.2" @@ -614,6 +652,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -636,6 +675,17 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.30" @@ -2205,6 +2255,9 @@ name = "similar" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +dependencies = [ + "serde", +] [[package]] name = "slab" @@ -2532,43 +2585,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "web-app" -version = "0.10.0" -dependencies = [ - "asn1-parser", - "base64 0.22.0", - "bcrypt", - "flate2", - "gloo-timers 0.3.0", - "hex", - "hmac-sha256", - "hmac-sha512", - "js-sys", - "log", - "md5", - "oid", - "paste", - "picky", - "picky-krb", - "rand 0.9.0-alpha.0", - "rand_chacha 0.9.0-alpha.0", - "rsa", - "serde", - "serde_json", - "serde_qs", - "sha1 0.11.0-pre.3", - "similar", - "time", - "wasm-bindgen", - "wasm-logger", - "web-sys", - "yew", - "yew-hooks", - "yew-notifications", - "yew-router", -] - [[package]] name = "web-sys" version = "0.3.69" @@ -2691,6 +2707,31 @@ dependencies = [ "yew-macro", ] +[[package]] +name = "yew-agent" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e27eaca61ea9d0e1a6589dce592283b0e62ced527d8a8b28447957295d588f5" +dependencies = [ + "futures", + "gloo-worker 0.4.0", + "serde", + "wasm-bindgen", + "yew", + "yew-agent-macro", +] + +[[package]] +name = "yew-agent-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ad00f6c9436d25c9225ed0fd8eea27e6d2886c1387bf934afdf91e9131b8b77" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "yew-hooks" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index 44f7e56e..eff6de25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "web-app" +name = "crypto-helper" version = "0.10.0" description = "The crypto-helper is an online app that helps to work with the diferent crypto algorithms." edition = "2021" @@ -14,11 +14,20 @@ members = [ "crates/prop-strategies" ] +[[bin]] +name = "crypto-helper-app" +path = "src/bin/main.rs" + +[[bin]] +name = "worker" +path = "src/bin/worker.rs" + [dependencies] yew = { version = "0.21", features = ["csr"] } yew-router = "0.18" yew-notifications = { git = "https://github.com/TheBestTvarynka/yew-notifications.git", features = ["standard-notification"] } yew-hooks = "0.3" +yew-agent = "0.3" # wasm js-sys = "0.3" @@ -57,4 +66,4 @@ oid = { version = "0.2", default-features = false } paste = "1.0" # diff -similar = "2.4" \ No newline at end of file +similar = { version = "2.4", features = ["serde"] } \ No newline at end of file diff --git a/index.html b/index.html index 878295b5..01fc53ea 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ Crypto helper + @@ -32,5 +33,9 @@ + + + + \ No newline at end of file diff --git a/public/styles/diff/styles.scss b/public/styles/diff/styles.scss index ef2c1e73..1688760c 100644 --- a/public/styles/diff/styles.scss +++ b/public/styles/diff/styles.scss @@ -33,4 +33,6 @@ background-color: #edd5ce; padding-right: 0.3em; padding-left: 0.3em; + border-bottom: 2px solid #403735; + margin-right: 0.3em; } \ No newline at end of file diff --git a/public/styles/style.scss b/public/styles/style.scss index 9a2cbf09..ea5e6ff1 100644 --- a/public/styles/style.scss +++ b/public/styles/style.scss @@ -212,3 +212,92 @@ article { color: #dbcfbf; cursor: pointer; } + +.loader-wrapper { + margin-top: 1em; + margin-bottom: 1em; + width: 100%; + display: inline-flex; +} + +.loader { + color: #322a50; + font-size: 25px; + overflow: hidden; + width: 1em; + height: 1em; + border-radius: 50%; + margin: auto; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation: load6 1.3s infinite ease, round 1.3s infinite ease; + animation: load6 1.3s infinite ease, round 1.3s infinite ease; +} +@-webkit-keyframes load6 { + 0% { + box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; + } + 5%, + 95% { + box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; + } + 10%, + 59% { + box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; + } + 20% { + box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; + } + 38% { + box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em; + } + 100% { + box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; + } +} + +@keyframes load6 { + 0% { + box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; + } + 5%, + 95% { + box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; + } + 10%, + 59% { + box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; + } + 20% { + box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; + } + 38% { + box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, -0.82em -0.09em 0 -0.477em; + } + 100% { + box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; + } +} + +@-webkit-keyframes round { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes round { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +}; diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 00000000..bfdf0d8d --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,7 @@ +use crypto_helper::App; + +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + + yew::Renderer::::new().render(); +} diff --git a/src/bin/worker.rs b/src/bin/worker.rs new file mode 100644 index 00000000..b8cf3765 --- /dev/null +++ b/src/bin/worker.rs @@ -0,0 +1,8 @@ +use crypto_helper::diff::{DiffTask, JsonCodec}; +use yew_agent::Registrable; + +fn main() { + wasm_logger::init(wasm_logger::Config::default()); + + DiffTask::registrar().encoding::().register(); +} diff --git a/src/common/loader.rs b/src/common/loader.rs new file mode 100644 index 00000000..ff7258f9 --- /dev/null +++ b/src/common/loader.rs @@ -0,0 +1,10 @@ +use yew::{function_component, html, Html}; + +#[function_component(Loader)] +pub fn loader() -> Html { + html! { +
+
+
+ } +} diff --git a/src/common/mod.rs b/src/common/mod.rs index efbff582..faf59ec6 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,6 +1,7 @@ mod byte_input; mod bytes_viewer; mod checkbox; +mod loader; mod rc_slice; mod simple_output; mod switch; @@ -9,6 +10,7 @@ mod table; use base64::Engine; pub use byte_input::{build_byte_input, ByteInput}; pub use checkbox::Checkbox; +pub use loader::Loader; pub use rc_slice::RcSlice; pub use simple_output::build_simple_output; pub use switch::Switch; diff --git a/src/diff.rs b/src/diff.rs index 2eeda9e0..2f9568da 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -1,15 +1,21 @@ mod diff_algo; mod diff_viewer; +mod task; -use similar::{capture_diff_slices, Algorithm, DiffOp, TextDiff}; +use serde::{Deserialize, Serialize}; +use similar::{Algorithm, DiffOp, TextDiff}; use web_sys::{HtmlInputElement, KeyboardEvent}; use yew::html::onchange::Event; +use yew::platform::spawn_local; use yew::virtual_dom::VNode; -use yew::{classes, function_component, html, use_effect_with, use_state, Callback, Html, TargetCast}; +use yew::{function_component, html, use_effect_with, use_state_eq, Callback, Html, TargetCast}; +use yew_agent::oneshot::use_oneshot_runner; use yew_hooks::use_local_storage; use self::diff_algo::DiffAlgo; use self::diff_viewer::DiffViewer; +pub use self::task::{DiffTask, DiffTaskParams, JsonCodec}; +use crate::common::Loader; const DEFAULT_ORIGINAL: &str = "TheBestTvarynka TheBestTvarynka @@ -30,8 +36,8 @@ const ALL_ALGORITHMS: &[DiffAlgo] = &[ DiffAlgo(Algorithm::Patience), ]; -#[derive(Debug, Clone, PartialEq)] -struct DiffData { +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DiffData { pub original: Vec, pub changed: Vec, pub changes: Vec, @@ -47,6 +53,13 @@ impl DiffData { } } +#[derive(Debug, Clone, PartialEq, Eq)] +enum DiffsState { + None, + Loading, + Diffs(DiffData), +} + fn render_algorithm_options(current_algorithm: DiffAlgo) -> Vec { ALL_ALGORITHMS .iter() @@ -60,10 +73,10 @@ fn render_algorithm_options(current_algorithm: DiffAlgo) -> Vec { #[function_component(DiffPage)] pub fn diff_page() -> Html { - let original = use_state(|| DEFAULT_ORIGINAL.to_owned()); - let changed = use_state(|| DEFAULT_CHANGED.to_owned()); - let algorithm = use_state(|| DEFAULT_ALGORITHM); - let diffs = use_state(|| { + let original = use_state_eq(|| DEFAULT_ORIGINAL.to_owned()); + let changed = use_state_eq(|| DEFAULT_CHANGED.to_owned()); + let algorithm = use_state_eq(|| DEFAULT_ALGORITHM); + let diffs = use_state_eq(|| { let original = DEFAULT_ORIGINAL.chars().collect::>(); let changed = DEFAULT_CHANGED.chars().collect::>(); let changes = TextDiff::configure() @@ -71,26 +84,34 @@ pub fn diff_page() -> Html { .newline_terminated(true) .diff_chars(DEFAULT_ORIGINAL, DEFAULT_CHANGED); - DiffData { + DiffsState::Diffs(DiffData { original, changed, changes: changes.ops().to_owned(), - } + }) }); - let original_data = original.chars().collect::>(); - let changed_data = changed.chars().collect::>(); + let diff_task_params = DiffTaskParams { + original: original.chars().collect::>(), + changed: changed.chars().collect::>(), + algo: *algorithm, + }; let diffs_setter = diffs.setter(); - let algo = *algorithm; - let compute_diff = Callback::from(move |_: ()| { - let changes = capture_diff_slices(algo.into(), &original_data, &changed_data); - - diffs_setter.set(DiffData { - original: original_data.clone(), - changed: changed_data.clone(), - changes, - }); - }); + let diff_task = use_oneshot_runner::(); + let diffs_worker = { + Callback::from(move |_| { + diffs_setter.set(DiffsState::Loading); + + let diff_agent = diff_task.clone(); + let diff_task_params = diff_task_params.clone(); + let diffs_setter = diffs_setter.clone(); + + spawn_local(async move { + let diff_data = diff_agent.run(diff_task_params).await; + diffs_setter.set(DiffsState::Diffs(diff_data)); + }); + }) + }; let original_local_storage = use_local_storage::(LOCAL_STORAGE_ORIGINAL.to_owned()); let original_setter = original.setter(); @@ -118,7 +139,7 @@ pub fn diff_page() -> Html { } if flag { - diffs_setter.set(DiffData::empty()); + diffs_setter.set(DiffsState::None); } }); @@ -149,10 +170,12 @@ pub fn diff_page() -> Html { changed_setter.set(input.value()); }); - let diff = compute_diff.clone(); - let onclick = Callback::from(move |_| { - diff.emit(()); - }); + let onclick = { + let diffs_worker = diffs_worker.clone(); + Callback::from(move |_| { + diffs_worker.emit(()); + }) + }; let algorithm_setter = algorithm.setter(); let on_algorithm_change = Callback::from(move |event: Event| { @@ -164,12 +187,12 @@ pub fn diff_page() -> Html { let onkeydown = Callback::from(move |event: KeyboardEvent| { if event.ctrl_key() && event.code() == "Enter" { - compute_diff.emit(()); + diffs_worker.emit(()); } }); html! { -
+
{"Diff algorithm:"}
@@ -200,7 +223,11 @@ pub fn diff_page() -> Html { {"(ctrl+enter)"}
- + {match (*diffs).clone() { + DiffsState::None => html! {}, + DiffsState::Loading => html! { }, + DiffsState::Diffs(diff) => html! { }, + }}
} } diff --git a/src/diff/diff_algo.rs b/src/diff/diff_algo.rs index e888f94f..2c278807 100644 --- a/src/diff/diff_algo.rs +++ b/src/diff/diff_algo.rs @@ -1,13 +1,15 @@ use std::fmt::{Display, Formatter}; use std::ops::Deref; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use similar::Algorithm; const MYERS: &str = "Myers"; const PATIENCE: &str = "Patience"; const LCS: &str = "Lcs"; -#[derive(Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq, Debug)] pub struct DiffAlgo(pub Algorithm); impl From for Algorithm { @@ -58,3 +60,38 @@ impl TryFrom<&str> for DiffAlgo { }) } } + +impl<'de> Deserialize<'de> for DiffAlgo { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DiffAlgoVisitor; + + impl Visitor<'_> for DiffAlgoVisitor { + type Value = DiffAlgo; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("valid DiffAlgo") + } + + fn visit_str(self, v: &str) -> Result + where + E: Error, + { + DiffAlgo::try_from(v).map_err(|err| E::custom(format!("Can not deserialize DiffAlgo: {:?}", err))) + } + } + + deserializer.deserialize_str(DiffAlgoVisitor) + } +} + +impl Serialize for DiffAlgo { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(self.as_ref()) + } +} diff --git a/src/diff/task.rs b/src/diff/task.rs new file mode 100644 index 00000000..8ba2e6f6 --- /dev/null +++ b/src/diff/task.rs @@ -0,0 +1,56 @@ +use js_sys::Uint8Array; +use serde::{Deserialize, Serialize}; +use similar::capture_diff_slices; +use wasm_bindgen::{JsCast, JsValue}; +use yew_agent::oneshot::oneshot; +use yew_agent::Codec; + +use crate::diff::diff_algo::DiffAlgo; +use crate::diff::DiffData; + +/// Codes for messages encoding/decoding between main thread and worker. +/// +/// We are using the custom codec because default `Bincode` fails to decode [DiffData]. +pub struct JsonCodec; + +impl Codec for JsonCodec { + fn encode(input: I) -> JsValue + where + I: Serialize, + { + let encoded = serde_json::to_string(&input).expect("Json serialization should not fail"); + JsValue::from(Uint8Array::from(encoded.as_bytes())) + } + + fn decode(input: JsValue) -> O + where + O: for<'de> Deserialize<'de>, + { + let encoded = input.dyn_into::().expect("JsValue should be Uint8Array"); + serde_json::from_slice(&encoded.to_vec()).expect("Json deserialization should not fail") + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +pub struct DiffTaskParams { + pub algo: DiffAlgo, + pub original: Vec, + pub changed: Vec, +} + +#[oneshot] +pub async fn DiffTask(params: DiffTaskParams) -> DiffData { + let DiffTaskParams { + algo, + original, + changed, + } = params; + + let changes = capture_diff_slices(algo.into(), &original, &changed); + + DiffData { + original, + changed, + changes, + } +} diff --git a/src/main.rs b/src/lib.rs similarity index 81% rename from src/main.rs rename to src/lib.rs index 506df233..20c6c44f 100644 --- a/src/main.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ mod about; mod asn1; mod common; mod crypto_helper; -mod diff; +pub mod diff; mod footer; mod header; mod jwt; @@ -23,9 +23,12 @@ use header::Header; use jwt::Jwt; use not_found::not_found; use yew::{function_component, html, Html}; +use yew_agent::oneshot::OneshotProvider; use yew_notifications::{Notification, NotificationFactory, NotificationsProvider}; use yew_router::{BrowserRouter, Routable, Switch}; +use crate::diff::{DiffTask, JsonCodec}; + #[derive(Clone, Routable, PartialEq)] enum Route { #[at("/")] @@ -51,7 +54,12 @@ fn switch(routes: Route) -> Html { Route::Asn1Parser => html! { }, Route::CryptoHelper => html! { }, Route::Jwt => html! { }, - Route::Diff => html! { }, + Route::Diff => html! { + // worker.js - will be autogenerated and placed in the root of the destination directory. + path="worker.js"> + + > + }, Route::About => html! { }, Route::NotFound => not_found(), } @@ -73,9 +81,3 @@ pub fn app() -> Html { } } - -fn main() { - wasm_logger::init(wasm_logger::Config::default()); - - yew::Renderer::::new().render(); -}