atproto blogging
1#![allow(non_snake_case)]
2
3use crate::components::notebook::NotebookSettingsPanel;
4use crate::components::{AppLink, AppLinkTarget};
5use crate::components::AuthorList;
6use crate::components::button::{Button, ButtonVariant};
7use dioxus::prelude::*;
8use jacquard::IntoStatic;
9use jacquard::smol_str::SmolStr;
10use jacquard::types::ident::AtIdentifier;
11use weaver_api::sh_weaver::notebook::book::Book;
12use weaver_api::sh_weaver::notebook::NotebookView;
13
14const NOTEBOOK_COVER_CSS: Asset = asset!("/assets/styling/notebook-cover.css");
15
16#[component]
17pub fn NotebookCover(
18 notebook: NotebookView<'static>,
19 title: String,
20 #[props(default = false)] is_owner: bool,
21 #[props(default)] ident: Option<AtIdentifier<'static>>,
22) -> Element {
23 use jacquard::from_data;
24
25 let mut show_settings = use_signal(|| false);
26
27 // Deserialize the book record from the view.
28 let book: Book<'static> = match from_data::<Book>(¬ebook.record) {
29 Ok(book) => book.into_static(),
30 Err(_) => {
31 return rsx! {
32 document::Stylesheet { href: NOTEBOOK_COVER_CSS }
33 div { class: "notebook-cover",
34 h1 { class: "notebook-cover-title", "{title}" }
35 div { "Error loading notebook details" }
36 }
37 };
38 }
39 };
40
41 let entry_count = book.entry_list.len();
42 let created_at = book.created_at.clone();
43 let notebook_uri = notebook.uri.clone().into_static();
44
45 let handle_settings_saved = move |_| {
46 show_settings.set(false);
47 };
48
49 let handle_settings_cancel = move |_| {
50 show_settings.set(false);
51 };
52
53 rsx! {
54 document::Stylesheet { href: NOTEBOOK_COVER_CSS }
55
56 div { class: "notebook-cover",
57 h1 { class: "notebook-cover-title", "{title}" }
58
59 // Authors section
60 if !notebook.authors.is_empty() {
61 {
62 let owner = notebook.uri.authority().clone().into_static();
63 rsx! {
64 div { class: "notebook-cover-authors",
65 AuthorList {
66 authors: notebook.authors.clone(),
67 owner_ident: Some(owner),
68 avatar_size: 48,
69 }
70 }
71 }
72 }
73 }
74
75 // Metadata
76 div { class: "notebook-cover-meta",
77 // Entry count
78 span { class: "notebook-cover-stat",
79 "{entry_count} "
80 if entry_count == 1 { "entry" } else { "entries" }
81 }
82
83 // Created date
84 if let Some(ref ca) = created_at {
85 {
86 let formatted_date = ca.as_ref().format("%B %d, %Y").to_string();
87 rsx! {
88 span { class: "notebook-cover-date",
89 "Created {formatted_date}"
90 }
91 }
92 }
93 }
94 }
95
96 // Tags if present
97 if let Some(ref tags) = notebook.tags {
98 if !tags.is_empty() {
99 div { class: "notebook-cover-tags",
100 for tag in tags.iter() {
101 span { class: "notebook-cover-tag", "{tag}" }
102 }
103 }
104 }
105 }
106
107 // Owner actions
108 if is_owner {
109 if let Some(ref owner_ident) = ident {
110 div { class: "notebook-cover-actions",
111 AppLink {
112 to: AppLinkTarget::NewDraft {
113 ident: owner_ident.clone(),
114 notebook: Some(SmolStr::from(title.as_str()))
115 },
116 class: Some("notebook-cover-action-link".to_string()),
117 Button {
118 variant: ButtonVariant::Outline,
119 "+ Add Entry"
120 }
121 }
122 Button {
123 variant: ButtonVariant::Ghost,
124 onclick: move |_| show_settings.toggle(),
125 if show_settings() { "Close Settings" } else { "Settings" }
126 }
127 }
128 }
129 }
130
131 // Settings panel (inline below actions)
132 if show_settings() {
133 div { class: "notebook-cover-settings",
134 NotebookSettingsPanel {
135 notebook_uri: notebook_uri.clone(),
136 book: book.clone(),
137 on_saved: handle_settings_saved,
138 on_cancel: handle_settings_cancel,
139 }
140 }
141 }
142 }
143 }
144}