import { Gtk } from "ags/gtk4"; import { createState } from "ags"; import GLib from "gi://GLib"; import Gio from "gi://Gio"; type Email = { subject: string; sender: string; date: string }; const REFRESH_SECONDS = 1800; const CACHE_FILE = GLib.build_filenamev([ GLib.get_user_cache_dir(), "ags", "unread-emails.json", ]); // Resolve script path relative to this source file. const sourceFile = Gio.File.new_for_uri(import.meta.url); const sourceDir = sourceFile.get_parent()!; const projectDir = sourceDir.get_parent()!.get_parent()!; const SCRIPT_PATH = projectDir .get_child("scripts") .get_child("ags-emails-fetch.sh") .get_path()!; // Helpers function readEmails(): Email[] { try { const file = Gio.File.new_for_path(CACHE_FILE); const [ok, contents] = file.load_contents(null); if (!ok) return []; const data = JSON.parse(new TextDecoder("utf-8").decode(contents)); if (!Array.isArray(data)) return []; return data.map((m: any) => ({ subject: m.subject || "(no subject)", sender: m.from?.name || m.from?.addr || "Unknown", date: m.date ? new Date(m.date).toLocaleString() : "Unknown date", })); } catch { return []; } } // Widget export default function Email() { const [emails, setEmails] = createState([]); let timerId = 0; let popover: Gtk.Popover | null = null; let popoverList: Gtk.Box | null = null; let btnLabel: Gtk.Label | null = null; // UI updates function updateButtonLabel() { if (!btnLabel) return; const count = emails().length; btnLabel.label = count > 0 ? `๏€ƒ ${count}` : "๏€ƒ"; } function rebuildPopoverContent() { if (!popoverList) return; // Clear existing let child = popoverList.get_first_child(); while (child) { const next = child.get_next_sibling(); popoverList.remove(child); child = next; } const current = emails(); if (current.length === 0) { const empty = new Gtk.Box({ halign: Gtk.Align.CENTER, valign: Gtk.Align.CENTER, orientation: Gtk.Orientation.VERTICAL, cssClasses: ["email-empty"], }); empty.append(new Gtk.Label({ label: "๐Ÿ“ญ", cssClasses: ["empty-icon"], })); empty.append(new Gtk.Label({ label: "No unread emails", cssClasses: ["status-text"], })); popoverList.append(empty); } else { for (const email of current) { const item = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, cssClasses: ["email-item"], }); item.append(new Gtk.Label({ label: email.subject, halign: Gtk.Align.START, cssClasses: ["email-subject"], })); const meta = new Gtk.Box({ spacing: 8 }); meta.append(new Gtk.Label({ label: email.sender, cssClasses: ["email-sender"], })); meta.append(new Gtk.Label({ label: "ยท", cssClasses: ["email-separator"], })); meta.append(new Gtk.Label({ label: email.date, cssClasses: ["email-date"], })); item.append(meta); popoverList.append(item); } } } function refresh() { const parsed = readEmails(); setEmails(parsed); updateButtonLabel(); rebuildPopoverContent(); } // Fetch script function fetchThenRefresh() { try { const proc = Gio.Subprocess.new( ["bash", SCRIPT_PATH], Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE, ); proc.wait_async(null, (_proc: Gio.Subprocess, result: Gio.AsyncResult) => { try { proc.wait_finish(result); } catch (e) { console.error("email fetch script failed:", e); } refresh(); }); } catch (e) { console.error("failed to launch email fetch:", e); refresh(); // still read whatever cache exists } } // Build UI const root = new Gtk.Box({ cssClasses: ["email-bar"] }); // Button showing icon + count const button = new Gtk.Button({ cssClasses: ["email-btn"] }); btnLabel = new Gtk.Label({ label: "๏€ƒ", cssClasses: ["email-label"] }); button.set_child(btnLabel); root.append(button); // Create the popover container (vertical box) const popoverContent = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 0, cssClasses: ["email-popover-content"], }); // Thunderbird button const emailClientButton = new Gtk.Button({ cssClasses: ["email-client-button"] }); emailClientButton.set_halign(Gtk.Align.END); const emailClientButtonLabel = new Gtk.Label({ label: "Open Thunderbird", cssClasses: ["email-client-label"], }); emailClientButton.set_child(emailClientButtonLabel); emailClientButton.connect("clicked", () => { try { Gio.Subprocess.new( ["thunderbird"], Gio.SubprocessFlags.NONE ); } catch (e) { console.error("Failed to launch Thunderbird:", e); } popover?.popdown(); }); // Scrolled window for emails const scrolled = new Gtk.ScrolledWindow({ vexpand: true, cssClasses: ["email-scroll"], }); scrolled.set_max_content_height(400); scrolled.set_min_content_width(320); popoverList = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, spacing: 8, cssClasses: ["email-list"], }); scrolled.set_child(popoverList); popoverContent.append(emailClientButton); popoverContent.append(scrolled); popover = new Gtk.Popover({ autohide: true, has_arrow: false, cssClasses: ["email-popover"], }); popover.set_parent(button); popover.set_child(popoverContent); button.connect("clicked", () => { if (popover!.is_visible()) { popover!.popdown(); } else { popover!.popup(); } }); // Timer timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, REFRESH_SECONDS, () => { fetchThenRefresh(); return GLib.SOURCE_CONTINUE; }); root.connect("destroy", () => { if (timerId) GLib.Source.remove(timerId); }); refresh(); fetchThenRefresh(); return root; }