diff --git a/gallery/easel/route/view/weather/templates/index.html b/gallery/easel/route/view/weather/templates/index.html
index 78d8044..aa28342 100644
--- a/gallery/easel/route/view/weather/templates/index.html
+++ b/gallery/easel/route/view/weather/templates/index.html
@@ -33,66 +33,29 @@
type="submit">{{_("Search")}}
+{% if locations %}
+
+{% endif %}
+
{% endblock %}
\ No newline at end of file
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/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..97cde50
--- /dev/null
+++ b/static/src/weather/weather.ts
@@ -0,0 +1,113 @@
+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.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();