summaryrefslogtreecommitdiff
path: root/src/modules
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-03-25 21:51:59 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-03-25 21:51:59 +1100
commite8a40f31c904baeaa1817cd3e418df5ce71302c1 (patch)
treede5b15228d2e7227560db91131006ee55af82049 /src/modules
parentsidebar: media handle no players (diff)
downloadcaelestia-shell-e8a40f31c904baeaa1817cd3e418df5ce71302c1.tar.gz
caelestia-shell-e8a40f31c904baeaa1817cd3e418df5ce71302c1.tar.bz2
caelestia-shell-e8a40f31c904baeaa1817cd3e418df5ce71302c1.zip
sidebar: create upcoming module
Requires ical.js and curl
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/sidebar/dashboard.tsx3
-rw-r--r--src/modules/sidebar/modules/upcoming.tsx83
2 files changed, 86 insertions, 0 deletions
diff --git a/src/modules/sidebar/dashboard.tsx b/src/modules/sidebar/dashboard.tsx
index 86921e6..936502b 100644
--- a/src/modules/sidebar/dashboard.tsx
+++ b/src/modules/sidebar/dashboard.tsx
@@ -5,6 +5,7 @@ import { bind, GLib, monitorFile, Variable } from "astal";
import { Gtk } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris";
import Notifications from "./modules/notifications";
+import Upcoming from "./modules/upcoming";
const lengthStr = (length: number) =>
`${Math.floor(length / 60)}:${Math.floor(length % 60)
@@ -127,5 +128,7 @@ export default () => (
))}
<box className="separator" />
<Notifications />
+ <box className="separator" />
+ <Upcoming />
</box>
);
diff --git a/src/modules/sidebar/modules/upcoming.tsx b/src/modules/sidebar/modules/upcoming.tsx
new file mode 100644
index 0000000..e2389e8
--- /dev/null
+++ b/src/modules/sidebar/modules/upcoming.tsx
@@ -0,0 +1,83 @@
+import Calendar, { type IEvent } from "@/services/calendar";
+import { setupCustomTooltip } from "@/utils/widgets";
+import { bind, GLib } from "astal";
+import { Gtk } from "astal/gtk3";
+
+const getDateHeader = (events: IEvent[]) => {
+ const date = events[0].event.startDate;
+ const isToday = date.toJSDate().toDateString() === new Date().toDateString();
+ return (
+ (isToday ? "Today • " : "") +
+ GLib.DateTime.new_from_unix_local(date.toUnixTime()).format("%B %-d • %A") +
+ ` • ${events.length} event${events.length === 1 ? "" : "s"}`
+ );
+};
+
+const getEventHeader = (e: IEvent) => {
+ const start = GLib.DateTime.new_from_unix_local(e.event.startDate.toUnixTime());
+ const time = `${start.format("%-I")}${start.get_minute() > 0 ? `:${start.get_minute()}` : ""}${start.format("%P")}`;
+ return `${time} <b>${e.event.summary}</b>`;
+};
+
+const getEventTooltip = (e: IEvent) => {
+ const start = GLib.DateTime.new_from_unix_local(e.event.startDate.toUnixTime());
+ const end = GLib.DateTime.new_from_unix_local(e.event.endDate.toUnixTime());
+ const sameAmPm = start.format("%P") === end.format("%P");
+ const time = `${start.format(`%A, %-d %B • %-I:%M${sameAmPm ? "" : "%P"}`)} — ${end.format("%-I:%M%P")}`;
+ const locIfExists = e.event.location ? ` ${e.event.location}\n` : "";
+ const descIfExists = e.event.description ? `󰒿 ${e.event.description}\n` : "";
+ return `<b>${e.event.summary}</b>\n${time}\n${locIfExists}${descIfExists}󰃭 ${e.calendar}`;
+};
+
+const Event = (event: IEvent) => (
+ <box className="event" setup={self => setupCustomTooltip(self, getEventTooltip(event), { useMarkup: true })}>
+ <box className={`calendar-indicator c${Calendar.get_default().getCalendarIndex(event.calendar)}`} />
+ <box vertical>
+ <label truncate useMarkup xalign={0} label={getEventHeader(event)} />
+ {event.event.location && <label truncate xalign={0} label={event.event.location} className="sublabel" />}
+ {event.event.description && (
+ <label truncate useMarkup xalign={0} label={event.event.description} className="sublabel" />
+ )}
+ </box>
+ </box>
+);
+
+const Day = ({ events }: { events: IEvent[] }) => (
+ <box vertical className="day">
+ <label className="date" xalign={0} label={getDateHeader(events)} />
+ <box vertical className="events">
+ {events.map(Event)}
+ </box>
+ </box>
+);
+
+const List = () => (
+ <box vertical valign={Gtk.Align.START}>
+ {bind(Calendar.get_default(), "upcoming").as(u =>
+ Object.values(u)
+ .sort((a, b) => a[0].event.startDate.compare(b[0].event.startDate))
+ .map(e => <Day events={e} />)
+ )}
+ </box>
+);
+
+export default () => (
+ <box vertical className="upcoming">
+ <box className="header-bar">
+ <label
+ label={bind(Calendar.get_default(), "numUpcoming").as(n => `${n} upcoming event${n === 1 ? "" : "s"}`)}
+ />
+ <box hexpand />
+ <button
+ className={bind(Calendar.get_default(), "loading").as(l => (l ? "enabled" : ""))}
+ sensitive={bind(Calendar.get_default(), "loading").as(l => !l)}
+ cursor="pointer"
+ onClicked={() => Calendar.get_default().updateCalendars().catch(console.error)}
+ label="󰑓 Reload"
+ />
+ </box>
+ <scrollable className="list" hscroll={Gtk.PolicyType.NEVER}>
+ <List />
+ </scrollable>
+ </box>
+);