diff --git a/gallery/easel/route/view/weather/__init__.py b/gallery/easel/route/view/weather/__init__.py
index 3c4b6d9..eaebf8f 100644
--- a/gallery/easel/route/view/weather/__init__.py
+++ b/gallery/easel/route/view/weather/__init__.py
@@ -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")
diff --git a/gallery/easel/route/view/weather/templates/index.html b/gallery/easel/route/view/weather/templates/index.html
index 78d8044..21ea15a 100644
--- a/gallery/easel/route/view/weather/templates/index.html
+++ b/gallery/easel/route/view/weather/templates/index.html
@@ -1,23 +1,6 @@
{% extends "base.html" %}
{% block title %}{{_("Weather")}}{% endblock %}
-{# {% block header %}
-
-
- {{provider or providers[0]}}
-
-
-
-{% endblock %} #}
-
{% block content %}
{{_("Weather")}}
+{% if locations %}
+
+{% endif %}
+
{% endblock %}
\ No newline at end of file
diff --git a/gallery/easel/route/view/weather/templates/weather.html b/gallery/easel/route/view/weather/templates/weather.html
index 3922e0a..b8d7a02 100644
--- a/gallery/easel/route/view/weather/templates/weather.html
+++ b/gallery/easel/route/view/weather/templates/weather.html
@@ -137,7 +137,7 @@
{% for value in response.values %}
- {{value.precipitation or ' '}}
+ {{(value.precipitation | round(2)) if value.precipitation else ' '}}
{% endfor %}
diff --git a/gallery/painting/gismeteo/api.py b/gallery/painting/gismeteo/api.py
index f553b6e..8e042c5 100644
--- a/gallery/painting/gismeteo/api.py
+++ b/gallery/painting/gismeteo/api.py
@@ -85,6 +85,7 @@ class GismeteoApi(WeatherApi):
Location(
id=f"{item['slug']}-{item['id']}",
name=item["translations"]["kk"]["city"]["name"],
+ provider=self.provider,
lat=item["coordinates"]["latitude"],
lon=item["coordinates"]["longitude"],
country=item["translations"]["kk"]["country"]["name"],
diff --git a/gallery/painting/openweather/api.py b/gallery/painting/openweather/api.py
index 6c66095..48c9f39 100644
--- a/gallery/painting/openweather/api.py
+++ b/gallery/painting/openweather/api.py
@@ -38,6 +38,7 @@ class OpenWeatherApi(WeatherApi):
Location(
id=f"{item.lat}:{item.lon}",
name=item.name,
+ provider=self.provider,
lat=item.lat,
lon=item.lon,
country=item.country,
diff --git a/gallery/sketch/weather/model.py b/gallery/sketch/weather/model.py
index 86c99da..f13ae37 100644
--- a/gallery/sketch/weather/model.py
+++ b/gallery/sketch/weather/model.py
@@ -1,5 +1,6 @@
import datetime
-from enum import Enum
+from enum import StrEnum, auto
+from typing import Self
from pydantic import BaseModel
@@ -12,6 +13,7 @@ class Model(BaseModel):
class Location(Model):
id: str
name: str
+ provider: str
lat: float
lon: float
country: str
@@ -20,21 +22,21 @@ class Location(Model):
subdistrict: str
-class Cloudness(str, Enum):
- CLEAR = "clear"
- PARTLY_CLOUDY = "party_cloudy"
- CLOUDY = "cloudy"
- MAINLY_CLOUDY = "mainly_cloudy"
+class Cloudness(StrEnum):
+ CLEAR = auto()
+ PARTLY_CLOUDY = auto()
+ CLOUDY = auto()
+ MAINLY_CLOUDY = auto()
-class Precipitation(str, Enum):
- NO = "no"
- SMALL_RAIN = "small_rain"
- RAIN = "rain"
- HEAVY_RAIN = "heavy_rain"
- SHOWER = "shower"
- SNOW = "snow"
- HEAVY_SNOW = "heavy_snow"
+class Precipitation(StrEnum):
+ NO = auto()
+ SMALL_RAIN = auto()
+ RAIN = auto()
+ HEAVY_RAIN = auto()
+ SHOWER = auto()
+ SNOW = auto()
+ HEAVY_SNOW = auto()
class Sky(Model):
@@ -44,16 +46,16 @@ class Sky(Model):
fog: bool
-class WindDirection(str, Enum):
- CALM = "calm"
- N = "N"
- NE = "NE"
- E = "E"
- SE = "SE"
- S = "S"
- SW = "SW"
- W = "W"
- NW = "NW"
+class WindDirection(StrEnum):
+ CALM = auto()
+ N = auto()
+ NE = auto()
+ E = auto()
+ SE = auto()
+ S = auto()
+ SW = auto()
+ W = auto()
+ NW = auto()
class WindDirectionDeg(float):
@@ -89,7 +91,7 @@ class WindDirectionDeg(float):
raise ValueError(self)
@classmethod
- def from_direction(cls, direction: WindDirection) -> "WindDirectionDeg":
+ def from_direction(cls, direction: WindDirection) -> Self:
return cls(
{
WindDirection.CALM: -1,
diff --git a/static/src/index.ts b/static/src/index.ts
index cd01b14..f0b971c 100644
--- a/static/src/index.ts
+++ b/static/src/index.ts
@@ -3,6 +3,7 @@ import "./components";
import "./language";
import "./main.scss";
import "./theme";
+import "./weather/weather";
document.addEventListener("DOMContentLoaded", (event) => {
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
diff --git a/static/src/lib/bootstrap.scss b/static/src/lib/bootstrap.scss
index 406e2a8..c3010c3 100644
--- a/static/src/lib/bootstrap.scss
+++ b/static/src/lib/bootstrap.scss
@@ -30,7 +30,7 @@
//@import "bootstrap/scss/accordion";
//@import "bootstrap/scss/breadcrumb";
//@import "bootstrap/scss/pagination";
-//@import "bootstrap/scss/badge";
+@import "bootstrap/scss/badge";
//@import "bootstrap/scss/alert";
//@import "bootstrap/scss/progress";
@import "bootstrap/scss/list-group";
diff --git a/static/src/main.scss b/static/src/main.scss
index 51d728e..6a1a80e 100644
--- a/static/src/main.scss
+++ b/static/src/main.scss
@@ -4,7 +4,7 @@
@import "./lib/weather-icons/weather-icons";
@import "./widget.scss";
-@import "./weather.scss";
+@import "./weather/weather.scss";
.table.table-compact {
td {
diff --git a/static/src/weather.scss b/static/src/weather/weather.scss
similarity index 100%
rename from static/src/weather.scss
rename to static/src/weather/weather.scss
diff --git a/static/src/weather/weather.ts b/static/src/weather/weather.ts
new file mode 100644
index 0000000..9a0195c
--- /dev/null
+++ b/static/src/weather/weather.ts
@@ -0,0 +1,116 @@
+export interface Location {
+ id: string;
+ name: string;
+ provider: string;
+ country_code: string;
+ country?: string;
+ district?: string;
+ subdistrict?: string;
+}
+
+export class WeatherLocationStorage {
+ private storageKey: string = "weather:locations";
+
+ getAll(): Location[] {
+ const locations = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}");
+ return Object.values(locations);
+ }
+
+ add(location: Location) {
+ const locations = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}");
+ locations[location.id] = location;
+ window.localStorage.setItem(this.storageKey, JSON.stringify(locations));
+ }
+
+ remove(id: string) {
+ const locations = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}");
+ delete locations[id];
+ window.localStorage.setItem(this.storageKey, JSON.stringify(locations));
+ }
+}
+
+export class WeatherLocationManager {
+ loadLocations(selector: string) {
+ const locations = window.weatherLocationStorage.getAll();
+ const container = document.querySelector(selector);
+ if (container) {
+ container.innerHTML = "";
+ for (const location of locations) {
+ const element = new WeatherLocationElement();
+ element.setAttribute("location", JSON.stringify(location));
+ element.setAttribute("removable", "");
+ container.appendChild(element);
+ }
+ }
+ }
+}
+
+class WeatherLocationElement extends HTMLElement {
+ static observedAttributes = ["location", "removable"];
+
+ location: Location | null = null;
+
+ constructor() {
+ super();
+ this.handleClick = this.handleClick.bind(this);
+ this.handleRemoveClick = this.handleRemoveClick.bind(this);
+ }
+
+ connectedCallback() {
+ this.location = JSON.parse(this.getAttribute("location") || "{}");
+ this.innerHTML = `
+
+
+
+ ${this.location?.name}
+
+ ${this.location?.country}, ${this.location?.district}, ${this.location?.subdistrict}
+
+
+
+ ${this.location?.provider}
+
+ ✕
+
+
+ `;
+ this.addEventListener("click", this.handleClick);
+ if (this.hasAttribute("removable")) {
+ this.querySelector("[data-remove]")?.addEventListener("click", this.handleRemoveClick);
+ } else {
+ this.querySelector("[data-remove]")?.remove();
+ }
+ }
+
+ disconnectedCallback() {
+ this.removeEventListener("click", this.handleClick);
+ }
+
+ handleClick(event: MouseEvent) {
+ if (this.location) {
+ window.weatherLocationStorage.add(this.location);
+ }
+ }
+
+ handleRemoveClick(event: MouseEvent) {
+ event.preventDefault();
+ event.stopPropagation();
+ if (this.location) {
+ window.weatherLocationStorage.remove(this.location.id);
+ }
+ this.remove();
+ }
+}
+
+customElements.define("weather-location", WeatherLocationElement);
+
+declare global {
+ interface Window {
+ weatherLocationStorage: WeatherLocationStorage;
+ weatherLocationManager: WeatherLocationManager;
+ }
+}
+
+window.weatherLocationStorage = new WeatherLocationStorage();
+window.weatherLocationManager = new WeatherLocationManager();