Skip to main content

Detail

Struct Detail 

Source
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

Source

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    }
Source

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    }
Source

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    }
Source

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    }
Source

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§

Source§

impl Clone for Detail

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 Detail

Source§

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

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

impl Serialize for Detail

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

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.