summaryrefslogtreecommitdiff
path: root/src/modules/popdowns
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/popdowns')
-rw-r--r--src/modules/popdowns/index.tsx9
-rw-r--r--src/modules/popdowns/notifications.tsx65
-rw-r--r--src/modules/popdowns/updates.tsx88
3 files changed, 162 insertions, 0 deletions
diff --git a/src/modules/popdowns/index.tsx b/src/modules/popdowns/index.tsx
new file mode 100644
index 0000000..db3245b
--- /dev/null
+++ b/src/modules/popdowns/index.tsx
@@ -0,0 +1,9 @@
+import Notifications from "./notifications";
+import Updates from "./updates";
+
+export default () => {
+ <Notifications />;
+ <Updates />;
+
+ return null;
+};
diff --git a/src/modules/popdowns/notifications.tsx b/src/modules/popdowns/notifications.tsx
new file mode 100644
index 0000000..bb08c13
--- /dev/null
+++ b/src/modules/popdowns/notifications.tsx
@@ -0,0 +1,65 @@
+import { bind } from "astal";
+import { Astal, Gtk } from "astal/gtk3";
+import AstalNotifd from "gi://AstalNotifd";
+import Notification from "../../widgets/notification";
+import PopdownWindow from "../../widgets/popdownwindow";
+
+const List = () => (
+ <box
+ vertical
+ valign={Gtk.Align.START}
+ className="list"
+ setup={self => {
+ const notifd = AstalNotifd.get_default();
+ const map = new Map<number, Notification>();
+
+ const addNotification = (notification: AstalNotifd.Notification) => {
+ const notif = (<Notification notification={notification} />) as Notification;
+ notif.connect("destroy", () => map.get(notification.id) === notif && map.delete(notification.id));
+ map.get(notification.id)?.destroyWithAnims();
+ map.set(notification.id, notif);
+
+ self.pack_end(
+ <eventbox
+ // Dismiss on middle click
+ onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()}
+ >
+ {notif}
+ </eventbox>,
+ false,
+ false,
+ 0
+ );
+ };
+
+ notifd
+ .get_notifications()
+ .sort((a, b) => a.time - b.time)
+ .forEach(addNotification);
+
+ self.hook(notifd, "notified", (_, id) => addNotification(notifd.get_notification(id)));
+ self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims());
+ }}
+ />
+);
+
+export default () => (
+ <PopdownWindow
+ name="notifications"
+ count={bind(AstalNotifd.get_default(), "notifications").as(n => n.length)}
+ headerButtons={[
+ {
+ label: "Silence",
+ onClicked: () => (AstalNotifd.get_default().dontDisturb = !AstalNotifd.get_default().dontDisturb),
+ className: bind(AstalNotifd.get_default(), "dontDisturb").as(d => (d ? "enabled" : "")),
+ },
+ {
+ label: "Clear",
+ onClicked: () => AstalNotifd.get_default().notifications.forEach(n => n.dismiss()),
+ },
+ ]}
+ emptyIcon="notifications_active"
+ emptyLabel="All caught up!"
+ list={<List />}
+ />
+);
diff --git a/src/modules/popdowns/updates.tsx b/src/modules/popdowns/updates.tsx
new file mode 100644
index 0000000..359fe81
--- /dev/null
+++ b/src/modules/popdowns/updates.tsx
@@ -0,0 +1,88 @@
+import { bind, execAsync, Variable } from "astal";
+import { App, Astal, Gtk } from "astal/gtk3";
+import Updates, { Repo as IRepo, Update as IUpdate } from "../../services/updates";
+import { MenuItem } from "../../utils/widgets";
+import PopdownWindow from "../../widgets/popdownwindow";
+
+const constructItem = (label: string, exec: string, quiet = true) =>
+ new MenuItem({
+ label,
+ onActivate() {
+ App.get_window("updates")?.hide();
+ execAsync(exec).catch(e => !quiet && console.error(e));
+ },
+ });
+
+const Update = (update: IUpdate) => {
+ const menu = new Gtk.Menu();
+ menu.append(constructItem("Open info in browser", `xdg-open '${update.url}'`, false));
+ menu.append(constructItem("Open info in terminal", `uwsm app -- foot -H pacman -Qi ${update.name}`));
+ menu.append(constructItem("Reinstall", `uwsm app -T -- yay -S ${update.name}`));
+ menu.append(constructItem("Remove with dependencies", `uwsm app -T -- yay -Rns ${update.name}`));
+
+ return (
+ <button
+ onClick={(_, event) => event.button === Astal.MouseButton.SECONDARY && menu.popup_at_pointer(null)}
+ onDestroy={() => menu.destroy()}
+ >
+ <label
+ truncate
+ xalign={0}
+ label={`${update.name} (${update.version.old} -> ${update.version.new})\n ${update.description}`}
+ />
+ </button>
+ );
+};
+
+const Repo = (repo: IRepo) => {
+ const expanded = Variable(false);
+
+ return (
+ <box vertical className="repo">
+ <button className="wrapper" cursor="pointer" onClicked={() => expanded.set(!expanded.get())}>
+ <box className="header">
+ <label className="icon" label={repo.icon} />
+ <label label={`${repo.name} (${repo.updates.length})`} />
+ <box hexpand />
+ <label className="icon" label={bind(expanded).as(e => (e ? "expand_less" : "expand_more"))} />
+ </box>
+ </button>
+ <revealer
+ revealChild={bind(expanded)}
+ transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
+ transitionDuration={200}
+ >
+ <box vertical className="list">
+ {repo.updates.map(Update)}
+ </box>
+ </revealer>
+ </box>
+ );
+};
+
+const List = () => (
+ <box vertical valign={Gtk.Align.START} className="repos">
+ {bind(Updates.get_default(), "updateData").as(d => d.repos.map(Repo))}
+ </box>
+);
+
+export default () => (
+ <PopdownWindow
+ name="updates"
+ count={bind(Updates.get_default(), "numUpdates")}
+ headerButtons={[
+ {
+ label: "Update all",
+ onClicked: () =>
+ execAsync("uwsm app -T -- yay")
+ .then(() => Updates.get_default().getUpdates())
+ // Ignore errors
+ .catch(() => {}),
+ },
+ { label: "Reload", onClicked: () => Updates.get_default().getUpdates() },
+ ]}
+ emptyIcon="deployed_code_history"
+ emptyLabel="All packages up to date!"
+ list={<List />}
+ />
+);