+67
-210
@@ -8,14 +8,13 @@ for rendering.
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from dreader import create_ebook_reader
|
||||
import base64
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def generate_library_html(books: List[Dict[str, str]]) -> str:
|
||||
def generate_library_html(books: List[Dict[str, str]], save_covers_to_disk: bool = False) -> str:
|
||||
"""
|
||||
Generate HTML for the library view showing all books in a grid.
|
||||
Generate HTML for the library view showing all books in a simple table.
|
||||
|
||||
Args:
|
||||
books: List of book dictionaries with keys:
|
||||
@@ -23,140 +22,46 @@ def generate_library_html(books: List[Dict[str, str]]) -> str:
|
||||
- author: Book author
|
||||
- filename: EPUB filename
|
||||
- cover_data: Optional base64 encoded cover image
|
||||
- cover_path: Optional path to saved cover image (if save_covers_to_disk=True)
|
||||
save_covers_to_disk: If True, expect cover_path instead of cover_data
|
||||
|
||||
Returns:
|
||||
Complete HTML string for library view
|
||||
"""
|
||||
books_html = []
|
||||
for book in books:
|
||||
cover_img = ''
|
||||
if book.get('cover_data'):
|
||||
cover_img = f'<img src="data:image/png;base64,{book["cover_data"]}" alt="Cover">'
|
||||
else:
|
||||
# Placeholder if no cover
|
||||
cover_img = f'<div class="no-cover">{book["title"][:1]}</div>'
|
||||
|
||||
books_html.append(f'''
|
||||
<td class="book-item" data-filename="{book['filename']}">
|
||||
<table style="width: 100%;">
|
||||
<tr>
|
||||
<td class="cover-cell">
|
||||
{cover_img}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title-cell">{book['title']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="author-cell">{book['author']}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
''')
|
||||
|
||||
# Arrange books in rows of 3
|
||||
# Build table rows
|
||||
rows = []
|
||||
for i in range(0, len(books_html), 3):
|
||||
row_books = books_html[i:i+3]
|
||||
# Pad with empty cells if needed
|
||||
while len(row_books) < 3:
|
||||
row_books.append('<td class="book-item empty"></td>')
|
||||
rows.append(f'<tr>{"".join(row_books)}</tr>')
|
||||
|
||||
html = f'''
|
||||
for book in books:
|
||||
# Add cover image cell if available
|
||||
if save_covers_to_disk and book.get('cover_path'):
|
||||
cover_cell = f'<td><img src="{book["cover_path"]}" width="150"/></td>'
|
||||
elif book.get('cover_data'):
|
||||
cover_cell = f'<td><img src="data:image/png;base64,{book["cover_data"]}" width="150"/></td>'
|
||||
else:
|
||||
cover_cell = '<td>[No cover]</td>'
|
||||
|
||||
# Add book info cell
|
||||
info_cell = f'<td><b>{book["title"]}</b><br/>{book["author"]}</td>'
|
||||
|
||||
rows.append(f'<tr>{cover_cell}{info_cell}</tr>')
|
||||
|
||||
table_html = '\n'.join(rows)
|
||||
|
||||
return f'''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Library</title>
|
||||
<style>
|
||||
* {{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20px;
|
||||
}}
|
||||
.header {{
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
.library-grid {{
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 20px;
|
||||
}}
|
||||
.book-item {{
|
||||
background-color: white;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
cursor: pointer;
|
||||
vertical-align: top;
|
||||
width: 33%;
|
||||
}}
|
||||
.book-item:hover {{
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
}}
|
||||
.book-item.empty {{
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
cursor: default;
|
||||
}}
|
||||
.cover-cell {{
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
}}
|
||||
.cover-cell img {{
|
||||
max-width: 200px;
|
||||
max-height: 300px;
|
||||
border: 1px solid #ddd;
|
||||
}}
|
||||
.no-cover {{
|
||||
width: 200px;
|
||||
height: 300px;
|
||||
background-color: #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 72px;
|
||||
color: #999;
|
||||
margin: 0 auto;
|
||||
}}
|
||||
.title-cell {{
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}}
|
||||
.author-cell {{
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>My Library</h1>
|
||||
<p>{len(books)} books</p>
|
||||
</div>
|
||||
|
||||
<table class="library-grid">
|
||||
{"".join(rows)}
|
||||
</table>
|
||||
<h1>My Library</h1>
|
||||
<p>{len(books)} books</p>
|
||||
<table>
|
||||
{table_html}
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
return html
|
||||
</html>'''
|
||||
|
||||
|
||||
def generate_reader_html(book_title: str, book_author: str, page_image_data: str) -> str:
|
||||
@@ -418,7 +323,7 @@ def generate_settings_overlay() -> str:
|
||||
return html
|
||||
|
||||
|
||||
def generate_toc_overlay(chapters: List[Dict]) -> str:
|
||||
def generate_toc_overlay(chapters: List[Dict], page_size: tuple = (800, 1200)) -> str:
|
||||
"""
|
||||
Generate HTML for the table of contents overlay.
|
||||
|
||||
@@ -426,105 +331,57 @@ def generate_toc_overlay(chapters: List[Dict]) -> str:
|
||||
chapters: List of chapter dictionaries with keys:
|
||||
- index: Chapter index
|
||||
- title: Chapter title
|
||||
page_size: Page dimensions (width, height) for sizing the overlay
|
||||
|
||||
Returns:
|
||||
HTML string for TOC overlay
|
||||
HTML string for TOC overlay (60% popup with transparent background)
|
||||
"""
|
||||
chapter_rows = []
|
||||
for chapter in chapters:
|
||||
chapter_rows.append(f'''
|
||||
<tr class="chapter-row" data-chapter-index="{chapter['index']}">
|
||||
<td class="chapter-cell">{chapter['title']}</td>
|
||||
</tr>
|
||||
''')
|
||||
# Build chapter list items with clickable links for pyWebLayout query
|
||||
chapter_items = []
|
||||
for i, chapter in enumerate(chapters):
|
||||
title = chapter["title"]
|
||||
|
||||
# Wrap each row in a paragraph with an inline link
|
||||
# For very short titles (I, II), pad the link text to ensure it's clickable
|
||||
link_text = f'{i+1}. {title}'
|
||||
if len(title) <= 2:
|
||||
# Add extra padding spaces inside the link to make it easier to click
|
||||
link_text = f'{i+1}. {title} ' # Extra spaces for padding
|
||||
|
||||
chapter_items.append(
|
||||
f'<p style="padding: 12px; margin: 5px 0; background-color: #f0f0f0; '
|
||||
f'border-left: 3px solid #000;">'
|
||||
f'<a href="chapter:{chapter["index"]}" style="text-decoration: none; color: #000;">'
|
||||
f'{link_text}</a></p>'
|
||||
)
|
||||
|
||||
# Render simple white panel - compositing will be done by OverlayManager
|
||||
html = f'''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Table of Contents</title>
|
||||
<style>
|
||||
* {{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}}
|
||||
.overlay-panel {{
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
||||
padding: 20px;
|
||||
min-width: 500px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}}
|
||||
.overlay-header {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}}
|
||||
.overlay-title {{
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.close-button {{
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}}
|
||||
.close-button:hover {{
|
||||
background-color: #c82333;
|
||||
}}
|
||||
.chapters-container {{
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}}
|
||||
.chapters-table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}}
|
||||
.chapter-row {{
|
||||
cursor: pointer;
|
||||
}}
|
||||
.chapter-row:hover {{
|
||||
background-color: #f0f0f0;
|
||||
}}
|
||||
.chapter-cell {{
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="overlay-panel">
|
||||
<div class="overlay-header">
|
||||
<span class="overlay-title">Table of Contents</span>
|
||||
<button class="close-button" id="btn-close">Close</button>
|
||||
</div>
|
||||
<body style="background-color: white; margin: 0; padding: 25px; font-family: Arial, sans-serif;">
|
||||
|
||||
<div class="chapters-container">
|
||||
<table class="chapters-table">
|
||||
{"".join(chapter_rows)}
|
||||
</table>
|
||||
</div>
|
||||
<h1 style="color: #000; margin: 0 0 8px 0; font-size: 24px; text-align: center; font-weight: bold;">
|
||||
Table of Contents
|
||||
</h1>
|
||||
|
||||
<p style="text-align: center; color: #666; margin: 0 0 15px 0; padding-bottom: 12px;
|
||||
border-bottom: 2px solid #ccc; font-size: 13px;">
|
||||
{len(chapters)} chapters
|
||||
</p>
|
||||
|
||||
<div style="max-height: 600px; overflow-y: auto;">
|
||||
{"".join(chapter_items)}
|
||||
</div>
|
||||
|
||||
<p style="text-align: center; margin: 15px 0 0 0; padding-top: 12px;
|
||||
border-top: 2px solid #ccc; color: #888; font-size: 11px;">
|
||||
Tap a chapter to navigate • Tap outside to close
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
Reference in New Issue
Block a user