Spaces:
Running
Running
| import streamlit as st | |
| from pathlib import Path | |
| import os | |
| # Load custom CSS | |
| def load_css(): | |
| css_file = Path(__file__).parent / "custom.css" | |
| if css_file.exists(): | |
| with open(css_file) as f: | |
| st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True) | |
| else: | |
| st.warning("Custom CSS file not found. Some styles may be missing.") | |
| # Header component | |
| def header(): | |
| st.markdown(""" | |
| <div class="main-header"> | |
| <h1 class="title-text">Historical OCR Workshop</h1> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Create a page wrapper similar to the React component | |
| def page_wrapper(content_function, current_module=1): | |
| """ | |
| Creates a consistent page layout with navigation | |
| Args: | |
| content_function: Function that renders the page content | |
| current_module: Current module number (1-6) | |
| """ | |
| # Load custom CSS | |
| load_css() | |
| # Display header | |
| header() | |
| # Ensure session state for navigation | |
| if 'current_module' not in st.session_state: | |
| st.session_state.current_module = current_module | |
| # Main content area with bottom padding for the nav | |
| st.markdown('<div class="main-content">', unsafe_allow_html=True) | |
| # Call the content function to render the module content | |
| content_function() | |
| # Add spacer for fixed nav | |
| st.markdown('<div class="footer-spacer"></div>', unsafe_allow_html=True) | |
| # Navigation | |
| render_navigation(current_module) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Navigation component | |
| def render_navigation(current_module): | |
| # Define modules names like in React | |
| modules = ['Introduction', 'Historical Context', 'Methodology', 'Case Studies', 'Interactive OCR', 'Conclusion'] | |
| # Navigation container | |
| st.markdown(f""" | |
| <div class="nav-container"> | |
| <div class="nav-buttons"> | |
| {prev_button_html(current_module, modules)} | |
| {next_button_html(current_module, modules)} | |
| </div> | |
| <div class="nav-dots"> | |
| {nav_dots_html(current_module, modules)} | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Previous button HTML | |
| def prev_button_html(current_module, modules): | |
| if current_module > 1: | |
| prev_module = current_module - 1 | |
| return f""" | |
| <button class="prev-button" | |
| onclick="document.getElementById('nav_prev_{prev_module}').click()" | |
| aria-label="Go to previous module: {modules[prev_module-1]}"> | |
| ← Previous | |
| </button> | |
| """ | |
| return "" | |
| # Next button HTML | |
| def next_button_html(current_module, modules): | |
| if current_module < len(modules): | |
| next_module = current_module + 1 | |
| return f""" | |
| <button class="next-button" | |
| onclick="document.getElementById('nav_next_{next_module}').click()" | |
| aria-label="Go to next module: {modules[next_module-1]}"> | |
| Next → | |
| </button> | |
| """ | |
| return "" | |
| # Navigation dots HTML | |
| def nav_dots_html(current_module, modules): | |
| dots_html = "" | |
| for i, name in enumerate(modules, 1): | |
| active_class = "active" if i == current_module else "" | |
| dots_html += f""" | |
| <a class="nav-dot {active_class}" | |
| onclick="document.getElementById('nav_dot_{i}').click()" | |
| aria-current="{i == current_module}" | |
| aria-label="Go to module {i}: {name}"> | |
| {i} | |
| </a> | |
| """ | |
| return dots_html | |
| # Helper functions for container styles | |
| def gray_container(content, padding="1.5rem"): | |
| """Renders content in a gray container with consistent styling""" | |
| st.markdown(f'<div class="content-container" style="padding:{padding};">{content}</div>', unsafe_allow_html=True) | |
| def blue_container(content, padding="1.5rem"): | |
| """Renders content in a blue container with consistent styling""" | |
| st.markdown(f'<div class="blue-container" style="padding:{padding};">{content}</div>', unsafe_allow_html=True) | |
| def yellow_container(content, padding="1.5rem"): | |
| """Renders content in a yellow container with consistent styling""" | |
| st.markdown(f'<div class="yellow-container" style="padding:{padding};">{content}</div>', unsafe_allow_html=True) | |
| def card_grid(cards): | |
| """ | |
| Renders a responsive grid of cards | |
| Args: | |
| cards: List of HTML strings for each card | |
| """ | |
| grid_html = '<div class="card-grid">' | |
| for card in cards: | |
| grid_html += f'<div class="card">{card}</div>' | |
| grid_html += '</div>' | |
| st.markdown(grid_html, unsafe_allow_html=True) | |
| def module_card(number, title, description): | |
| """Creates a styled module card""" | |
| return f""" | |
| <div class="module-card"> | |
| <div class="module-number">Module {number}</div> | |
| <div class="module-title">{title}</div> | |
| <p>{description}</p> | |
| </div> | |
| """ | |
| def key_concept(content): | |
| """Renders a key concept box""" | |
| st.markdown(f'<div class="key-concept">{content}</div>', unsafe_allow_html=True) | |
| def research_question(content): | |
| """Renders a research question box""" | |
| st.markdown(f'<div class="research-question">{content}</div>', unsafe_allow_html=True) | |
| def quote(content, author=""): | |
| """Renders a quote with optional author""" | |
| quote_html = f'<div class="quote-container">{content}' | |
| if author: | |
| quote_html += f'<br/><br/><span style="font-size:0.9rem; text-align:right; display:block;">— {author}</span>' | |
| quote_html += '</div>' | |
| st.markdown(quote_html, unsafe_allow_html=True) | |
| def tool_container(content): | |
| """Renders content in a tool container""" | |
| st.markdown(f'<div class="tool-container">{content}</div>', unsafe_allow_html=True) | |
| def upload_container(content): | |
| """Renders content in an upload container""" | |
| st.markdown(f'<div class="upload-container">{content}</div>', unsafe_allow_html=True) |