Working on detect type

This commit is contained in:
Adrian Woźniak 2023-04-07 18:07:12 +02:00
parent 04f0620ba4
commit 1002f0c0e5
9 changed files with 229 additions and 130 deletions

View File

@ -18,3 +18,7 @@ strike {
display: inline; display: inline;
text-decoration: line-through; text-decoration: line-through;
} }
bq-rte {
display: block;
}

View File

@ -40,7 +40,6 @@ pub fn on_click_change_day(field_id: crate::FieldId, date: chrono::NaiveDateTime
ev.stop_propagation(); ev.stop_propagation();
ev.prevent_default(); ev.prevent_default();
// info!("{:?}", date);
crate::Msg::StyledDateTimeInputChanged( crate::Msg::StyledDateTimeInputChanged(
field_id, field_id,
StyledDateTimeChanged::DayChanged(Some(date)), StyledDateTimeChanged::DayChanged(Some(date)),
@ -113,6 +112,7 @@ pub fn on_click_change_select_selected(field_id: crate::FieldId, value: Option<u
ev(Ev::Click, move |ev| { ev(Ev::Click, move |ev| {
ev.stop_propagation(); ev.stop_propagation();
ev.prevent_default(); ev.prevent_default();
crate::Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(value)) crate::Msg::StyledSelectChanged(field_id, StyledSelectChanged::Changed(value))
}) })
} }
@ -120,6 +120,7 @@ pub fn on_click_change_select_selected(field_id: crate::FieldId, value: Option<u
pub fn on_click_change_select_remove_multi(field_id: crate::FieldId, value: u32) -> EvHandler { pub fn on_click_change_select_remove_multi(field_id: crate::FieldId, value: u32) -> EvHandler {
ev(Ev::Click, move |ev| { ev(Ev::Click, move |ev| {
ev.stop_propagation(); ev.stop_propagation();
crate::Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value)) crate::Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value))
}) })
} }
@ -162,6 +163,7 @@ fn noop(event: Ev) -> EvHandler {
ev(event, |ev| { ev(event, |ev| {
ev.stop_propagation(); ev.stop_propagation();
ev.prevent_default(); ev.prevent_default();
None as Option<crate::Msg> None as Option<crate::Msg>
}) })
} }

View File

@ -1,13 +1,14 @@
use seed::prelude::*; use seed::prelude::*;
use seed::*; use seed::*;
use web_sys::MouseEvent; use web_sys::{HtmlElement, MouseEvent};
use crate::components::styled_button::{ButtonVariant, StyledButton}; use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::{Icon, StyledIcon}; use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_select::{SelectVariant, StyledSelect, StyledSelectState}; use crate::components::styled_select::{SelectVariant, StyledSelect, StyledSelectState};
use crate::components::styled_select_child::StyledSelectOption; use crate::components::styled_select_child::StyledSelectOption;
use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant}; use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant};
use crate::{ButtonId, FieldId, Msg, RteField}; use crate::styled_select::StyledSelectChanged;
use crate::{BuildMsg, ButtonId, FieldId, Msg, RteField};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum HeadingSize { pub enum HeadingSize {
@ -102,18 +103,17 @@ pub enum RteMsg {
} }
impl RteMsg { impl RteMsg {
fn parse_str(ev: MouseEvent, target: &str, cols: u16, rows: u16) -> Self { fn parse_str(ev: MouseEvent, action_name: &str, cols: u16, rows: u16) -> Self {
match target { match action_name.trim() {
"bold" => Self::Bold, "bold" => Self::Bold,
"italic" => Self::Italic, "italic" => Self::Italic,
"underscore" => Self::Underscore, "underscore" => Self::Underscore,
"strikethrough" => Self::Strikethrough, "strikethrough" => Self::Strikethrough,
"listingDots" => Self::InsertUnorderedList, "listingDots" => Self::InsertUnorderedList,
"listingNumber" => Self::InsertOrderedList, "listingNumber" => Self::InsertOrderedList,
"subscript " => Self::Subscript, "subscript" => Self::Subscript,
"superscript" => Self::Superscript, "superscript" => Self::Superscript,
// "heading" => Self::InsertHeading(),
"table" => Self::TableSetVisibility(true), "table" => Self::TableSetVisibility(true),
"codeAlt" => Self::InsertCode(true), "codeAlt" => Self::InsertCode(true),
"closeRteTableTooltip" => Self::TableSetVisibility(false), "closeRteTableTooltip" => Self::TableSetVisibility(false),
@ -124,7 +124,7 @@ impl RteMsg {
_ => { _ => {
let target = ev.target().unwrap(); let target = ev.target().unwrap();
let h = seed::to_html_el(&target); let h = seed::to_html_el(&target);
error!("unknown rte command for element", h); error!("unknown rte command for element", action_name, h);
unreachable!(); unreachable!();
} }
} }
@ -468,6 +468,104 @@ impl StyledRteState {
} }
} }
#[derive(Debug)]
pub struct DetectFontStyle(FieldId);
impl BuildMsg for DetectFontStyle {
fn build(&self, element: &Option<web_sys::Element>) -> Option<Msg> {
let Some(el) = element else {
return None;
};
let el = el.dyn_ref::<HtmlElement>();
let Some(mut el) = el else {
return None;
};
let mut parent;
loop {
match el.tag_name().as_str() {
"BQ-RTE" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::Normal.as_u32())),
))
}
"H1" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::H1.as_u32())),
))
}
"H2" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::H2.as_u32())),
))
}
"H3" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::H3.as_u32())),
))
}
"H4" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::H4.as_u32())),
))
}
"H5" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::H5.as_u32())),
))
}
"H6" => {
return Some(Msg::StyledSelectChanged(
self.0.clone(),
StyledSelectChanged::Changed(Some(HeadingSize::H6.as_u32())),
))
}
"BODY" => return None,
_ => {}
}
parent = el.parent_element();
let Some(parent) = &parent else {
return None;
};
let Some(parent) = parent.dyn_ref::<HtmlElement>() else {
return None;
};
el = parent;
}
}
fn sender_allowed(&self, element: &Option<web_sys::Element>) -> bool {
let Some(el) = element else {
return false;
};
let el = el.dyn_ref::<HtmlElement>();
let Some(mut el) = el else {
return false;
};
let mut parent;
loop {
match el.tag_name().as_str() {
"BQ-RTE" => return false,
"BODY" => return false,
_ => {}
}
parent = el.parent_element();
let Some(parent) = &parent else {
return false;
};
let Some(parent) = parent.dyn_ref::<HtmlElement>() else {
return false;
};
el = parent;
}
}
}
pub struct StyledRte<'component> { pub struct StyledRte<'component> {
pub field_id: FieldId, pub field_id: FieldId,
pub table_tooltip: Option<&'component StyledRteTableState>, pub table_tooltip: Option<&'component StyledRteTableState>,
@ -514,72 +612,32 @@ impl<'outer> StyledRte<'outer> {
}) })
}; };
let change_handler = { let first_row = self.first_row(click_handler.clone());
let field_id = self.field_id.clone();
ev(Ev::Change, move |event| {
event
.target()
.as_ref()
.ok_or("Can't get event target reference")
.and_then(util::get_value)
.ok()
.and_then(|s| s.parse::<u16>().ok())
.map(|n| Msg::Rte(field_id, RteMsg::TableSetRows(n)))
})
};
let first_row = Self::first_row(click_handler.clone()); let click_detect_font_style = mouse_ev(Ev::Click, |ev| {
let second_row = self.second_row(click_handler, change_handler); let target = ev.target()?;
let el = to_html_el(&target);
tracing::info!("{:?} {:?}", el.tag_name(), el.id());
None as Option<Msg>
});
div![ div![
C!["styledRte"], C!["styledRte"],
attrs![At::Id => id], attrs![At::Id => id],
div![C!["bar"], first_row, second_row], div![C!["bar"], first_row],
div![ div![
C!["editorWrapper"], C!["editorWrapper"],
div![ custom![
Tag::from("bq-rte"),
C!["editor", self.field_id.to_str()], C!["editor", self.field_id.to_str()],
attrs![At::ContentEditable => true], attrs![At::ContentEditable => true],
], click_detect_font_style
]
] ]
] ]
} }
fn first_row(click_handler: EventHandler<Msg>) -> Node<Msg> { fn first_row(&self, click_handler: EventHandler<Msg>) -> Node<Msg> {
let justify = {
let justify_all_button = Self::styled_rte_button(
"Justify All",
ButtonId::JustifyAll,
Icon::JustifyAll,
click_handler.clone(),
);
let justify_center_button = Self::styled_rte_button(
"Justify Center",
ButtonId::JustifyCenter,
Icon::JustifyCenter,
click_handler.clone(),
);
let justify_left_button = Self::styled_rte_button(
"Justify Left",
ButtonId::JustifyLeft,
Icon::JustifyLeft,
click_handler.clone(),
);
let justify_right_button = Self::styled_rte_button(
"Justify Right",
ButtonId::JustifyRight,
Icon::JustifyRight,
click_handler.clone(),
);
div![
C!["group justify"],
justify_all_button,
justify_center_button,
justify_left_button,
justify_right_button
]
};
let formatting = { let formatting = {
let bold_button = let bold_button =
Self::styled_rte_button("Bold", ButtonId::Bold, Icon::Bold, click_handler.clone()); Self::styled_rte_button("Bold", ButtonId::Bold, Icon::Bold, click_handler.clone());
@ -615,7 +673,7 @@ impl<'outer> StyledRte<'outer> {
"Superscript", "Superscript",
ButtonId::Superscript, ButtonId::Superscript,
Icon::Superscript, Icon::Superscript,
click_handler, click_handler.clone(),
); );
div![ div![
@ -628,16 +686,19 @@ impl<'outer> StyledRte<'outer> {
superscript_button, superscript_button,
] ]
}; };
let change_handler = {
div![C!["row firstRow"], formatting, justify] let field_id = self.field_id.clone();
} ev(Ev::Change, move |event| {
event
fn second_row( .target()
&self, .as_ref()
click_handler: EventHandler<Msg>, .ok_or("Can't get event target reference")
change_handler: EventHandler<Msg>, .and_then(util::get_value)
) -> Node<Msg> { .ok()
let font_group = self.font_styles(); .and_then(|s| s.parse::<u16>().ok())
.map(|n| Msg::Rte(field_id, RteMsg::TableSetRows(n)))
})
};
let insert_group = { let insert_group = {
let table_tooltip = self.table_tooltip(click_handler.clone(), change_handler); let table_tooltip = self.table_tooltip(click_handler.clone(), change_handler);
@ -679,7 +740,9 @@ impl<'outer> StyledRte<'outer> {
] ]
}; };
div![C!["row secondRow"], font_group, insert_group,] let font_group = self.font_styles();
div![C!["row firstRow"], font_group, formatting, insert_group]
} }
fn font_styles(&self) -> Node<Msg> { fn font_styles(&self) -> Node<Msg> {
@ -689,13 +752,14 @@ impl<'outer> StyledRte<'outer> {
} else { } else {
HeadingSize::Normal HeadingSize::Normal
}; };
let variant = SelectVariant::Empty;
let selected = vec![StyledSelectOption { let selected = vec![StyledSelectOption {
name: Some(selected.as_str()), name: Some(selected.as_str()),
icon: None, icon: None,
text: Some(selected.as_str()), text: Some(selected.as_str()),
value: selected.as_u32(), value: selected.as_u32(),
class_list: "", class_list: "",
variant: SelectVariant::Normal, variant,
}]; }];
let options = Some(HeadingSize::all().iter().map(|h| StyledSelectOption { let options = Some(HeadingSize::all().iter().map(|h| StyledSelectOption {
@ -704,12 +768,12 @@ impl<'outer> StyledRte<'outer> {
text: Some(h.as_str()), text: Some(h.as_str()),
value: h.as_u32(), value: h.as_u32(),
class_list: "", class_list: "",
variant: SelectVariant::Normal, variant,
})); }));
let font = StyledSelect { let font = StyledSelect {
id: self.field_id.clone(), id: self.field_id.clone(),
variant: SelectVariant::Normal, variant,
dropdown_width: Some(96), dropdown_width: Some(96),
name: "", name: "",
valid: true, valid: true,

View File

@ -9,10 +9,8 @@ pub fn styled_tip<B>(letter: BrowserKey, model: &Model, builder: B) -> Node<Msg>
where where
B: BuildMsg + 'static, B: BuildMsg + 'static,
{ {
model model.key_triggers.insert(letter.clone(), Box::new(builder));
.key_triggers
.borrow_mut()
.insert(letter.clone(), Box::new(builder));
div![ div![
C!["proTip"], C!["proTip"],
strong![C!["strong"], "Pro tip: "], strong![C!["strong"], "Pro tip: "],

View File

@ -63,10 +63,18 @@ pub enum OperationKind {
} }
pub trait BuildMsg: std::fmt::Debug { pub trait BuildMsg: std::fmt::Debug {
fn build(&self) -> Msg; fn build(&self, element: &Option<web_sys::Element>) -> Option<Msg>;
fn sender_allowed(&self, tag_name: Option<&str>) -> bool { fn sender_allowed(&self, element: &Option<web_sys::Element>) -> bool {
tag_name == Some("BODY") if element.is_none() {
return false;
}
let tag = element.as_ref().map(|el| el.tag_name());
matches!(tag.as_deref(), Some("BODY"))
}
fn handle_element(&self, _el: &web_sys::HtmlElement) -> Option<Msg> {
None
} }
} }
@ -274,7 +282,7 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
} }
fn view(model: &Model) -> Node<Msg> { fn view(model: &Model) -> Node<Msg> {
model.key_triggers.borrow_mut().clear(); model.key_triggers.clear();
match model.page { match model.page {
Page::Project Page::Project
@ -390,27 +398,30 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
); );
let key_triggers = model.key_triggers.clone(); let key_triggers = model.key_triggers.clone();
let sender_clone = sender.clone(); {
shared::on_event::keydown(move |ev: KeyboardEvent| { let sender_clone = sender.clone();
let sender = sender_clone.clone(); shared::on_event::keydown(move |ev: KeyboardEvent| {
let key_triggers = key_triggers.clone(); let sender = sender_clone.clone();
let event = seed::to_keyboard_event(&ev); let key_triggers = key_triggers.clone();
let event = seed::to_keyboard_event(&ev);
let active = seed::document().active_element().map(|el| el.tag_name()); let active: Option<web_sys::Element> = seed::document().active_element();
let Ok(key) = event.key().parse::<BrowserKey>() else { let Ok(key) = event.key().parse::<BrowserKey>() else {
return;
};
if let Some(b) = key_triggers.borrow().get(&key) {
if !b.sender_allowed(active.as_deref()) {
return; return;
} };
ev.prevent_default(); if let Some(b) = key_triggers.0.borrow().get(&key) {
ev.stop_propagation(); if !b.sender_allowed(&active) {
let msg = b.build(); return;
sender.clone()(Some(msg)); }
}; ev.prevent_default();
}); ev.stop_propagation();
if let Some(msg) = b.build(&active) {
sender.clone()(Some(msg));
}
};
});
}
{ {
let sender_clone = sender.clone(); let sender_clone = sender.clone();

View File

@ -25,17 +25,18 @@ use crate::{BuildMsg, FieldId, Msg};
pub struct CloseCreateIssueModal; pub struct CloseCreateIssueModal;
impl BuildMsg for CloseCreateIssueModal { impl BuildMsg for CloseCreateIssueModal {
fn build(&self) -> Msg { fn build(&self, element: &Option<web_sys::Element>) -> Option<Msg> {
Msg::ModalDropped Some(Msg::ModalDropped)
} }
fn sender_allowed(&self, tag_name: Option<&str>) -> bool { fn sender_allowed(&self, element: &Option<web_sys::Element>) -> bool {
matches!(tag_name, Some("BODY") | Some("TEXTAREA")) let tag_name = element.as_ref().map(|el| el.tag_name());
matches!(tag_name.as_deref(), Some("BODY") | Some("TEXTAREA"))
} }
} }
pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> { pub fn view(model: &Model, modal: &AddIssueModal) -> Node<Msg> {
model.key_triggers.borrow_mut().insert( model.key_triggers.insert(
BrowserKey::UiKey(UiKey::Escape), BrowserKey::UiKey(UiKey::Escape),
Box::new(CloseCreateIssueModal), Box::new(CloseCreateIssueModal),
); );

View File

@ -31,15 +31,16 @@ mod comments;
pub struct CloseAddComment; pub struct CloseAddComment;
impl BuildMsg for CloseAddComment { impl BuildMsg for CloseAddComment {
fn build(&self) -> Msg { fn build(&self, _element: &Option<web_sys::Element>) -> Option<Msg> {
Msg::ModalChanged(FieldChange::ToggleCommentForm( Some(Msg::ModalChanged(FieldChange::ToggleCommentForm(
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)), FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
false, false,
)) )))
} }
fn sender_allowed(&self, tag_name: Option<&str>) -> bool { fn sender_allowed(&self, element: &Option<web_sys::Element>) -> bool {
matches!(tag_name, Some("TEXTAREA")) let tag = element.as_ref().map(|el| el.tag_name());
tag.as_deref() == Some("TEXTAREA")
} }
} }
@ -51,11 +52,10 @@ pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
return Node::Empty; return Node::Empty;
}; };
{ model
let mut b = model.key_triggers.borrow_mut(); .key_triggers
b.insert(BrowserKey::UiKey(UiKey::Escape), Box::new(CloseIssueModal)); .insert(BrowserKey::UiKey(UiKey::Escape), Box::new(CloseIssueModal))
b.insert(BrowserKey::UiKey(UiKey::Escape), Box::new(CloseAddComment)); .insert(BrowserKey::UiKey(UiKey::Escape), Box::new(CloseAddComment));
}
StyledModal { StyledModal {
variant: ModalVariant::Center, variant: ModalVariant::Center,
@ -294,12 +294,11 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
pub struct EnableCommentBuilder; pub struct EnableCommentBuilder;
impl BuildMsg for EnableCommentBuilder { impl BuildMsg for EnableCommentBuilder {
fn build(&self) -> Msg { fn build(&self, _element: &Option<web_sys::Element>) -> Option<Msg> {
tracing::info!("{self:?}"); Some(Msg::ModalChanged(FieldChange::ToggleCommentForm(
Msg::ModalChanged(FieldChange::ToggleCommentForm(
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)), FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
true, true,
)) )))
} }
} }
@ -307,9 +306,8 @@ impl BuildMsg for EnableCommentBuilder {
pub struct CloseIssueModal; pub struct CloseIssueModal;
impl BuildMsg for CloseIssueModal { impl BuildMsg for CloseIssueModal {
fn build(&self) -> Msg { fn build(&self, _element: &Option<web_sys::Element>) -> Option<Msg> {
tracing::info!("{self:?}"); Some(Msg::ModalDropped)
Msg::ModalDropped
} }
} }

View File

@ -1,4 +1,6 @@
use std::cell::RefCell;
use std::collections::hash_map::HashMap; use std::collections::hash_map::HashMap;
use std::rc::Rc;
use bitque_data::*; use bitque_data::*;
use seed::app::Orders; use seed::app::Orders;
@ -17,8 +19,8 @@ use crate::pages::reports_page::model::ReportsPage;
use crate::pages::sign_in_page::model::SignInPage; use crate::pages::sign_in_page::model::SignInPage;
use crate::pages::sign_up_page::model::SignUpPage; use crate::pages::sign_up_page::model::SignUpPage;
use crate::pages::users_page::model::UsersPage; use crate::pages::users_page::model::UsersPage;
use crate::{BuildMsg, Msg};
use crate::shared::keys::BrowserKey; use crate::shared::keys::BrowserKey;
use crate::{BuildMsg, Msg};
pub trait IssueModal { pub trait IssueModal {
fn epic_id_value(&self) -> Option<u32>; fn epic_id_value(&self) -> Option<u32>;
@ -277,11 +279,31 @@ pub struct Model {
pub epic_ids: Vec<EpicId>, pub epic_ids: Vec<EpicId>,
pub epics_by_id: HashMap<EpicId, Epic>, pub epics_by_id: HashMap<EpicId, Epic>,
pub key_triggers: std::rc::Rc<std::cell::RefCell<HashMap<BrowserKey, Box<dyn BuildMsg>>>>, pub key_triggers: KeyTriggers,
pub distinct_key_up: crate::shared::on_event::Distinct, pub distinct_key_up: crate::shared::on_event::Distinct,
pub show_extras: bool, pub show_extras: bool,
} }
#[derive(Debug, Clone)]
pub struct KeyTriggers(pub Rc<RefCell<HashMap<BrowserKey, Box<dyn BuildMsg>>>>);
impl Default for KeyTriggers {
fn default() -> Self {
Self(Rc::new(RefCell::new(HashMap::with_capacity(64))))
}
}
impl KeyTriggers {
pub fn insert(&self, key: BrowserKey, value: Box<dyn BuildMsg>) -> &Self {
self.0.borrow_mut().insert(key, value);
self
}
pub fn clear(&self) {
self.0.borrow_mut().clear();
}
}
impl Model { impl Model {
pub fn new(host_url: String, ws_url: String, page: Page) -> Self { pub fn new(host_url: String, ws_url: String, page: Page) -> Self {
Self { Self {
@ -319,7 +341,7 @@ impl Model {
show_extras: false, show_extras: false,
modals_stack: vec![], modals_stack: vec![],
modals: Modals::default(), modals: Modals::default(),
key_triggers: std::rc::Rc::new(std::cell::RefCell::new(HashMap::with_capacity(20))), key_triggers: KeyTriggers::default(),
distinct_key_up: crate::shared::on_event::distinct(), distinct_key_up: crate::shared::on_event::distinct(),
} }
} }

View File

@ -16,8 +16,8 @@ mod filters;
struct CreateIssueShortcut(i32); struct CreateIssueShortcut(i32);
impl BuildMsg for CreateIssueShortcut { impl BuildMsg for CreateIssueShortcut {
fn build(&self) -> Msg { fn build(&self, _element: &Option<web_sys::Element>) -> Option<Msg> {
Msg::ModalOpened(ModalType::AddIssue(None)) Some(Msg::ModalOpened(ModalType::AddIssue(None)))
} }
} }
@ -25,7 +25,6 @@ pub fn view(model: &Model) -> Node<Msg> {
{ {
model model
.key_triggers .key_triggers
.borrow_mut()
.insert(BrowserKey::Character('c'), Box::new(CreateIssueShortcut(0))); .insert(BrowserKey::Character('c'), Box::new(CreateIssueShortcut(0)));
} }