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;
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.prevent_default();
// info!("{:?}", date);
crate::Msg::StyledDateTimeInputChanged(
field_id,
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.stop_propagation();
ev.prevent_default();
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 {
ev(Ev::Click, move |ev| {
ev.stop_propagation();
crate::Msg::StyledSelectChanged(field_id, StyledSelectChanged::RemoveMulti(value))
})
}
@ -162,6 +163,7 @@ fn noop(event: Ev) -> EvHandler {
ev(event, |ev| {
ev.stop_propagation();
ev.prevent_default();
None as Option<crate::Msg>
})
}

View File

@ -1,13 +1,14 @@
use seed::prelude::*;
use seed::*;
use web_sys::MouseEvent;
use web_sys::{HtmlElement, MouseEvent};
use crate::components::styled_button::{ButtonVariant, StyledButton};
use crate::components::styled_icon::{Icon, StyledIcon};
use crate::components::styled_select::{SelectVariant, StyledSelect, StyledSelectState};
use crate::components::styled_select_child::StyledSelectOption;
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)]
pub enum HeadingSize {
@ -102,18 +103,17 @@ pub enum RteMsg {
}
impl RteMsg {
fn parse_str(ev: MouseEvent, target: &str, cols: u16, rows: u16) -> Self {
match target {
fn parse_str(ev: MouseEvent, action_name: &str, cols: u16, rows: u16) -> Self {
match action_name.trim() {
"bold" => Self::Bold,
"italic" => Self::Italic,
"underscore" => Self::Underscore,
"strikethrough" => Self::Strikethrough,
"listingDots" => Self::InsertUnorderedList,
"listingNumber" => Self::InsertOrderedList,
"subscript " => Self::Subscript,
"subscript" => Self::Subscript,
"superscript" => Self::Superscript,
// "heading" => Self::InsertHeading(),
"table" => Self::TableSetVisibility(true),
"codeAlt" => Self::InsertCode(true),
"closeRteTableTooltip" => Self::TableSetVisibility(false),
@ -124,7 +124,7 @@ impl RteMsg {
_ => {
let target = ev.target().unwrap();
let h = seed::to_html_el(&target);
error!("unknown rte command for element", h);
error!("unknown rte command for element", action_name, h);
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 field_id: FieldId,
pub table_tooltip: Option<&'component StyledRteTableState>,
@ -514,72 +612,32 @@ impl<'outer> StyledRte<'outer> {
})
};
let change_handler = {
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 first_row = Self::first_row(click_handler.clone());
let second_row = self.second_row(click_handler, change_handler);
let click_detect_font_style = mouse_ev(Ev::Click, |ev| {
let target = ev.target()?;
let el = to_html_el(&target);
tracing::info!("{:?} {:?}", el.tag_name(), el.id());
None as Option<Msg>
});
div![
C!["styledRte"],
attrs![At::Id => id],
div![C!["bar"], first_row, second_row],
div![C!["bar"], first_row],
div![
C!["editorWrapper"],
div![
custom![
Tag::from("bq-rte"),
C!["editor", self.field_id.to_str()],
attrs![At::ContentEditable => true],
],
click_detect_font_style
]
]
]
}
fn first_row(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
]
};
fn first_row(&self, click_handler: EventHandler<Msg>) -> Node<Msg> {
let formatting = {
let bold_button =
Self::styled_rte_button("Bold", ButtonId::Bold, Icon::Bold, click_handler.clone());
@ -615,7 +673,7 @@ impl<'outer> StyledRte<'outer> {
"Superscript",
ButtonId::Superscript,
Icon::Superscript,
click_handler,
click_handler.clone(),
);
div![
@ -628,16 +686,19 @@ impl<'outer> StyledRte<'outer> {
superscript_button,
]
};
div![C!["row firstRow"], formatting, justify]
}
fn second_row(
&self,
click_handler: EventHandler<Msg>,
change_handler: EventHandler<Msg>,
) -> Node<Msg> {
let font_group = self.font_styles();
let change_handler = {
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 insert_group = {
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> {
@ -689,13 +752,14 @@ impl<'outer> StyledRte<'outer> {
} else {
HeadingSize::Normal
};
let variant = SelectVariant::Empty;
let selected = vec![StyledSelectOption {
name: Some(selected.as_str()),
icon: None,
text: Some(selected.as_str()),
value: selected.as_u32(),
class_list: "",
variant: SelectVariant::Normal,
variant,
}];
let options = Some(HeadingSize::all().iter().map(|h| StyledSelectOption {
@ -704,12 +768,12 @@ impl<'outer> StyledRte<'outer> {
text: Some(h.as_str()),
value: h.as_u32(),
class_list: "",
variant: SelectVariant::Normal,
variant,
}));
let font = StyledSelect {
id: self.field_id.clone(),
variant: SelectVariant::Normal,
variant,
dropdown_width: Some(96),
name: "",
valid: true,

View File

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

View File

@ -63,10 +63,18 @@ pub enum OperationKind {
}
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 {
tag_name == Some("BODY")
fn sender_allowed(&self, element: &Option<web_sys::Element>) -> bool {
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> {
model.key_triggers.borrow_mut().clear();
model.key_triggers.clear();
match model.page {
Page::Project
@ -390,27 +398,30 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
);
let key_triggers = model.key_triggers.clone();
let sender_clone = sender.clone();
shared::on_event::keydown(move |ev: KeyboardEvent| {
let sender = sender_clone.clone();
let key_triggers = key_triggers.clone();
let event = seed::to_keyboard_event(&ev);
{
let sender_clone = sender.clone();
shared::on_event::keydown(move |ev: KeyboardEvent| {
let sender = sender_clone.clone();
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 {
return;
};
if let Some(b) = key_triggers.borrow().get(&key) {
if !b.sender_allowed(active.as_deref()) {
let Ok(key) = event.key().parse::<BrowserKey>() else {
return;
}
ev.prevent_default();
ev.stop_propagation();
let msg = b.build();
sender.clone()(Some(msg));
};
});
};
if let Some(b) = key_triggers.0.borrow().get(&key) {
if !b.sender_allowed(&active) {
return;
}
ev.prevent_default();
ev.stop_propagation();
if let Some(msg) = b.build(&active) {
sender.clone()(Some(msg));
}
};
});
}
{
let sender_clone = sender.clone();

View File

@ -25,17 +25,18 @@ use crate::{BuildMsg, FieldId, Msg};
pub struct CloseCreateIssueModal;
impl BuildMsg for CloseCreateIssueModal {
fn build(&self) -> Msg {
Msg::ModalDropped
fn build(&self, element: &Option<web_sys::Element>) -> Option<Msg> {
Some(Msg::ModalDropped)
}
fn sender_allowed(&self, tag_name: Option<&str>) -> bool {
matches!(tag_name, Some("BODY") | Some("TEXTAREA"))
fn sender_allowed(&self, element: &Option<web_sys::Element>) -> bool {
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> {
model.key_triggers.borrow_mut().insert(
model.key_triggers.insert(
BrowserKey::UiKey(UiKey::Escape),
Box::new(CloseCreateIssueModal),
);

View File

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

View File

@ -1,4 +1,6 @@
use std::cell::RefCell;
use std::collections::hash_map::HashMap;
use std::rc::Rc;
use bitque_data::*;
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_up_page::model::SignUpPage;
use crate::pages::users_page::model::UsersPage;
use crate::{BuildMsg, Msg};
use crate::shared::keys::BrowserKey;
use crate::{BuildMsg, Msg};
pub trait IssueModal {
fn epic_id_value(&self) -> Option<u32>;
@ -277,11 +279,31 @@ pub struct Model {
pub epic_ids: Vec<EpicId>,
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 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 {
pub fn new(host_url: String, ws_url: String, page: Page) -> Self {
Self {
@ -319,7 +341,7 @@ impl Model {
show_extras: false,
modals_stack: vec![],
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(),
}
}

View File

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