171 lines
No EOL
5.3 KiB
TypeScript
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;
|
|
} |