238 lines
6.8 KiB
TypeScript
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;
|
|
}
|