base of reflex re write. centered clock and weather

This commit is contained in:
Death916 2025-04-21 05:17:44 -07:00
parent aba3a7b641
commit 3bec50f40e
24 changed files with 1563 additions and 6838 deletions

6
.gitignore vendored
View file

@ -1,3 +1,9 @@
.web
.states
assets/external/
*.db
__pycache__/
*.py[cod]
*.png *.png
*.txt *.txt
chromedriver chromedriver

View file

@ -1,85 +0,0 @@
/* assets/styles.css */
/* Basic ticker styling */
.ticker-wrap {
width: 100%;
overflow: hidden;
background-color: #f0f0f0; /* Light background for visibility */
padding: 10px 0;
margin-top: 20px;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.ticker-move {
/* Animation properties will be set dynamically via style prop */
display: inline-block;
white-space: nowrap;
padding-right: 100%; /* Ensures the loop effect */
animation-name: ticker;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
/* The animation */
@keyframes ticker {
0% {
transform: translateX(0);
}
100% {
/* Translate left by the width of the text */
/* The actual value is set dynamically if possible, otherwise estimate */
transform: translateX(-100%); /* Simplified fallback */
}
}
/* Style for individual news items if needed */
.news-item {
margin: 0 15px; /* Spacing between items */
font-size: 1em;
}
/* Styling for score boxes */
.score-container {
display: flex;
flex-direction: column; /* Stack games vertically */
gap: 8px; /* Space between score boxes */
align-items: center; /* Center boxes if container is wider */
}
.score-box {
border: 1px solid #ddd;
padding: 8px 12px;
border-radius: 4px;
background-color: #f9f9f9;
min-width: 200px; /* Ensure minimum width */
text-align: center;
}
.game-score {
font-weight: bold;
font-size: 1.1em;
display: block; /* Put score on its own line */
margin-bottom: 4px;
}
.game-period, .game-status {
font-size: 0.9em;
color: #555;
display: block; /* Put status on its own line */
}
/* Make MLB scores display in columns if desired */
.mlb-score-container {
display: block; /* Override flex */
column-count: 2;
column-gap: 15px;
padding: 0 10px; /* Add some padding */
}
/* Ensure MLB boxes break correctly */
.mlb-score-container .score-box {
display: inline-block; /* Necessary for column break */
width: 95%; /* Adjust width as needed */
margin-bottom: 10px; /* Space below items in columns */
break-inside: avoid-column; /* Try to prevent breaking inside a box */
}

View file

@ -1,4 +0,0 @@
*.png
*.txt
chromedriver
*.pyc

View file

@ -1 +0,0 @@
__version__ = '0.1.0'

View file

@ -1,35 +0,0 @@
import datetime
from dash import html, dcc, Input, Output, State
import alarm
class AlarmModule:
def __init__(self, app):
self.app = app
self.alarm_obj = self.get_alarm_object()
self._alarm_time = None
self.setup_callbacks()
def get_alarm_object(self):
return alarm.Alarm()
def setup_callbacks(self):
@self.app.callback(
Output('time-input', 'style'),
Input('clock-display', 'n_clicks'),
State('time-input', 'style')
)
def toggle_time_input(n_clicks, current_style):
if n_clicks and n_clicks % 2 == 1:
return {'display': 'block', 'margin': '0 auto'}
return {'display': 'none', 'margin': '0 auto'}
@self.app.callback(
Output('output-selected-time', 'children'),
Input('time-input', 'value')
)
def process_selected_time(time_value):
if time_value:
self._alarm_time = time_value
self.alarm_obj.add_alarm(time_value, datetime.datetime.now())
return f'Alarm time: {time_value}'
return 'No time selected yet.'

View file

@ -1,57 +0,0 @@
from dash import Dash, html, dcc
from weather_module import WeatherModule
from news_module import NewsModule
from scores_module import ScoresModule
from alarm_module import AlarmModule
from clock_module import ClockModule
def create_app():
app = Dash(__name__)
app.layout = html.Div([
html.H1(id='clock-display', style={'textAlign': 'center', 'cursor': 'pointer'}),
dcc.Input(id='time-input', type='time', style={'display': 'none', 'margin': '0 auto'}),
html.Div(id='output-selected-time', style={'textAlign': 'center', 'marginBottom': '20px'}),
html.Div([
html.Div([
html.H2("NBA Scores"),
html.Div(id='nba-scores-display', className='score-container')
], id='nba-container', style={'flex': '1'}),
html.Div(id='weather-display', style={"display": "flex", "justify-content": "center", "margin-bottom":"20px", 'flex':'1'}),
html.Div([
html.H2("MLB Scores"),
html.Div(id='mlb-scores-display', className='score-container',style={'column-count': '2', 'column-gap': '10px'})
], id='mlb-container', style={'flex': '1'}),
], id='main-content-container', style={"display": "flex", "gap": "20px", 'flex-wrap': 'wrap'}),
html.Div(id='news-ticker'),
dcc.Interval(id='clock-interval', interval=60000, n_intervals=0),
dcc.Interval(id='weather-interval', interval=550000, n_intervals=0),
dcc.Interval(id='news-interval', interval=300000, n_intervals=0),
dcc.Interval(id='nba-interval', interval=300000, n_intervals=0),
dcc.Interval(id='mlb-interval', interval=300000, n_intervals=0)
])
ClockModule(app)
WeatherModule(app)
NewsModule(app)
scores_module = ScoresModule(app)
scores_module.setup_mlb_callbacks()
alarm_module = AlarmModule(app)
def check_alarms():
trigg = alarm_module.alarm_obj.check_alarm()
if trigg:
print("ALARM TRIGGERED!")
check_alarms()
return app
if __name__ == '__main__':
app = create_app()
app.run_server(debug=False, host='0.0.0.0', port=8050)

View file

@ -1,224 +0,0 @@
body {
background-color: #1a1a2e;
color: #e6e6fa;
font-family: 'Arial', sans-serif;
margin: 0;
padding: 10px;
font-size: 16px;
overflow-x: hidden;
min-width: 360px;
}
h1 {
font-size: 2rem;
margin-bottom: 10px;
}
h2 {
font-size: 1.5rem;
margin-bottom: 10px;
}
/* Main Content Container (Scores and Weather) */
#main-content-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
width: 100%;
justify-content: center;
}
#nba-container,
#mlb-container {
flex: 1;
min-width: 300px;
}
/* Individual Score Containers */
.score-container {
width: 100%;
padding: 0; /* Remove padding from score container */
margin: 0; /* remove margin from score container */
}
#mlb-scores-display {
column-count: 1;
}
.score-box {
background-color: #232338;
padding: 2px 5px; /* Reduced padding on score boxes */
margin-bottom: 2px; /* Reduced margin on score boxes */
border-radius: 6px;
border: 1px solid #4a4a82;
display: flex; /* Use flexbox for inner alignment */
flex-direction: column; /* Stack the game-score and game-period vertically */
justify-content: center; /* Center vertically */
}
.game-score {
font-size: 0.9em;
color: #e6e6fa;
text-align: center;
margin: 2px 0; /* Add a small top/bottom margin */
}
.game-period {
font-size: 0.8em;
color: #b39ddb;
text-align: center;
margin: 2px 0; /* Add a small top/bottom margin */
}
/* Weather Styles */
#weather-display {
background-color: #232338;
border-radius: 10px;
padding: 10px;
margin: 10px auto;
max-width: 100%;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
border: 1px solid #4a4a82;
transition: all 0.3s ease;
flex: 1;
}
#weather-display:hover {
box-shadow: 0 6px 8px rgba(179, 157, 219, 0.2);
transform: translateY(-2px);
}
#weather-display img {
width: 100%;
border-radius: 8px;
margin-top: 5px;
}
/* News Ticker Styles */
.ticker {
position: fixed;
bottom: 0;
width: 100%;
overflow: hidden;
white-space: nowrap;
background-color: #232338;
padding: 5px;
border-radius: 8px;
border: 1px solid #4a4a82;
}
.news-item {
display: inline-block;
padding-right: 30px;
color: #d1c4e9;
animation: ticker linear infinite;
}
@keyframes ticker {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
/* Media Queries */
@media (min-width: 750px) {
/* Landscape layout */
body {
padding: 20px;
font-size: 18px;
max-width: 1500px;
margin: 0 auto;
}
#main-content-container {
flex-wrap: nowrap;
justify-content: space-around;
}
#mlb-scores-display {
column-count: 2;
}
.score-container {
padding: 0; /* Remove padding from score container */
margin: 0; /* remove margin from score container */
}
.score-box {
padding: 2px 5px; /* Reduced padding on score boxes */
margin-bottom: 2px; /* Reduced margin on score boxes */
}
#weather-display {
padding: 20px;
margin: 0;
max-width: 400px;
}
.ticker {
padding: 15px;
}
.news-item {
padding-right: 50px;
}
}
@media (max-width: 480px) {
/* Phone screens */
body {
font-size: 14px;
padding: 0;
transform-origin: top left;
transform: scale(0.6);
width: 166.66%;
margin-left: -33.33%;
min-width: 0;
}
h1 {
font-size: 1.5rem;
}
h2 {
font-size: 1.2rem;
}
#main-content-container {
gap: 1px;
}
#nba-container,
#mlb-container {
min-width: 0;
}
.score-container {
padding: 0; /* Remove padding from score container */
margin: 0; /* remove margin from score container */
}
.score-box {
padding: 2px 5px; /* Reduced padding on score boxes */
margin-bottom: 2px; /* Reduced margin on score boxes */
}
.game-score {
font-size: 0.8em;
margin: 2px 0; /* Add a small top/bottom margin */
}
.game-period {
font-size: 0.7em;
margin: 2px 0; /* Add a small top/bottom margin */
}
.ticker {
padding: 5px;
}
.news-item {
padding-right: 15px;
}
}

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -1,15 +0,0 @@
from time import strftime, localtime
from dash import Input, Output
class ClockModule:
def __init__(self, app):
self.app = app
self.setup_callbacks()
def setup_callbacks(self):
@self.app.callback(
Output('clock-display', 'children'),
Input('clock-interval', 'n_intervals')
)
def update_time(n):
return strftime("%B %d, %I:%M %p", localtime())

View file

@ -0,0 +1,57 @@
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
import reflex as rx
from datetime import datetime, timezone
import asyncio
from rxconfig import config
from utils.weather import Weather
from utils.scores import NBAScores, mlbScores
from utils.news import News
from utils.alarm import Alarm
class State(rx.State):
"""The app state."""
def index() -> rx.Component:
return rx.container(
rx.center(
rx.vstack(
rx.button(
rx.moment(interval=1000, format="HH:mm:ss"),
font_size="4xl",
font_weight="bold",
color="white",
background_color="#6f42c1",
),
rx.box(
rx.image(
src=Weather().get_weather_screenshot(),
alt="Weather",
width="100%",
height="auto",
background_color="white",
),
),
),
),
),
app = rx.App()
app.add_page(index)

View file

@ -1,107 +0,0 @@
#deathclock
import datetime
import time
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QTimer, QObject, Signal, Slot, Property
from time import strftime, localtime
import requests
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import os
import news
import weather
class clock():
def update_time(self):
curr_time = strftime("%B %d, %I:%M %p", localtime())
engine.rootObjects()[0].setProperty('currTime', curr_time)
return curr_time
def time_and_date():
print(time.asctime())
def alarm():
alarm_time = input("what time should the alarm be set?")
def ring():
pass
"""
class gui():
def handleTouchAreaPressed(self, signal):
# Implement your desired behavior when the left area is pressed
print("here touch area")
leftTouchAreaMouse = engine.rootObjects()[0].findChild("leftTouchAreaMouse")
leftTouchAreaMouse.connect(b"touchAreaPressed", self.handleTouchAreaPressed)
print("Left area pressed!")
"""
def main():
#gui_obj = gui()
app = QGuiApplication(sys.argv)
global engine
engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load( 'main.qml')
# create instance of weather class
weather_obj = weather.Weather()
weather_obj.weatherUpdated.connect(lambda weather_map_path: engine.rootContext().setContextProperty("weatherMapPath", weather_map_path))
# set timer for weather map
weatherTimer = QTimer()
weatherTimer.setInterval(300000) # 10 minutes
weatherTimer.timeout.connect(weather_obj.download_sacramento_weather_map(engine))
weather_obj.download_sacramento_weather_map(engine)
weatherTimer.start()
# create instance of clock class
timeupdate = clock()
# start timer for clock
timer = QTimer()
timer.setInterval(100) # msecs 100 = 1/10th sec
timer.timeout.connect(timeupdate.update_time)
timer.start()
# create instance of news class
news_obj = news.news()
news_ticker = news_obj.get_news()
#print(news_obj._news_dict)
#print(news_ticker)
news_context = engine.rootContext()
news_context.setContextProperty("news", str(news_ticker))
#start timer for news
news_timer = QTimer()
news_timer.timeout.connect(news_obj.get_news)
news_timer.setInterval(300000) # 10 minutes #changed to 5 mins
news_timer.start()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

View file

@ -1,66 +0,0 @@
import datetime
import asyncio
from dash import html, Input, Output, callback, no_update
from dash.exceptions import PreventUpdate
from news import News
class NewsModule:
def __init__(self, app):
self.app = app
self.news_obj = self.get_news_object()
self._last_news_update = datetime.datetime(2000, 1, 1)
self._cached_news = self.create_loading_message() # Initial loading message
self._initial_run = True
self.setup_callbacks()
def get_news_object(self):
return News()
def create_loading_message(self):
return html.Div("Loading...")
def setup_callbacks(self):
@self.app.callback(
Output('news-ticker', 'children'),
Input('news-interval', 'n_intervals')
)
def update_news(n):
if n is None:
return self._cached_news
current_time = datetime.datetime.now()
time_since_update = (current_time - self._last_news_update).total_seconds()
# Only update if it's been more than 5 minutes or it's the initial run
if time_since_update < 300 and not self._initial_run:
return self._cached_news
try:
print("UPDATING NEWS...")
# Create a new event loop for this request
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
headlines_dict = loop.run_until_complete(self.news_obj.get_news())
loop.close()
if not headlines_dict:
return html.Div("No news available at this time.", className="ticker")
combined_items = " | ".join([f"{data['source']}: {headline}"
for headline, data in headlines_dict.items()])
text_px = len(combined_items) * 8
scroll_speed = 75
duration = max(text_px / scroll_speed, 20)
ticker_style = {"animationDuration": f"{duration}s"}
self._cached_news = html.Div(
html.Span(combined_items, className="news-item", style=ticker_style),
className='ticker'
)
self._last_news_update = current_time
self._initial_run = False
return self._cached_news
except Exception as e:
print(f"Error updating news: {e}")
return html.Div("No news available.")

View file

@ -1,69 +0,0 @@
from dash import html, Input, Output
from scores import NBAScores # Import NBAScores class
from scores import mlbScores # Import mlbScores class
class ScoresModule:
def __init__(self, app):
self.app = app
self.scores_obj = self.get_scores_object()
self.mlb_scores_obj = self.get_mlb_scores_object()
self.setup_callbacks()
def get_scores_object(self):
return NBAScores()
def setup_callbacks(self):
@self.app.callback(
Output('nba-scores-display', 'children'),
Input('nba-interval', 'n_intervals')
)
def update_scores(n):
try:
games = self.scores_obj.get_scores()
if not games:
print("no nba games found")
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']}"),
html.Span(" @ "),
html.Span(f"{game['home_team']} {game['home_score']}")
], className='game-score'),
html.Div(f"Period: {game['period']}", className='game-period')
], className='score-box')
for game in games
], className='score-container')
except Exception as e:
return html.Div("Scores unavailable")
def get_mlb_scores_object(self):
return mlbScores()
def setup_mlb_callbacks(self):
@self.app.callback(
Output('mlb-scores-display', 'children'),
Input('mlb-interval', 'n_intervals')
)
def update_mlb_scores(n):
try:
games = self.mlb_scores_obj.get_scores()
print("Updating MLB Scores")
if not games:
print("no mlb games found")
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']}"),
html.Span(" @ "),
html.Span(f"{game['home_team']} {game['home_score']}")
], className='game-score'),
html.Div(f"Status: {game['status']}", className='game-period')
], className='score-box')
for game in games
], className='score-container')
except Exception as e:
print(f"Error updating MLB Scores: {e}")
return html.Div("Scores unavailable")

View file

@ -1,36 +0,0 @@
import datetime
import os
from dash import html, Input, Output
from weather import Weather # Import Weather class
class WeatherModule:
def __init__(self, app):
self.app = app
self.weather_obj = self.get_weather_object()
self.setup_callbacks()
def get_weather_object(self):
return Weather()
def setup_callbacks(self):
@self.app.callback(
Output('weather-display', 'children'),
Input('weather-interval', 'n_intervals')
)
def update_weather(n):
try:
print("UPDATING WEATHER...")
screenshot_path = self.weather_obj.get_weather_screenshot()
image_name = os.path.basename(screenshot_path)
return html.Div(
[
html.H2("Sacramento Weather"),
html.Img(
src=self.app.get_asset_url(image_name + f"?v={datetime.datetime.now().timestamp()}"),
style={"width": "100%", "display": "block", "image-rendering": "crisp-edges"}
),
],
style={"width": "600px", "margin": "0 auto", "border": "1px solid black"}
)
except Exception as e:
return html.Div(f"Weather update error: {str(e)}")

View file

@ -5,17 +5,16 @@ description = ""
authors = [{ name = "Death916", email = "tavn1992@gmail.com" }] authors = [{ name = "Death916", email = "tavn1992@gmail.com" }]
requires-python = ">=3.9,<3.11.4" requires-python = ">=3.9,<3.11.4"
dependencies = [ dependencies = [
"pyside6>=6.6.1,<7",
"requests>=2.31.0,<3", "requests>=2.31.0,<3",
"selenium>=4.16.0,<5",
"feedparser>=6.0.11,<7", "feedparser>=6.0.11,<7",
"rich>=13.7.1,<14", "rich>=13.7.1,<14",
"dash>=2.18.2,<3", "dash>=2.18.2,<3",
"nba-api>=1.6.1,<2", "nba-api>=1.6.1,<2",
"playwright>=1.49.1,<2", "playwright>=1.49.1,<2",
"aiofiles>=24.1.0", "aiofiles>=24.1.0",
"aiohttp>=3.11.13",
"mlb-statsapi>=1.8.1", "mlb-statsapi>=1.8.1",
"reflex>=0.6.8",
"aiohttp>=3.11.18",
] ]
[dependency-groups] [dependency-groups]

View file

View file

@ -1,5 +0,0 @@
from deathclock import __version__
def test_version():
assert __version__ == '0.1.0'

View file

@ -25,4 +25,3 @@ class Alarm:
print("Alarm triggered!") print("Alarm triggered!")
return True return True
return False return False

1874
uv.lock generated

File diff suppressed because it is too large Load diff