handle close Epic
This commit is contained in:
parent
a8ff5101be
commit
fc3052630a
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -628,6 +628,7 @@ dependencies = [
|
|||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"derive_enum_iter",
|
"derive_enum_iter",
|
||||||
"derive_enum_primitive",
|
"derive_enum_primitive",
|
||||||
|
"derive_more",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures",
|
"futures",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
@ -2232,10 +2233,10 @@ dependencies = [
|
|||||||
"lettre 0.10.3",
|
"lettre 0.10.3",
|
||||||
"lettre_email",
|
"lettre_email",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
|
||||||
"openssl-sys",
|
"openssl-sys",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tracing",
|
||||||
"uuid 1.3.0",
|
"uuid 1.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3193,7 +3194,6 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-wasm-bindgen",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"uuid 1.3.0",
|
"uuid 1.3.0",
|
||||||
"version_check 0.9.4",
|
"version_check 0.9.4",
|
||||||
@ -3418,12 +3418,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -4227,7 +4221,6 @@ dependencies = [
|
|||||||
"cfg-if 0.1.10",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
"memory_units",
|
"memory_units",
|
||||||
"spin",
|
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ chrono = { version = "*", features = ["serde"] }
|
|||||||
diesel = { version = "2.0.3", features = ["postgres", "numeric", "uuid", "r2d2"], optional = true }
|
diesel = { version = "2.0.3", features = ["postgres", "numeric", "uuid", "r2d2"], optional = true }
|
||||||
diesel-derive-enum = { version = "2.0.1", features = ["postgres"] }
|
diesel-derive-enum = { version = "2.0.1", features = ["postgres"] }
|
||||||
derive_enum_primitive = { workspace = true, optional = true }
|
derive_enum_primitive = { workspace = true, optional = true }
|
||||||
#diesel-derive-enum = { path = "../derive_enum_sql", features = ["postgres"] }
|
|
||||||
diesel-derive-more = { version = "1.1.3" }
|
diesel-derive-more = { version = "1.1.3" }
|
||||||
diesel-derive-newtype = { version = "2.0.0-rc.0", optional = true }
|
diesel-derive-newtype = { version = "2.0.0-rc.0", optional = true }
|
||||||
serde = { version = "*" }
|
serde = { version = "*" }
|
||||||
|
@ -32,7 +32,7 @@ serde = { version = "*", features = ["derive"] }
|
|||||||
serde_json = { version = ">=0.8.0, <2.0" }
|
serde_json = { version = ">=0.8.0, <2.0" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
toml = { version = "0.7.3" }
|
toml = { version = "0.7.3" }
|
||||||
tracing = "0"
|
tracing = { version = "0" }
|
||||||
tracing-subscriber = { version = "0", features = ['env-filter', 'thread_local', 'serde_json'] }
|
tracing-subscriber = { version = "0", features = ['env-filter', 'thread_local', 'serde_json'] }
|
||||||
web-actor = { workspace = true, features = ["local-storage"] }
|
web-actor = { workspace = true, features = ["local-storage"] }
|
||||||
websocket-actor = { workspace = true }
|
websocket-actor = { workspace = true }
|
||||||
|
@ -20,8 +20,8 @@ futures = { version = "*" }
|
|||||||
lettre = { version = "0.10.0-rc.3" }
|
lettre = { version = "0.10.0-rc.3" }
|
||||||
lettre_email = { version = "*" }
|
lettre_email = { version = "*" }
|
||||||
libc = { version = "0.2.0", default-features = false }
|
libc = { version = "0.2.0", default-features = false }
|
||||||
log = { version = "*" }
|
|
||||||
openssl-sys = { version = "*", features = ["vendored"] }
|
openssl-sys = { version = "*", features = ["vendored"] }
|
||||||
serde = { version = "*" }
|
serde = { version = "*" }
|
||||||
toml = { version = "*" }
|
toml = { version = "*" }
|
||||||
uuid = { version = "1.3.0", features = ["serde", "v4", "v5"] }
|
uuid = { version = "1.3.0", features = ["serde", "v4", "v5"] }
|
||||||
|
tracing = { version = "0" }
|
||||||
|
@ -53,12 +53,12 @@ impl Handler<Invite> for MailExecutor {
|
|||||||
.header(ContentType::TEXT_HTML)
|
.header(ContentType::TEXT_HTML)
|
||||||
.body(html)
|
.body(html)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{:?}", e);
|
tracing::error!("{:?}", e);
|
||||||
MailError::MalformedBody
|
MailError::MalformedBody
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
transport.send(&mail).map(|_| ()).map_err(|e| {
|
transport.send(&mail).map(|_| ()).map_err(|e| {
|
||||||
log::error!("Mailer: {}", e);
|
tracing::error!("Mailer: {}", e);
|
||||||
MailError::FailedToSendEmail
|
MailError::FailedToSendEmail
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ impl Handler<Welcome> for MailExecutor {
|
|||||||
bind_token = msg.bind_token,
|
bind_token = msg.bind_token,
|
||||||
);
|
);
|
||||||
if cfg!(debug_assetrions) {
|
if cfg!(debug_assetrions) {
|
||||||
log::info!("Sending email:\n{}", html);
|
tracing::info!("Sending email:\n{}", html);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mail = lettre::Message::builder()
|
let mail = lettre::Message::builder()
|
||||||
@ -55,12 +55,12 @@ impl Handler<Welcome> for MailExecutor {
|
|||||||
.header(ContentType::TEXT_HTML)
|
.header(ContentType::TEXT_HTML)
|
||||||
.body(html)
|
.body(html)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
log::error!("{:?}", e);
|
tracing::error!("{:?}", e);
|
||||||
MailError::MalformedBody
|
MailError::MalformedBody
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
transport.send(&mail).map(|_| ()).map_err(|e| {
|
transport.send(&mail).map(|_| ()).map_err(|e| {
|
||||||
log::error!("{:?}", e);
|
tracing::error!("{:?}", e);
|
||||||
MailError::FailedToSendEmail
|
MailError::FailedToSendEmail
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ derive_enum_primitive = { workspace = true }
|
|||||||
dotenv = { version = "*" }
|
dotenv = { version = "*" }
|
||||||
futures = { version = "0.3.6" }
|
futures = { version = "0.3.6" }
|
||||||
js-sys = { version = "*", default-features = false }
|
js-sys = { version = "*", default-features = false }
|
||||||
seed = { version = "0", features = ['serde-wasm-bindgen'] }
|
seed = { version = "0", features = [] }
|
||||||
serde = { version = "1", features = ['derive'] }
|
serde = { version = "1", features = ['derive'] }
|
||||||
serde_json = { version = "*" }
|
serde_json = { version = "*" }
|
||||||
tracing = { version = "0.1.37" }
|
tracing = { version = "0.1.37" }
|
||||||
@ -31,9 +31,10 @@ tracing-subscriber-wasm = { version = "0.1.0" }
|
|||||||
uuid = { version = "1.3.0", features = ["serde"] }
|
uuid = { version = "1.3.0", features = ["serde"] }
|
||||||
wasm-bindgen = { version = "*", features = ["enable-interning"] }
|
wasm-bindgen = { version = "*", features = ["enable-interning"] }
|
||||||
wasm-bindgen-futures = { version = "*" }
|
wasm-bindgen-futures = { version = "*" }
|
||||||
wee_alloc = { version = "*", features = ["static_array_backend"] }
|
wee_alloc = { version = "*", features = [] }
|
||||||
wasm-sockets = { version = "1", features = [] }
|
wasm-sockets = { version = "1", features = [] }
|
||||||
strum = { version = "*" }
|
strum = { version = "*" }
|
||||||
|
derive_more = { version = "*" }
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "*"
|
version = "*"
|
||||||
|
@ -131,6 +131,8 @@ pub fn on_click_change_page(href: String) -> EvHandler {
|
|||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
|
|
||||||
if let Ok(url) = seed::Url::from_str(href.as_str()) {
|
if let Ok(url) = seed::Url::from_str(href.as_str()) {
|
||||||
|
blur_active();
|
||||||
|
|
||||||
url.go_and_push();
|
url.go_and_push();
|
||||||
return resolve_page(url).map(crate::Msg::ChangePage);
|
return resolve_page(url).map(crate::Msg::ChangePage);
|
||||||
}
|
}
|
||||||
@ -191,3 +193,9 @@ pub fn on_event_change_text_value(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn blur_active() {
|
||||||
|
if let Some(el) = seed::document().active_element() {
|
||||||
|
seed::to_html_el(&el).blur().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
mod events;
|
pub mod events;
|
||||||
pub mod styled_avatar;
|
pub mod styled_avatar;
|
||||||
pub mod styled_button;
|
pub mod styled_button;
|
||||||
pub mod styled_checkbox;
|
pub mod styled_checkbox;
|
||||||
|
@ -13,6 +13,7 @@ pub struct StyledTextarea<'l> {
|
|||||||
pub update_event: Ev,
|
pub update_event: Ev,
|
||||||
pub placeholder: &'l str,
|
pub placeholder: &'l str,
|
||||||
pub disable_auto_resize: bool,
|
pub disable_auto_resize: bool,
|
||||||
|
pub textarea_handlers: Vec<EventHandler<Msg>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> Default for StyledTextarea<'l> {
|
impl<'l> Default for StyledTextarea<'l> {
|
||||||
@ -26,11 +27,17 @@ impl<'l> Default for StyledTextarea<'l> {
|
|||||||
update_event: Ev::Change,
|
update_event: Ev::Change,
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
disable_auto_resize: false,
|
disable_auto_resize: false,
|
||||||
|
textarea_handlers: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'l> StyledTextarea<'l> {
|
impl<'l> StyledTextarea<'l> {
|
||||||
|
pub fn with_textarea_handler(mut self, h: EventHandler<Msg>) -> Self {
|
||||||
|
self.textarea_handlers.push(h);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
// height = `calc( (${$0.value.split("\n").length}px * ( 15 * 1.4285 )) + 17px +
|
// height = `calc( (${$0.value.split("\n").length}px * ( 15 * 1.4285 )) + 17px +
|
||||||
// 2px)` where:
|
// 2px)` where:
|
||||||
// * 15 is font-size
|
// * 15 is font-size
|
||||||
@ -47,6 +54,7 @@ impl<'l> StyledTextarea<'l> {
|
|||||||
update_event,
|
update_event,
|
||||||
placeholder,
|
placeholder,
|
||||||
disable_auto_resize,
|
disable_auto_resize,
|
||||||
|
textarea_handlers,
|
||||||
} = self;
|
} = self;
|
||||||
let id = id.expect("Text area requires FieldId");
|
let id = id.expect("Text area requires FieldId");
|
||||||
let mut style_list = Vec::with_capacity(3);
|
let mut style_list = Vec::with_capacity(3);
|
||||||
@ -107,6 +115,7 @@ impl<'l> StyledTextarea<'l> {
|
|||||||
At::Rows => if disable_auto_resize { "5" } else { "auto" },
|
At::Rows => if disable_auto_resize { "5" } else { "auto" },
|
||||||
At::Data => height
|
At::Data => height
|
||||||
],
|
],
|
||||||
|
textarea_handlers,
|
||||||
value,
|
value,
|
||||||
// resize_handler,
|
// resize_handler,
|
||||||
text_input_handler,
|
text_input_handler,
|
||||||
|
@ -2,21 +2,22 @@ use seed::prelude::*;
|
|||||||
use seed::*;
|
use seed::*;
|
||||||
|
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use crate::shared::keys::BrowserKey;
|
||||||
use crate::{BuildMsg, Msg};
|
use crate::{BuildMsg, Msg};
|
||||||
|
|
||||||
pub fn styled_tip<B>(letter: char, model: &Model, builder: B) -> Node<Msg>
|
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
|
.key_triggers
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(letter, Box::new(builder));
|
.insert(letter.clone(), Box::new(builder));
|
||||||
div![
|
div![
|
||||||
C!["proTip"],
|
C!["proTip"],
|
||||||
strong![C!["strong"], "Pro tip: "],
|
strong![C!["strong"], "Pro tip: "],
|
||||||
"press ",
|
"press ",
|
||||||
span![C!["tipLetter", letter.to_string()], letter.to_string()],
|
span![C!["tipLetter", format!("{letter}")], format!("{letter}")],
|
||||||
" to comment"
|
" to comment"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -7,22 +7,21 @@ pub use components::*;
|
|||||||
pub use fields::*;
|
pub use fields::*;
|
||||||
pub use images::*;
|
pub use images::*;
|
||||||
use seed::prelude::*;
|
use seed::prelude::*;
|
||||||
use web_sys::File;
|
use tracing::info;
|
||||||
|
use web_sys::{File, KeyboardEvent};
|
||||||
|
|
||||||
use crate::components::styled_date_time_input::StyledDateTimeChanged;
|
use crate::components::styled_date_time_input::StyledDateTimeChanged;
|
||||||
use crate::components::styled_rte::RteMsg;
|
use crate::components::styled_rte::RteMsg;
|
||||||
use crate::components::styled_select::StyledSelectChanged;
|
use crate::components::styled_select::StyledSelectChanged;
|
||||||
use crate::components::styled_tooltip;
|
|
||||||
use crate::components::styled_tooltip::{TooltipVariant as StyledTooltip, TooltipVariant};
|
use crate::components::styled_tooltip::{TooltipVariant as StyledTooltip, TooltipVariant};
|
||||||
use crate::modals::DebugMsg;
|
use crate::modals::DebugMsg;
|
||||||
use crate::model::{ModalType, Model, Page};
|
use crate::model::{ModalType, Model, Page};
|
||||||
use crate::pages::issues_and_filters::IssuesAndFiltersMsg;
|
use crate::pages::issues_and_filters::IssuesAndFiltersMsg;
|
||||||
use crate::pages::sign_in_page::SignInMsg;
|
use crate::pages::sign_in_page::SignInMsg;
|
||||||
use crate::shared::go_to_login;
|
use crate::shared::go_to_login;
|
||||||
|
use crate::shared::keys::BrowserKey;
|
||||||
use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg};
|
use crate::ws::{flush_queue, open_socket, read_incoming, send_ws_msg};
|
||||||
|
|
||||||
// use crate::shared::styled_rte::RteMsg;
|
|
||||||
|
|
||||||
mod changes;
|
mod changes;
|
||||||
mod components;
|
mod components;
|
||||||
mod fields;
|
mod fields;
|
||||||
@ -35,8 +34,8 @@ mod shared;
|
|||||||
pub mod validations;
|
pub mod validations;
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
// #[global_allocator]
|
#[global_allocator]
|
||||||
// static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@ -65,6 +64,10 @@ pub enum OperationKind {
|
|||||||
|
|
||||||
pub trait BuildMsg: std::fmt::Debug {
|
pub trait BuildMsg: std::fmt::Debug {
|
||||||
fn build(&self) -> Msg;
|
fn build(&self) -> Msg;
|
||||||
|
|
||||||
|
fn sender_allowed(&self, tag_name: Option<&str>) -> bool {
|
||||||
|
tag_name == Some("BODY")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -155,7 +158,7 @@ pub enum Msg {
|
|||||||
ResourceChanged(ResourceKind, OperationKind, Option<i32>),
|
ResourceChanged(ResourceKind, OperationKind, Option<i32>),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
if model.ws.is_none() {
|
if model.ws.is_none() {
|
||||||
open_socket(model, orders);
|
open_socket(model, orders);
|
||||||
}
|
}
|
||||||
@ -215,7 +218,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
tracing::info!("msg {:?}", msg);
|
info!("msg {:?}", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
match &msg {
|
match &msg {
|
||||||
@ -232,13 +235,13 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
model.page = *page;
|
model.page = *page;
|
||||||
}
|
}
|
||||||
Msg::ToggleTooltip(variant) => match variant {
|
Msg::ToggleTooltip(variant) => match variant {
|
||||||
styled_tooltip::TooltipVariant::About => {
|
TooltipVariant::About => {
|
||||||
model.about_tooltip_visible = !model.about_tooltip_visible;
|
model.about_tooltip_visible = !model.about_tooltip_visible;
|
||||||
}
|
}
|
||||||
styled_tooltip::TooltipVariant::Messages => {
|
TooltipVariant::Messages => {
|
||||||
model.messages_tooltip_visible = !model.messages_tooltip_visible;
|
model.messages_tooltip_visible = !model.messages_tooltip_visible;
|
||||||
}
|
}
|
||||||
styled_tooltip::TooltipVariant::CodeBuilder => {}
|
TooltipVariant::CodeBuilder => {}
|
||||||
TooltipVariant::TableBuilder => {}
|
TooltipVariant::TableBuilder => {}
|
||||||
TooltipVariant::DateTimeBuilder => {}
|
TooltipVariant::DateTimeBuilder => {}
|
||||||
},
|
},
|
||||||
@ -270,7 +273,7 @@ fn update(msg: Msg, model: &mut model::Model, orders: &mut impl Orders<Msg>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(model: &model::Model) -> Node<Msg> {
|
fn view(model: &Model) -> Node<Msg> {
|
||||||
model.key_triggers.borrow_mut().clear();
|
model.key_triggers.borrow_mut().clear();
|
||||||
|
|
||||||
match model.page {
|
match model.page {
|
||||||
@ -326,9 +329,8 @@ fn resolve_page(url: Url) -> Option<Page> {
|
|||||||
Some(page)
|
Some(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[wasm_bindgen(start)]
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let app = seed::App::start("app", init, update, view);
|
let app = App::start("app", init, update, view);
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
{
|
{
|
||||||
use tracing_subscriber_wasm::MakeConsoleWriter;
|
use tracing_subscriber_wasm::MakeConsoleWriter;
|
||||||
@ -340,7 +342,7 @@ pub fn main() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
crate::shared::on_event::keydown(move |ev| {
|
shared::on_event::keydown(move |ev| {
|
||||||
let app = app.clone();
|
let app = app.clone();
|
||||||
let event = seed::to_keyboard_event(&ev);
|
let event = seed::to_keyboard_event(&ev);
|
||||||
|
|
||||||
@ -362,7 +364,7 @@ pub fn main() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
crate::shared::on_event::keydown(move |_ev| {
|
shared::on_event::keydown(move |_ev| {
|
||||||
let element = match seed::document().active_element() {
|
let element = match seed::document().active_element() {
|
||||||
Some(el) => el,
|
Some(el) => el,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -374,7 +376,7 @@ pub fn main() {
|
|||||||
if element.get_attribute("rows").as_deref() != Some("auto") {
|
if element.get_attribute("rows").as_deref() != Some("auto") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
crate::components::styled_textarea::handle_resize(&element);
|
styled_textarea::handle_resize(&element);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -389,27 +391,25 @@ 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();
|
let sender_clone = sender.clone();
|
||||||
crate::shared::on_event::keypress(move |ev| {
|
shared::on_event::keydown(move |ev: KeyboardEvent| {
|
||||||
let sender = sender_clone.clone();
|
let sender = sender_clone.clone();
|
||||||
let key_triggers = key_triggers.clone();
|
let key_triggers = key_triggers.clone();
|
||||||
let event = seed::to_keyboard_event(&ev);
|
let event = seed::to_keyboard_event(&ev);
|
||||||
|
|
||||||
if seed::document()
|
let active = seed::document().active_element().map(|el| el.tag_name());
|
||||||
.active_element()
|
|
||||||
.map(|el| el.tag_name() != "BODY")
|
|
||||||
.unwrap_or(true)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ev.prevent_default();
|
|
||||||
ev.stop_propagation();
|
|
||||||
|
|
||||||
let key: String = event.key();
|
let Ok(key) = event.key().parse::<BrowserKey>() else {
|
||||||
let t = key_triggers.borrow();
|
return;
|
||||||
if let Some(b) = key.chars().next().and_then(|c| t.get(&c)) {
|
};
|
||||||
|
if let Some(b) = key_triggers.borrow().get(&key) {
|
||||||
|
if !b.sender_allowed(active.as_deref()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.prevent_default();
|
||||||
|
ev.stop_propagation();
|
||||||
let msg = b.build();
|
let msg = b.build();
|
||||||
sender.clone()(Some(msg));
|
sender.clone()(Some(msg));
|
||||||
}
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -435,7 +435,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
|||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn authorize_or_redirect(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
fn authorize_or_redirect(model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
let pathname = seed::document().location().unwrap().pathname().unwrap();
|
let pathname = seed::document().location().unwrap().pathname().unwrap();
|
||||||
match crate::shared::read_auth_token() {
|
match shared::read_auth_token() {
|
||||||
Ok(token) => {
|
Ok(token) => {
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
WsMsgSession::AuthorizeLoad(token).into(),
|
WsMsgSession::AuthorizeLoad(token).into(),
|
||||||
|
@ -5,6 +5,7 @@ use crate::components::styled_button::*;
|
|||||||
use crate::components::styled_confirm_modal::*;
|
use crate::components::styled_confirm_modal::*;
|
||||||
use crate::components::styled_icon::*;
|
use crate::components::styled_icon::*;
|
||||||
use crate::components::styled_modal::*;
|
use crate::components::styled_modal::*;
|
||||||
|
use crate::events::blur_active;
|
||||||
use crate::modals::epics_delete::Model;
|
use crate::modals::epics_delete::Model;
|
||||||
use crate::{model, Msg};
|
use crate::{model, Msg};
|
||||||
|
|
||||||
@ -55,6 +56,8 @@ fn warning(model: &model::Model, modal: &Model) -> Node<Msg> {
|
|||||||
on_click: Some(mouse_ev(Ev::Click, move |ev| {
|
on_click: Some(mouse_ev(Ev::Click, move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
|
blur_active();
|
||||||
|
|
||||||
Msg::ModalDropped
|
Msg::ModalDropped
|
||||||
})),
|
})),
|
||||||
variant: ButtonVariant::Secondary,
|
variant: ButtonVariant::Secondary,
|
||||||
|
@ -67,7 +67,11 @@ pub fn update(msg: &Msg, model: &mut crate::model::Model, orders: &mut impl Orde
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::ResourceChanged(ResourceKind::Issue, OperationKind::SingleCreated, _) => {
|
Msg::ResourceChanged(
|
||||||
|
ResourceKind::Issue | ResourceKind::Epic,
|
||||||
|
OperationKind::SingleCreated,
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
orders.skip().send_msg(Msg::ModalDropped);
|
orders.skip().send_msg(Msg::ModalDropped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,10 +17,29 @@ use crate::components::styled_textarea::StyledTextarea;
|
|||||||
use crate::modals::epic_field;
|
use crate::modals::epic_field;
|
||||||
use crate::modals::issues_create::{events, Model as AddIssueModal, Type};
|
use crate::modals::issues_create::{events, Model as AddIssueModal, Type};
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use crate::shared::keys::{BrowserKey, UiKey};
|
||||||
use crate::shared::validate::Validator;
|
use crate::shared::validate::Validator;
|
||||||
use crate::{FieldId, Msg};
|
use crate::{BuildMsg, FieldId, Msg};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CloseCreateIssueModal;
|
||||||
|
|
||||||
|
impl BuildMsg for CloseCreateIssueModal {
|
||||||
|
fn build(&self) -> Msg {
|
||||||
|
Msg::ModalDropped
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sender_allowed(&self, tag_name: Option<&str>) -> bool {
|
||||||
|
matches!(tag_name, 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(
|
||||||
|
BrowserKey::UiKey(UiKey::Escape),
|
||||||
|
Box::new(CloseCreateIssueModal),
|
||||||
|
);
|
||||||
|
|
||||||
let issue_type = modal
|
let issue_type = modal
|
||||||
.type_state
|
.type_state
|
||||||
.values
|
.values
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use bitque_data::IssueId;
|
use bitque_data::IssueId;
|
||||||
use seed::prelude::*;
|
use seed::prelude::*;
|
||||||
|
|
||||||
pub type EvHandler = seed::EventHandler<crate::Msg>;
|
pub type EvHandler = EventHandler<crate::Msg>;
|
||||||
|
|
||||||
pub fn on_click_close_modal() -> EvHandler {
|
pub fn on_click_close_modal() -> EvHandler {
|
||||||
mouse_ev(Ev::Click, |ev| {
|
mouse_ev(Ev::Click, |ev| {
|
||||||
|
@ -21,27 +21,50 @@ use crate::modals::epic_field;
|
|||||||
use crate::modals::issues_edit::Model as EditIssueModal;
|
use crate::modals::issues_edit::Model as EditIssueModal;
|
||||||
use crate::modals::time_tracking::time_tracking_field;
|
use crate::modals::time_tracking::time_tracking_field;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
|
use crate::shared::keys::{BrowserKey, UiKey};
|
||||||
use crate::shared::tracking_widget::tracking_link;
|
use crate::shared::tracking_widget::tracking_link;
|
||||||
use crate::{BuildMsg, EditIssueModalSection, FieldChange, FieldId, Msg};
|
use crate::{BuildMsg, EditIssueModalSection, FieldChange, FieldId, Msg};
|
||||||
|
|
||||||
mod comments;
|
mod comments;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CloseAddComment;
|
||||||
|
|
||||||
|
impl BuildMsg for CloseAddComment {
|
||||||
|
fn build(&self) -> Msg {
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
pub fn view(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
model
|
let Some(_) = model
|
||||||
.issues_by_id
|
.issues_by_id
|
||||||
.get(&modal.id)
|
.get(&modal.id) else {
|
||||||
.map(|_issue| {
|
return Node::Empty;
|
||||||
StyledModal {
|
};
|
||||||
variant: ModalVariant::Center,
|
|
||||||
width: Some(1014),
|
{
|
||||||
with_icon: false,
|
let mut b = model.key_triggers.borrow_mut();
|
||||||
children: vec![details(model, modal)],
|
b.insert(BrowserKey::UiKey(UiKey::Escape), Box::new(CloseIssueModal));
|
||||||
class_list: "",
|
b.insert(BrowserKey::UiKey(UiKey::Escape), Box::new(CloseAddComment));
|
||||||
}
|
}
|
||||||
.render()
|
|
||||||
})
|
StyledModal {
|
||||||
.unwrap_or(Node::Empty)
|
variant: ModalVariant::Center,
|
||||||
|
width: Some(1014),
|
||||||
|
with_icon: false,
|
||||||
|
children: vec![details(model, modal)],
|
||||||
|
class_list: "",
|
||||||
|
}
|
||||||
|
.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -179,7 +202,7 @@ fn type_select_option(t: IssueType, text: &str) -> StyledSelectOption<'_> {
|
|||||||
text: Some(text),
|
text: Some(text),
|
||||||
icon: Some(
|
icon: Some(
|
||||||
StyledIcon {
|
StyledIcon {
|
||||||
icon: t.into(),
|
icon: Icon::from(t),
|
||||||
class_list: name,
|
class_list: name,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
@ -259,7 +282,7 @@ fn left_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
|||||||
div![
|
div![
|
||||||
C!["right"],
|
C!["right"],
|
||||||
create_comment,
|
create_comment,
|
||||||
styled_tip('m', model, EnableCommentBuilder)
|
styled_tip(BrowserKey::Character('m'), model, EnableCommentBuilder)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
comments
|
comments
|
||||||
@ -272,6 +295,7 @@ pub struct EnableCommentBuilder;
|
|||||||
|
|
||||||
impl BuildMsg for EnableCommentBuilder {
|
impl BuildMsg for EnableCommentBuilder {
|
||||||
fn build(&self) -> Msg {
|
fn build(&self) -> Msg {
|
||||||
|
tracing::info!("{self:?}");
|
||||||
Msg::ModalChanged(FieldChange::ToggleCommentForm(
|
Msg::ModalChanged(FieldChange::ToggleCommentForm(
|
||||||
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
||||||
true,
|
true,
|
||||||
@ -279,6 +303,16 @@ impl BuildMsg for EnableCommentBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CloseIssueModal;
|
||||||
|
|
||||||
|
impl BuildMsg for CloseIssueModal {
|
||||||
|
fn build(&self) -> Msg {
|
||||||
|
tracing::info!("{self:?}");
|
||||||
|
Msg::ModalDropped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
fn right_modal_column(model: &Model, modal: &EditIssueModal) -> Node<Msg> {
|
||||||
let EditIssueModal {
|
let EditIssueModal {
|
||||||
|
@ -31,6 +31,12 @@ pub fn build_comment_form(form: &CommentForm) -> Vec<Node<Msg>> {
|
|||||||
placeholder: "Add a comment...",
|
placeholder: "Add a comment...",
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
.with_textarea_handler(ev("blur", |_| {
|
||||||
|
Msg::ModalChanged(FieldChange::ToggleCommentForm(
|
||||||
|
FieldId::EditIssueModal(EditIssueModalSection::Comment(CommentFieldId::Body)),
|
||||||
|
false,
|
||||||
|
))
|
||||||
|
}))
|
||||||
.render();
|
.render();
|
||||||
|
|
||||||
let submit = StyledButton {
|
let submit = StyledButton {
|
||||||
|
@ -18,6 +18,7 @@ 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::{BuildMsg, Msg};
|
||||||
|
use crate::shared::keys::BrowserKey;
|
||||||
|
|
||||||
pub trait IssueModal {
|
pub trait IssueModal {
|
||||||
fn epic_id_value(&self) -> Option<u32>;
|
fn epic_id_value(&self) -> Option<u32>;
|
||||||
@ -276,7 +277,7 @@ 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<char, Box<dyn BuildMsg>>>>,
|
pub key_triggers: std::rc::Rc<std::cell::RefCell<HashMap<BrowserKey, Box<dyn BuildMsg>>>>,
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use seed::*;
|
|||||||
|
|
||||||
use crate::components::styled_icon::*;
|
use crate::components::styled_icon::*;
|
||||||
use crate::components::styled_link::*;
|
use crate::components::styled_link::*;
|
||||||
|
use crate::events::blur_active;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::Msg;
|
use crate::Msg;
|
||||||
|
|
||||||
@ -136,6 +137,8 @@ fn issue_entry(
|
|||||||
mouse_ev("click", move |ev| {
|
mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
|
|
||||||
|
blur_active();
|
||||||
Msg::SetActiveIssue(Some(id))
|
Msg::SetActiveIssue(Some(id))
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use bitque_data::{EpicId, UserId};
|
use bitque_data::{EpicId, UserId};
|
||||||
use seed::prelude::*;
|
use seed::prelude::*;
|
||||||
|
|
||||||
|
use crate::events::blur_active;
|
||||||
use crate::model::Page;
|
use crate::model::Page;
|
||||||
use crate::{AvatarFilterActive, BoardPageChange, Msg, PageChanged};
|
use crate::{AvatarFilterActive, BoardPageChange, Msg, PageChanged};
|
||||||
|
|
||||||
@ -66,7 +67,9 @@ pub fn on_click_edit_issue(issue_id: i32) -> EvHandler {
|
|||||||
ev(Ev::Click, move |ev| {
|
ev(Ev::Click, move |ev| {
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
seed::Url::new()
|
blur_active();
|
||||||
|
|
||||||
|
Url::new()
|
||||||
.add_path_part("issues")
|
.add_path_part("issues")
|
||||||
.add_path_part(format!("{}", issue_id))
|
.add_path_part(format!("{}", issue_id))
|
||||||
.go_and_push();
|
.go_and_push();
|
||||||
|
@ -11,7 +11,7 @@ use crate::{
|
|||||||
BoardPageChange, EditIssueModalSection, FieldId, Msg, OperationKind, PageChanged, ResourceKind,
|
BoardPageChange, EditIssueModalSection, FieldId, Msg, OperationKind, PageChanged, ResourceKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Orders<Msg>) {
|
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||||
if model.user.is_none() {
|
if model.user.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ pub fn update(msg: Msg, model: &mut crate::model::Model, orders: &mut impl Order
|
|||||||
}
|
}
|
||||||
Msg::DeleteIssue(issue_id) => {
|
Msg::DeleteIssue(issue_id) => {
|
||||||
send_ws_msg(
|
send_ws_msg(
|
||||||
bitque_data::WsMsg::Issue(WsMsgIssue::IssueDelete(issue_id)),
|
WsMsg::Issue(WsMsgIssue::IssueDelete(issue_id)),
|
||||||
model.ws.as_ref(),
|
model.ws.as_ref(),
|
||||||
orders,
|
orders,
|
||||||
);
|
);
|
||||||
|
@ -3,14 +3,32 @@ use seed::*;
|
|||||||
|
|
||||||
use crate::components::styled_button::StyledButton;
|
use crate::components::styled_button::StyledButton;
|
||||||
use crate::components::styled_icon::{Icon, StyledIcon};
|
use crate::components::styled_icon::{Icon, StyledIcon};
|
||||||
use crate::model::Model;
|
use crate::events::blur_active;
|
||||||
|
use crate::model::{ModalType, Model};
|
||||||
use crate::shared::inner_layout;
|
use crate::shared::inner_layout;
|
||||||
use crate::Msg;
|
use crate::shared::keys::BrowserKey;
|
||||||
|
use crate::{BuildMsg, Msg};
|
||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
mod filters;
|
mod filters;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct CreateIssueShortcut(i32);
|
||||||
|
|
||||||
|
impl BuildMsg for CreateIssueShortcut {
|
||||||
|
fn build(&self) -> Msg {
|
||||||
|
Msg::ModalOpened(ModalType::AddIssue(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn view(model: &Model) -> Node<Msg> {
|
pub fn view(model: &Model) -> Node<Msg> {
|
||||||
|
{
|
||||||
|
model
|
||||||
|
.key_triggers
|
||||||
|
.borrow_mut()
|
||||||
|
.insert(BrowserKey::Character('c'), Box::new(CreateIssueShortcut(0)));
|
||||||
|
}
|
||||||
|
|
||||||
let project_section = [
|
let project_section = [
|
||||||
breadcrumbs(model),
|
breadcrumbs(model),
|
||||||
header(model),
|
header(model),
|
||||||
@ -36,11 +54,21 @@ fn header(model: &Model) -> Node<Msg> {
|
|||||||
if !model.show_extras {
|
if !model.show_extras {
|
||||||
return Node::Empty;
|
return Node::Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let on_click = mouse_ev("click", |_| {
|
||||||
|
blur_active();
|
||||||
|
});
|
||||||
|
|
||||||
div![
|
div![
|
||||||
id!["projectBoardHeader"],
|
id!["projectBoardHeader"],
|
||||||
div![id!["boardName"], C!["headerChild"], "Kanban board"],
|
div![id!["boardName"], C!["headerChild"], "Kanban board"],
|
||||||
a![
|
a![
|
||||||
attrs![At::Href => "https://gitlab.com/adrian.wozniak/bitque", At::Target => "__blank", At::Rel => "noreferrer noopener"],
|
attrs![
|
||||||
|
At::Href => "https://gitlab.com/adrian.wozniak/bitque",
|
||||||
|
At::Target => "__blank",
|
||||||
|
At::Rel => "noreferrer noopener"
|
||||||
|
],
|
||||||
|
on_click,
|
||||||
StyledButton::secondary_with_text_and_icon(
|
StyledButton::secondary_with_text_and_icon(
|
||||||
"Repository",
|
"Repository",
|
||||||
StyledIcon::from(Icon::Github).render(),
|
StyledIcon::from(Icon::Github).render(),
|
||||||
|
@ -136,7 +136,7 @@ fn this_month_graph(page: &ReportsPage, this_month_updated: &[&Issue]) -> Node<M
|
|||||||
At::Y => SVG_DRAWABLE_HEIGHT as f64 - height, // reverse draw origin
|
At::Y => SVG_DRAWABLE_HEIGHT as f64 - height, // reverse draw origin
|
||||||
At::Width => piece_width,
|
At::Width => piece_width,
|
||||||
At::Height => height,
|
At::Height => height,
|
||||||
At::Style => "fill: rgb(255, 0, 0);",
|
At::Style => "fill: #d04437;",
|
||||||
At::Title => format!("Number of issues: {}", num_issues),
|
At::Title => format!("Number of issues: {}", num_issues),
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -4,6 +4,7 @@ use seed::prelude::*;
|
|||||||
use seed::*;
|
use seed::*;
|
||||||
|
|
||||||
use crate::components::styled_icon::{Icon, StyledIcon};
|
use crate::components::styled_icon::{Icon, StyledIcon};
|
||||||
|
use crate::events::blur_active;
|
||||||
use crate::model::{Model, Page};
|
use crate::model::{Model, Page};
|
||||||
use crate::shared::divider;
|
use crate::shared::divider;
|
||||||
use crate::ws::enqueue_ws_msg;
|
use crate::ws::enqueue_ws_msg;
|
||||||
@ -105,7 +106,9 @@ fn sidebar_link_item(model: &Model, name: &str, icon: Icon, page: Option<Page>)
|
|||||||
mouse_ev("click", move |ev| {
|
mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
seed::Url::new()
|
|
||||||
|
blur_active();
|
||||||
|
Url::new()
|
||||||
.set_path(p.to_path().split('/').filter(|s| !s.is_empty()))
|
.set_path(p.to_path().split('/').filter(|s| !s.is_empty()))
|
||||||
.go_and_push();
|
.go_and_push();
|
||||||
Msg::ChangePage(p)
|
Msg::ChangePage(p)
|
||||||
|
204
crates/web/src/shared/keys.rs
Normal file
204
crates/web/src/shared/keys.rs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use derive_more::Display;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Display)]
|
||||||
|
pub enum UiKey {
|
||||||
|
Accept,
|
||||||
|
Again,
|
||||||
|
Attn,
|
||||||
|
Cancel,
|
||||||
|
ContextMenu,
|
||||||
|
Escape,
|
||||||
|
Execute,
|
||||||
|
Find,
|
||||||
|
Help,
|
||||||
|
Pause,
|
||||||
|
Play,
|
||||||
|
Props,
|
||||||
|
Select,
|
||||||
|
ZoomIn,
|
||||||
|
ZoomOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for UiKey {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"Accept" => Self::Accept,
|
||||||
|
"Again" => Self::Again,
|
||||||
|
"Attn" => Self::Attn,
|
||||||
|
"Cancel" => Self::Cancel,
|
||||||
|
"ContextMenu" => Self::ContextMenu,
|
||||||
|
"Escape" => Self::Escape,
|
||||||
|
"Execute" => Self::Execute,
|
||||||
|
"Find" => Self::Find,
|
||||||
|
"Help" => Self::Help,
|
||||||
|
"Pause" => Self::Pause,
|
||||||
|
"Play" => Self::Play,
|
||||||
|
"Props" => Self::Props,
|
||||||
|
"Select" => Self::Select,
|
||||||
|
"ZoomIn" => Self::ZoomIn,
|
||||||
|
"ZoomOut" => Self::ZoomOut,
|
||||||
|
_ => return Err(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Display)]
|
||||||
|
pub enum DeviceKey {
|
||||||
|
BrightnessDown,
|
||||||
|
BrightnessUp,
|
||||||
|
Eject,
|
||||||
|
LogOff,
|
||||||
|
Power,
|
||||||
|
PowerOff,
|
||||||
|
PrintScreen,
|
||||||
|
Hibernate,
|
||||||
|
Standby,
|
||||||
|
WakeUp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DeviceKey {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"BrightnessDown" => Self::BrightnessDown,
|
||||||
|
"BrightnessUp" => Self::BrightnessUp,
|
||||||
|
"Eject" => Self::Eject,
|
||||||
|
"LogOff" => Self::LogOff,
|
||||||
|
"Power" => Self::Power,
|
||||||
|
"PowerOff" => Self::PowerOff,
|
||||||
|
"PrintScreen" => Self::PrintScreen,
|
||||||
|
"Hibernate" => Self::Hibernate,
|
||||||
|
"Standby" => Self::Standby,
|
||||||
|
"WakeUp" => Self::WakeUp,
|
||||||
|
_ => return Err(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Display)]
|
||||||
|
pub enum IMEICompositionKey {
|
||||||
|
AllCandidates,
|
||||||
|
Alphanumeric,
|
||||||
|
CodeInput,
|
||||||
|
Compose,
|
||||||
|
Convert,
|
||||||
|
Dead,
|
||||||
|
FinalMode,
|
||||||
|
GroupFirst,
|
||||||
|
GroupLast,
|
||||||
|
GroupNext,
|
||||||
|
GroupPrevious,
|
||||||
|
ModeChange,
|
||||||
|
NextCandidate,
|
||||||
|
NonConvert,
|
||||||
|
PreviousCandidate,
|
||||||
|
Process,
|
||||||
|
SingleCandidate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for IMEICompositionKey {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"AllCandidates" => Self::AllCandidates,
|
||||||
|
"Alphanumeric" => Self::Alphanumeric,
|
||||||
|
"CodeInput" => Self::CodeInput,
|
||||||
|
"Compose" => Self::Compose,
|
||||||
|
"Convert" => Self::Convert,
|
||||||
|
"Dead" => Self::Dead,
|
||||||
|
"FinalMode" => Self::FinalMode,
|
||||||
|
"GroupFirst" => Self::GroupFirst,
|
||||||
|
"GroupLast" => Self::GroupLast,
|
||||||
|
"GroupNext" => Self::GroupNext,
|
||||||
|
"GroupPrevious" => Self::GroupPrevious,
|
||||||
|
"ModeChange" => Self::ModeChange,
|
||||||
|
"NextCandidate" => Self::NextCandidate,
|
||||||
|
"NonConvert" => Self::NonConvert,
|
||||||
|
"PreviousCandidate" => Self::PreviousCandidate,
|
||||||
|
"Process" => Self::Process,
|
||||||
|
"SingleCandidate" => Self::SingleCandidate,
|
||||||
|
_ => return Err(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Display)]
|
||||||
|
pub enum GeneralFunctionKey {
|
||||||
|
F1,
|
||||||
|
F2,
|
||||||
|
F3,
|
||||||
|
F4,
|
||||||
|
F5,
|
||||||
|
F6,
|
||||||
|
F7,
|
||||||
|
F8,
|
||||||
|
F9,
|
||||||
|
F10,
|
||||||
|
F11,
|
||||||
|
F12,
|
||||||
|
Soft1,
|
||||||
|
Soft2,
|
||||||
|
Soft3,
|
||||||
|
Soft4,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for GeneralFunctionKey {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s {
|
||||||
|
"F1 " => Self::F1,
|
||||||
|
"F2" => Self::F2,
|
||||||
|
"F3 " => Self::F3,
|
||||||
|
"F4" => Self::F4,
|
||||||
|
"F5 " => Self::F5,
|
||||||
|
"F6" => Self::F6,
|
||||||
|
"F7 " => Self::F7,
|
||||||
|
"F8" => Self::F8,
|
||||||
|
"F9 " => Self::F9,
|
||||||
|
"F10" => Self::F10,
|
||||||
|
"F11 " => Self::F11,
|
||||||
|
"F12" => Self::F12,
|
||||||
|
"Soft1 " => Self::Soft1,
|
||||||
|
"Soft2" => Self::Soft2,
|
||||||
|
"Soft3 " => Self::Soft3,
|
||||||
|
"Soft4" => Self::Soft4,
|
||||||
|
_ => return Err(()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Clone, Display)]
|
||||||
|
pub enum BrowserKey {
|
||||||
|
UiKey(UiKey),
|
||||||
|
DeviceKey(DeviceKey),
|
||||||
|
IMEICompositionKey(IMEICompositionKey),
|
||||||
|
GeneralFunctionKey(GeneralFunctionKey),
|
||||||
|
Character(char),
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for BrowserKey {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.parse::<UiKey>()
|
||||||
|
.map(Self::UiKey)
|
||||||
|
.or_else(|_| s.parse().map(Self::DeviceKey))
|
||||||
|
.or_else(|_| s.parse().map(Self::IMEICompositionKey))
|
||||||
|
.or_else(|_| s.parse().map(Self::GeneralFunctionKey))
|
||||||
|
.or_else(|_| {
|
||||||
|
Ok(if s.len() == 1 {
|
||||||
|
Self::Character(s.chars().next().unwrap())
|
||||||
|
} else {
|
||||||
|
Self::Unknown(s.into())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ use crate::{resolve_page, Msg};
|
|||||||
|
|
||||||
pub mod aside;
|
pub mod aside;
|
||||||
pub mod drag;
|
pub mod drag;
|
||||||
|
pub mod keys;
|
||||||
pub mod navbar_left;
|
pub mod navbar_left;
|
||||||
pub mod on_event;
|
pub mod on_event;
|
||||||
pub mod tracking_widget;
|
pub mod tracking_widget;
|
||||||
|
@ -8,6 +8,7 @@ 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_tooltip;
|
use crate::components::styled_tooltip;
|
||||||
use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant};
|
use crate::components::styled_tooltip::{StyledTooltip, TooltipVariant};
|
||||||
|
use crate::events::blur_active;
|
||||||
use crate::model::Model;
|
use crate::model::Model;
|
||||||
use crate::shared::divider;
|
use crate::shared::divider;
|
||||||
use crate::ws::send_ws_msg;
|
use crate::ws::send_ws_msg;
|
||||||
@ -106,10 +107,16 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
let go_to_profile = mouse_ev("click", move |ev| {
|
let go_to_profile = mouse_ev("click", move |ev| {
|
||||||
ev.stop_propagation();
|
ev.stop_propagation();
|
||||||
ev.prevent_default();
|
ev.prevent_default();
|
||||||
seed::Url::new().add_path_part("profile").go_and_push();
|
|
||||||
|
blur_active();
|
||||||
|
Url::new().add_path_part("profile").go_and_push();
|
||||||
Msg::ChangePage(Page::Profile)
|
Msg::ChangePage(Page::Profile)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let on_click_logo = mouse_ev("click", |_| {
|
||||||
|
blur_active();
|
||||||
|
});
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
about_tooltip_popup(model),
|
about_tooltip_popup(model),
|
||||||
messages_tooltip_popup(model),
|
messages_tooltip_popup(model),
|
||||||
@ -118,7 +125,8 @@ pub fn render(model: &Model) -> Vec<Node<Msg>> {
|
|||||||
a![
|
a![
|
||||||
C!["logoLink"],
|
C!["logoLink"],
|
||||||
attrs![At::Href => "/"],
|
attrs![At::Href => "/"],
|
||||||
div![C!["styledLogo"], logo_svg]
|
on_click_logo,
|
||||||
|
div![C!["styledLogo"], logo_svg],
|
||||||
],
|
],
|
||||||
issue_nav,
|
issue_nav,
|
||||||
div![
|
div![
|
||||||
@ -151,6 +159,12 @@ fn navbar_left_item(
|
|||||||
) -> Node<Msg> {
|
) -> Node<Msg> {
|
||||||
let styled_icon = icon.into_nav_item_icon();
|
let styled_icon = icon.into_nav_item_icon();
|
||||||
|
|
||||||
|
let on_click = on_click.unwrap_or_else(|| {
|
||||||
|
mouse_ev("click", |_| {
|
||||||
|
blur_active();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
a![
|
a![
|
||||||
C!["item"],
|
C!["item"],
|
||||||
attrs![At::Href => href.unwrap_or("#")],
|
attrs![At::Href => href.unwrap_or("#")],
|
||||||
@ -205,13 +219,19 @@ fn message_ui(model: &Model, message: &Message) -> Option<Node<Msg>> {
|
|||||||
empty![]
|
empty![]
|
||||||
} else {
|
} else {
|
||||||
let link_icon = StyledIcon::from(Icon::Link).render();
|
let link_icon = StyledIcon::from(Icon::Link).render();
|
||||||
|
|
||||||
|
let on_click_hyper = mouse_ev("click", |_| {
|
||||||
|
blur_active();
|
||||||
|
});
|
||||||
|
|
||||||
div![
|
div![
|
||||||
C!["hyperlink"],
|
C!["hyperlink"],
|
||||||
a![
|
a![
|
||||||
C!["styledLink"],
|
C!["styledLink"],
|
||||||
attrs![At::Href => hyper_link],
|
attrs![At::Href => hyper_link],
|
||||||
|
on_click_hyper,
|
||||||
link_icon,
|
link_icon,
|
||||||
hyper_link
|
hyper_link,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@ -303,7 +323,9 @@ fn about_tooltip_popup(model: &Model) -> Node<Msg> {
|
|||||||
.render();
|
.render();
|
||||||
|
|
||||||
let on_click = mouse_ev(Ev::Click, |_| {
|
let on_click = mouse_ev(Ev::Click, |_| {
|
||||||
Msg::ToggleTooltip(styled_tooltip::TooltipVariant::About)
|
blur_active();
|
||||||
|
|
||||||
|
Msg::ToggleTooltip(TooltipVariant::About)
|
||||||
});
|
});
|
||||||
let body = div![
|
let body = div![
|
||||||
on_click,
|
on_click,
|
||||||
|
@ -124,19 +124,12 @@ where
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keypress<Cb>(cb: Cb)
|
|
||||||
where
|
|
||||||
Cb: FnMut(web_sys::KeyboardEvent) + 'static,
|
|
||||||
{
|
|
||||||
on("keypress", cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on<Cb>(event: &str, cb: Cb)
|
pub fn on<Cb>(event: &str, cb: Cb)
|
||||||
where
|
where
|
||||||
Cb: FnMut(web_sys::KeyboardEvent) + 'static,
|
Cb: FnMut(web_sys::KeyboardEvent) + 'static,
|
||||||
{
|
{
|
||||||
let handler = Closure::wrap(Box::new(cb) as Box<dyn FnMut(_)>);
|
let handler = Closure::wrap(Box::new(cb) as Box<dyn FnMut(_)>);
|
||||||
seed::window()
|
document()
|
||||||
.add_event_listener_with_callback(event, handler.as_ref().unchecked_ref())
|
.add_event_listener_with_callback(event, handler.as_ref().unchecked_ref())
|
||||||
.expect("Failed to mount global key handler");
|
.expect("Failed to mount global key handler");
|
||||||
handler.forget();
|
handler.forget();
|
||||||
@ -147,7 +140,7 @@ pub async fn wait_frame() -> f64 {
|
|||||||
let handler = Closure::wrap(Box::new(move |f| {
|
let handler = Closure::wrap(Box::new(move |f| {
|
||||||
let _ = sender.unbounded_send(f);
|
let _ = sender.unbounded_send(f);
|
||||||
}) as Box<dyn FnMut(f64)>);
|
}) as Box<dyn FnMut(f64)>);
|
||||||
let _ = seed::window().request_animation_frame(handler.as_ref().unchecked_ref());
|
let _ = window().request_animation_frame(handler.as_ref().unchecked_ref());
|
||||||
handler.forget();
|
handler.forget();
|
||||||
receiver.next().await.unwrap_or_default()
|
receiver.next().await.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user