sidebar: notifpane + scroll switch panes

Also placeholder for empty lists (notifs and events)
To switch panes, primary click + scroll
This commit is contained in:
2 * r + 2 * t 2025-03-26 13:03:20 +11:00
parent c336bd184e
commit 4ae1651720
7 changed files with 94 additions and 25 deletions

View file

@ -73,6 +73,15 @@
}
}
.empty {
color: scheme.$subtext0;
font-size: lib.s(18);
.icon {
font-size: lib.s(48);
}
}
.user {
@include lib.spacing(15);

View file

@ -1,3 +1,4 @@
import type SideBar from "@/modules/sidebar";
import type { Monitor } from "@/services/monitors";
import Players from "@/services/players";
import Updates from "@/services/updates";
@ -72,10 +73,19 @@ const togglePopup = (self: JSX.Element, event: Astal.ClickEvent, name: string) =
}
};
const switchPane = (name: string) => {
const sidebar = App.get_window("sidebar") as SideBar | null;
if (sidebar) {
if (sidebar.visible && sidebar.shown.get() === name) sidebar.hide();
else sidebar.show();
sidebar.shown.set(name);
}
};
const OSIcon = () => (
<button
className="module os-icon"
onClick={(self, event) => event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "sideleft")}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("dashboard")}
>
{osIcon}
</button>
@ -446,9 +456,7 @@ const PkgUpdates = () => (
const NotifCount = () => (
<button
onClick={(self, event) =>
event.button === Astal.MouseButton.PRIMARY && togglePopup(self, event, "notifications")
}
onClick={(_, event) => event.button === Astal.MouseButton.PRIMARY && switchPane("notifpane")}
setup={self =>
setupCustomTooltip(
self,

View file

@ -127,7 +127,7 @@ export default () => (
<Media player={p} />
))}
<box className="separator" />
<Notifications />
<Notifications compact />
<box className="separator" />
<Upcoming />
</box>

View file

@ -1,7 +1,8 @@
import type { Monitor } from "@/services/monitors";
import { bind, register, Variable } from "astal";
import { App, Astal, Gtk, Widget } from "astal/gtk3";
import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import Dashboard from "./dashboard";
import NotifPane from "./notifpane";
@register()
export default class SideBar extends Widget.Window {
@ -18,17 +19,29 @@ export default class SideBar extends Widget.Window {
// visible: false,
});
const panes = [<Dashboard />, <NotifPane />];
this.add(
<box vertical className="sidebar">
<stack
vexpand
transitionType={Gtk.StackTransitionType.SLIDE_UP_DOWN}
transitionDuration={200}
shown={bind(this.shown)}
>
<Dashboard />
</stack>
</box>
<eventbox
onScroll={(_, event) => {
if (event.modifier & Gdk.ModifierType.BUTTON1_MASK) {
const index = panes.findIndex(p => p.name === this.shown.get()) + (event.delta_y < 0 ? -1 : 1);
if (index < 0 || index >= panes.length) return;
this.shown.set(panes[index].name);
}
}}
>
<box vertical className="sidebar">
<stack
vexpand
transitionType={Gtk.StackTransitionType.SLIDE_UP_DOWN}
transitionDuration={200}
shown={bind(this.shown)}
>
{panes}
</stack>
</box>
</eventbox>
);
}
}

View file

@ -3,7 +3,7 @@ import { bind } from "astal";
import { Astal, Gtk } from "astal/gtk3";
import AstalNotifd from "gi://AstalNotifd";
const List = () => (
const List = ({ compact }: { compact?: boolean }) => (
<box
vertical
valign={Gtk.Align.START}
@ -13,7 +13,7 @@ const List = () => (
const map = new Map<number, Notification>();
const addNotification = (notification: AstalNotifd.Notification) => {
const notif = (<Notification notification={notification} compact />) as Notification;
const notif = (<Notification notification={notification} compact={compact} />) as Notification;
notif.connect("destroy", () => map.get(notification.id) === notif && map.delete(notification.id));
map.get(notification.id)?.destroyWithAnims();
map.set(notification.id, notif);
@ -42,7 +42,16 @@ const List = () => (
/>
);
export default () => (
const NoNotifs = () => (
<box homogeneous name="empty">
<box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="empty">
<label className="icon" label="mark_email_unread" />
<label label="All caught up!" />
</box>
</box>
);
export default ({ compact }: { compact?: boolean }) => (
<box vertical className="notifications">
<box className="header-bar">
<label
@ -63,8 +72,15 @@ export default () => (
label="󰎟 Clear"
/>
</box>
<scrollable expand hscroll={Gtk.PolicyType.NEVER}>
<List />
</scrollable>
<stack
transitionType={Gtk.StackTransitionType.CROSSFADE}
transitionDuration={200}
shown={bind(AstalNotifd.get_default(), "notifications").as(n => (n.length > 0 ? "list" : "empty"))}
>
<NoNotifs />
<scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list">
<List compact={compact} />
</scrollable>
</stack>
</box>
);

View file

@ -61,6 +61,15 @@ const List = () => (
</box>
);
const NoEvents = () => (
<box homogeneous name="empty">
<box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="empty">
<label className="icon" label="calendar_month" />
<label label="No upcoming events" />
</box>
</box>
);
export default () => (
<box vertical className="upcoming">
<box className="header-bar">
@ -76,8 +85,15 @@ export default () => (
label="󰑓 Reload"
/>
</box>
<scrollable className="list" hscroll={Gtk.PolicyType.NEVER}>
<List />
</scrollable>
<stack
transitionType={Gtk.StackTransitionType.CROSSFADE}
transitionDuration={200}
shown={bind(Calendar.get_default(), "numUpcoming").as(n => (n > 0 ? "list" : "empty"))}
>
<NoEvents />
<scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list">
<List />
</scrollable>
</stack>
</box>
);

View file

@ -0,0 +1,7 @@
import Notifications from "./modules/notifications";
export default () => (
<box vertical className="pane notifpane" name="notifpane">
<Notifications />
</box>
);