pub struct Status { /* private fields */ }Expand description
A word for how a thing stands, and how that word reads.
Implementations§
Source§impl Status
impl Status
Sourcepub fn new(word: impl Into<String>, tone: Tone) -> Self
pub fn new(word: impl Into<String>, tone: Tone) -> Self
Examples found in repository?
examples/library/main.rs (line 692)
683 fn render(&self, _args: &ViewArgs, ctx: &Context) -> Result<ViewData, ApiError> {
684 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
685 let books: Vec<Book> = ctx.optional_rows(BOOKS_FILE)?;
686
687 let card = |branch: &Branch| {
688 let (here, out) = shelf_counts(&books, &branch.code);
689 let open = branch.open.unwrap_or(false);
690 let mut card = Card::new(branch.name.as_str())
691 .status(if open {
692 Status::new("Open", Tone::Good)
693 } else {
694 // A shut branch is a fact about it rather than a fault, so
695 // it is neutral—which is also what reads quieter.
696 Status::new("Shut", Tone::Neutral)
697 })
698 .identifier(branch.code.as_str())
699 .subtitle(format!(
700 "{}, {} staff",
701 librarian(branch),
702 branch.staff.unwrap_or(0)
703 ))
704 .row("Books here", here)
705 .row("Out on loan", out)
706 .link(ViewLink::new("branch").arg("branch", &branch.code));
707 if branch.hours.is_empty() {
708 card = card.sentence(
709 "No opening hours are recorded, so nothing here says when it can be visited.",
710 );
711 }
712 card
713 };
714
715 let group = |heading: &str, open: bool| {
716 CardGroup::new(heading).cards(
717 branches
718 .iter()
719 .filter(|b| b.open.unwrap_or(false) == open)
720 .map(&card),
721 )
722 };
723
724 Ok(ViewData::new()
725 .note("A card is a branch. Open one for what it holds and what is out.")
726 .group(group("Open", true))
727 .group(group("Shut", false)))
728 }
729}
730
731// ── The Branch view, which is one branch in detail ───────────────────────────
732
733/// One branch: what it has out, what is on its shelves, and when it is open.
734///
735/// It is reached from a card rather than from the switcher, so its one
736/// parameter is hidden: the page draws no control for it, and the card's link
737/// is what answers it. The parameter is a select all the same, so a link to a
738/// branch that has since gone falls back to the first one rather than to a page
739/// about nothing.
740struct BranchDetail;
741
742impl BranchDetail {
743 /// The books a branch holds, best rated first, which is the order the
744 /// numbered section is a ranking in.
745 fn by_rating<'a>(books: &'a [Book], code: &str, lent: bool) -> Vec<&'a Book> {
746 let mut held: Vec<&Book> = books
747 .iter()
748 .filter(|b| b.shelved.contains_key(code) && b.lent.unwrap_or(false) == lent)
749 .collect();
750 held.sort_by(|a, b| {
751 b.rating
752 .unwrap_or(0.0)
753 .total_cmp(&a.rating.unwrap_or(0.0))
754 .then_with(|| a.title.cmp(&b.title))
755 });
756 held
757 }
758
759 /// What a form asks before a book goes out. The four kinds of field are all
760 /// here, since this is the page the bundle's form controls are developed
761 /// against.
762 fn loan_form(title: &str) -> Form {
763 Form::new("lend-a-book")
764 .arg("title", title)
765 .field(Field::text("borrower", "Borrower"))
766 .field(Field::number("days", "Days out").default(21))
767 .field(Field::date("from", "Date lent").default(civil_from_days(today())))
768 .field(
769 Field::one_of(
770 "condition",
771 "Condition it left in",
772 ["As new", "Good", "Worn"],
773 )
774 .default("Good"),
775 )
776 }
777}
778
779impl ViewLogic for BranchDetail {
780 fn name(&self) -> &'static str {
781 "branch"
782 }
783
784 fn title(&self) -> &'static str {
785 "Branch"
786 }
787
788 /// This page is about one branch, and which one arrives through a card's
789 /// link. A switcher entry for it would open whichever branch the parameter
790 /// happens to default to, which is nobody's question.
791 fn in_switcher(&self) -> bool {
792 false
793 }
794
795 fn params(&self, ctx: &Context, _asked: &ViewArgs) -> Result<Vec<Param>, ApiError> {
796 let branches: Vec<Branch> = ctx.optional_rows(BRANCHES_FILE)?;
797 let codes: Vec<SelectOption> = branches
798 .iter()
799 .map(|b| SelectOption::labelled(&b.code, &b.name))
800 .collect();
801 let first = branches.first().map(|b| b.code.clone()).unwrap_or_default();
802 Ok(vec![
803 Param::select("branch", "Branch", codes)
804 .default(first)
805 .hidden(),
806 ])
807 }
808
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§
impl Eq for Status
impl StructuralPartialEq for Status
Auto Trait Implementations§
impl Freeze for Status
impl RefUnwindSafe for Status
impl Send for Status
impl Sync for Status
impl Unpin for Status
impl UnsafeUnpin for Status
impl UnwindSafe for Status
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