Download app.py from HansTran/TESTING: direct link, hf CLI and curl.
- Browser
- Download file 7.79 kB
-
https://huggingface.co/spaces/HansTran/TESTING/resolve/main/app.py
- Command line
-
hf download hf://spaces/HansTran/TESTING/app.py
-
curl -L -o app.py https://huggingface.co/spaces/HansTran/TESTING/resolve/main/app.py
7.79 kB
| import streamlit as st | |
| from components.buttons import render_top_buttons | |
| from components.card import render_card | |
| from components.header import render_header | |
| from services.api import delete_card as api_delete_card | |
| from services.api import load_cards, load_tabs, save_card, save_tab, update_card | |
| from utils.time_utils import build_card_payload, normalize_cards, sort_cards_by_upcoming_time | |
| st.set_page_config( | |
| page_title="Time Card Generator", | |
| page_icon="⏱️", | |
| layout="wide", | |
| initial_sidebar_state="collapsed", | |
| ) | |
| def load_css() -> None: | |
| with open("assets/style.css", "r", encoding="utf-8") as css_file: | |
| st.markdown(f"<style>{css_file.read()}</style>", unsafe_allow_html=True) | |
| def initialize_state() -> None: | |
| if "cards" not in st.session_state: | |
| st.session_state.cards = [] | |
| if "tabs" not in st.session_state: | |
| st.session_state.tabs = [] | |
| if "cards_loaded" not in st.session_state: | |
| st.session_state.cards_loaded = False | |
| if "status_message" not in st.session_state: | |
| st.session_state.status_message = None | |
| if "status_type" not in st.session_state: | |
| st.session_state.status_type = "info" | |
| if "current_tab_id" not in st.session_state: | |
| st.session_state.current_tab_id = "" | |
| if "new_tab_counter" not in st.session_state: | |
| st.session_state.new_tab_counter = 1 | |
| if "new_tab_name" not in st.session_state: | |
| st.session_state.new_tab_name = "" | |
| def set_status(message: str, status_type: str = "info") -> None: | |
| st.session_state.status_message = message | |
| st.session_state.status_type = status_type | |
| def clear_status() -> None: | |
| st.session_state.status_message = None | |
| st.session_state.status_type = "info" | |
| def fetch_cards_once() -> None: | |
| if st.session_state.cards_loaded: | |
| return | |
| try: | |
| st.session_state.tabs = load_tabs() | |
| if not st.session_state.tabs: | |
| default_tab = save_tab({"title": "Tab 1"}) | |
| st.session_state.tabs = [default_tab] | |
| st.session_state.new_tab_counter = 2 | |
| else: | |
| st.session_state.new_tab_counter = len(st.session_state.tabs) + 1 | |
| if not st.session_state.current_tab_id: | |
| st.session_state.current_tab_id = st.session_state.tabs[0]["id"] | |
| st.session_state.cards = normalize_cards(load_cards()) | |
| assign_missing_tab_ids() | |
| st.session_state.cards_loaded = True | |
| clear_status() | |
| except Exception as exc: # noqa: BLE001 | |
| st.session_state.cards = [] | |
| st.session_state.tabs = [] | |
| st.session_state.cards_loaded = True | |
| set_status(f"Unable to load saved cards: {exc}", "error") | |
| def reload_cards() -> None: | |
| try: | |
| st.session_state.tabs = load_tabs() | |
| if not st.session_state.tabs: | |
| default_tab = save_tab({"title": "Tab 1"}) | |
| st.session_state.tabs = [default_tab] | |
| tab_ids = {tab["id"] for tab in st.session_state.tabs} | |
| if st.session_state.current_tab_id not in tab_ids: | |
| st.session_state.current_tab_id = st.session_state.tabs[0]["id"] | |
| st.session_state.new_tab_counter = len(st.session_state.tabs) + 1 | |
| st.session_state.cards = normalize_cards(load_cards()) | |
| assign_missing_tab_ids() | |
| st.session_state.cards_loaded = True | |
| clear_status() | |
| set_status("Cards refreshed.", "success") | |
| except Exception as exc: # noqa: BLE001 | |
| set_status(f"Refresh failed: {exc}", "error") | |
| def add_card() -> None: | |
| card = build_card_payload() | |
| card["tabId"] = st.session_state.current_tab_id | |
| st.session_state.cards.append(card) | |
| clear_status() | |
| def add_tab() -> None: | |
| try: | |
| typed_title = st.session_state.get("new_tab_name", "").strip() | |
| title = typed_title or f"Tab {st.session_state.new_tab_counter}" | |
| new_tab = save_tab({"title": title}) | |
| st.session_state.tabs.append(new_tab) | |
| st.session_state.current_tab_id = new_tab["id"] | |
| st.session_state.new_tab_counter += 1 | |
| st.session_state.new_tab_name = "" | |
| clear_status() | |
| set_status("Tab created.", "success") | |
| except Exception as exc: # noqa: BLE001 | |
| set_status(f"Unable to create tab: {exc}", "error") | |
| def assign_missing_tab_ids() -> None: | |
| if not st.session_state.tabs: | |
| return | |
| fallback_tab_id = st.session_state.tabs[0]["id"] | |
| for card in st.session_state.cards: | |
| if not card.get("tabId"): | |
| card["tabId"] = fallback_tab_id | |
| def delete_local_card(index: int) -> None: | |
| st.session_state.cards.pop(index) | |
| clear_status() | |
| def handle_delete(index: int) -> None: | |
| card = st.session_state.cards[index] | |
| card_id = card.get("id") | |
| if not card_id: | |
| delete_local_card(index) | |
| return | |
| try: | |
| api_delete_card(card_id) | |
| delete_local_card(index) | |
| set_status("Card deleted.", "success") | |
| except Exception as exc: # noqa: BLE001 | |
| set_status(f"Delete failed: {exc}", "error") | |
| def handle_save(index: int) -> None: | |
| card = st.session_state.cards[index] | |
| if not card["title"].strip(): | |
| set_status("Title is required before saving.", "warning") | |
| return | |
| try: | |
| if card.get("id"): | |
| updated_card = update_card(card) | |
| st.session_state.cards[index] = updated_card | |
| set_status("Card updated.", "success") | |
| else: | |
| saved_card = save_card(card) | |
| st.session_state.cards[index] = saved_card | |
| set_status("Card saved.", "success") | |
| except Exception as exc: # noqa: BLE001 | |
| set_status(f"Save failed: {exc}", "error") | |
| load_css() | |
| initialize_state() | |
| fetch_cards_once() | |
| render_top_buttons(add_card, add_tab, reload_cards) | |
| render_header() | |
| if st.session_state.status_message: | |
| getattr(st, st.session_state.status_type)(st.session_state.status_message) | |
| if st.session_state.tabs: | |
| tab_title_by_id = {tab["id"]: tab["title"] for tab in st.session_state.tabs} | |
| tab_ids = [tab["id"] for tab in st.session_state.tabs] | |
| current_tab_id = ( | |
| st.session_state.current_tab_id | |
| if st.session_state.current_tab_id in tab_ids | |
| else tab_ids[0] | |
| ) | |
| selected_tab_id = st.radio( | |
| "Tabs", | |
| options=tab_ids, | |
| format_func=lambda tab_id: tab_title_by_id.get(tab_id, "Untitled Tab"), | |
| horizontal=True, | |
| key="selected_tab_id", | |
| index=tab_ids.index(current_tab_id), | |
| label_visibility="collapsed", | |
| ) | |
| st.session_state.current_tab_id = selected_tab_id | |
| visible_cards = [ | |
| card for card in st.session_state.cards | |
| if card.get("tabId", "") == st.session_state.current_tab_id | |
| ] | |
| visible_cards = sort_cards_by_upcoming_time(visible_cards) | |
| visible_card_ids = [card.get("id") or card.get("clientKey") for card in visible_cards] | |
| display_indices = [] | |
| for visible_id in visible_card_ids: | |
| for index, card in enumerate(st.session_state.cards): | |
| candidate_id = card.get("id") or card.get("clientKey") | |
| if card.get("tabId", "") == st.session_state.current_tab_id and candidate_id == visible_id: | |
| display_indices.append(index) | |
| break | |
| if not display_indices: | |
| st.markdown( | |
| """ | |
| <div class="empty-state"> | |
| <h3>No cards in this tab</h3> | |
| <p>Add a card or switch to another tab.</p> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| card_columns = st.columns(3, gap="medium") | |
| for position, index in enumerate(display_indices): | |
| with card_columns[position % 3]: | |
| rank_tone = "danger" if position < 2 else "warning" if position < 4 else "default" | |
| render_card( | |
| index=index, | |
| on_save=handle_save, | |
| on_delete=handle_delete, | |
| rank_tone=rank_tone, | |
| ) | |