Documentation updates

This commit is contained in:
Franz Dietrich 2021-05-25 14:58:25 +02:00
parent 5c6fd4b5ae
commit b84c7ab62a
2 changed files with 89 additions and 51 deletions

View File

@ -88,8 +88,6 @@ pub enum Msg {
fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) { fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::UrlChanged(url) => { Msg::UrlChanged(url) => {
log!("Url changed!");
model.page = Page::init(url.0, orders, model.i18n.clone()); model.page = Page::init(url.0, orders, model.i18n.clone());
} }
Msg::ListLinks(msg) => { Msg::ListLinks(msg) => {
@ -186,6 +184,7 @@ impl<'a> Urls<'a> {
// View // View
// ------ ------ // ------ ------
/// Render the menu and the subpages.
fn view(model: &Model) -> Node<Msg> { fn view(model: &Model) -> Node<Msg> {
div![ div![
C!["page"], C!["page"],
@ -193,6 +192,8 @@ fn view(model: &Model) -> Node<Msg> {
view_content(&model.page, &model.base_url), view_content(&model.page, &model.base_url),
] ]
} }
/// Render the subpages.
fn view_content(page: &Page, url: &Url) -> Node<Msg> { fn view_content(page: &Page, url: &Url) -> Node<Msg> {
div![ div![
C!["container"], C!["container"],

View File

@ -28,36 +28,49 @@ macro_rules! unwrap_or_return {
}; };
} }
/// Unwrap a result and return it's content, or return from the function with another expression.
macro_rules! unwrap_or_send {
( $e:expr, $result:expr, $orders:expr) => {
match $e {
Some(x) => x,
None => {
$orders.send_msg($result);
return;},
}
};
}
/// Setup the page /// Setup the page
pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model { pub fn init(mut url: Url, orders: &mut impl Orders<Msg>, i18n: I18n) -> Model {
log!(url); // fetch the links to fill the list.
orders.send_msg(Msg::Query(QueryMsg::Fetch)); orders.send_msg(Msg::Query(QueryMsg::Fetch));
// if the url contains create_link set the edit_link variable.
// This variable then opens the create link dialog.
let edit_link = match url.next_path_part() { let edit_link = match url.next_path_part() {
Some("create_link") => Some(RefCell::new(LinkDelta::default())), Some("create_link") => Some(RefCell::new(LinkDelta::default())),
None | Some(_) => None, None | Some(_) => None,
}; };
log!(edit_link);
Model { Model {
links: Vec::new(), links: Vec::new(), // will contain the links to display
i18n, i18n, // to translate
formconfig: LinkRequestForm::default(), formconfig: LinkRequestForm::default(), // when requesting links the form is stored here
inputs: EnumMap::default(), inputs: EnumMap::default(), // the input fields for the searches
edit_link, edit_link, // if set this will trigger a link edit dialog
last_message: None, last_message: None, // if a message to the user is recieved
question: None, question: None, // some operations should be confirmed
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Model { pub struct Model {
links: Vec<FullLink>, links: Vec<FullLink>, // will contain the links to display
i18n: I18n, i18n: I18n, // to translate
formconfig: LinkRequestForm, formconfig: LinkRequestForm, // when requesting links the form is stored here
inputs: EnumMap<LinkOverviewColumns, FilterInput>, inputs: EnumMap<LinkOverviewColumns, FilterInput>, // the input fields for the searches
edit_link: Option<RefCell<LinkDelta>>, edit_link: Option<RefCell<LinkDelta>>, // if set this will trigger a link edit dialog
last_message: Option<Status>, last_message: Option<Status>, // if a message to the user is recieved
question: Option<EditMsg>, question: Option<EditMsg>, // some operations should be confirmed
} }
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
@ -67,10 +80,10 @@ struct FilterInput {
#[derive(Clone)] #[derive(Clone)]
pub enum Msg { pub enum Msg {
Query(QueryMsg), Query(QueryMsg), // Messages related to querying links
Edit(EditMsg), Edit(EditMsg), // Messages related to editing links
ClearAll, ClearAll, // Clear all messages
SetMessage(String), SetMessage(String), // Set a message to the user
} }
#[derive(Clone)] #[derive(Clone)]
@ -100,19 +113,23 @@ pub enum EditMsg {
DeletedLink(Status), DeletedLink(Status),
} }
/// hide all dialogs
fn clear_all(model: &mut Model) {
model.edit_link = None;
model.last_message = None;
model.question = None;
}
/// React to environment changes /// React to environment changes
pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) { pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
Msg::Query(msg) => process_query_messages(msg, model, orders), Msg::Query(msg) => process_query_messages(msg, model, orders),
Msg::Edit(msg) => process_edit_messages(msg, model, orders), Msg::Edit(msg) => process_edit_messages(msg, model, orders),
Msg::ClearAll => { Msg::ClearAll => {
model.edit_link = None; clear_all(model)
model.last_message = None;
model.question = None;
} }
Msg::SetMessage(msg) => { Msg::SetMessage(msg) => {
model.edit_link = None; clear_all(model);
model.question = None;
model.last_message = Some(Status::Error(Message { message: msg })); model.last_message = Some(Status::Error(Message { message: msg }));
} }
} }
@ -125,6 +142,7 @@ pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut imp
orders.skip(); // No need to rerender orders.skip(); // No need to rerender
load_links(model, orders) load_links(model, orders)
} }
// Default to ascending ordering but if the links are already sorted according to this collumn toggle between ascending and descending ordering.
QueryMsg::OrderBy(column) => { QueryMsg::OrderBy(column) => {
model.formconfig.order = model.formconfig.order.as_ref().map_or_else( model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
|| { || {
@ -144,8 +162,11 @@ pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut imp
}) })
}, },
); );
// After setting up the ordering fetch the links from the server again with the new filter settings.
// If the new filters and ordering include more links the list would be incomplete otherwise.
orders.send_msg(Msg::Query(QueryMsg::Fetch)); orders.send_msg(Msg::Query(QueryMsg::Fetch));
// Also sort the links locally - can probably removed...
model.links.sort_by(match column { model.links.sort_by(match column {
LinkOverviewColumns::Code => { LinkOverviewColumns::Code => {
|o: &FullLink, t: &FullLink| o.link.code.cmp(&t.link.code) |o: &FullLink, t: &FullLink| o.link.code.cmp(&t.link.code)
@ -199,27 +220,29 @@ fn load_links(model: &Model, orders: &mut impl Orders<Msg>) {
let data = model.formconfig.clone(); let data = model.formconfig.clone();
orders.perform_cmd(async { orders.perform_cmd(async {
let data = data; let data = data;
// create a request
let request = unwrap_or_return!( let request = unwrap_or_return!(
Request::new("/admin/json/list_links/") Request::new("/admin/json/list_links/")
.method(Method::Post) .method(Method::Post)
.json(&data), .json(&data),
Msg::SetMessage("Failed to parse data".to_string()) Msg::SetMessage("Failed to parse data".to_string())
); );
// send the request and recieve a response
let response = unwrap_or_return!( let response = unwrap_or_return!(
fetch(request).await, fetch(request).await,
Msg::SetMessage("Failed to send data".to_string()) Msg::SetMessage("Failed to send data".to_string())
); );
// check the html status to be 200
let response = unwrap_or_return!( let response = unwrap_or_return!(
response.check_status(), response.check_status(),
Msg::SetMessage("Wrong response code".to_string()) Msg::SetMessage("Wrong response code".to_string())
); );
// unpack the response into the `Vec<FullLink>`
let links: Vec<FullLink> = unwrap_or_return!( let links: Vec<FullLink> = unwrap_or_return!(
response.json().await, response.json().await,
Msg::SetMessage("Invalid response".to_string()) Msg::SetMessage("Invalid response".to_string())
); );
// The message that is sent by perform_cmd after this async block is completed
Msg::Query(QueryMsg::Received(links)) Msg::Query(QueryMsg::Received(links))
}); });
} }
@ -228,17 +251,16 @@ fn load_links(model: &Model, orders: &mut impl Orders<Msg>) {
pub fn process_edit_messages(msg: EditMsg, model: &mut Model, orders: &mut impl Orders<Msg>) { pub fn process_edit_messages(msg: EditMsg, model: &mut Model, orders: &mut impl Orders<Msg>) {
match msg { match msg {
EditMsg::EditSelected(link) => { EditMsg::EditSelected(link) => {
log!("Editing link: ", link); clear_all(model);
model.last_message = None;
model.edit_link = Some(RefCell::new(link)) model.edit_link = Some(RefCell::new(link))
} }
EditMsg::CreateNewLink => { EditMsg::CreateNewLink => {
log!("Create new link!"); clear_all(model);
model.edit_link = Some(RefCell::new(LinkDelta::default())) model.edit_link = Some(RefCell::new(LinkDelta::default()))
} }
EditMsg::Created(success_msg) => { EditMsg::Created(success_msg) => {
clear_all(model);
model.last_message = Some(success_msg); model.last_message = Some(success_msg);
model.edit_link = None;
orders.send_msg(Msg::Query(QueryMsg::Fetch)); orders.send_msg(Msg::Query(QueryMsg::Fetch));
} }
EditMsg::EditCodeChanged(s) => { EditMsg::EditCodeChanged(s) => {
@ -262,34 +284,38 @@ pub fn process_edit_messages(msg: EditMsg, model: &mut Model, orders: &mut impl
save_link(model, orders); save_link(model, orders);
} }
EditMsg::FailedToCreateLink => { EditMsg::FailedToCreateLink => {
orders.send_msg(Msg::SetMessage("Failed to create this link!".to_string()));
log!("Failed to create Link"); log!("Failed to create Link");
} }
// capture including the message part
link @ EditMsg::MayDeleteSelected(..) => { link @ EditMsg::MayDeleteSelected(..) => {
log!("Deleting link: ", link); clear_all(model);
model.last_message = None;
model.edit_link = None;
model.question = Some(link) model.question = Some(link)
} }
EditMsg::DeleteSelected(link) => { EditMsg::DeleteSelected(link) => {
orders.perform_cmd(async { orders.perform_cmd(async {
let data = link; let data = link;
// create the request
let request = unwrap_or_return!( let request = unwrap_or_return!(
Request::new("/admin/json/delete_link/") Request::new("/admin/json/delete_link/")
.method(Method::Post) .method(Method::Post)
.json(&data), .json(&data),
Msg::SetMessage("serialization failed".to_string()) Msg::SetMessage("serialization failed".to_string())
); );
// perform the request and recieve a respnse
let response = let response =
unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToDeleteLink)); unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToDeleteLink));
// check the status of the response
let response = unwrap_or_return!( let response = unwrap_or_return!(
response.check_status(), response.check_status(),
Msg::SetMessage("Wrong response code!".to_string()) Msg::SetMessage("Wrong response code!".to_string())
); );
// deserialize the response
let message: Status = unwrap_or_return!( let message: Status = unwrap_or_return!(
response.json().await, response.json().await,
Msg::SetMessage( Msg::SetMessage(
"Failed to parse the response the link might be deleted however!" "Failed to parse the response! The link might or might not be deleted!"
.to_string() .to_string()
) )
); );
@ -301,25 +327,25 @@ pub fn process_edit_messages(msg: EditMsg, model: &mut Model, orders: &mut impl
log!("Failed to delete Link"); log!("Failed to delete Link");
} }
EditMsg::DeletedLink(message) => { EditMsg::DeletedLink(message) => {
clear_all(model);
model.last_message = Some(message); model.last_message = Some(message);
model.edit_link = None;
model.question = None;
orders.send_msg(Msg::Query(QueryMsg::Fetch)); orders.send_msg(Msg::Query(QueryMsg::Fetch));
} }
} }
} }
/// Save a link to the server. /// Send a link save request to the server.
fn save_link(model: &Model, orders: &mut impl Orders<Msg>) { fn save_link(model: &Model, orders: &mut impl Orders<Msg>) {
let edit_link = if let Some(e) = model.edit_link.as_ref() { // get the link to save
e let edit_link = unwrap_or_send!(
} else { model.edit_link.as_ref(),
orders.send_msg(Msg::SetMessage("Please enter a link".to_string())); Msg::SetMessage("Please enter a link".to_string()),
return; orders
}; );
let data = edit_link.borrow().clone(); let data = edit_link.borrow().clone();
orders.perform_cmd(async { orders.perform_cmd(async {
let data = data; let data = data;
// create the request
let request = unwrap_or_return!( let request = unwrap_or_return!(
Request::new(match data.edit { Request::new(match data.edit {
EditMode::Create => "/admin/json/create_link/", EditMode::Create => "/admin/json/create_link/",
@ -329,15 +355,16 @@ fn save_link(model: &Model, orders: &mut impl Orders<Msg>) {
.json(&data), .json(&data),
Msg::SetMessage("Failed to encode the link!".to_string()) Msg::SetMessage("Failed to encode the link!".to_string())
); );
// perform the request
let response = let response =
unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToCreateLink)); unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToCreateLink));
log!(response); // check the response status
let response = unwrap_or_return!( let response = unwrap_or_return!(
response.check_status(), response.check_status(),
Msg::SetMessage("Wrong response code".to_string()) Msg::SetMessage("Wrong response code".to_string())
); );
// Parse the response
let message: Status = unwrap_or_return!( let message: Status = unwrap_or_return!(
response.json().await, response.json().await,
Msg::SetMessage("Invalid response!".to_string()) Msg::SetMessage("Invalid response!".to_string())
@ -354,8 +381,10 @@ fn save_link(model: &Model, orders: &mut impl Orders<Msg>) {
#[must_use] #[must_use]
pub fn view(model: &Model) -> Node<Msg> { pub fn view(model: &Model) -> Node<Msg> {
let lang = &model.i18n.clone(); let lang = &model.i18n.clone();
// shortcut for translating
let t = move |key: &str| lang.translate(key, None); let t = move |key: &str| lang.translate(key, None);
section![ section![
// display a message if any
if let Some(message) = &model.last_message { if let Some(message) = &model.last_message {
div![ div![
C!["message", "center"], C!["message", "center"],
@ -371,6 +400,7 @@ pub fn view(model: &Model) -> Node<Msg> {
} else { } else {
section![] section![]
}, },
// Display a question if any
if let Some(question) = &model.question { if let Some(question) = &model.question {
div![ div![
C!["message", "center"], C!["message", "center"],
@ -399,6 +429,8 @@ pub fn view(model: &Model) -> Node<Msg> {
} else { } else {
section![] section![]
}, },
// display the list of links
table![ table![
// Add the headlines // Add the headlines
view_link_table_head(&t), view_link_table_head(&t),
@ -407,10 +439,13 @@ pub fn view(model: &Model) -> Node<Msg> {
// Add all the content lines // Add all the content lines
model.links.iter().map(view_link) model.links.iter().map(view_link)
], ],
// A fetch button - this should not be needed and will be removed in future.
button![ button![
ev(Ev::Click, |_| Msg::Query(QueryMsg::Fetch)), ev(Ev::Click, |_| Msg::Query(QueryMsg::Fetch)),
"Fetch links" "Fetch links"
], ],
// Display the edit dialog if any
if let Some(l) = &model.edit_link { if let Some(l) = &model.edit_link {
edit_or_create_link(l, t) edit_or_create_link(l, t)
} else { } else {
@ -501,6 +536,7 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
input_ev(Ev::Input, |s| Msg::Query(QueryMsg::AuthorFilterChanged(s))), input_ev(Ev::Input, |s| Msg::Query(QueryMsg::AuthorFilterChanged(s))),
el_ref(&model.inputs[LinkOverviewColumns::Author].filter_input), el_ref(&model.inputs[LinkOverviewColumns::Author].filter_input),
]], ]],
// statistics and the delete column cannot be filtered
td![], td![],
td![], td![],
] ]
@ -508,7 +544,7 @@ fn view_link_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> N
/// display a single link /// display a single link
fn view_link(l: &FullLink) -> Node<Msg> { fn view_link(l: &FullLink) -> Node<Msg> {
// Ugly hack // Ugly hack - this is needed to be able to move the l into the closures... l.clone() in place does not work.
let link = LinkDelta::from(l.clone()); let link = LinkDelta::from(l.clone());
let link2 = LinkDelta::from(l.clone()); let link2 = LinkDelta::from(l.clone());
let link3 = LinkDelta::from(l.clone()); let link3 = LinkDelta::from(l.clone());
@ -554,6 +590,7 @@ fn view_link(l: &FullLink) -> Node<Msg> {
fn edit_or_create_link<F: Fn(&str) -> String>(l: &RefCell<LinkDelta>, t: F) -> Node<Msg> { fn edit_or_create_link<F: Fn(&str) -> String>(l: &RefCell<LinkDelta>, t: F) -> Node<Msg> {
let link = l.borrow(); let link = l.borrow();
div![ div![
// close button top right
C!["editdialog", "center"], C!["editdialog", "center"],
div![ div![
C!["closebutton"], C!["closebutton"],