Skip to main content

Status

Struct Status 

Source
pub struct Status { /* private fields */ }
Expand description

A word for how a thing stands, and how that word reads.

Implementations§

Source§

impl Status

Source

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§

Source§

impl Clone for Status

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Status

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Status

Source§

impl PartialEq for Status

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for Status

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for Status

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.