feat(weather): improve weather locations list

This commit is contained in:
2026-06-24 15:45:15 +03:00
parent df2f1d0d81
commit d82fc4ea46
11 changed files with 180 additions and 101 deletions

View File

@@ -50,13 +50,7 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
router = APIRouter(prefix="/weather")
@router.get("/", response_class=RedirectResponse)
async def get_weather(request: AppRequest):
default_provider = request.app.state.api.get_api_providers(WeatherApi.TYPE)[0]
return RedirectResponse(default_provider)
@router.get("/{provider}", response_class=HTMLResponse)
@router.get("/", response_class=HTMLResponse)
async def get_weather_index(request: AppRequest, weather_api: WeatherApiDepends, query: str | None = None):
locations = (await weather_api.find_locations(query)) if query else []
return templates.TemplateResponse(
@@ -69,7 +63,7 @@ async def get_weather_index(request: AppRequest, weather_api: WeatherApiDepends,
@router.get("/{provider}/{location}", response_class=RedirectResponse)
async def get_weather_default(location: str):
async def get_weather(location: str):
return RedirectResponse(f"{location}/tag/today")

View File

@@ -1,23 +1,6 @@
{% extends "base.html" %}
{% block title %}{{_("Weather")}}{% endblock %}
{# {% block header %}
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false">
{{provider or providers[0]}}
</button>
<ul class="dropdown-menu">
{% for provider in providers %}
<li><a class="dropdown-item"
href="{{ url_for('get_weather_index').include_query_params(provider=provider) }}">{{provider}}</a></li>
{% endfor %}
</ul>
</div>
{% endblock %} #}
{% block content %}
<h1>{{_("Weather")}}</h1>
<form action=""
@@ -29,70 +12,51 @@
id="query"
name="query"
placeholder="{{_('Enter the city name')}}">
<input type="hidden"
class="form-control"
id="provider"
name="provider"
value="{{provider or providers[0]}}">
<button id="providerBtn"
class="btn btn-secondary dropdown-toggle"
type="text"
data-bs-toggle="dropdown"
aria-expanded="false">{{provider or providers[0]}}</button>
<ul class="dropdown-menu dropdown-menu-end">
{% for provider in providers %}
<li>
<a class="dropdown-item"
onclick="provider.value='{{provider}}'; providerBtn.innerText='{{provider}}'">{{provider}}</a>
</li>
{% endfor %}
</ul>
<button class="btn btn-primary"
type="submit">{{_("Search")}}</button>
</div>
</form>
{% if locations %}
<ul id="locations"
class="list-group mb-5">
{% for location in locations %}
<a href="{{ url_for('get_weather_default', provider=request.path_params.provider, location=location.id) }}"
class="list-group-item list-group-item-action px-4"
onclick="saveLocation({id:'{{location.id}}', name:'{{location.name}}'});">
<span class="fi fi-{{location.country_code}} me-1"></span>
<span class="text-primary">{{location.name}}</span>
<span class="small ms-1 text-secondary">
{{location.country}}, {{location.district}}, {{location.subdistrict}}
</span>
<span></span>
</a>
<weather-location location="{{location.model_dump() | tojson | forceescape}}"></weather-location>
{% endfor %}
</ul>
<hr>
{% endif %}
<ul id="storedLocations"
class="list-group mb-5">
</ul>
<script>
(function () {
const getStorageKey = () => {
const provider = window.location.pathname.split('/').pop();
return `locations:${provider}`;
}
document.loadLocations = () => {
const provider = window.location.pathname.split('/').pop();
console.log('!', provider);
const locations = JSON.parse(window.localStorage.getItem(getStorageKey()) || '{}');
const container = document.querySelector('#locations');
container.innerHTML = '';
for (const [id, name] of Object.entries(locations)) {
const element = document.createElement('a');
element.href = `${window.location.pathname}/${id}`;
element.className = 'list-group-item list-group-item-action px-4 d-flex justify-content-between align-items-start';
element.innerHTML = `
<span class="text-primary me-auto">${name}</span>
<span class="text-danger" onclick="removeLocation('${id}'); event.preventDefault();">&#x2715;</span>
`;
container.appendChild(element);
}
}
document.saveLocation = (location) => {
const locations = JSON.parse(window.localStorage.getItem(getStorageKey()) || '{}');
locations[location.id] = location.name;
window.localStorage.setItem(getStorageKey(), JSON.stringify(locations));
}
document.removeLocation = (id) => {
const locations = JSON.parse(window.localStorage.getItem(getStorageKey()) || '{}');
delete locations[id];
window.localStorage.setItem(getStorageKey(), JSON.stringify(locations));
document.loadLocations();
}
const params = new URLSearchParams(window.location.search);
const searchQuery = params.get('query');
if (searchQuery) {
document.querySelector('#query').value = searchQuery;
} else {
document.loadLocations();
}
document.addEventListener("DOMContentLoaded", (event) => {
weatherLocationManager.loadLocations('#storedLocations');
});
})();
</script>
{% endblock %}

View File

@@ -137,7 +137,7 @@
{% for value in response.values %}
<td class="precipitation"
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
<span class="value">{{value.precipitation or ''}}</span>
<span class="value">{{(value.precipitation | round(2)) if value.precipitation else ''}}</span>
</td>
{% endfor %}
</tr>