Make secret more logsafe, implement add and edit for links
This commit is contained in:
+34
-1
@@ -5,6 +5,7 @@ pub mod pages;
|
||||
use pages::list_links;
|
||||
use pages::list_users;
|
||||
use seed::{div, log, prelude::*, App, Url, C};
|
||||
use shared::datatypes::User;
|
||||
|
||||
use crate::i18n::{I18n, Lang};
|
||||
|
||||
@@ -14,6 +15,7 @@ use crate::i18n::{I18n, Lang};
|
||||
|
||||
fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
orders.subscribe(Msg::UrlChanged);
|
||||
orders.send_msg(Msg::GetLoggedUser);
|
||||
|
||||
log!(url);
|
||||
|
||||
@@ -24,6 +26,7 @@ fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
base_url: Url::new().add_path_part("app"),
|
||||
page: Page::init(url, orders, lang.clone()),
|
||||
i18n: lang,
|
||||
user: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +40,7 @@ struct Model {
|
||||
base_url: Url,
|
||||
page: Page,
|
||||
i18n: i18n::I18n,
|
||||
user: Option<User>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -76,6 +80,8 @@ pub enum Msg {
|
||||
UrlChanged(subs::UrlChanged),
|
||||
ListLinks(list_links::Msg),
|
||||
ListUsers(list_users::Msg),
|
||||
GetLoggedUser,
|
||||
UserReceived(User),
|
||||
NoMessage,
|
||||
}
|
||||
|
||||
@@ -97,6 +103,29 @@ fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
}
|
||||
Msg::NoMessage => (),
|
||||
Msg::GetLoggedUser => {
|
||||
orders.skip(); // No need to rerender/ complicated way to move into the closure
|
||||
orders.perform_cmd(async {
|
||||
let response = fetch(
|
||||
Request::new("/admin/json/get_logged_user/")
|
||||
.method(Method::Post)
|
||||
.json(&())
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await
|
||||
.expect("HTTP request failed");
|
||||
|
||||
let user: User = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
.await
|
||||
.expect("deserialization failed");
|
||||
|
||||
Msg::UserReceived(user)
|
||||
});
|
||||
}
|
||||
Msg::UserReceived(user) => model.user = Some(user),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +169,10 @@ impl<'a> Urls<'a> {
|
||||
self.base_url().add_path_part("list_links")
|
||||
}
|
||||
#[must_use]
|
||||
pub fn create_link(self) -> Url {
|
||||
self.list_links().add_path_part("create_link")
|
||||
}
|
||||
#[must_use]
|
||||
pub fn list_users(self) -> Url {
|
||||
self.base_url().add_path_part("list_users")
|
||||
}
|
||||
@@ -156,7 +189,7 @@ impl<'a> Urls<'a> {
|
||||
fn view(model: &Model) -> Node<Msg> {
|
||||
div![
|
||||
C!["page"],
|
||||
navigation::navigation(&model.i18n, &model.base_url,),
|
||||
navigation::navigation(&model.i18n, &model.base_url, &model.user),
|
||||
view_content(&model.page, &model.base_url),
|
||||
]
|
||||
}
|
||||
|
||||
+25
-20
@@ -1,29 +1,34 @@
|
||||
use fluent::fluent_args;
|
||||
use seed::{a, attrs, div, li, nav, ol, prelude::*, Url};
|
||||
use shared::datatypes::User;
|
||||
|
||||
use crate::{i18n::I18n, Msg};
|
||||
#[must_use]
|
||||
pub fn navigation(i18n: &I18n, base_url: &Url) -> Node<Msg> {
|
||||
let username = fluent_args![ "username" => "enaut"];
|
||||
macro_rules! t {
|
||||
{ $key:expr } => {
|
||||
{
|
||||
i18n.translate($key, None)
|
||||
}
|
||||
};
|
||||
{ $key:expr, $args:expr } => {
|
||||
{
|
||||
i18n.translate($key, Some(&$args))
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn navigation(i18n: &I18n, base_url: &Url, user: &Option<User>) -> Node<Msg> {
|
||||
let t = move |key: &str| i18n.translate(key, None);
|
||||
let welcome = if let Some(user) = user {
|
||||
i18n.translate(
|
||||
"welcome-user",
|
||||
Some(&fluent_args![ "username" => user.username.clone()]),
|
||||
)
|
||||
} else {
|
||||
t("welcome")
|
||||
};
|
||||
nav![
|
||||
ol![
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).list_links()},
|
||||
t!("list-links"),
|
||||
t("list-links"),
|
||||
],],
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).create_link()},
|
||||
ev(Ev::Click, |_| Msg::ListLinks(
|
||||
super::pages::list_links::Msg::Edit(
|
||||
super::pages::list_links::EditMsg::CreateNewLink
|
||||
)
|
||||
)),
|
||||
t("add-link"),
|
||||
],],
|
||||
li![a![ev(Ev::Click, |_| Msg::NoMessage), t!("add-link"),],],
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).create_user()},
|
||||
ev(Ev::Click, |_| Msg::ListUsers(
|
||||
@@ -31,19 +36,19 @@ pub fn navigation(i18n: &I18n, base_url: &Url) -> Node<Msg> {
|
||||
super::pages::list_users::UserEditMsg::CreateNewUser
|
||||
)
|
||||
)),
|
||||
t!("invite-user"),
|
||||
t("invite-user"),
|
||||
],],
|
||||
li![a![
|
||||
attrs! {At::Href => crate::Urls::new(base_url).list_users()},
|
||||
t!("list-users"),
|
||||
t("list-users"),
|
||||
],],
|
||||
],
|
||||
ol![
|
||||
li![div![t!("welcome-user", username)]],
|
||||
li![div![welcome]],
|
||||
li![a![
|
||||
attrs! {At::Href => "#"},
|
||||
ev(Ev::Click, |_| Msg::NoMessage),
|
||||
t!("logout"),
|
||||
t("logout"),
|
||||
]]
|
||||
]
|
||||
]
|
||||
|
||||
+412
-60
@@ -1,25 +1,50 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use enum_map::EnumMap;
|
||||
use seed::{a, attrs, button, h1, input, log, prelude::*, section, table, td, th, tr, Url, C};
|
||||
use fluent::fluent_args;
|
||||
use seed::{
|
||||
a, attrs, button, div, h1, img, input, log, prelude::*, section, span, table, td, th, tr, Url,
|
||||
C,
|
||||
};
|
||||
|
||||
use shared::{
|
||||
apirequests::general::Ordering,
|
||||
apirequests::{
|
||||
general::Operation,
|
||||
links::{LinkOverviewColumns, LinkRequestForm},
|
||||
general::{EditMode, Message, Operation, Status},
|
||||
links::{LinkDelta, LinkOverviewColumns, LinkRequestForm},
|
||||
},
|
||||
datatypes::FullLink,
|
||||
};
|
||||
|
||||
use crate::i18n::I18n;
|
||||
|
||||
pub fn init(_: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
|
||||
orders.send_msg(Msg::Fetch);
|
||||
/// Unwrap a result and return it's content, or return from the function with another expression.
|
||||
macro_rules! unwrap_or_return {
|
||||
( $e:expr, $result:expr) => {
|
||||
match $e {
|
||||
Ok(x) => x,
|
||||
Err(_) => return $result,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
|
||||
log!(url);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
let edit_link = match url.next_path_part() {
|
||||
Some("create_link") => Some(RefCell::new(LinkDelta::default())),
|
||||
None | Some(_) => None,
|
||||
};
|
||||
log!(edit_link);
|
||||
|
||||
Model {
|
||||
links: Vec::new(),
|
||||
i18n,
|
||||
formconfig: LinkRequestForm::default(),
|
||||
inputs: EnumMap::default(),
|
||||
edit_link,
|
||||
last_message: None,
|
||||
question: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +54,9 @@ pub struct Model {
|
||||
i18n: I18n,
|
||||
formconfig: LinkRequestForm,
|
||||
inputs: EnumMap<LinkOverviewColumns, FilterInput>,
|
||||
edit_link: Option<RefCell<LinkDelta>>,
|
||||
last_message: Option<Status>,
|
||||
question: Option<EditMsg>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@@ -38,6 +66,14 @@ struct FilterInput {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Msg {
|
||||
Query(QueryMsg),
|
||||
Edit(EditMsg),
|
||||
ClearAll,
|
||||
SetMessage(String),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum QueryMsg {
|
||||
Fetch,
|
||||
OrderBy(LinkOverviewColumns),
|
||||
Received(Vec<FullLink>),
|
||||
@@ -46,36 +82,51 @@ pub enum Msg {
|
||||
TargetFilterChanged(String),
|
||||
AuthorFilterChanged(String),
|
||||
}
|
||||
/// All the messages on link editing
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EditMsg {
|
||||
EditSelected(LinkDelta),
|
||||
CreateNewLink,
|
||||
Created(Status),
|
||||
EditCodeChanged(String),
|
||||
EditDescriptionChanged(String),
|
||||
EditTargetChanged(String),
|
||||
MayDeleteSelected(LinkDelta),
|
||||
DeleteSelected(LinkDelta),
|
||||
SaveLink,
|
||||
FailedToCreateLink,
|
||||
FailedToDeleteLink,
|
||||
DeletedLink(Status),
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Sould only panic on bugs.
|
||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
Msg::Fetch => {
|
||||
orders.skip(); // No need to rerender
|
||||
let data = model.formconfig.clone(); // complicated way to move into the closure
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
let response = fetch(
|
||||
Request::new("/admin/json/list_links/")
|
||||
.method(Method::Post)
|
||||
.json(&data)
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await
|
||||
.expect("HTTP request failed");
|
||||
|
||||
let user: Vec<FullLink> = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
.await
|
||||
.expect("deserialization failed");
|
||||
|
||||
Msg::Received(user)
|
||||
});
|
||||
Msg::Query(msg) => process_query_messages(msg, model, orders),
|
||||
Msg::Edit(msg) => process_edit_messages(msg, model, orders),
|
||||
Msg::ClearAll => {
|
||||
model.edit_link = None;
|
||||
model.last_message = None;
|
||||
model.question = None;
|
||||
}
|
||||
Msg::OrderBy(column) => {
|
||||
Msg::SetMessage(msg) => {
|
||||
model.edit_link = None;
|
||||
model.question = None;
|
||||
model.last_message = Some(Status::Error(Message { message: msg }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
/// Sould only panic on bugs.
|
||||
pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
QueryMsg::Fetch => {
|
||||
orders.skip(); // No need to rerender
|
||||
load_links(model, orders)
|
||||
}
|
||||
QueryMsg::OrderBy(column) => {
|
||||
model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
|
||||
|| {
|
||||
Some(Operation {
|
||||
@@ -94,7 +145,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
})
|
||||
},
|
||||
);
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
|
||||
model.links.sort_by(match column {
|
||||
LinkOverviewColumns::Code => {
|
||||
@@ -114,44 +165,231 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
}
|
||||
})
|
||||
}
|
||||
Msg::Received(response) => {
|
||||
QueryMsg::Received(response) => {
|
||||
model.links = response;
|
||||
}
|
||||
Msg::CodeFilterChanged(s) => {
|
||||
QueryMsg::CodeFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Code].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
Msg::DescriptionFilterChanged(s) => {
|
||||
QueryMsg::DescriptionFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Description].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
Msg::TargetFilterChanged(s) => {
|
||||
QueryMsg::TargetFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Target].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
Msg::AuthorFilterChanged(s) => {
|
||||
QueryMsg::AuthorFilterChanged(s) => {
|
||||
log!("Filter is: ", &s);
|
||||
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||
model.formconfig.filter[LinkOverviewColumns::Author].sieve = sanit;
|
||||
orders.send_msg(Msg::Fetch);
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
}
|
||||
}
|
||||
fn load_links(model: &Model, orders: &mut impl Orders<Msg>) {
|
||||
let data = model.formconfig.clone(); // complicated way to move into the closure
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
let request = unwrap_or_return!(
|
||||
Request::new("/admin/json/list_links/")
|
||||
.method(Method::Post)
|
||||
.json(&data),
|
||||
Msg::SetMessage("Failed to parse data".to_string())
|
||||
);
|
||||
let response = unwrap_or_return!(
|
||||
fetch(request).await,
|
||||
Msg::SetMessage("Failed to send data".to_string())
|
||||
);
|
||||
|
||||
let response = unwrap_or_return!(
|
||||
response.check_status(),
|
||||
Msg::SetMessage("Wrong response code".to_string())
|
||||
);
|
||||
|
||||
let links: Vec<FullLink> = unwrap_or_return!(
|
||||
response.json().await,
|
||||
Msg::SetMessage("Invalid response".to_string())
|
||||
);
|
||||
|
||||
Msg::Query(QueryMsg::Received(links))
|
||||
});
|
||||
}
|
||||
|
||||
pub fn process_edit_messages(msg: EditMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
|
||||
match msg {
|
||||
EditMsg::EditSelected(link) => {
|
||||
log!("Editing link: ", link);
|
||||
model.last_message = None;
|
||||
model.edit_link = Some(RefCell::new(link))
|
||||
}
|
||||
EditMsg::CreateNewLink => {
|
||||
log!("Create new link!");
|
||||
model.edit_link = Some(RefCell::new(LinkDelta::default()))
|
||||
}
|
||||
EditMsg::Created(success_msg) => {
|
||||
model.last_message = Some(success_msg);
|
||||
model.edit_link = None;
|
||||
orders.send_msg(Msg::Query(QueryMsg::Fetch));
|
||||
}
|
||||
EditMsg::EditCodeChanged(s) => {
|
||||
if let Some(ref le) = model.edit_link {
|
||||
le.try_borrow_mut().expect("Failed to borrow mutably").code = s;
|
||||
}
|
||||
}
|
||||
EditMsg::EditDescriptionChanged(s) => {
|
||||
if let Some(ref le) = model.edit_link {
|
||||
le.try_borrow_mut().expect("Failed to borrow mutably").title = s;
|
||||
}
|
||||
}
|
||||
EditMsg::EditTargetChanged(s) => {
|
||||
if let Some(ref le) = model.edit_link {
|
||||
le.try_borrow_mut()
|
||||
.expect("Failed to borrow mutably")
|
||||
.target = s;
|
||||
}
|
||||
}
|
||||
EditMsg::SaveLink => {
|
||||
save_link(model, orders);
|
||||
}
|
||||
EditMsg::FailedToCreateLink => {
|
||||
log!("Failed to create Link");
|
||||
}
|
||||
link @ EditMsg::MayDeleteSelected(..) => {
|
||||
log!("Deleting link: ", link);
|
||||
model.last_message = None;
|
||||
model.edit_link = None;
|
||||
model.question = Some(link)
|
||||
}
|
||||
EditMsg::DeleteSelected(link) => {
|
||||
orders.perform_cmd(async {
|
||||
let data = link;
|
||||
let response = unwrap_or_return!(
|
||||
fetch(
|
||||
Request::new("/admin/json/delete_link/")
|
||||
.method(Method::Post)
|
||||
.json(&data)
|
||||
.expect("serialization failed"),
|
||||
)
|
||||
.await,
|
||||
Msg::Edit(EditMsg::FailedToDeleteLink)
|
||||
);
|
||||
|
||||
let response = unwrap_or_return!(
|
||||
response.check_status(),
|
||||
Msg::SetMessage("Wrong response code!".to_string())
|
||||
);
|
||||
let message: Status = unwrap_or_return!(
|
||||
response.json().await,
|
||||
Msg::SetMessage(
|
||||
"Failed to parse the response the link might be deleted however!"
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
|
||||
Msg::Edit(EditMsg::DeletedLink(message))
|
||||
});
|
||||
}
|
||||
EditMsg::FailedToDeleteLink => {
|
||||
log!("Failed to delete Link");
|
||||
}
|
||||
EditMsg::DeletedLink(message) => {
|
||||
log!("Deleted link", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn save_link(model: &Model, orders: &mut impl Orders<Msg>) {
|
||||
let data = model
|
||||
.edit_link
|
||||
.as_ref()
|
||||
.expect("should exist!")
|
||||
.borrow()
|
||||
.clone();
|
||||
orders.perform_cmd(async {
|
||||
let data = data;
|
||||
let request = unwrap_or_return!(
|
||||
Request::new(match data.edit {
|
||||
EditMode::Create => "/admin/json/create_link/",
|
||||
EditMode::Edit => "/admin/json/edit_link/",
|
||||
})
|
||||
.method(Method::Post)
|
||||
.json(&data),
|
||||
Msg::SetMessage("Failed to encode the link!".to_string())
|
||||
);
|
||||
let response =
|
||||
unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToCreateLink));
|
||||
|
||||
log!(response);
|
||||
let response = unwrap_or_return!(
|
||||
response.check_status(),
|
||||
Msg::SetMessage("Wrong response code".to_string())
|
||||
);
|
||||
|
||||
let message: Status = unwrap_or_return!(
|
||||
response.json().await,
|
||||
Msg::SetMessage("Invalid response!".to_string())
|
||||
);
|
||||
|
||||
Msg::Edit(EditMsg::Created(message))
|
||||
});
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
/// # Panics
|
||||
/// Sould only panic on bugs.
|
||||
pub fn view(model: &Model) -> Node<Msg> {
|
||||
let lang = model.i18n.clone();
|
||||
let lang = &model.i18n.clone();
|
||||
let t = move |key: &str| lang.translate(key, None);
|
||||
section![
|
||||
h1!("List Links Page from list_links"),
|
||||
if let Some(message) = &model.last_message {
|
||||
div![
|
||||
C!["message", "center"],
|
||||
div![
|
||||
C!["closebutton"],
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
match message {
|
||||
Status::Success(m) | Status::Error(m) => &m.message,
|
||||
}
|
||||
]
|
||||
} else {
|
||||
section![]
|
||||
},
|
||||
if let Some(question) = &model.question {
|
||||
div![
|
||||
C!["message", "center"],
|
||||
div![
|
||||
C!["closebutton"],
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
if let EditMsg::MayDeleteSelected(l) = question.clone() {
|
||||
div![
|
||||
lang.translate(
|
||||
"really-delete",
|
||||
Some(&fluent_args!["code" => l.code.clone()])
|
||||
),
|
||||
a![t("no"), C!["button"], ev(Ev::Click, |_| Msg::ClearAll)],
|
||||
a![
|
||||
t("yes"),
|
||||
C!["button"],
|
||||
ev(Ev::Click, move |_| Msg::Edit(EditMsg::DeleteSelected(l)))
|
||||
]
|
||||
]
|
||||
} else {
|
||||
span!()
|
||||
}
|
||||
]
|
||||
} else {
|
||||
section![]
|
||||
},
|
||||
table![
|
||||
// Add the headlines
|
||||
view_link_table_head(&t),
|
||||
@@ -160,34 +398,51 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
// Add all the content lines
|
||||
model.links.iter().map(view_link)
|
||||
],
|
||||
button![ev(Ev::Click, |_| Msg::Fetch), "Fetch links"]
|
||||
button![
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::Fetch)),
|
||||
"Fetch links"
|
||||
],
|
||||
if let Some(l) = &model.edit_link {
|
||||
edit_or_create_link(l, t)
|
||||
} else {
|
||||
section!()
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
fn view_link_table_head<F: Fn(&str) -> String>(t: F) -> Node<Msg> {
|
||||
tr![
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Code)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Code
|
||||
))),
|
||||
t("link-code")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Description
|
||||
)),
|
||||
))),
|
||||
t("link-description")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Target)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Target
|
||||
))),
|
||||
t("link-target")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Author)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Author
|
||||
))),
|
||||
t("username")
|
||||
],
|
||||
th![
|
||||
ev(Ev::Click, |_| Msg::OrderBy(LinkOverviewColumns::Statistics)),
|
||||
ev(Ev::Click, |_| Msg::Query(QueryMsg::OrderBy(
|
||||
LinkOverviewColumns::Statistics
|
||||
))),
|
||||
t("statistics")
|
||||
]
|
||||
],
|
||||
th![]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -200,7 +455,7 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::CodeFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::CodeFilterChanged(s))),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Code].filter_input),
|
||||
]],
|
||||
td![input![
|
||||
@@ -210,7 +465,9 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::DescriptionFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(
|
||||
QueryMsg::DescriptionFilterChanged(s)
|
||||
)),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Description].filter_input),
|
||||
]],
|
||||
td![input![
|
||||
@@ -220,7 +477,7 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::TargetFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::TargetFilterChanged(s))),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Target].filter_input),
|
||||
]],
|
||||
td![input![
|
||||
@@ -230,19 +487,114 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
|
||||
At::Type => "search",
|
||||
At::Placeholder => t("search-placeholder")
|
||||
},
|
||||
input_ev(Ev::Input, Msg::AuthorFilterChanged),
|
||||
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::AuthorFilterChanged(s))),
|
||||
el_ref(&model.inputs[LinkOverviewColumns::Author].filter_input),
|
||||
]],
|
||||
td![]
|
||||
td![],
|
||||
td![],
|
||||
]
|
||||
}
|
||||
|
||||
fn view_link(l: &FullLink) -> Node<Msg> {
|
||||
// Ugly hack
|
||||
let link = LinkDelta::from(l.clone());
|
||||
let link2 = LinkDelta::from(l.clone());
|
||||
let link3 = LinkDelta::from(l.clone());
|
||||
let link4 = LinkDelta::from(l.clone());
|
||||
let link5 = LinkDelta::from(l.clone());
|
||||
tr![
|
||||
td![&l.link.code],
|
||||
td![&l.link.title],
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link))),
|
||||
&l.link.code
|
||||
]
|
||||
},
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link2))),
|
||||
&l.link.title
|
||||
]
|
||||
},
|
||||
td![a![attrs![At::Href => &l.link.target], &l.link.target]],
|
||||
td![&l.user.username],
|
||||
td![&l.clicks.number]
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link3))),
|
||||
&l.user.username
|
||||
]
|
||||
},
|
||||
{
|
||||
td![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::EditSelected(link4))),
|
||||
&l.clicks.number
|
||||
]
|
||||
},
|
||||
{
|
||||
td![img![
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::MayDeleteSelected(link5))),
|
||||
C!["trashicon"],
|
||||
attrs!(At::Src => "/static/trash.svg")
|
||||
]]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn edit_or_create_link<F: Fn(&str) -> String>(l: &RefCell<LinkDelta>, t: F) -> Node<Msg> {
|
||||
let link = l.borrow();
|
||||
div![
|
||||
C!["editdialog", "center"],
|
||||
div![
|
||||
C!["closebutton"],
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
h1![match &link.edit {
|
||||
EditMode::Edit => t("edit-link"),
|
||||
EditMode::Create => t("create-link"),
|
||||
}],
|
||||
table![
|
||||
tr![
|
||||
th![t("link-description")],
|
||||
td![input![
|
||||
attrs! {
|
||||
At::Value => &link.title,
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("link-description")
|
||||
},
|
||||
input_ev(Ev::Input, |s| {
|
||||
Msg::Edit(EditMsg::EditDescriptionChanged(s))
|
||||
}),
|
||||
]]
|
||||
],
|
||||
tr![
|
||||
th![t("link-target")],
|
||||
td![input![
|
||||
attrs! {
|
||||
At::Value => &link.target,
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("link-target")
|
||||
},
|
||||
input_ev(Ev::Input, |s| { Msg::Edit(EditMsg::EditTargetChanged(s)) }),
|
||||
]]
|
||||
],
|
||||
tr![
|
||||
th![t("link-code")],
|
||||
td![input![
|
||||
attrs! {
|
||||
At::Value => &link.code,
|
||||
At::Type => "text",
|
||||
At::Placeholder => t("password")
|
||||
},
|
||||
input_ev(Ev::Input, |s| { Msg::Edit(EditMsg::EditCodeChanged(s)) }),
|
||||
],]
|
||||
]
|
||||
],
|
||||
a![
|
||||
match &link.edit {
|
||||
EditMode::Edit => t("edit-link"),
|
||||
EditMode::Create => t("create-link"),
|
||||
},
|
||||
C!["button"],
|
||||
ev(Ev::Click, |_| Msg::Edit(EditMsg::SaveLink))
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use seed::{
|
||||
use shared::{
|
||||
apirequests::general::{Operation, Ordering},
|
||||
apirequests::{
|
||||
general::{EditMode, SuccessMessage},
|
||||
general::{EditMode, Status},
|
||||
users::{UserDelta, UserOverviewColumns, UserRequestForm},
|
||||
},
|
||||
datatypes::User,
|
||||
@@ -37,7 +37,7 @@ pub struct Model {
|
||||
formconfig: UserRequestForm,
|
||||
inputs: EnumMap<UserOverviewColumns, FilterInput>,
|
||||
user_edit: Option<RefCell<UserDelta>>,
|
||||
last_message: Option<SuccessMessage>,
|
||||
last_message: Option<Status>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@@ -68,7 +68,7 @@ pub enum UserQueryMsg {
|
||||
pub enum UserEditMsg {
|
||||
EditUserSelected(UserDelta),
|
||||
CreateNewUser,
|
||||
UserCreated(SuccessMessage),
|
||||
UserCreated(Status),
|
||||
EditUsernameChanged(String),
|
||||
EditEmailChanged(String),
|
||||
EditPasswordChanged(String),
|
||||
@@ -236,7 +236,7 @@ pub fn process_user_edit_messages(
|
||||
Err(_) => return Msg::Edit(UserEditMsg::FailedToCreateUser),
|
||||
};
|
||||
|
||||
let message: SuccessMessage = response
|
||||
let message: Status = response
|
||||
.check_status() // ensure we've got 2xx status
|
||||
.expect("status check failed")
|
||||
.json()
|
||||
@@ -268,7 +268,6 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
keyboard_ev(Ev::KeyDown, |keyboard_event| {
|
||||
IF!(keyboard_event.key() == "Escape" => Msg::ClearAll)
|
||||
}),
|
||||
h1!("List Users Page from list_users"),
|
||||
if let Some(message) = &model.last_message {
|
||||
div![
|
||||
C!["message", "center"],
|
||||
@@ -277,7 +276,11 @@ pub fn view(model: &Model) -> Node<Msg> {
|
||||
a!["\u{d7}"],
|
||||
ev(Ev::Click, |_| Msg::ClearAll)
|
||||
],
|
||||
&message.message
|
||||
match message {
|
||||
Status::Success(m) | Status::Error(m) => {
|
||||
&m.message
|
||||
}
|
||||
}
|
||||
]
|
||||
} else {
|
||||
section![]
|
||||
|
||||
Reference in New Issue
Block a user