jroshell/widget/Bar/Email.tsx
2026-06-08 14:24:57 +02:00

238 lines
6.8 KiB
TypeScript

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<Email[]>([]);
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;
}