shows all news but in one box

This commit is contained in:
Death916 2025-01-15 03:09:52 -08:00
parent b8a702d4be
commit a6b77a3c37
4 changed files with 121 additions and 100 deletions

View file

@ -11,22 +11,33 @@ app = Dash(__name__)
weather_obj = Weather() weather_obj = Weather()
news_obj = News() news_obj = News()
scores_obj = NBAScores() 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([ app.layout = html.Div([
html.H1(id='clock-display', style={'textAlign': 'center'}), 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'), html.Div(id='news-ticker', className='ticker'),
# Intervals
dcc.Interval(id='clock-interval', interval=1000, n_intervals=0), dcc.Interval(id='clock-interval', interval=1000, n_intervals=0),
dcc.Interval(id='weather-interval', interval=300000, n_intervals=0), dcc.Interval(id='weather-interval', interval=300000, n_intervals=0),
dcc.Interval(id='news-interval', interval=300000, n_intervals=0), dcc.Interval(id='news-interval', interval=300000, n_intervals=0),
dcc.Interval(id='nba-interval', interval=60000, 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)
])
]) ])
@app.callback( @app.callback(
@ -56,13 +67,52 @@ def update_weather(n):
Input('news-interval', 'n_intervals') Input('news-interval', 'n_intervals')
) )
def update_news(n): def update_news(n):
try: global _last_news_update, _cached_news, _initial_run
news_dict = news_obj.get_news() current_time = datetime.datetime.now()
items = [html.Span(f"{headline} | ", className='news-item') # On first run or if 5 minutes have elapsed since last update
for headline in news_dict.keys()] if _initial_run or (current_time - _last_news_update).total_seconds() >= 300:
return html.Div(items) print("Fetching fresh news...")
except Exception as e: try:
return html.Div(f"News feed error: {str(e)}") 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 #get the scores from the scores API
@app.callback( @app.callback(
@ -72,7 +122,10 @@ def update_news(n):
def update_scores(n): def update_scores(n):
try: try:
games = scores_obj.get_scores() 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.Div([ html.Div([
html.Span(f"{game['away_team']} {game['away_score']}"), 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']}") html.Span(f"{game['home_team']} {game['home_score']}")
], className='game-score'), ], className='game-score'),
html.Div(f"Period: {game['period']}", className='game-period') html.Div(f"Period: {game['period']}", className='game-period')
], className='score-box') ], className='score-box')
for game in games for game in games
] ], className='score-container')
except Exception as e: except Exception as e:
return html.Div("Scores unavailable") return html.Div("Scores unavailable")

View file

@ -1,26 +1,4 @@
/* assets/style.css */ body {
/* 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 {
background-color: #1a1a2e; background-color: #1a1a2e;
color: #e6e6fa; color: #e6e6fa;
font-family: 'Arial', sans-serif; font-family: 'Arial', sans-serif;
@ -28,13 +6,42 @@
padding: 20px; padding: 20px;
} }
#curr-time { /* Container for the scores and weather */
font-size: 4em; #scores-weather-container {
color: #b39ddb; display: flex;
text-shadow: 0 0 10px rgba(179, 157, 219, 0.3); justify-content: space-between;
margin: 20px 0; 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 { #weather-display {
background-color: #232338; background-color: #232338;
border-radius: 10px; border-radius: 10px;
@ -43,64 +50,17 @@
max-width: 600px; max-width: 600px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
border: 1px solid #4a4a82; 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 { #weather-display:hover {
box-shadow: 0 6px 8px rgba(179, 157, 219, 0.2); box-shadow: 0 6px 8px rgba(179, 157, 219, 0.2);
transform: translateY(-2px); transform: translateY(-2px);
transition: all 0.3s ease;
} }
.score-box { #weather-display img {
background-color: #232338; width: 100%;
margin: 10px 0;
padding: 15px;
border-radius: 8px; border-radius: 8px;
border: 1px solid #4a4a82; margin-top: 10px;
}
.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;
} }

View file

@ -16,7 +16,7 @@ class News:
# Get latest news from each feed # Get latest news from each feed
for feed in feeds: for feed in feeds:
d = feedparser.parse(feed) 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 if self._news_dict_length >= 20: # Max 20 total entries
return self._news_dict return self._news_dict
@ -27,5 +27,12 @@ class News:
'summary': post.summary 'summary': post.summary
} }
self._news_dict_length += 1 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 return self._news_dict