jroshell/widget/Overlay/WeatherWidget.tsx
2026-06-06 13:53:38 +02:00

171 lines
No EOL
5.3 KiB
TypeScript

import { Gtk } from "ags/gtk4";
import GLib from "gi://GLib";
import Gio from "gi://Gio";
import { watchFile } from "../../lib/fileMonitor";
interface WeatherData {
current: {
temp: number;
wind: number;
icon: string;
};
today: {
high: number;
low: number;
rain_pct: number;
icon: string;
};
forecast: Array<{
day: string;
high: number;
low: number;
rain: number;
icon: string;
}>;
}
export default function WeatherWidget() {
const cachePath = GLib.build_filenamev([
GLib.get_user_cache_dir(),
"ags",
"weather.json",
]);
// --- Current ---
const currentIcon = new Gtk.Label({ cssClasses: ["weather-current-icon"] });
const currentTemp = new Gtk.Label({ cssClasses: ["weather-current-temp"] });
const currentWind = new Gtk.Label({ cssClasses: ["weather-current-wind"] });
// --- Today summary ---
const todayIcon = new Gtk.Label({ cssClasses: ["weather-today-icon"] });
const todayHighLow = new Gtk.Label({ cssClasses: ["weather-today-highlow"] });
const rainPill = new Gtk.Label({ cssClasses: ["weather-rain-pill"] });
// --- Forecast ---
const forecastBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
homogeneous: true,
cssClasses: ["weather-forecast-box"],
});
function makeForecastDay(day: WeatherData["forecast"][number]) {
const wrap = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
cssClasses: ["weather-forecast-day"],
});
wrap.append(new Gtk.Label({ label: day.day, cssClasses: ["weather-forecast-label"] }));
wrap.append(new Gtk.Label({ label: day.icon, cssClasses: ["weather-forecast-icon"] }));
wrap.append(new Gtk.Label({
label: `${Math.round(day.high)}° ${Math.round(day.low)}°`,
cssClasses: ["weather-forecast-temps"],
}));
// Rain bar
const barBg = new Gtk.Box({ cssClasses: ["weather-rain-bar-bg"], heightRequest: 4 });
const barFg = new Gtk.Box({
cssClasses: ["weather-rain-bar-fg"],
heightRequest: 4,
widthRequest: Math.round(day.rain * 0.6), // max ~60px at 100%
});
barBg.append(barFg);
wrap.append(barBg);
wrap.append(new Gtk.Label({
label: `${day.rain}%`,
cssClasses: ["weather-forecast-rain"],
}));
return wrap;
}
function updateDisplay(data: WeatherData) {
// Current
currentIcon.label = data.current.icon;
currentTemp.label = `${Math.round(data.current.temp)}°`;
currentWind.label = `💨 ${data.current.wind} m/s`;
// Today
todayIcon.label = data.today.icon;
todayHighLow.label = `${Math.round(data.today.high)}° ▼ ${Math.round(data.today.low)}°`;
rainPill.label = `🌧 ${data.today.rain_pct}%`;
// Forecast
let child = forecastBox.get_first_child();
while (child) {
const next = child.get_next_sibling();
forecastBox.remove(child);
child = next;
}
data.forecast.forEach(d => forecastBox.append(makeForecastDay(d)));
}
function readWeatherFile(): WeatherData | null {
try {
const file = Gio.File.new_for_path(cachePath);
if (!file.query_exists(null)) return null;
const [ok, contents] = file.load_contents(null);
if (ok) return JSON.parse(new TextDecoder("utf-8").decode(contents));
} catch (e) {
log(`WeatherWidget: ${e}`);
}
return null;
}
const initial = readWeatherFile();
if (initial) updateDisplay(initial);
// Build the root widget
const root = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
cssClasses: ["grid-card", "weather-card"],
hexpand: true,
});
watchFile(cachePath, () => {
const d = readWeatherFile();
if (d) updateDisplay(d);
}, root);
// Build the UI structure
const currentRow = new Gtk.Box({
cssClasses: ["weather-current-row"],
halign: Gtk.Align.CENTER,
});
currentRow.append(currentIcon);
const currentInfoBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
valign: Gtk.Align.CENTER,
});
currentInfoBox.append(currentTemp);
currentInfoBox.append(currentWind);
currentRow.append(currentInfoBox);
root.append(currentRow);
root.append(new Gtk.Box({ vexpand: true }));
const sep1 = new Gtk.Box({ cssClasses: ["weather-sep"], heightRequest: 1, hexpand: true });
root.append(sep1);
const todayRow = new Gtk.Box({
cssClasses: ["weather-today-row"],
halign: Gtk.Align.CENTER,
});
todayRow.append(todayIcon);
const todayInfoBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
valign: Gtk.Align.CENTER,
});
todayInfoBox.append(todayHighLow);
todayInfoBox.append(rainPill);
todayRow.append(todayInfoBox);
root.append(todayRow);
root.append(new Gtk.Box({ vexpand: true }));
const sep2 = new Gtk.Box({ cssClasses: ["weather-sep"], heightRequest: 1, hexpand: true });
root.append(sep2);
root.append(forecastBox);
return root;
}