diff --git a/leptos/Cargo.toml b/leptos/Cargo.toml index d317212..e40ee4e 100644 --- a/leptos/Cargo.toml +++ b/leptos/Cargo.toml @@ -18,6 +18,7 @@ leptos-use = { version = "0.15.7", features = [ leptos_meta = "0.7.7" leptos_router = { version = "0.7.7", features = ["tracing"] } reactive_stores = "0.1.8" +reqwest.workspace = true serde = { workspace = true, features = ["derive"] } stylance = "0.5.5" thaw = { version = "0.4.4", features = ["csr"] } @@ -29,4 +30,8 @@ web-sys = { version = "0.3.77", features = [ "CanvasRenderingContext2d", "MediaRecorder", "AudioNode", + "File", + "FileList", ] } +itertools.workspace = true +strum = { workspace = true, features = ["derive"] } diff --git a/leptos/README.md b/leptos/README.md index f1f6b07..a3c3475 100644 --- a/leptos/README.md +++ b/leptos/README.md @@ -4,5 +4,15 @@ A simple SPA using leptos. ![](https://file.notion.so/f/f/5f1a3a94-a29f-407d-80af-617105cb793d/8938aed8-802c-4a23-9d47-10cd593ef1ca/image.png?table=block&id=1c0e579b-ff89-80df-abb6-c7766ae48094&spaceId=5f1a3a94-a29f-407d-80af-617105cb793d&expirationTimestamp=1742860800000&signature=FwK8fJ19MquzJKhRT0vRtm7JaOl7yjWOgEYxAz95gtE&downloadName=image.png) -- A QR Scanner +## A QR Scanner + +Scans QR code in `single` or `multiple` mode. + - A Audio Recorder + +## A Form + +The values are stored in local storage at key set by field `Code`. + +![leptos_gif](https://github.com/user-attachments/assets/41e725d9-bb92-4976-9880-e849d378cbde_gif](https://github.com/user-attachments/assets/41e725d9-bb92-4976-9880-e849d378cbde) + diff --git a/leptos/app.css b/leptos/app.css index 9d3b3dd..d3bd57a 100644 --- a/leptos/app.css +++ b/leptos/app.css @@ -46,3 +46,8 @@ main { max-height: 100px; background-color: ivory; } + +.ehr_list-372e0a1 label { + width: 150px; + align-items: center; +} diff --git a/leptos/rust-toolchain.toml b/leptos/rust-toolchain.toml index e7b48e6..0954352 100644 --- a/leptos/rust-toolchain.toml +++ b/leptos/rust-toolchain.toml @@ -1,2 +1,4 @@ [toolchain] +channel = "stable" targets = ["wasm32-unknwon-unknown"] +profile = "minimal" diff --git a/leptos/src/_app.module.scss b/leptos/src/_app.module.scss index 9a1ddc6..ccdeb56 100644 --- a/leptos/src/_app.module.scss +++ b/leptos/src/_app.module.scss @@ -46,3 +46,8 @@ main { max-height: 100px; background-color: ivory; } + +.ehr_list label { + width: 150px; + align-items: center; +} diff --git a/leptos/src/app.rs b/leptos/src/app.rs index e8546d5..b1d6b9a 100644 --- a/leptos/src/app.rs +++ b/leptos/src/app.rs @@ -34,6 +34,7 @@ pub fn App() -> impl IntoView {
+ @@ -52,6 +53,7 @@ fn AppSpinner() -> impl IntoView { let loading = store.loading(); view! { + // Show spinner when global `loading` is set to true. diff --git a/leptos/src/components/audio.rs b/leptos/src/components/audio.rs index aad895c..ae85f95 100644 --- a/leptos/src/components/audio.rs +++ b/leptos/src/components/audio.rs @@ -45,19 +45,21 @@ pub fn AudioStream() -> impl IntoView { }) as Box); Effect::new(move |_| { - node.get().map(|v| match stream.get() { - Some(Ok(s)) => { - tracing::info!("Setting stream {s:?} to src..."); - v.set_src_object(Some(&s)); - let recorder = MediaRecorder::new_with_media_stream(&s).unwrap(); - recorder.set_ondataavailable(Some(on_data_available.as_ref().unchecked_ref())); - recorder.start_with_time_slice(500).unwrap(); + if let Some(v) = node.get() { + match stream.get() { + Some(Ok(s)) => { + tracing::info!("Setting stream {s:?} to src..."); + v.set_src_object(Some(&s)); + let recorder = MediaRecorder::new_with_media_stream(&s).unwrap(); + recorder.set_ondataavailable(Some(on_data_available.as_ref().unchecked_ref())); + recorder.start_with_time_slice(500).unwrap(); + } + Some(Err(e)) => tracing::error!("Failed to get media stream: {e:?}"), + None => tracing::debug!("No stream yet"), } - Some(Err(e)) => tracing::error!("Failed to get media stream: {e:?}"), - None => tracing::debug!("No stream yet"), - }); + } }); - + // start/stop recording let _effect = Effect::watch( move || start_rec.get(), @@ -103,12 +105,10 @@ pub fn AudioStream() -> impl IntoView { view! { // Eventually I was to draw something related to audio stream here. - + } } diff --git a/leptos/src/components/form.rs b/leptos/src/components/form.rs new file mode 100644 index 0000000..746e2e9 --- /dev/null +++ b/leptos/src/components/form.rs @@ -0,0 +1,65 @@ +//! A sample forms + +use strum::IntoEnumIterator; + +use codee::string::JsonSerdeCodec; +use leptos::prelude::*; +use leptos_qr_scanner::Scan; +use leptos_use::storage::use_local_storage; +use reactive_stores::Store; +use thaw::*; + +use crate::components::*; +use crate::css::styles; +use crate::storage::KeyVal; +use crate::storage::{GlobalState, GlobalStateStoreFields}; + +#[derive(Debug, strum::EnumIter, strum::Display)] +enum Gender { + Male, + Female, + Other, +} + +#[component] +pub fn Form() -> impl IntoView { + let storage_key = RwSignal::new("".to_string()); + + let (state, set_state, _) = use_local_storage::(storage_key); + + let upload_patient_consent_form = move |file_list: FileList| { + let len = file_list.length(); + for i in 0..len { + if let Some(file) = file_list.get(i) { + tracing::info!("File to upload: {}", file.name()); + } + } + }; + + view! { +
"Form"
+ + + // Everything starts with this key + + + + + // Patient + + + + + + + "Drag file here" + + + + } +} diff --git a/leptos/src/components/input.rs b/leptos/src/components/input.rs new file mode 100644 index 0000000..c5fc94d --- /dev/null +++ b/leptos/src/components/input.rs @@ -0,0 +1,79 @@ +//! Input component. + +use crate::storage::KeyVal; +use itertools::Itertools; +use leptos::prelude::*; +use thaw::*; + +#[component] +pub fn InputWithLabel( + key: String, + state: Signal, + set_state: WriteSignal, +) -> impl IntoView { + let label = key.split("_").join(" "); + let key1 = key.to_string(); + + view! { + + + // Could not get thaw::Input to change when value in parent changes. + + + } +} + +#[component] +pub fn SelectWithLabel( + key: String, + options: Vec, + state: Signal, + set_state: WriteSignal, +) -> impl IntoView { + + let label = key.split("_").join(" "); + let key1 = key.to_string(); + + view! { + + + + + } +} + +#[component] +pub fn ListItem( + label: String, + children: Children, +) -> impl IntoView { + view! { + + + {children()} + + } +} diff --git a/leptos/src/components/mod.rs b/leptos/src/components/mod.rs index 8ef4491..8af89f5 100644 --- a/leptos/src/components/mod.rs +++ b/leptos/src/components/mod.rs @@ -11,3 +11,9 @@ pub use qr::*; pub(crate) mod audio; pub use audio::*; + +pub(crate) mod form; +pub use form::*; + +pub(crate) mod input; +pub use input::*; diff --git a/leptos/src/storage.rs b/leptos/src/storage.rs index fd7b73c..bb53b84 100644 --- a/leptos/src/storage.rs +++ b/leptos/src/storage.rs @@ -2,18 +2,26 @@ #![allow(dead_code)] +use std::collections::HashMap; use reactive_stores::Store; +// Local store to store data before we #[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Default)] -pub(crate) struct LocalStorage { - pub trainer: String, +pub(crate) struct EhrSection1 { + pub sample_code: String, + pub patient_name: String, } -impl LocalStorage { +// Local store to store data before we +#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Default)] +pub(crate) struct KeyVal(pub HashMap); + +impl EhrSection1 { /// Key to use in local storage - pub const KEY: &'static str = "login-state"; + pub const KEY: &'static str = "ehr-section-1"; } + /// Global state to be shared across components. #[derive(Clone, Debug, Default, Store)] pub struct GlobalState {