diff --git a/deathclock/__pycache__/news.cpython-311.pyc b/deathclock/__pycache__/news.cpython-311.pyc index a5b1fe4..3d7b861 100644 Binary files a/deathclock/__pycache__/news.cpython-311.pyc and b/deathclock/__pycache__/news.cpython-311.pyc differ diff --git a/deathclock/app.py b/deathclock/app.py index 5492455..0b1e44c 100644 --- a/deathclock/app.py +++ b/deathclock/app.py @@ -11,22 +11,33 @@ app = Dash(__name__) weather_obj = Weather() news_obj = News() scores_obj = NBAScores() - - +#uses arbitrary date in the past as initial value +_last_news_update = datetime.datetime(2000, 1, 1) +_cached_news = [] +_initial_run = True +_timer_elapsed = False app.layout = html.Div([ html.H1(id='clock-display', style={'textAlign': 'center'}), - html.Div(id='weather-display', style={'textAlign': 'center'}), + html.Div([ + # Container for scores and weather + html.Div([ + html.Div([ + html.H2("NBA Scores"), + html.Div(id='nba-scores-display', className='score-container') + ]), + html.Div(id='weather-display') + ], id='scores-weather-container'), + ]), html.Div(id='news-ticker', className='ticker'), + # Intervals dcc.Interval(id='clock-interval', interval=1000, n_intervals=0), dcc.Interval(id='weather-interval', interval=300000, n_intervals=0), dcc.Interval(id='news-interval', interval=300000, n_intervals=0), - - html.Div([ - html.H2("NBA Scores"), - html.Div(id='nba-scores-display', className='score-container'), - dcc.Interval(id='nba-interval', interval=60000, n_intervals=0) - ]) + dcc.Interval(id='nba-interval', interval=60000, n_intervals=0) ]) + + + @app.callback( @@ -56,13 +67,52 @@ def update_weather(n): Input('news-interval', 'n_intervals') ) def update_news(n): - try: - news_dict = news_obj.get_news() - items = [html.Span(f"{headline} | ", className='news-item') - for headline in news_dict.keys()] - return html.Div(items) - except Exception as e: - return html.Div(f"News feed error: {str(e)}") + global _last_news_update, _cached_news, _initial_run + current_time = datetime.datetime.now() + # On first run or if 5 minutes have elapsed since last update + if _initial_run or (current_time - _last_news_update).total_seconds() >= 300: + print("Fetching fresh news...") + try: + headlines_dict = news_obj.get_news() + if not isinstance(headlines_dict, dict): + return html.Div("News update error: Invalid data format") + if not headlines_dict: + return html.Div("No news fetched") + + combined_text = " | ".join(headlines_dict.keys()) + text_px = len(combined_text) * 8 # Approx 8px per character + scroll_speed = 500 # px per second + duration = text_px / scroll_speed # seconds to scroll across + + # Enforce a floor duration so it's not too quick for short text + if duration < 20: + duration = 20 + + ticker_style = {"animationDuration": f"{duration}s", "whiteSpace": "nowrap"} + + items = [ + html.Div( + f"{headline} | ", + className="news-item", + style=ticker_style + ) + for headline in headlines_dict.keys() + ] + + _cached_news = html.Div(items, style=ticker_style) + _last_news_update = current_time + _initial_run = False + return _cached_news + + except Exception as e: + return html.Div(f"News feed error: {str(e)}") + + print("Returning cached news...") + return _cached_news + + + + #get the scores from the scores API @app.callback( @@ -72,7 +122,10 @@ def update_news(n): def update_scores(n): try: games = scores_obj.get_scores() - return [ + if not games: + return html.Div("No games available", className='text-center') + + return html.Div([ html.Div([ html.Div([ html.Span(f"{game['away_team']} {game['away_score']}"), @@ -80,9 +133,10 @@ def update_scores(n): html.Span(f"{game['home_team']} {game['home_score']}") ], className='game-score'), html.Div(f"Period: {game['period']}", className='game-period') - ], className='score-box') + ], className='score-box') for game in games - ] + ], className='score-container') + except Exception as e: return html.Div("Scores unavailable") diff --git a/deathclock/assets/style.css b/deathclock/assets/style.css index 3000129..68b48ac 100644 --- a/deathclock/assets/style.css +++ b/deathclock/assets/style.css @@ -1,26 +1,4 @@ -/* assets/style.css */ - -/* News ticker container */ -.ticker { - overflow: hidden; - white-space: nowrap; - background-color: #f0f0f0; - padding: 10px; - } - - /* Each headline or news item */ - .news-item { - display: inline-block; - padding: 0 2rem; - animation: scroll-left 20s linear infinite; - } - - /* Keyframes for scrolling left */ - @keyframes scroll-left { - 0% { transform: translateX(100%); } - 100% { transform: translateX(-100%); } - } - body { +body { background-color: #1a1a2e; color: #e6e6fa; font-family: 'Arial', sans-serif; @@ -28,13 +6,42 @@ padding: 20px; } -#curr-time { - font-size: 4em; - color: #b39ddb; - text-shadow: 0 0 10px rgba(179, 157, 219, 0.3); - margin: 20px 0; +/* Container for the scores and weather */ +#scores-weather-container { + display: flex; + justify-content: space-between; + align-items: flex-start; + width: 100%; } +/* Scores Styles */ +.score-container { + max-width: 300px; + padding: 10px; + flex: 1; +} + +.score-box { + background-color: #232338; + padding: 8px; + margin-bottom: 8px; + border-radius: 6px; + border: 1px solid #4a4a82; +} + +.game-score { + font-size: 0.9em; + color: #e6e6fa; + text-align: center; +} + +.game-period { + font-size: 0.8em; + color: #b39ddb; + text-align: center; +} + +/* Weather Styles */ #weather-display { background-color: #232338; border-radius: 10px; @@ -43,64 +50,17 @@ max-width: 600px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); border: 1px solid #4a4a82; + transition: all 0.3s ease; + flex: 2; } -#weather-display img { - max-width: 100%; - border-radius: 8px; - margin-top: 10px; -} - -.ticker { - background-color: #232338; - padding: 15px; - border-radius: 8px; - margin: 20px 0; - border: 1px solid #4a4a82; - overflow: hidden; - white-space: nowrap; -} - -.news-item { - display: inline-block; - padding: 0 30px; - color: #d1c4e9; - animation: ticker 30s linear infinite; -} - -@keyframes ticker { - 0% { transform: translateX(100%); } - 100% { transform: translateX(-100%); } -} - -/* Add subtle hover effects */ #weather-display:hover { box-shadow: 0 6px 8px rgba(179, 157, 219, 0.2); transform: translateY(-2px); - transition: all 0.3s ease; } -.score-box { - background-color: #232338; - margin: 10px 0; - padding: 15px; +#weather-display img { + width: 100%; border-radius: 8px; - border: 1px solid #4a4a82; -} - -.game-score { - font-size: 1.2em; - color: #e6e6fa; - margin-bottom: 5px; -} - -.game-period { - font-size: 0.9em; - color: #b39ddb; -} - -.score-box:hover { - transform: translateY(-2px); - box-shadow: 0 4px 6px rgba(179, 157, 219, 0.2); - transition: all 0.3s ease; + margin-top: 10px; } diff --git a/deathclock/news.py b/deathclock/news.py index eaec10c..1e98347 100644 --- a/deathclock/news.py +++ b/deathclock/news.py @@ -16,7 +16,7 @@ class News: # Get latest news from each feed for feed in feeds: d = feedparser.parse(feed) - for post in d.entries[:3]: # Limit to 3 entries per feed + for post in d.entries[:10]: # Limit to 3 entries per feed if self._news_dict_length >= 20: # Max 20 total entries return self._news_dict @@ -27,5 +27,12 @@ class News: 'summary': post.summary } self._news_dict_length += 1 + # Store last 20 news items in text file + + + with open("news.txt", "w") as f: + for headline in list(self._news_dict.keys())[-20:]: + f.write(f"{headline}\n") + return self._news_dict