Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

task(recovery): Redo seed enter recovery page #1915

Merged
merged 12 commits into from
Mar 12, 2024
7 changes: 6 additions & 1 deletion kit/src/elements/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use uuid::Uuid;
pub type ValidationError = String;
use crate::elements::label::Label;
use crate::elements::loader::Loader;

use common::icons::outline::Shape as Icon;
use common::icons::Icon as IconElement;

Expand Down Expand Up @@ -151,6 +150,7 @@ pub struct Props<'a> {
select_on_focus: Option<bool>,
onchange: Option<EventHandler<'a, (String, bool)>>,
onreturn: Option<EventHandler<'a, (String, bool, Code)>>,
onfocus: Option<EventHandler<'a, ()>>,
reset: Option<UseState<bool>>,
#[props(default = false)]
disable_onblur: bool,
Expand Down Expand Up @@ -416,6 +416,11 @@ pub fn Input<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> {
maxlength: "{max_length}",
"type": "{typ}",
placeholder: "{cx.props.placeholder}",
onfocus: move |_| {
if let Some(e) = &cx.props.onfocus {
e.call(())
}
},
onblur: move |_| {
if onblur_active {
emit_return(&cx, val.read().to_string(), *valid.current(), Code::Enter);
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/settings/sub_pages/profile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ pub fn ProfileSettings(cx: Scope) -> Element {
class: "col",
span {
aria_label: "seed-word-number-{((idx * 2) + 1).to_string()}",
class: "num", ((idx * 2) + 1).to_string()
class: "num disable-select", ((idx * 2) + 1).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 1).to_string()}",
Expand All @@ -650,7 +650,7 @@ pub fn ProfileSettings(cx: Scope) -> Element {
class: "col",
span {
aria_label: "seed-word-number-{((idx * 2) + 2).to_string()}",
class: "num", ((idx * 2) + 2).to_string()
class: "num disable-select", ((idx * 2) + 2).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 2).to_string()}",
Expand Down
5 changes: 2 additions & 3 deletions ui/src/layouts/log_in/copy_seed_words.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,9 @@ fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Elemen
class: "row",
div {
class: "col",

span {
aria_label: "seed-word-number-{((idx * 2) + 1).to_string()}",
class: "num", ((idx * 2) + 1).to_string()
class: "num disable-select", ((idx * 2) + 1).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 1).to_string()}",
Expand All @@ -80,7 +79,7 @@ fn SeedWords(cx: Scope, page: UseState<AuthPages>, words: Vec<String>) -> Elemen
class: "col",
span {
aria_label: "seed-word-number-{((idx * 2) + 2).to_string()}",
class: "num", ((idx * 2) + 2).to_string()
class: "num disable-select", ((idx * 2) + 2).to_string()
},
span {
aria_label: "seed-word-value-{((idx * 2) + 2).to_string()}",
Expand Down
9 changes: 9 additions & 0 deletions ui/src/layouts/log_in/enter_seed_handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let page = document.getElementById("enter-seed-words-layout");
let inputs = page.getElementsByTagName("input");
for (let input of inputs) {
input.addEventListener("paste", event => {
event.preventDefault();
let paste = (event.clipboardData || window.clipboardData).getData("text");
dioxus.send(paste)
})
}
150 changes: 124 additions & 26 deletions ui/src/layouts/log_in/enter_seed_words.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ use common::{
use dioxus::prelude::*;
use dioxus_desktop::{use_window, LogicalSize};
use futures::{channel::oneshot, StreamExt};
use kit::elements::{button::Button, input, label::Label, Appearance};
use kit::elements::{
button::Button,
input::{self, Options},
label::Label,
Appearance,
};

use crate::get_app_style;

Expand Down Expand Up @@ -38,17 +43,41 @@ struct Cmd {
pub fn Layout(cx: Scope, pin: UseRef<String>, page: UseState<AuthPages>) -> Element {
let state = use_ref(cx, State::load);
let loading = use_state(cx, || false);
let input = use_ref(cx, String::new);
let input: &UseRef<Vec<_>> = use_ref(cx, || (0..12).map(|_| String::new()).collect());
let seed_error = use_state(cx, || None);
let focus = use_ref(cx, || 0);

let window = use_window(cx);

if !matches!(&*page.current(), AuthPages::Success(_)) {
window.set_inner_size(LogicalSize {
width: 500.0,
height: 280.0,
height: 440.0,
});
}

let eval = use_eval(cx);
use_effect(cx, (), move |_| {
to_owned![eval, input];
async move {
if let Ok(eval) = eval(include_str!("./enter_seed_handler.js")) {
loop {
if let Ok(val) = eval.recv().await {
let paste = val.to_string();
let paste = &paste[1..(paste.len() - 1)]; // Trim the apostrophes from the input
if !paste.is_empty() {
let phrases = paste.split("\\n").collect::<Vec<_>>();
for i in 0..12 {
if i < phrases.len() {
input.with_mut(|v: &mut Vec<String>| v[i] = phrases[i].into());
}
}
}
}
}
}
}
});
// todo: show toasts to inform user of errors.
let ch = use_coroutine(cx, |mut rx: UnboundedReceiver<Cmd>| {
to_owned![loading, page, seed_error];
Expand Down Expand Up @@ -109,28 +138,97 @@ pub fn Layout(cx: Scope, pin: UseRef<String>, page: UseState<AuthPages>) -> Elem
aria_label: "instructions",
get_local_text("enter-seed-words.instructions")
},
input::Input {
aria_label: "recovery-seed-input".into(),
focus: true,
placeholder: get_local_text("enter-seed-words.placeholder"),
onchange: move |(x, is_valid): (String, bool)| {
if x.is_empty() || seed_error.get().is_some() {
seed_error.set(None);
}
if is_valid {
*input.write_silent() = x;
} else{
seed_error.set(Some(SeedError::ValidationError));
}
},
onreturn: move |_|{
loading.set(true);
ch.send(Cmd {
seed_words: input.read().clone(),
passphrase: pin.read().clone()
});
}
},
div {
class: "seed-words",
(0..6).map(|idx|{
let idx = idx * 2;
let other = idx + 1;
rsx!(div {
class: "row",
div {
class: "col",
span {
aria_label: "seed-word-number-{(idx + 1).to_string()}",
class: "num disable-select", (idx + 1).to_string()
},
input::Input {
aria_label: "recovery-seed-input".into(),
value: input.read()[idx].clone(),
select_on_focus: *focus.read() == idx,
focus: *focus.read() == idx, // select class gets removed on focus. this forces an update
placeholder: "".into(),
disable_onblur: true,
options: Options {
clear_on_submit: false,
..Default::default()
},
onfocus: move |_|{
*focus.write() = idx;
},
onchange: move |(x, is_valid): (String, bool)| {
if x.is_empty() || seed_error.get().is_some() {
seed_error.set(None);
}
if is_valid {
input.with_mut(|v|v[idx] = x);
} else{
seed_error.set(Some(SeedError::ValidationError));
}
},
onreturn: move |_| {
let f = *focus.read();
*focus.write() = (f + 1) % 12;
}
},
},
div {
class: "col",
span {
aria_label: "seed-word-number-{(other + 1).to_string()}",
class: "num disable-select", (other + 1).to_string()
},
input::Input {
aria_label: "recovery-seed-input".into(),
value: input.read()[other].clone(),
focus: *focus.read() == other,
select_on_focus: *focus.read() == other, // select class gets removed on focus. this forces an update
placeholder: "".into(),
disable_onblur: true,
options: Options {
clear_on_submit: false,
..Default::default()
},
onfocus: move |_|{
*focus.write() = other;
},
onchange: move |(x, is_valid): (String, bool)| {
if x.is_empty() || seed_error.get().is_some() {
seed_error.set(None);
}
if is_valid {
input.with_mut(|v|v[other] = x);
} else{
seed_error.set(Some(SeedError::ValidationError));
}
},
onreturn: move |_| {
if other == 11 {
loading.set(true);
log::debug!("seed {}", input.read().join(" "));
ch.send(Cmd {
seed_words: input.read().join(" ").clone(),
passphrase: pin.read().clone()
});
} else {
let f = *focus.read();
*focus.write() = (f + 1) % 12;
}
}
},
}
})
})
}
seed_error.as_ref().map(|e| rsx!(
span {
aria_label: "input-error",
Expand All @@ -155,7 +253,7 @@ pub fn Layout(cx: Scope, pin: UseRef<String>, page: UseState<AuthPages>) -> Elem
onpress: move |_| {
loading.set(true);
ch.send(Cmd {
seed_words: input.read().clone(),
seed_words: input.read().join(" ").clone(),
passphrase: pin.read().clone()
});
}
Expand Down
4 changes: 4 additions & 0 deletions ui/src/layouts/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@
flex: 1;
display: inline-flex;
flex-direction: row;
.input {
background-color: transparent;
min-height: 0;
}
}

.row {
Expand Down
Loading