pub struct Detail { /* private fields */ }Expand description
One thing in full: a header saying what it is and how it stands, and sections of rows around it.
Implementations§
Source§impl Detail
impl Detail
Sourcepub fn new(title: impl Into<String>) -> Self
pub fn new(title: impl Into<String>) -> Self
Examples found in repository?
examples/library/main.rs (line 889)
809 fn render(&self, args: &ViewArgs, ctx: &Context) -> Result<ViewData, ApiError> {
810 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
811 let books: Vec<Book> = ctx.optional_rows(BOOKS_FILE)?;
812 let code = args.get_or("branch", "");
813 let branch = branches
814 .iter()
815 .find(|b| b.code == code)
816 .ok_or_else(|| ApiError::new(404, format!("no branch has the code \"{code}\"")))?;
817
818 let (here, out) = shelf_counts(&books, code);
819 let now = today();
820
821 // What is out, with how it stands against its due date. A row's own
822 // link is the catalogue entry, and one book's link is a `javascript:`
823 // URL, which the page shows as text rather than following.
824 let mut lent = DetailSection::main("Out on loan");
825 for book in BranchDetail::by_rating(&books, code, true) {
826 let late = days_from_civil(&book.due).map(|due| now - due);
827 let mut row = DetailRow::new(book.title.as_str()).link(book.link.as_str());
828 if !book.due.is_empty() {
829 row = row.fact(format!("due {}", book.due));
830 }
831 row = match late {
832 Some(days) if days > 0 => row.fact(format!("{days} day(s) late")),
833 Some(days) => row.fact(format!("{} day(s) to go", -days)),
834 None => row.note("No due date is recorded."),
835 };
836 lent = lent.row(row.button(Button::disabled("Chase the borrower", "Not built yet")));
837 }
838 if out == 0 {
839 lent = lent.note("Everything this branch holds is on the shelf.");
840 }
841
842 // What can go out, ranked, with the one action this example offers.
843 let mut shelf = DetailSection::main("On the shelf")
844 .numbered()
845 .note("Best rated first.");
846 for book in BranchDetail::by_rating(&books, code, false) {
847 let mut row = DetailRow::new(book.title.as_str());
848 if let Some(rating) = book.rating {
849 row = row.fact(format!("rated {rating}"));
850 }
851 if let Some(year) = book.year {
852 row = row.fact(year);
853 }
854 if !book.notes.trim().is_empty() {
855 row = row.note(book.notes.as_str());
856 }
857 shelf = shelf.row(
858 row.button(Button::link("Catalogue", book.link.as_str()))
859 .button(Button::form(
860 "Lend it out",
861 BranchDetail::loan_form(&book.title),
862 )),
863 );
864 }
865 if here == out {
866 shelf = shelf.note("Nothing this branch holds is on the shelf today.");
867 }
868
869 let mut hours = DetailSection::side("When it is open").collapsed_on_phone();
870 for (day, open) in &branch.hours {
871 hours = hours.row(DetailRow::new(day.as_str()).fact(open.as_str()));
872 }
873 if branch.hours.is_empty() {
874 hours = hours.note("No hours are recorded for this branch.");
875 }
876
877 // A count per genre, which is a section of facts rather than of things
878 // to do, so it sits beside the page rather than in it.
879 let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
880 for book in books.iter().filter(|b| b.shelved.contains_key(code)) {
881 *counts.entry(book.genre.as_str()).or_default() += 1;
882 }
883 let mut genres = DetailSection::side("Genres here");
884 for (genre, count) in counts {
885 genres = genres.row(DetailRow::new(genre).fact(count));
886 }
887
888 Ok(ViewData::new().detail(
889 Detail::new(branch.name.as_str())
890 .status(if branch.open.unwrap_or(false) {
891 Status::new("Open", Tone::Good)
892 } else {
893 Status::new("Shut", Tone::Neutral)
894 })
895 .subtitle(format!(
896 "{}, {} staff, {here} book(s) here, {out} out",
897 librarian(branch),
898 branch.staff.unwrap_or(0)
899 ))
900 .back(ViewLink::new("all-branches"))
901 .section(lent)
902 .section(shelf)
903 .section(hours)
904 .section(genres),
905 ))
906 }Sourcepub fn status(self, status: Status) -> Self
pub fn status(self, status: Status) -> Self
How the thing stands, as a card would say it, and as often as a card may say it.
Examples found in repository?
examples/library/main.rs (lines 890-894)
809 fn render(&self, args: &ViewArgs, ctx: &Context) -> Result<ViewData, ApiError> {
810 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
811 let books: Vec<Book> = ctx.optional_rows(BOOKS_FILE)?;
812 let code = args.get_or("branch", "");
813 let branch = branches
814 .iter()
815 .find(|b| b.code == code)
816 .ok_or_else(|| ApiError::new(404, format!("no branch has the code \"{code}\"")))?;
817
818 let (here, out) = shelf_counts(&books, code);
819 let now = today();
820
821 // What is out, with how it stands against its due date. A row's own
822 // link is the catalogue entry, and one book's link is a `javascript:`
823 // URL, which the page shows as text rather than following.
824 let mut lent = DetailSection::main("Out on loan");
825 for book in BranchDetail::by_rating(&books, code, true) {
826 let late = days_from_civil(&book.due).map(|due| now - due);
827 let mut row = DetailRow::new(book.title.as_str()).link(book.link.as_str());
828 if !book.due.is_empty() {
829 row = row.fact(format!("due {}", book.due));
830 }
831 row = match late {
832 Some(days) if days > 0 => row.fact(format!("{days} day(s) late")),
833 Some(days) => row.fact(format!("{} day(s) to go", -days)),
834 None => row.note("No due date is recorded."),
835 };
836 lent = lent.row(row.button(Button::disabled("Chase the borrower", "Not built yet")));
837 }
838 if out == 0 {
839 lent = lent.note("Everything this branch holds is on the shelf.");
840 }
841
842 // What can go out, ranked, with the one action this example offers.
843 let mut shelf = DetailSection::main("On the shelf")
844 .numbered()
845 .note("Best rated first.");
846 for book in BranchDetail::by_rating(&books, code, false) {
847 let mut row = DetailRow::new(book.title.as_str());
848 if let Some(rating) = book.rating {
849 row = row.fact(format!("rated {rating}"));
850 }
851 if let Some(year) = book.year {
852 row = row.fact(year);
853 }
854 if !book.notes.trim().is_empty() {
855 row = row.note(book.notes.as_str());
856 }
857 shelf = shelf.row(
858 row.button(Button::link("Catalogue", book.link.as_str()))
859 .button(Button::form(
860 "Lend it out",
861 BranchDetail::loan_form(&book.title),
862 )),
863 );
864 }
865 if here == out {
866 shelf = shelf.note("Nothing this branch holds is on the shelf today.");
867 }
868
869 let mut hours = DetailSection::side("When it is open").collapsed_on_phone();
870 for (day, open) in &branch.hours {
871 hours = hours.row(DetailRow::new(day.as_str()).fact(open.as_str()));
872 }
873 if branch.hours.is_empty() {
874 hours = hours.note("No hours are recorded for this branch.");
875 }
876
877 // A count per genre, which is a section of facts rather than of things
878 // to do, so it sits beside the page rather than in it.
879 let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
880 for book in books.iter().filter(|b| b.shelved.contains_key(code)) {
881 *counts.entry(book.genre.as_str()).or_default() += 1;
882 }
883 let mut genres = DetailSection::side("Genres here");
884 for (genre, count) in counts {
885 genres = genres.row(DetailRow::new(genre).fact(count));
886 }
887
888 Ok(ViewData::new().detail(
889 Detail::new(branch.name.as_str())
890 .status(if branch.open.unwrap_or(false) {
891 Status::new("Open", Tone::Good)
892 } else {
893 Status::new("Shut", Tone::Neutral)
894 })
895 .subtitle(format!(
896 "{}, {} staff, {here} book(s) here, {out} out",
897 librarian(branch),
898 branch.staff.unwrap_or(0)
899 ))
900 .back(ViewLink::new("all-branches"))
901 .section(lent)
902 .section(shelf)
903 .section(hours)
904 .section(genres),
905 ))
906 }Sourcepub fn subtitle(self, subtitle: impl Into<String>) -> Self
pub fn subtitle(self, subtitle: impl Into<String>) -> Self
Examples found in repository?
examples/library/main.rs (lines 895-899)
809 fn render(&self, args: &ViewArgs, ctx: &Context) -> Result<ViewData, ApiError> {
810 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
811 let books: Vec<Book> = ctx.optional_rows(BOOKS_FILE)?;
812 let code = args.get_or("branch", "");
813 let branch = branches
814 .iter()
815 .find(|b| b.code == code)
816 .ok_or_else(|| ApiError::new(404, format!("no branch has the code \"{code}\"")))?;
817
818 let (here, out) = shelf_counts(&books, code);
819 let now = today();
820
821 // What is out, with how it stands against its due date. A row's own
822 // link is the catalogue entry, and one book's link is a `javascript:`
823 // URL, which the page shows as text rather than following.
824 let mut lent = DetailSection::main("Out on loan");
825 for book in BranchDetail::by_rating(&books, code, true) {
826 let late = days_from_civil(&book.due).map(|due| now - due);
827 let mut row = DetailRow::new(book.title.as_str()).link(book.link.as_str());
828 if !book.due.is_empty() {
829 row = row.fact(format!("due {}", book.due));
830 }
831 row = match late {
832 Some(days) if days > 0 => row.fact(format!("{days} day(s) late")),
833 Some(days) => row.fact(format!("{} day(s) to go", -days)),
834 None => row.note("No due date is recorded."),
835 };
836 lent = lent.row(row.button(Button::disabled("Chase the borrower", "Not built yet")));
837 }
838 if out == 0 {
839 lent = lent.note("Everything this branch holds is on the shelf.");
840 }
841
842 // What can go out, ranked, with the one action this example offers.
843 let mut shelf = DetailSection::main("On the shelf")
844 .numbered()
845 .note("Best rated first.");
846 for book in BranchDetail::by_rating(&books, code, false) {
847 let mut row = DetailRow::new(book.title.as_str());
848 if let Some(rating) = book.rating {
849 row = row.fact(format!("rated {rating}"));
850 }
851 if let Some(year) = book.year {
852 row = row.fact(year);
853 }
854 if !book.notes.trim().is_empty() {
855 row = row.note(book.notes.as_str());
856 }
857 shelf = shelf.row(
858 row.button(Button::link("Catalogue", book.link.as_str()))
859 .button(Button::form(
860 "Lend it out",
861 BranchDetail::loan_form(&book.title),
862 )),
863 );
864 }
865 if here == out {
866 shelf = shelf.note("Nothing this branch holds is on the shelf today.");
867 }
868
869 let mut hours = DetailSection::side("When it is open").collapsed_on_phone();
870 for (day, open) in &branch.hours {
871 hours = hours.row(DetailRow::new(day.as_str()).fact(open.as_str()));
872 }
873 if branch.hours.is_empty() {
874 hours = hours.note("No hours are recorded for this branch.");
875 }
876
877 // A count per genre, which is a section of facts rather than of things
878 // to do, so it sits beside the page rather than in it.
879 let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
880 for book in books.iter().filter(|b| b.shelved.contains_key(code)) {
881 *counts.entry(book.genre.as_str()).or_default() += 1;
882 }
883 let mut genres = DetailSection::side("Genres here");
884 for (genre, count) in counts {
885 genres = genres.row(DetailRow::new(genre).fact(count));
886 }
887
888 Ok(ViewData::new().detail(
889 Detail::new(branch.name.as_str())
890 .status(if branch.open.unwrap_or(false) {
891 Status::new("Open", Tone::Good)
892 } else {
893 Status::new("Shut", Tone::Neutral)
894 })
895 .subtitle(format!(
896 "{}, {} staff, {here} book(s) here, {out} out",
897 librarian(branch),
898 branch.staff.unwrap_or(0)
899 ))
900 .back(ViewLink::new("all-branches"))
901 .section(lent)
902 .section(shelf)
903 .section(hours)
904 .section(genres),
905 ))
906 }Sourcepub fn back(self, link: ViewLink) -> Self
pub fn back(self, link: ViewLink) -> Self
The view this page was reached from, which the header offers as the way back. Its title is the bundle’s to write, from the app it already knows.
Examples found in repository?
examples/library/main.rs (line 900)
809 fn render(&self, args: &ViewArgs, ctx: &Context) -> Result<ViewData, ApiError> {
810 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
811 let books: Vec<Book> = ctx.optional_rows(BOOKS_FILE)?;
812 let code = args.get_or("branch", "");
813 let branch = branches
814 .iter()
815 .find(|b| b.code == code)
816 .ok_or_else(|| ApiError::new(404, format!("no branch has the code \"{code}\"")))?;
817
818 let (here, out) = shelf_counts(&books, code);
819 let now = today();
820
821 // What is out, with how it stands against its due date. A row's own
822 // link is the catalogue entry, and one book's link is a `javascript:`
823 // URL, which the page shows as text rather than following.
824 let mut lent = DetailSection::main("Out on loan");
825 for book in BranchDetail::by_rating(&books, code, true) {
826 let late = days_from_civil(&book.due).map(|due| now - due);
827 let mut row = DetailRow::new(book.title.as_str()).link(book.link.as_str());
828 if !book.due.is_empty() {
829 row = row.fact(format!("due {}", book.due));
830 }
831 row = match late {
832 Some(days) if days > 0 => row.fact(format!("{days} day(s) late")),
833 Some(days) => row.fact(format!("{} day(s) to go", -days)),
834 None => row.note("No due date is recorded."),
835 };
836 lent = lent.row(row.button(Button::disabled("Chase the borrower", "Not built yet")));
837 }
838 if out == 0 {
839 lent = lent.note("Everything this branch holds is on the shelf.");
840 }
841
842 // What can go out, ranked, with the one action this example offers.
843 let mut shelf = DetailSection::main("On the shelf")
844 .numbered()
845 .note("Best rated first.");
846 for book in BranchDetail::by_rating(&books, code, false) {
847 let mut row = DetailRow::new(book.title.as_str());
848 if let Some(rating) = book.rating {
849 row = row.fact(format!("rated {rating}"));
850 }
851 if let Some(year) = book.year {
852 row = row.fact(year);
853 }
854 if !book.notes.trim().is_empty() {
855 row = row.note(book.notes.as_str());
856 }
857 shelf = shelf.row(
858 row.button(Button::link("Catalogue", book.link.as_str()))
859 .button(Button::form(
860 "Lend it out",
861 BranchDetail::loan_form(&book.title),
862 )),
863 );
864 }
865 if here == out {
866 shelf = shelf.note("Nothing this branch holds is on the shelf today.");
867 }
868
869 let mut hours = DetailSection::side("When it is open").collapsed_on_phone();
870 for (day, open) in &branch.hours {
871 hours = hours.row(DetailRow::new(day.as_str()).fact(open.as_str()));
872 }
873 if branch.hours.is_empty() {
874 hours = hours.note("No hours are recorded for this branch.");
875 }
876
877 // A count per genre, which is a section of facts rather than of things
878 // to do, so it sits beside the page rather than in it.
879 let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
880 for book in books.iter().filter(|b| b.shelved.contains_key(code)) {
881 *counts.entry(book.genre.as_str()).or_default() += 1;
882 }
883 let mut genres = DetailSection::side("Genres here");
884 for (genre, count) in counts {
885 genres = genres.row(DetailRow::new(genre).fact(count));
886 }
887
888 Ok(ViewData::new().detail(
889 Detail::new(branch.name.as_str())
890 .status(if branch.open.unwrap_or(false) {
891 Status::new("Open", Tone::Good)
892 } else {
893 Status::new("Shut", Tone::Neutral)
894 })
895 .subtitle(format!(
896 "{}, {} staff, {here} book(s) here, {out} out",
897 librarian(branch),
898 branch.staff.unwrap_or(0)
899 ))
900 .back(ViewLink::new("all-branches"))
901 .section(lent)
902 .section(shelf)
903 .section(hours)
904 .section(genres),
905 ))
906 }Sourcepub fn section(self, section: DetailSection) -> Self
pub fn section(self, section: DetailSection) -> Self
Examples found in repository?
examples/library/main.rs (line 901)
809 fn render(&self, args: &ViewArgs, ctx: &Context) -> Result<ViewData, ApiError> {
810 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
811 let books: Vec<Book> = ctx.optional_rows(BOOKS_FILE)?;
812 let code = args.get_or("branch", "");
813 let branch = branches
814 .iter()
815 .find(|b| b.code == code)
816 .ok_or_else(|| ApiError::new(404, format!("no branch has the code \"{code}\"")))?;
817
818 let (here, out) = shelf_counts(&books, code);
819 let now = today();
820
821 // What is out, with how it stands against its due date. A row's own
822 // link is the catalogue entry, and one book's link is a `javascript:`
823 // URL, which the page shows as text rather than following.
824 let mut lent = DetailSection::main("Out on loan");
825 for book in BranchDetail::by_rating(&books, code, true) {
826 let late = days_from_civil(&book.due).map(|due| now - due);
827 let mut row = DetailRow::new(book.title.as_str()).link(book.link.as_str());
828 if !book.due.is_empty() {
829 row = row.fact(format!("due {}", book.due));
830 }
831 row = match late {
832 Some(days) if days > 0 => row.fact(format!("{days} day(s) late")),
833 Some(days) => row.fact(format!("{} day(s) to go", -days)),
834 None => row.note("No due date is recorded."),
835 };
836 lent = lent.row(row.button(Button::disabled("Chase the borrower", "Not built yet")));
837 }
838 if out == 0 {
839 lent = lent.note("Everything this branch holds is on the shelf.");
840 }
841
842 // What can go out, ranked, with the one action this example offers.
843 let mut shelf = DetailSection::main("On the shelf")
844 .numbered()
845 .note("Best rated first.");
846 for book in BranchDetail::by_rating(&books, code, false) {
847 let mut row = DetailRow::new(book.title.as_str());
848 if let Some(rating) = book.rating {
849 row = row.fact(format!("rated {rating}"));
850 }
851 if let Some(year) = book.year {
852 row = row.fact(year);
853 }
854 if !book.notes.trim().is_empty() {
855 row = row.note(book.notes.as_str());
856 }
857 shelf = shelf.row(
858 row.button(Button::link("Catalogue", book.link.as_str()))
859 .button(Button::form(
860 "Lend it out",
861 BranchDetail::loan_form(&book.title),
862 )),
863 );
864 }
865 if here == out {
866 shelf = shelf.note("Nothing this branch holds is on the shelf today.");
867 }
868
869 let mut hours = DetailSection::side("When it is open").collapsed_on_phone();
870 for (day, open) in &branch.hours {
871 hours = hours.row(DetailRow::new(day.as_str()).fact(open.as_str()));
872 }
873 if branch.hours.is_empty() {
874 hours = hours.note("No hours are recorded for this branch.");
875 }
876
877 // A count per genre, which is a section of facts rather than of things
878 // to do, so it sits beside the page rather than in it.
879 let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
880 for book in books.iter().filter(|b| b.shelved.contains_key(code)) {
881 *counts.entry(book.genre.as_str()).or_default() += 1;
882 }
883 let mut genres = DetailSection::side("Genres here");
884 for (genre, count) in counts {
885 genres = genres.row(DetailRow::new(genre).fact(count));
886 }
887
888 Ok(ViewData::new().detail(
889 Detail::new(branch.name.as_str())
890 .status(if branch.open.unwrap_or(false) {
891 Status::new("Open", Tone::Good)
892 } else {
893 Status::new("Shut", Tone::Neutral)
894 })
895 .subtitle(format!(
896 "{}, {} staff, {here} book(s) here, {out} out",
897 librarian(branch),
898 branch.staff.unwrap_or(0)
899 ))
900 .back(ViewLink::new("all-branches"))
901 .section(lent)
902 .section(shelf)
903 .section(hours)
904 .section(genres),
905 ))
906 }Trait Implementations§
Auto Trait Implementations§
impl Freeze for Detail
impl RefUnwindSafe for Detail
impl Send for Detail
impl Sync for Detail
impl Unpin for Detail
impl UnsafeUnpin for Detail
impl UnwindSafe for Detail
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more