mirror of
https://github.com/Death916/deathclock.git
synced 2026-04-10 03:04:40 -07:00
shows all news but in one box
This commit is contained in:
parent
b8a702d4be
commit
a6b77a3c37
4 changed files with 121 additions and 100 deletions
Binary file not shown.
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue