feat(app): add html weather view

This commit is contained in:
2024-07-25 15:51:27 +03:00
parent 234a2b7b0e
commit b3d88997eb
22 changed files with 393 additions and 34 deletions

View File

12
gismeteo/route/api.py Normal file
View File

@@ -0,0 +1,12 @@
from fastapi import FastAPI
from gismeteo import dateutil
from gismeteo.api import GismeteoApi
def mount(app: FastAPI):
@app.get("/api/weather/{location}/{date}")
async def get_weather(location: str, date: str):
api = GismeteoApi()
result = await api.get_day(location, dateutil.parse(date))
return [item._asdict() for item in result]

View File

@@ -0,0 +1,37 @@
from anyio import Path
from fastapi import FastAPI
from fastapi.openapi.docs import (
get_redoc_html,
get_swagger_ui_html,
get_swagger_ui_oauth2_redirect_html,
)
from fastapi.staticfiles import StaticFiles
def mount(app: FastAPI):
app.mount(
"/docs/static",
StaticFiles(directory=Path(__file__).parent / "static"),
)
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - Swagger UI",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
swagger_js_url="docs/static/swagger-ui-bundle.js",
swagger_css_url="docs/static/swagger-ui.css",
)
@app.get(app.swagger_ui_oauth2_redirect_url, include_in_schema=False)
async def swagger_ui_redirect():
return get_swagger_ui_oauth2_redirect_html()
@app.get("/redoc", include_in_schema=False)
async def redoc_html():
return get_redoc_html(
openapi_url=app.openapi_url,
title=app.title + " - ReDoc",
redoc_js_url="docs/static/redoc.standalone.js",
)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,43 @@
import datetime
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from gismeteo import dateutil
from gismeteo.api import GismeteoApi
from gismeteo.mock import MOCK_DATA
from .filters import cloudness_icon, wind_direction_icon
def mount(app: FastAPI):
base_dir = Path(__file__).parent
app.mount("/static", StaticFiles(directory=base_dir / "static"), name="static")
templates = Jinja2Templates(directory=base_dir / "templates")
templates.env.filters["wind_direction_icon"] = wind_direction_icon
templates.env.filters["cloudness_icon"] = cloudness_icon
@app.get("/weather/{location}")
async def get_weather_base(location: str):
return RedirectResponse(f"{location}/today")
@app.get("/weather/{location}/{date}", response_class=HTMLResponse)
async def get_weather(request: Request, location: str, date: str):
if date == "mock":
values = MOCK_DATA.values
else:
api = GismeteoApi()
values = await api.get_day(location, dateutil.parse(date))
return templates.TemplateResponse(
request=request,
name="weather.html",
context={
"datetime": datetime,
"location": location,
"date": dateutil.parse(date),
"values": values,
},
)

View File

@@ -0,0 +1,27 @@
def wind_direction_icon(wind_direction: str) -> str:
return {
"С": "🡫",
"СВ": "🡯",
"В": "🡨",
"ЮВ": "🡬",
"Ю": "🡡",
"ЮЗ": "🡭",
"З": "🡪",
"СЗ": "🡦",
"штиль": "",
}.get(wind_direction, wind_direction)
def cloudness_icon(cloudness: str) -> str:
return {
"Ясно": "☀️",
"Малооблачно, без осадков": "🌤️",
"Облачно, без осадков": "",
"Малооблачно, небольшой дождь": "🌦️",
"Пасмурно, без осадков": "☁️",
"Облачно, небольшой дождь": "🌧️",
"Облачно, дождь": "🌧️",
"Облачно, небольшой дождь, гроза": "⛈️",
"Малооблачно, дождь": "🌦️",
"Пасмурно, небольшой дождь": "🌧️",
}.get(cloudness, cloudness)

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

View File

@@ -0,0 +1,81 @@
body {
font-size: 1.5rem;
}
h3 {
margin: 0.5rem 0;
}
a.button {
text-decoration: none;
color: inherit;
}
.button.disabled {
pointer-events: none;
cursor: default;
color: gray;
}
table {
/* width: 100%; */
table-layout: fixed;
border-collapse: collapse;
}
table,
th,
td {
/* border: 1px solid rgba(0, 0, 0, 0.2); */
text-align: center;
}
td {
padding: 0.1rem 0.4rem;
}
.header {
font-size: 1rem;
text-align: left;
}
.date {
font-size: 1.5rem;
background: rgba(0, 0, 0, 0.1);
}
.date.now {
background: rgba(0, 128, 255, 0.2);
}
.cloudness .icon {
font-size: 2rem;
}
.temperature.positive .value {
color: orangered;
}
.temperature.negative .value {
color: blue;
}
.wind .direction {
font-size: 1rem;
}
.wind .gust {
font-size: 1rem;
}
.precipitation .value {
color: blue;
}
.pressure .value {
color: blueviolet;
}
.humidity .value {
color: blue;
}

View File

@@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible"
content="ie=edge">
<title>Weather - {{date.strftime('%a, %d %B %Y')}}</title>
<link rel="stylesheet"
href="/static/style.css">
<link rel="icon"
href="/static/favicon.ico"
type="image/x-icon">
</head>
<body>
<h3>
<a class="button {{'disabled' if date == datetime.date.today() else ''}}"
href="{{date - datetime.timedelta(days=1)}}">🡨</a>
<span>{{date.strftime('%a, %d %B %Y')}}</span>
<a class="button"
href="{{date + datetime.timedelta(days=1)}}">🡪</a>
</h3>
<table>
<tbody>
<!-- date -->
<tr>
{% for value in values %}
<td
class="date {{'now' if value.date < datetime.datetime.now() and value.date + datetime.timedelta(hours=3) > datetime.datetime.now() else ''}}">
<span class="value">{{value.date.strftime('%H:%M')}}</span>
</td>
{% endfor %}
</tr>
<!-- cloudness -->
<tr>
<td colspan="{{values | length}}"
class="header">
Облачность
</td>
</tr>
<tr>
{% for value in values %}
<td class="cloudness">
<span class="icon">{{value.cloudness | cloudness_icon}}</span>
</td>
{% endfor %}
</tr>
<!-- temperature -->
<tr>
<td colspan="{{values | length}}"
class="header">
Температура, °C
</td>
</tr>
<tr>
{% for value in values %}
<td class="temperature {{'positive' if value.temperature > 0 else 'negative'}}"
style="background-color: rgba(255, 128, 128, {{(value.temperature - 10) * 0.015}});">
<span class="value">{{value.temperature}}</span>
</td>
{% endfor %}
</tr>
<!-- wind_direction -->
<tr>
<td colspan="{{values | length}}"
class="header">
Направление ветра
</td>
</tr>
<tr>
{% for value in values %}
<td class="wind">
<span class="icon">{{value.wind_direction | wind_direction_icon}}</span>
<span class="direction">{{value.wind_direction}}</span>
</td>
{% endfor %}
</tr>
<!-- wind_speed -->
<tr>
<td colspan="{{values | length}}"
class="header">
Скорость ветра, м/с
</td>
</tr>
<tr>
{% for value in values %}
<td class="wind"
style="background-color: rgba(128, 128, 128, {{value.wind_speed * 0.05}});">
<span class="speed">{{value.wind_speed}}</span>
{% if value.wind_gust != value.wind_speed %}
<span class="gust">
({{value.wind_gust}})
</span>
{% endif %}
</td>
{% endfor %}
</tr>
<!-- precipitation -->
<tr>
<td colspan="{{values | length}}"
class="header">
Осадки, мм
</td>
</tr>
<tr>
{% for value in values %}
<td class="precipitation"
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
<span class="value">{{value.precipitation or ''}}</span>
</td>
{% endfor %}
</tr>
<!-- pressure -->
<tr>
<td colspan="{{values | length}}"
class="header">
Давление, мм рт. ст.
</td>
</tr>
<tr>
{% for value in values %}
<td class="pressure"
style="background-color: rgba(128, 0, 255, {{(value.pressure - 720) * 0.008}});">
<span class="value">{{value.pressure}}</span>
</td>
{% endfor %}
</tr>
<!-- humidity -->
<tr>
<td colspan="{{values | length}}"
class="header">
Влажность, %
</td>
</tr>
<tr>
{% for value in values %}
<td class="humidity"
style="background-color: rgba(128, 128, 255, {{value.humidity * 0.005}});">
<span class="value">{{value.humidity}}</span>
</td>
{% endfor %}
</tr>
</tbody>
</table>
<script src="/static/index.js"></script>
</body>
</html>