Fix input, fix submit handle, add validations
This commit is contained in:
parent
2f870a5a0a
commit
b22210d55a
@ -1,9 +1,8 @@
|
||||
#login > .styledForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 124.5px auto 24px;
|
||||
margin: 0 auto 24px;
|
||||
width: 400px;
|
||||
/*padding: 32px 40px;*/
|
||||
background: rgb(255, 255, 255) none repeat scroll 0 0;
|
||||
border-radius: 3px;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 0 10px;
|
||||
@ -11,6 +10,23 @@
|
||||
color: var(--textMedium);
|
||||
}
|
||||
|
||||
#login > .styledForm:first-of-type {
|
||||
margin-top: 124.5px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#login > .styledForm:last-of-type {
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0 10px 10px;
|
||||
}
|
||||
|
||||
#login > .styledForm:first-of-type > .formElement {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#login > .styledForm:last-of-type > .formElement {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#login > .styledForm > .formElement > .formHeading {
|
||||
color: var(--textMedium);
|
||||
font-size: 1em;
|
||||
|
@ -1,4 +1,9 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use seed::{prelude::*, *};
|
||||
use uuid::Uuid;
|
||||
|
||||
use jirs_data::WsMsg;
|
||||
|
||||
use crate::api::send_ws_msg;
|
||||
use crate::model::{LoginPage, Page, PageContent};
|
||||
@ -6,12 +11,9 @@ use crate::shared::styled_button::StyledButton;
|
||||
use crate::shared::styled_field::StyledField;
|
||||
use crate::shared::styled_form::StyledForm;
|
||||
use crate::shared::styled_icon::{Icon, StyledIcon};
|
||||
use crate::shared::styled_textarea::StyledTextarea;
|
||||
use crate::shared::styled_input::StyledInput;
|
||||
use crate::shared::{outer_layout, write_auth_token, ToNode};
|
||||
use crate::{model, FieldId, LoginFieldId, Msg};
|
||||
use jirs_data::WsMsg;
|
||||
use std::str::FromStr;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
||||
if model.page != Page::Login {
|
||||
@ -31,12 +33,15 @@ pub fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>)
|
||||
match msg {
|
||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Username), value) => {
|
||||
page.username = value;
|
||||
page.username_touched = true;
|
||||
}
|
||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Email), value) => {
|
||||
page.email = value;
|
||||
page.email_touched = true;
|
||||
}
|
||||
Msg::InputChanged(FieldId::Login(LoginFieldId::Token), value) => {
|
||||
page.token = value;
|
||||
page.token_touched = true;
|
||||
}
|
||||
Msg::SignInRequest => {
|
||||
send_ws_msg(WsMsg::AuthenticateRequest(
|
||||
@ -77,11 +82,10 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
_ => return empty![],
|
||||
};
|
||||
|
||||
let username = StyledTextarea::build()
|
||||
.one_line()
|
||||
.update_on(Ev::Change)
|
||||
let username = StyledInput::build(FieldId::Login(LoginFieldId::Username))
|
||||
.value(page.username.as_str())
|
||||
.build(FieldId::Login(LoginFieldId::Username))
|
||||
.valid(!page.username_touched || page.username.len() > 1)
|
||||
.build()
|
||||
.into_node();
|
||||
let username_field = StyledField::build()
|
||||
.label("Username")
|
||||
@ -89,11 +93,10 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let email = StyledTextarea::build()
|
||||
.one_line()
|
||||
.update_on(Ev::Change)
|
||||
let email = StyledInput::build(FieldId::Login(LoginFieldId::Email))
|
||||
.value(page.email.as_str())
|
||||
.build(FieldId::Login(LoginFieldId::Email))
|
||||
.valid(!page.email_touched || is_email(page.email.as_str()))
|
||||
.build()
|
||||
.into_node();
|
||||
let email_field = StyledField::build()
|
||||
.label("E-Mail")
|
||||
@ -102,7 +105,9 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
.into_node();
|
||||
|
||||
let submit = if page.login_success {
|
||||
StyledButton::build().success().text("✓")
|
||||
StyledButton::build()
|
||||
.success()
|
||||
.text("✓ Please check your mail")
|
||||
} else {
|
||||
StyledButton::build()
|
||||
.primary()
|
||||
@ -126,11 +131,24 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
span!["Why I don't see password?"]
|
||||
];
|
||||
|
||||
let token = StyledTextarea::build()
|
||||
.one_line()
|
||||
.update_on(Ev::Input)
|
||||
let sign_in_form = StyledForm::build()
|
||||
.heading("Sign In to your account")
|
||||
.on_submit(ev(Ev::Submit, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.prevent_default();
|
||||
Msg::SignInRequest
|
||||
}))
|
||||
.add_field(username_field)
|
||||
.add_field(email_field)
|
||||
.add_field(submit_field)
|
||||
.add_field(no_pass_section)
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let token = StyledInput::build(FieldId::Login(LoginFieldId::Token))
|
||||
.value(page.token.as_str())
|
||||
.build(FieldId::Login(LoginFieldId::Token))
|
||||
.valid(!page.token_touched || is_token(page.token.as_str()))
|
||||
.build()
|
||||
.into_node();
|
||||
let token_field = StyledField::build()
|
||||
.label("Single use token")
|
||||
@ -145,17 +163,42 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
.into_node();
|
||||
let submit_token_field = StyledField::build().input(submit_token).build().into_node();
|
||||
|
||||
let form = StyledForm::build()
|
||||
.heading("Sign In to your account")
|
||||
.add_field(username_field)
|
||||
.add_field(email_field)
|
||||
.add_field(submit_field)
|
||||
.add_field(no_pass_section)
|
||||
let bind_token_form = StyledForm::build()
|
||||
.on_submit(ev(Ev::Submit, |ev| {
|
||||
ev.stop_propagation();
|
||||
ev.prevent_default();
|
||||
Msg::BindClientRequest
|
||||
}))
|
||||
.add_field(token_field)
|
||||
.add_field(submit_token_field)
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let children = vec![form];
|
||||
let children = vec![sign_in_form, bind_token_form];
|
||||
outer_layout(model, "login", children)
|
||||
}
|
||||
|
||||
fn is_token(s: &str) -> bool {
|
||||
uuid::Uuid::from_str(s).is_ok()
|
||||
}
|
||||
|
||||
fn is_email(s: &str) -> bool {
|
||||
let mut has_at = false;
|
||||
let mut has_dot = false;
|
||||
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\n' | ' ' | '\t' | '\r' => return false,
|
||||
'@' if !has_at => {
|
||||
has_at = true;
|
||||
}
|
||||
'@' if has_at => return false,
|
||||
'.' if has_at => {
|
||||
has_dot = true;
|
||||
}
|
||||
_ if has_dot => return true,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -151,9 +151,9 @@ pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let description = StyledTextarea::build()
|
||||
let description = StyledTextarea::build(FieldId::AddIssueModal(IssueFieldId::Description))
|
||||
.height(110)
|
||||
.build(FieldId::AddIssueModal(IssueFieldId::Description))
|
||||
.build()
|
||||
.into_node();
|
||||
let description_field = StyledField::build()
|
||||
.label("Description")
|
||||
|
@ -372,15 +372,15 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||
..
|
||||
} = modal;
|
||||
|
||||
let title = StyledTextarea::build()
|
||||
.value(payload.title.as_str())
|
||||
.add_class("textarea")
|
||||
.max_height(48)
|
||||
.height(0)
|
||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||
IssueFieldId::Title,
|
||||
)))
|
||||
.into_node();
|
||||
let title = StyledTextarea::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||
IssueFieldId::Title,
|
||||
)))
|
||||
.value(payload.title.as_str())
|
||||
.add_class("textarea")
|
||||
.max_height(48)
|
||||
.height(0)
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let description_text = payload.description.as_ref().cloned().unwrap_or_default();
|
||||
let description = StyledEditor::build(FieldId::EditIssueModal(EditIssueModalSection::Issue(
|
||||
@ -468,13 +468,13 @@ fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
|
||||
))
|
||||
});
|
||||
|
||||
let text_area = StyledTextarea::build()
|
||||
.value(form.body.as_str())
|
||||
.placeholder("Add a comment...")
|
||||
.build(FieldId::EditIssueModal(EditIssueModalSection::Comment(
|
||||
CommentFieldId::Body,
|
||||
)))
|
||||
.into_node();
|
||||
let text_area = StyledTextarea::build(FieldId::EditIssueModal(EditIssueModalSection::Comment(
|
||||
CommentFieldId::Body,
|
||||
)))
|
||||
.value(form.body.as_str())
|
||||
.placeholder("Add a comment...")
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let submit = StyledButton::build()
|
||||
.variant(ButtonVariant::Primary)
|
||||
|
@ -225,6 +225,10 @@ pub struct LoginPage {
|
||||
pub token: String,
|
||||
pub login_success: bool,
|
||||
pub bad_token: String,
|
||||
// touched
|
||||
pub username_touched: bool,
|
||||
pub email_touched: bool,
|
||||
pub token_touched: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -84,12 +84,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
PageContent::ProjectSettings(page) => page,
|
||||
_ => return empty![],
|
||||
};
|
||||
let name = StyledTextarea::build()
|
||||
let name = StyledTextarea::build(FieldId::ProjectSettings(ProjectFieldId::Name))
|
||||
.value(page.payload.name.as_ref().cloned().unwrap_or_default())
|
||||
.height(39)
|
||||
.max_height(39)
|
||||
.disable_auto_resize()
|
||||
.build(FieldId::ProjectSettings(ProjectFieldId::Name))
|
||||
.build()
|
||||
.into_node();
|
||||
let name_field = StyledField::build()
|
||||
.label("Name")
|
||||
@ -98,12 +98,12 @@ pub fn view(model: &model::Model) -> Node<Msg> {
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let url = StyledTextarea::build()
|
||||
let url = StyledTextarea::build(FieldId::ProjectSettings(ProjectFieldId::Url))
|
||||
.height(39)
|
||||
.max_height(39)
|
||||
.disable_auto_resize()
|
||||
.value(page.payload.url.as_ref().cloned().unwrap_or_default())
|
||||
.build(FieldId::ProjectSettings(ProjectFieldId::Url))
|
||||
.build()
|
||||
.into_node();
|
||||
let url_field = StyledField::build()
|
||||
.label("Url")
|
||||
|
@ -1,185 +1,185 @@
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::shared::styled_textarea::StyledTextarea;
|
||||
use crate::shared::ToNode;
|
||||
use crate::{FieldChange, FieldId, Msg};
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
|
||||
pub enum Mode {
|
||||
Editor,
|
||||
View,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyledEditorState {
|
||||
pub mode: Mode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyledEditor {
|
||||
id: FieldId,
|
||||
text: String,
|
||||
mode: Mode,
|
||||
update_event: Ev,
|
||||
}
|
||||
|
||||
impl StyledEditor {
|
||||
pub fn build(id: FieldId) -> StyledEditorBuilder {
|
||||
StyledEditorBuilder {
|
||||
id,
|
||||
text: String::new(),
|
||||
mode: Mode::View,
|
||||
update_event: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StyledEditorBuilder {
|
||||
id: FieldId,
|
||||
text: String,
|
||||
mode: Mode,
|
||||
update_event: Option<Ev>,
|
||||
}
|
||||
|
||||
impl StyledEditorBuilder {
|
||||
pub fn text<S>(mut self, text: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.text = text.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(mut self, mode: Mode) -> Self {
|
||||
self.mode = mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> StyledEditor {
|
||||
StyledEditor {
|
||||
id: self.id,
|
||||
text: self.text,
|
||||
mode: self.mode,
|
||||
update_event: self.update_event.unwrap_or_else(|| Ev::KeyUp),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_on(mut self, ev: Ev) -> Self {
|
||||
self.update_event = Some(ev);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNode for StyledEditor {
|
||||
fn into_node(self) -> Node<Msg> {
|
||||
render(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(values: StyledEditor) -> Node<Msg> {
|
||||
let StyledEditor {
|
||||
id,
|
||||
text,
|
||||
mode,
|
||||
update_event,
|
||||
} = values;
|
||||
|
||||
let field_id = id.clone();
|
||||
let on_editor_clicked = mouse_ev(Ev::Click, move |ev| {
|
||||
ev.stop_propagation();
|
||||
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::Editor))
|
||||
});
|
||||
|
||||
let field_id = id.clone();
|
||||
let on_view_clicked = mouse_ev(Ev::Click, move |ev| {
|
||||
ev.stop_propagation();
|
||||
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::View))
|
||||
});
|
||||
|
||||
let editor_id = format!("editor-{}", id);
|
||||
let view_id = format!("view-{}", id);
|
||||
let name = format!("styled-editor-{}", id);
|
||||
|
||||
let text_area = StyledTextarea::build()
|
||||
.height(40)
|
||||
.update_on(update_event)
|
||||
.value(text.as_str())
|
||||
.build(id)
|
||||
.into_node();
|
||||
|
||||
let parsed = comrak::markdown_to_html(
|
||||
text.as_str(),
|
||||
&comrak::ComrakOptions {
|
||||
hardbreaks: false,
|
||||
smart: true,
|
||||
github_pre_lang: true,
|
||||
width: 0,
|
||||
default_info_string: None,
|
||||
unsafe_: false,
|
||||
ext_strikethrough: true,
|
||||
ext_tagfilter: true,
|
||||
ext_table: true,
|
||||
ext_autolink: true,
|
||||
ext_tasklist: true,
|
||||
ext_superscript: true,
|
||||
ext_header_ids: None,
|
||||
ext_footnotes: true,
|
||||
ext_description_lists: true,
|
||||
},
|
||||
);
|
||||
let parsed_node = Node::from_html(parsed.as_str());
|
||||
|
||||
let (editor_radio_node, view_radio_node) = match mode {
|
||||
Mode::Editor => (
|
||||
seed::input![
|
||||
id![editor_id.as_str()],
|
||||
attrs![At::Type => "radio"; At::Name => name.as_str(); At::Class => "editorRadio"; At::Checked => true],
|
||||
],
|
||||
seed::input![
|
||||
id![view_id.as_str()],
|
||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Class => "viewRadio";],
|
||||
],
|
||||
),
|
||||
Mode::View => (
|
||||
seed::input![
|
||||
id![editor_id.as_str()],
|
||||
class!["editorRadio"],
|
||||
attrs![At::Type => "radio"; At::Name => name.as_str();],
|
||||
],
|
||||
seed::input![
|
||||
id![view_id.as_str()],
|
||||
class!["viewRadio"],
|
||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
div![
|
||||
attrs![At::Class => "styledEditor"],
|
||||
label![
|
||||
if mode == Mode::Editor {
|
||||
class!["navbar editorTab activeTab"]
|
||||
} else {
|
||||
class!["navbar editorTab"]
|
||||
},
|
||||
attrs![At::For => editor_id.as_str()],
|
||||
"Editor",
|
||||
on_editor_clicked
|
||||
],
|
||||
label![
|
||||
if mode == Mode::View {
|
||||
class!["navbar viewTab activeTab"]
|
||||
} else {
|
||||
class!["navbar viewTab"]
|
||||
},
|
||||
attrs![At::For => view_id.as_str()],
|
||||
"View",
|
||||
on_view_clicked
|
||||
],
|
||||
editor_radio_node,
|
||||
text_area,
|
||||
view_radio_node,
|
||||
div![attrs![At::Class => "view"], parsed_node],
|
||||
]
|
||||
}
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::shared::styled_textarea::StyledTextarea;
|
||||
use crate::shared::ToNode;
|
||||
use crate::{FieldChange, FieldId, Msg};
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, PartialEq, Hash)]
|
||||
pub enum Mode {
|
||||
Editor,
|
||||
View,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyledEditorState {
|
||||
pub mode: Mode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyledEditor {
|
||||
id: FieldId,
|
||||
text: String,
|
||||
mode: Mode,
|
||||
update_event: Ev,
|
||||
}
|
||||
|
||||
impl StyledEditor {
|
||||
pub fn build(id: FieldId) -> StyledEditorBuilder {
|
||||
StyledEditorBuilder {
|
||||
id,
|
||||
text: String::new(),
|
||||
mode: Mode::View,
|
||||
update_event: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StyledEditorBuilder {
|
||||
id: FieldId,
|
||||
text: String,
|
||||
mode: Mode,
|
||||
update_event: Option<Ev>,
|
||||
}
|
||||
|
||||
impl StyledEditorBuilder {
|
||||
pub fn text<S>(mut self, text: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.text = text.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(mut self, mode: Mode) -> Self {
|
||||
self.mode = mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> StyledEditor {
|
||||
StyledEditor {
|
||||
id: self.id,
|
||||
text: self.text,
|
||||
mode: self.mode,
|
||||
update_event: self.update_event.unwrap_or_else(|| Ev::KeyUp),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_on(mut self, ev: Ev) -> Self {
|
||||
self.update_event = Some(ev);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNode for StyledEditor {
|
||||
fn into_node(self) -> Node<Msg> {
|
||||
render(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(values: StyledEditor) -> Node<Msg> {
|
||||
let StyledEditor {
|
||||
id,
|
||||
text,
|
||||
mode,
|
||||
update_event,
|
||||
} = values;
|
||||
|
||||
let field_id = id.clone();
|
||||
let on_editor_clicked = mouse_ev(Ev::Click, move |ev| {
|
||||
ev.stop_propagation();
|
||||
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::Editor))
|
||||
});
|
||||
|
||||
let field_id = id.clone();
|
||||
let on_view_clicked = mouse_ev(Ev::Click, move |ev| {
|
||||
ev.stop_propagation();
|
||||
Msg::ModalChanged(FieldChange::TabChanged(field_id, Mode::View))
|
||||
});
|
||||
|
||||
let editor_id = format!("editor-{}", id);
|
||||
let view_id = format!("view-{}", id);
|
||||
let name = format!("styled-editor-{}", id);
|
||||
|
||||
let text_area = StyledTextarea::build(id)
|
||||
.height(40)
|
||||
.update_on(update_event)
|
||||
.value(text.as_str())
|
||||
.build()
|
||||
.into_node();
|
||||
|
||||
let parsed = comrak::markdown_to_html(
|
||||
text.as_str(),
|
||||
&comrak::ComrakOptions {
|
||||
hardbreaks: false,
|
||||
smart: true,
|
||||
github_pre_lang: true,
|
||||
width: 0,
|
||||
default_info_string: None,
|
||||
unsafe_: false,
|
||||
ext_strikethrough: true,
|
||||
ext_tagfilter: true,
|
||||
ext_table: true,
|
||||
ext_autolink: true,
|
||||
ext_tasklist: true,
|
||||
ext_superscript: true,
|
||||
ext_header_ids: None,
|
||||
ext_footnotes: true,
|
||||
ext_description_lists: true,
|
||||
},
|
||||
);
|
||||
let parsed_node = Node::from_html(parsed.as_str());
|
||||
|
||||
let (editor_radio_node, view_radio_node) = match mode {
|
||||
Mode::Editor => (
|
||||
seed::input![
|
||||
id![editor_id.as_str()],
|
||||
attrs![At::Type => "radio"; At::Name => name.as_str(); At::Class => "editorRadio"; At::Checked => true],
|
||||
],
|
||||
seed::input![
|
||||
id![view_id.as_str()],
|
||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Class => "viewRadio";],
|
||||
],
|
||||
),
|
||||
Mode::View => (
|
||||
seed::input![
|
||||
id![editor_id.as_str()],
|
||||
class!["editorRadio"],
|
||||
attrs![At::Type => "radio"; At::Name => name.as_str();],
|
||||
],
|
||||
seed::input![
|
||||
id![view_id.as_str()],
|
||||
class!["viewRadio"],
|
||||
attrs![ At::Type => "radio"; At::Name => name.as_str(); At::Checked => true],
|
||||
],
|
||||
),
|
||||
};
|
||||
|
||||
div![
|
||||
attrs![At::Class => "styledEditor"],
|
||||
label![
|
||||
if mode == Mode::Editor {
|
||||
class!["navbar editorTab activeTab"]
|
||||
} else {
|
||||
class!["navbar editorTab"]
|
||||
},
|
||||
attrs![At::For => editor_id.as_str()],
|
||||
"Editor",
|
||||
on_editor_clicked
|
||||
],
|
||||
label![
|
||||
if mode == Mode::View {
|
||||
class!["navbar viewTab activeTab"]
|
||||
} else {
|
||||
class!["navbar viewTab"]
|
||||
},
|
||||
attrs![At::For => view_id.as_str()],
|
||||
"View",
|
||||
on_view_clicked
|
||||
],
|
||||
editor_radio_node,
|
||||
text_area,
|
||||
view_radio_node,
|
||||
div![attrs![At::Class => "view"], parsed_node],
|
||||
]
|
||||
}
|
||||
|
@ -1,62 +1,79 @@
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::shared::ToNode;
|
||||
use crate::Msg;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyledForm {
|
||||
heading: String,
|
||||
fields: Vec<Node<Msg>>,
|
||||
}
|
||||
|
||||
impl StyledForm {
|
||||
pub fn build() -> StyledFormBuilder {
|
||||
StyledFormBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNode for StyledForm {
|
||||
fn into_node(self) -> Node<Msg> {
|
||||
render(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StyledFormBuilder {
|
||||
fields: Vec<Node<Msg>>,
|
||||
heading: String,
|
||||
}
|
||||
|
||||
impl StyledFormBuilder {
|
||||
pub fn add_field(mut self, node: Node<Msg>) -> Self {
|
||||
self.fields.push(node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn heading<S>(mut self, heading: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.heading = heading.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> StyledForm {
|
||||
StyledForm {
|
||||
heading: self.heading,
|
||||
fields: self.fields,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(values: StyledForm) -> Node<Msg> {
|
||||
let StyledForm { heading, fields } = values;
|
||||
div![
|
||||
attrs![At::Class => "styledForm"],
|
||||
div![
|
||||
class!["formElement"],
|
||||
div![class!["formHeading"], heading],
|
||||
fields
|
||||
],
|
||||
]
|
||||
}
|
||||
use seed::{prelude::*, *};
|
||||
|
||||
use crate::shared::ToNode;
|
||||
use crate::Msg;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StyledForm {
|
||||
heading: String,
|
||||
fields: Vec<Node<Msg>>,
|
||||
on_submit: Option<EventHandler<Msg>>,
|
||||
}
|
||||
|
||||
impl StyledForm {
|
||||
pub fn build() -> StyledFormBuilder {
|
||||
StyledFormBuilder::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToNode for StyledForm {
|
||||
fn into_node(self) -> Node<Msg> {
|
||||
render(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StyledFormBuilder {
|
||||
fields: Vec<Node<Msg>>,
|
||||
heading: String,
|
||||
on_submit: Option<EventHandler<Msg>>,
|
||||
}
|
||||
|
||||
impl StyledFormBuilder {
|
||||
pub fn add_field(mut self, node: Node<Msg>) -> Self {
|
||||
self.fields.push(node);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn heading<S>(mut self, heading: S) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
{
|
||||
self.heading = heading.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_submit(mut self, on_submit: EventHandler<Msg>) -> Self {
|
||||
self.on_submit = Some(on_submit);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> StyledForm {
|
||||
StyledForm {
|
||||
heading: self.heading,
|
||||
fields: self.fields,
|
||||
on_submit: self.on_submit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(values: StyledForm) -> Node<Msg> {
|
||||
let StyledForm {
|
||||
heading,
|
||||
fields,
|
||||
on_submit,
|
||||
} = values;
|
||||
let handlers = match on_submit {
|
||||
Some(handler) => vec![handler],
|
||||
_ => vec![],
|
||||
};
|
||||
seed::form![
|
||||
handlers,
|
||||
attrs![At::Class => "styledForm"],
|
||||
div![
|
||||
class!["formElement"],
|
||||
div![class!["formHeading"], heading],
|
||||
fields
|
||||
],
|
||||
]
|
||||
}
|
||||
|
@ -94,17 +94,17 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
||||
_ => empty![],
|
||||
};
|
||||
let field_id = id.clone();
|
||||
let change_handler = keyboard_ev(Ev::KeyUp, move |event| {
|
||||
use wasm_bindgen::JsCast;
|
||||
let change_handler = ev(Ev::Input, move |event| {
|
||||
event.stop_propagation();
|
||||
let value = event
|
||||
.target()
|
||||
.unwrap()
|
||||
.dyn_ref::<web_sys::HtmlInputElement>()
|
||||
.unwrap()
|
||||
.value();
|
||||
let target = event.target().unwrap();
|
||||
let input = seed::to_input(&target);
|
||||
let value = input.value();
|
||||
Msg::InputChanged(field_id, value)
|
||||
});
|
||||
let key_handler = ev(Ev::KeyUp, move |event| {
|
||||
event.stop_propagation();
|
||||
Msg::NoOp
|
||||
});
|
||||
|
||||
div![
|
||||
attrs!(At::Class => wrapper_class_list.join(" ")),
|
||||
@ -116,6 +116,7 @@ pub fn render(values: StyledInput) -> Node<Msg> {
|
||||
At::Type => input_type.unwrap_or_else(|| "text".to_string()),
|
||||
],
|
||||
change_handler,
|
||||
key_handler,
|
||||
],
|
||||
]
|
||||
}
|
||||
|
@ -22,13 +22,24 @@ impl ToNode for StyledTextarea {
|
||||
}
|
||||
|
||||
impl StyledTextarea {
|
||||
pub fn build() -> StyledTextareaBuilder {
|
||||
StyledTextareaBuilder::default()
|
||||
pub fn build(field_id: FieldId) -> StyledTextareaBuilder {
|
||||
StyledTextareaBuilder {
|
||||
id: field_id,
|
||||
height: None,
|
||||
max_height: None,
|
||||
on_change: None,
|
||||
value: "".to_string(),
|
||||
class_list: vec![],
|
||||
update_event: None,
|
||||
placeholder: None,
|
||||
disable_auto_resize: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct StyledTextareaBuilder {
|
||||
id: FieldId,
|
||||
height: Option<usize>,
|
||||
max_height: Option<usize>,
|
||||
on_change: Option<EventHandler<Msg>>,
|
||||
@ -40,11 +51,6 @@ pub struct StyledTextareaBuilder {
|
||||
}
|
||||
|
||||
impl StyledTextareaBuilder {
|
||||
#[inline]
|
||||
pub fn one_line(self) -> Self {
|
||||
self.disable_auto_resize().height(39).max_height(39)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn height(mut self, height: usize) -> Self {
|
||||
self.height = Some(height);
|
||||
@ -94,9 +100,9 @@ impl StyledTextareaBuilder {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn build(self, id: FieldId) -> StyledTextarea {
|
||||
pub fn build(self) -> StyledTextarea {
|
||||
StyledTextarea {
|
||||
id,
|
||||
id: self.id,
|
||||
value: self.value,
|
||||
height: self.height.unwrap_or(110),
|
||||
class_list: self.class_list,
|
||||
@ -145,34 +151,51 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
||||
|
||||
if disable_auto_resize {
|
||||
style_list.push("resize: none".to_string());
|
||||
style_list.push(format!(
|
||||
"height: {h}px; max-height: {h}px; min-height: {h}px",
|
||||
h = max_height
|
||||
));
|
||||
}
|
||||
|
||||
let mut handlers = vec![];
|
||||
|
||||
let handler_disable_auto_resize = disable_auto_resize;
|
||||
let resize_handler = ev(Ev::KeyUp, move |event| {
|
||||
use wasm_bindgen::JsCast;
|
||||
event.stop_propagation();
|
||||
if handler_disable_auto_resize {
|
||||
return Msg::NoOp;
|
||||
}
|
||||
|
||||
let target = match event.target() {
|
||||
Some(el) => el,
|
||||
_ => return Msg::NoOp,
|
||||
};
|
||||
let text_area = target.dyn_ref::<web_sys::HtmlTextAreaElement>().unwrap();
|
||||
let value: String = text_area.value();
|
||||
let target = event.target().unwrap();
|
||||
let textarea = seed::to_textarea(&target);
|
||||
let value = textarea.value();
|
||||
let min_height = get_min_height(value.as_str(), height as f64, handler_disable_auto_resize);
|
||||
|
||||
text_area
|
||||
textarea
|
||||
.style()
|
||||
.set_css_text(format!("height: {min_height}px", min_height = min_height).as_str());
|
||||
Msg::NoOp
|
||||
});
|
||||
handlers.push(resize_handler);
|
||||
|
||||
let handler_disable_auto_resize = disable_auto_resize;
|
||||
let text_input_handler = ev(update_event, move |event| {
|
||||
event.stop_propagation();
|
||||
|
||||
let target = event.target().unwrap();
|
||||
let text = seed::to_textarea(&target).value();
|
||||
Msg::InputChanged(id, text)
|
||||
let textarea = seed::to_textarea(&target);
|
||||
let value = textarea.value();
|
||||
|
||||
if handler_disable_auto_resize && value.contains("\n") {
|
||||
event.prevent_default();
|
||||
}
|
||||
|
||||
Msg::InputChanged(
|
||||
id,
|
||||
if handler_disable_auto_resize {
|
||||
value.trim().to_string()
|
||||
} else {
|
||||
value
|
||||
},
|
||||
)
|
||||
});
|
||||
handlers.push(text_input_handler);
|
||||
|
||||
class_list.push("textAreaInput".to_string());
|
||||
|
||||
@ -185,9 +208,11 @@ pub fn render(values: StyledTextarea) -> Node<Msg> {
|
||||
At::AutoFocus => "true";
|
||||
At::Style => style_list.join(";");
|
||||
At::Placeholder => placeholder.unwrap_or_default();
|
||||
At::Rows => if disable_auto_resize { "1" } else { "auto" }
|
||||
],
|
||||
value,
|
||||
handlers,
|
||||
resize_handler,
|
||||
text_input_handler,
|
||||
]
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user