Spaces:
Sleeping
Sleeping
File size: 6,507 Bytes
9eb2ef0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
from fasthtml.common import *
from monsterui.all import *
import frontmatter
import pathlib
from fasthtml.components import Uk_theme_switcher
from monsterui.foundations import *
app, rt = fast_app(hdrs=Theme.gray.headers(daisy=True))
def load_book(file_path):
"""Load and parse a book's markdown file"""
with open(file_path) as f:
post = frontmatter.load(f)
return post
def create_mode_picker():
def _opt(val, txt, **kwargs): return Option(txt, value=val, **kwargs)
def _optgrp(key, lbl, opts): return Optgroup(data_key=key, label=lbl)(*opts)
group = _optgrp('mode', 'Mode',
[
_opt('light','Light',data_icon='sun'),
_opt('dark','Dark',data_icon='moon')
])
return Div(Uk_theme_switcher(
Select(group, hidden=True, selected=True),
id="mode-picker"
),
cls=stringify('p-4'))
def create_book_page(post):
"""Create the detailed book page using our template"""
metadata = post.metadata
return Container(
random_theme_script(),
DivCentered(
Card(
DivCentered(
H1(metadata["title"],
cls="text-transparent bg-clip-text bg-gradient-to-r from-primary via-muted to-primary transition-all duration-1000 hover:scale-105",
style="font-size: 2.5rem; font-weight: 700; -webkit-text-stroke: 0.5px rgba(0, 0, 0, 0.7);"),
H2(metadata["author"], cls=(TextT.muted)),
A(
Img(
src=metadata['cover_img_url'],
cls="rounded-lg hover:scale-105 shadow-lg transition-all duration-1000",
style="width:300px"
),
cls="rounded-lg overflow-hidden",
href=metadata['book_url']
),
cls="text-center space-y-6"
),
DivCentered(
Section(
DivHStacked(
Label(f"π
{metadata['date'].strftime('%B %d, %Y')}", cls=LabelT.secondary),
Label(f"π {metadata['genre']}", cls=LabelT.secondary),
cls="space-x-2"
),
cls=SectionT.xs
),
cls="mb-6"
),
Div(
render_md(
post.content,
class_map={
'h3': f'text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary {TextT.xl} {TextT.bold} mb-6 mt-8',
'ul': f'{ListT.disc} space-y-4 mb-8',
'li': f'{TextT.lg} {TextT.normal} leading-relaxed',
'ul ul': f'{ListT.circle} ml-8 mt-4',
'ul ul li': f'{TextT.normal} leading-relaxed',
'p': f'{TextT.lg} {TextT.normal} mb-4',
'img': 'rounded-lg shadow-md hover:shadow-xl transition-shadow duration-200',
'*[@class="gallery"]': 'flex flex-row items-center justify-center gap-8'
}
),
cls = 'space-y-6'
),
cls=CardT.default
)
),
cls=(ContainerT.lg, 'p-8'),
style="position: relative; overflow: hidden;"
)
def create_book_card(metadata, filename):
"""Create a card for the book listing"""
return A(
Card(
DivCentered(
Img(
src=metadata['cover_img_url'],
cls="rounded-lg shadow-md hover:scale-105 transition-all duration-300",
style="width:200px"
),
H3(metadata['title'],
cls="text-transparent bg-clip-text bg-gradient-to-r from-primary to-secondary"),
P(metadata['author'], cls=TextT.muted),
DivHStacked(
Label(metadata['genre'], cls=LabelT.secondary),
Label(metadata['date'].strftime('%B %d, %Y'), cls=LabelT.secondary),
cls="mt-4 space-x-2"
),
cls="space-y-4 p-4"
),
cls=(CardT.hover, "transition-all duration-300 hover:shadow-xl")
),
href=f"/book/{filename}",
)
def random_theme_script():
return Script("""
document.addEventListener('DOMContentLoaded', function() {
const themes = ['uk-theme-zinc', 'uk-theme-slate', 'uk-theme-red',
'uk-theme-rose', 'uk-theme-orange', 'uk-theme-green',
'uk-theme-blue', 'uk-theme-yellow', 'uk-theme-violet'];
const randomTheme = themes[Math.floor(Math.random() * themes.length)];
document.documentElement.className = randomTheme;
});
""")
@rt
def index():
"""Homepage with grid of book cards"""
books_path = pathlib.Path('books')
book_files = list(books_path.glob('*.md'))
# Create cards for each book
book_cards = []
for file in book_files:
post = load_book(file)
book_cards.append(create_book_card(post.metadata, file.stem))
return Container(
create_mode_picker(),
random_theme_script(),
Grid(*book_cards,
cols_sm=1, cols_md=2, cols_lg=3, cols_xl=4,
gap=6),
cls=(ContainerT.xl, 'p-8')
)
@rt("/book/{filename}")
def get(filename: str):
"""Individual book page"""
try:
books_path = pathlib.Path('books')
book_file = books_path / f"{filename}.md"
if not book_file.exists():
raise FileNotFoundError
post = load_book(book_file)
return create_book_page(post)
except (FileNotFoundError, ValueError) as e:
return Container(
DivCentered(
H1("Book Not Found", cls=TextT.error),
P("Sorry, we couldn't find the book you're looking for."),
A("Return to Library", href="/", cls=ButtonT.primary),
cls="space-y-6 py-12"
)
)
serve()
|