sidebar: time pane
Also some fixes for calendar recurring events Also fix reminders time
This commit is contained in:
parent
4d9346e580
commit
0216fd3dd2
6 changed files with 446 additions and 67 deletions
|
|
@ -1,4 +1,5 @@
|
|||
@use "sass:color";
|
||||
@use "sass:list";
|
||||
@use "scheme";
|
||||
@use "lib";
|
||||
@use "font";
|
||||
|
|
@ -315,9 +316,9 @@
|
|||
$-colours: scheme.$red, scheme.$sapphire, scheme.$flamingo, scheme.$maroon, scheme.$pink, scheme.$sky,
|
||||
scheme.$peach, scheme.$yellow, scheme.$green, scheme.$rosewater, scheme.$mauve, scheme.$teal,
|
||||
scheme.$blue;
|
||||
@for $i from 1 through length($-colours) {
|
||||
&.c#{$i} {
|
||||
background-color: nth($-colours, $i);
|
||||
@for $i from 1 through list.length($-colours) {
|
||||
&.calendar-#{$i} {
|
||||
background-color: list.nth($-colours, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -790,4 +791,166 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.calendar {
|
||||
@include lib.rounded(20);
|
||||
|
||||
background-color: color.change(scheme.$surface1, $alpha: 0.4);
|
||||
padding: lib.s(15);
|
||||
|
||||
.calendar-view {
|
||||
@include lib.spacing(10, true);
|
||||
|
||||
.header {
|
||||
@include lib.spacing(10);
|
||||
|
||||
& > button {
|
||||
@include lib.rounded(1000);
|
||||
@include lib.element-decel;
|
||||
|
||||
background-color: color.change(scheme.$surface2, $alpha: 0.4);
|
||||
min-width: lib.s(28);
|
||||
min-height: lib.s(28);
|
||||
font-size: lib.s(18);
|
||||
|
||||
&:first-child {
|
||||
padding: 0 lib.s(10);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: color.change(scheme.$surface2, $alpha: 0.6);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: color.change(scheme.$surface2, $alpha: 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.weekdays {
|
||||
@include lib.spacing(10);
|
||||
|
||||
& > label {
|
||||
min-width: lib.s(40);
|
||||
font-weight: bold;
|
||||
color: scheme.$subtext1;
|
||||
}
|
||||
}
|
||||
|
||||
.month {
|
||||
@include lib.spacing(10, true);
|
||||
}
|
||||
|
||||
.week {
|
||||
@include lib.spacing(10);
|
||||
}
|
||||
|
||||
.day {
|
||||
@include lib.rounded(1000);
|
||||
@include lib.element-decel;
|
||||
|
||||
min-width: lib.s(40);
|
||||
min-height: lib.s(40);
|
||||
|
||||
&.dim {
|
||||
color: scheme.$subtext0;
|
||||
}
|
||||
|
||||
&.today:not(.dim) {
|
||||
background-color: scheme.$primary;
|
||||
color: color.change(scheme.$base, $alpha: 1);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: scheme.$subtext0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: color.change(scheme.$overlay2, $alpha: 1);
|
||||
}
|
||||
|
||||
&.dim {
|
||||
color: scheme.$subtext0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: color.change(scheme.$overlay2, $alpha: 1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: color.change(scheme.$overlay1, $alpha: 1);
|
||||
}
|
||||
}
|
||||
|
||||
&.today:not(.dim) {
|
||||
background-color: scheme.$primary;
|
||||
color: color.change(scheme.$base, $alpha: 1);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: color.mix(scheme.$primary, scheme.$base, 80%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: color.mix(scheme.$primary, scheme.$base, 70%);
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
margin-top: lib.s(8);
|
||||
}
|
||||
|
||||
.indicator {
|
||||
@include lib.rounded(10);
|
||||
@include lib.element-decel;
|
||||
|
||||
min-height: lib.s(3);
|
||||
margin: 0 lib.s(8);
|
||||
}
|
||||
|
||||
$-max: 5;
|
||||
@for $i from 1 through $-max {
|
||||
&.events-#{$i} {
|
||||
$-colour: color.mix(scheme.$red, scheme.$green, calc(100% / $-max) * $i);
|
||||
|
||||
.indicator {
|
||||
background-color: $-colour;
|
||||
}
|
||||
|
||||
&:hover .indicator,
|
||||
&:focus .indicator {
|
||||
background-color: color.mix($-colour, scheme.$base, 80%);
|
||||
}
|
||||
|
||||
&:active .indicator {
|
||||
background-color: color.mix($-colour, scheme.$base, 70%);
|
||||
}
|
||||
|
||||
&.dim .indicator {
|
||||
background-color: color.mix($-colour, scheme.$base, 60%);
|
||||
}
|
||||
|
||||
&.today:not(.dim) {
|
||||
$-colour: color.mix($-colour, color.complement(scheme.$primary), 50%);
|
||||
|
||||
.indicator {
|
||||
background-color: $-colour;
|
||||
}
|
||||
|
||||
&:hover .indicator,
|
||||
&:focus .indicator {
|
||||
background-color: color.mix($-colour, scheme.$base, 80%);
|
||||
}
|
||||
|
||||
&:active .indicator {
|
||||
background-color: color.mix($-colour, scheme.$base, 70%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Connectivity from "./connectivity";
|
|||
import Dashboard from "./dashboard";
|
||||
import NotifPane from "./notifpane";
|
||||
import Packages from "./packages";
|
||||
import Time from "./time";
|
||||
|
||||
@register()
|
||||
export default class SideBar extends Widget.Window {
|
||||
|
|
@ -23,7 +24,7 @@ export default class SideBar extends Widget.Window {
|
|||
visible: false,
|
||||
});
|
||||
|
||||
const panes = [<Dashboard />, <Audio />, <Connectivity />, <Packages />, <NotifPane />];
|
||||
const panes = [<Dashboard />, <Audio />, <Connectivity />, <Packages />, <NotifPane />, <Time />];
|
||||
this.shown = Variable(panes[0].name);
|
||||
|
||||
this.add(
|
||||
|
|
|
|||
186
src/modules/sidebar/modules/calendar.tsx
Normal file
186
src/modules/sidebar/modules/calendar.tsx
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import Calendar from "@/services/calendar";
|
||||
import { setupCustomTooltip } from "@/utils/widgets";
|
||||
import { bind, GLib, Variable } from "astal";
|
||||
import { Gtk } from "astal/gtk3";
|
||||
import ical from "ical.js";
|
||||
|
||||
const isLeapYear = (year: number) => year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
|
||||
|
||||
const getMonthDays = (month: number, year: number) => {
|
||||
const leapYear = isLeapYear(year);
|
||||
if (month === 2 && leapYear) return leapYear ? 29 : 28;
|
||||
if ((month <= 7 && month % 2 === 1) || (month >= 8 && month % 2 === 0)) return 31;
|
||||
return 30;
|
||||
};
|
||||
|
||||
const getNextMonthDays = (month: number, year: number) => {
|
||||
if (month === 12) return 31;
|
||||
return getMonthDays(month + 1, year);
|
||||
};
|
||||
|
||||
const getPrevMonthDays = (month: number, year: number) => {
|
||||
if (month === 1) return 31;
|
||||
return getMonthDays(month - 1, year);
|
||||
};
|
||||
|
||||
export function getCalendarLayout(date: ical.Time) {
|
||||
const weekdayOfMonthFirst = date.startOfMonth().dayOfWeek(ical.Time.MONDAY);
|
||||
const daysInMonth = getMonthDays(date.month, date.year);
|
||||
const daysInPrevMonth = getPrevMonthDays(date.month, date.year);
|
||||
|
||||
const calendar: ical.Time[][] = [];
|
||||
let idx = -weekdayOfMonthFirst + 2;
|
||||
|
||||
for (let i = 0; i < 6; i++) {
|
||||
calendar.push([]);
|
||||
|
||||
for (let j = 0; j < 7; j++) {
|
||||
let cDay = idx++;
|
||||
let cMonth = date.month;
|
||||
let cYear = date.year;
|
||||
|
||||
if (idx < 0) {
|
||||
cDay = daysInPrevMonth + cDay;
|
||||
cMonth--;
|
||||
|
||||
if (cMonth < 0) {
|
||||
cMonth += 12;
|
||||
cYear--;
|
||||
}
|
||||
} else if (idx > daysInMonth) {
|
||||
cDay -= daysInMonth;
|
||||
cMonth++;
|
||||
|
||||
if (cMonth > 12) {
|
||||
cMonth -= 12;
|
||||
cYear++;
|
||||
}
|
||||
}
|
||||
|
||||
calendar[i].push(ical.Time.fromData({ day: cDay, month: cMonth, year: cYear }));
|
||||
}
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
const dateToMonthYear = (date: ical.Time) => {
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
];
|
||||
return `${months[date.month - 1]} ${date.year}`;
|
||||
};
|
||||
|
||||
const addMonths = (date: ical.Time, num: number) => {
|
||||
date = date.clone();
|
||||
if (num > 0) for (let i = 0; i < num; i++) date.adjust(getNextMonthDays(date.month, date.year), 0, 0, 0);
|
||||
else for (let i = 0; i > num; i--) date.adjust(-getPrevMonthDays(date.month, date.year), 0, 0, 0);
|
||||
return date;
|
||||
};
|
||||
|
||||
const getDayClassName = (day: ical.Time, current: Variable<ical.Time>) => {
|
||||
const isToday = day.toJSDate().toDateString() === new Date().toDateString() ? "today" : "";
|
||||
const numEvents = Math.min(5, Calendar.get_default().getEventsForDay(day).length);
|
||||
return `day ${isToday} ${day.month !== current.get().month ? "dim" : ""} events-${numEvents}`;
|
||||
};
|
||||
|
||||
const getDayTooltip = (day: ical.Time) => {
|
||||
const events = Calendar.get_default().getEventsForDay(day);
|
||||
if (!events.length) return "";
|
||||
const eventsStr = events
|
||||
.map(e => {
|
||||
const start = GLib.DateTime.new_from_unix_local(e.startDate.toUnixTime());
|
||||
const end = GLib.DateTime.new_from_unix_local(e.endDate.toUnixTime());
|
||||
const sameAmPm = start.format("%P") === end.format("%P");
|
||||
const time = `${start.format(`%-I:%M${sameAmPm ? "" : "%P"}`)} — ${end.format("%-I:%M%P")}`;
|
||||
return `<b>${e.event.summary.replaceAll("&", "&")}</b> • ${time}`;
|
||||
})
|
||||
.join("\n");
|
||||
return `${events.length} event${events.length === 1 ? "" : "s"}\n${eventsStr}`;
|
||||
};
|
||||
|
||||
const Day = ({ day, shown, current }: { day: ical.Time; shown: Variable<string>; current: Variable<ical.Time> }) => (
|
||||
<button
|
||||
className={bind(Calendar.get_default(), "calendars").as(() => getDayClassName(day, current))}
|
||||
cursor="pointer"
|
||||
onClicked={() => {
|
||||
shown.set("events");
|
||||
current.set(day);
|
||||
}}
|
||||
setup={self =>
|
||||
setupCustomTooltip(
|
||||
self,
|
||||
bind(Calendar.get_default(), "calendars").as(() => getDayTooltip(day)),
|
||||
{ useMarkup: true }
|
||||
)
|
||||
}
|
||||
>
|
||||
<box vertical>
|
||||
<label label={day.day.toString()} />
|
||||
<box className="indicator" />
|
||||
</box>
|
||||
</button>
|
||||
);
|
||||
|
||||
const CalendarView = ({ shown, current }: { shown: Variable<string>; current: Variable<ical.Time> }) => (
|
||||
<box vertical className="calendar-view" name="calendar">
|
||||
<box className="header">
|
||||
<button
|
||||
cursor="pointer"
|
||||
onClicked={() => current.set(ical.Time.now())}
|
||||
label={bind(current).as(dateToMonthYear)}
|
||||
/>
|
||||
<box hexpand />
|
||||
<button cursor="pointer" onClicked={() => current.set(addMonths(current.get(), -1))} label="" />
|
||||
<button cursor="pointer" onClicked={() => current.set(addMonths(current.get(), 1))} label="" />
|
||||
</box>
|
||||
<box halign={Gtk.Align.CENTER} className="weekdays">
|
||||
{["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map(d => (
|
||||
<label label={d} />
|
||||
))}
|
||||
</box>
|
||||
<box vertical halign={Gtk.Align.CENTER} className="month">
|
||||
{bind(current).as(c =>
|
||||
getCalendarLayout(c).map(r => (
|
||||
<box className="week">
|
||||
{r.map(d => (
|
||||
<Day day={d} shown={shown} current={current} />
|
||||
))}
|
||||
</box>
|
||||
))
|
||||
)}
|
||||
</box>
|
||||
</box>
|
||||
);
|
||||
const Events = ({ shown, current }: { shown: Variable<string>; current: Variable<ical.Time> }) => (
|
||||
<box className="events" name="events"></box>
|
||||
);
|
||||
|
||||
export default () => {
|
||||
const shown = Variable("calendar");
|
||||
const current = Variable(ical.Time.now());
|
||||
|
||||
return (
|
||||
<box vertical className="calendar">
|
||||
<stack
|
||||
transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}
|
||||
transitionDuration={150}
|
||||
shown={bind(shown)}
|
||||
>
|
||||
<CalendarView shown={shown} current={current} />
|
||||
<Events shown={shown} current={current} />
|
||||
</stack>
|
||||
</box>
|
||||
);
|
||||
};
|
||||
|
|
@ -4,7 +4,7 @@ import { bind, GLib } from "astal";
|
|||
import { Gtk } from "astal/gtk3";
|
||||
|
||||
const getDateHeader = (events: IEvent[]) => {
|
||||
const date = events[0].event.startDate;
|
||||
const date = events[0].startDate;
|
||||
const isToday = date.toJSDate().toDateString() === new Date().toDateString();
|
||||
return (
|
||||
(isToday ? "Today • " : "") +
|
||||
|
|
@ -14,13 +14,13 @@ const getDateHeader = (events: IEvent[]) => {
|
|||
};
|
||||
|
||||
const getEventHeader = (e: IEvent) => {
|
||||
const start = GLib.DateTime.new_from_unix_local(e.event.startDate.toUnixTime());
|
||||
const start = GLib.DateTime.new_from_unix_local(e.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 start = GLib.DateTime.new_from_unix_local(e.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")}`;
|
||||
|
|
@ -31,7 +31,7 @@ const getEventTooltip = (e: IEvent) => {
|
|||
|
||||
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 className={`calendar-indicator calendar-${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" />}
|
||||
|
|
@ -55,7 +55,7 @@ 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))
|
||||
.sort((a, b) => a[0].startDate.compare(b[0].startDate))
|
||||
.map(e => <Day events={e} />)
|
||||
)}
|
||||
</box>
|
||||
|
|
|
|||
14
src/modules/sidebar/time.tsx
Normal file
14
src/modules/sidebar/time.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Calendar from "./modules/calendar";
|
||||
import Upcoming from "./modules/upcoming";
|
||||
|
||||
const TimeDate = () => <box></box>;
|
||||
|
||||
export default () => (
|
||||
<box vertical className="pane time" name="time">
|
||||
<TimeDate />
|
||||
<box className="separator" />
|
||||
<Upcoming />
|
||||
<box className="separator" />
|
||||
<Calendar />
|
||||
</box>
|
||||
);
|
||||
|
|
@ -17,6 +17,8 @@ import ical from "ical.js";
|
|||
export interface IEvent {
|
||||
calendar: string;
|
||||
event: ical.Event;
|
||||
startDate: ical.Time;
|
||||
endDate: ical.Time;
|
||||
}
|
||||
|
||||
@register({ GTypeName: "Calendar" })
|
||||
|
|
@ -35,6 +37,8 @@ export default class Calendar extends GObject.Object {
|
|||
#loading: boolean = false;
|
||||
#calendars: { [name: string]: ical.Component } = {};
|
||||
#upcoming: { [date: string]: IEvent[] } = {};
|
||||
#cachedEvents: { [date: string]: IEvent[] } = {};
|
||||
#cachedMonths: Set<string> = new Set();
|
||||
|
||||
@property(Boolean)
|
||||
get loading() {
|
||||
|
|
@ -60,6 +64,59 @@ export default class Calendar extends GObject.Object {
|
|||
return Object.keys(this.#calendars).indexOf(name) + 1;
|
||||
}
|
||||
|
||||
getEventsForMonth(date: ical.Time) {
|
||||
const start = date.startOfMonth();
|
||||
|
||||
if (this.#cachedMonths.has(start.toJSDate().toDateString())) return this.#cachedEvents;
|
||||
|
||||
this.#cachedMonths.add(start.toJSDate().toDateString());
|
||||
const end = date.endOfMonth();
|
||||
|
||||
const modDates = new Set<string>();
|
||||
|
||||
for (const [name, cal] of Object.entries(this.#calendars)) {
|
||||
for (const e of cal.getAllSubcomponents()) {
|
||||
const event = new ical.Event(e);
|
||||
|
||||
// Skip invalid events
|
||||
if (!event.startDate) continue;
|
||||
|
||||
if (event.isRecurring()) {
|
||||
// Recurring events
|
||||
const iter = event.iterator();
|
||||
for (let next = iter.next(); next && next.compare(end) <= 0; next = iter.next())
|
||||
if (next.compare(start) >= 0) {
|
||||
const date = next.toJSDate().toDateString();
|
||||
if (!this.#cachedEvents.hasOwnProperty(date)) this.#cachedEvents[date] = [];
|
||||
|
||||
const end = next.clone();
|
||||
end.addDuration(event.duration);
|
||||
this.#cachedEvents[date].push({ calendar: name, event, startDate: next, endDate: end });
|
||||
modDates.add(date);
|
||||
}
|
||||
} else if (event.startDate.compare(start) >= 0 && event.startDate.compare(end) <= 0) {
|
||||
const date = event.startDate.toJSDate().toDateString();
|
||||
if (!this.#cachedEvents.hasOwnProperty(date)) this.#cachedEvents[date] = [];
|
||||
this.#cachedEvents[date].push({
|
||||
calendar: name,
|
||||
event,
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate,
|
||||
});
|
||||
modDates.add(date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const date of modDates) this.#cachedEvents[date].sort((a, b) => a.startDate.compare(b.startDate));
|
||||
|
||||
return this.#cachedEvents;
|
||||
}
|
||||
|
||||
getEventsForDay(date: ical.Time) {
|
||||
return this.getEventsForMonth(date)[date.toJSDate().toDateString()] ?? [];
|
||||
}
|
||||
|
||||
async updateCalendars() {
|
||||
this.#loading = true;
|
||||
this.notify("loading");
|
||||
|
|
@ -87,6 +144,9 @@ export default class Calendar extends GObject.Object {
|
|||
writeFileAsync(`${this.#cacheDir}/${webcal}`, icalStr).catch(console.error);
|
||||
}
|
||||
}
|
||||
this.#cachedEvents = {};
|
||||
this.#cachedMonths.clear();
|
||||
|
||||
this.notify("calendars");
|
||||
|
||||
this.updateUpcoming();
|
||||
|
|
@ -98,61 +158,34 @@ export default class Calendar extends GObject.Object {
|
|||
updateUpcoming() {
|
||||
this.#upcoming = {};
|
||||
|
||||
const today = ical.Time.now();
|
||||
const upcoming = ical.Time.now().adjust(config.upcomingDays.get(), 0, 0, 0);
|
||||
for (const [name, cal] of Object.entries(this.#calendars)) {
|
||||
for (const e of cal.getAllSubcomponents()) {
|
||||
const event = new ical.Event(e);
|
||||
|
||||
// Skip invalid events
|
||||
if (!event.startDate) continue;
|
||||
|
||||
if (event.isRecurring()) {
|
||||
// Recurring events
|
||||
const iter = event.iterator();
|
||||
for (let next = iter.next(); next && next.compare(upcoming) <= 0; next = iter.next())
|
||||
if (next.compare(today) >= 0) {
|
||||
const date = next.toJSDate().toDateString();
|
||||
if (!this.#upcoming.hasOwnProperty(date)) this.#upcoming[date] = [];
|
||||
|
||||
const rEvent = new ical.Event(e);
|
||||
rEvent.startDate = next;
|
||||
this.#upcoming[date].push({ calendar: name, event: rEvent });
|
||||
}
|
||||
} else if (event.startDate.compare(today) >= 0 && event.startDate.compare(upcoming) <= 0) {
|
||||
// Add to upcoming if in upcoming range
|
||||
const date = event.startDate.toJSDate().toDateString();
|
||||
if (!this.#upcoming.hasOwnProperty(date)) this.#upcoming[date] = [];
|
||||
this.#upcoming[date].push({ calendar: name, event });
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < config.upcomingDays.get(); i++) {
|
||||
const date = ical.Time.now().adjust(i, 0, 0, 0);
|
||||
const events = this.getEventsForDay(date);
|
||||
if (events.length > 0) this.#upcoming[date.toJSDate().toDateString()] = events;
|
||||
}
|
||||
|
||||
for (const events of Object.values(this.#upcoming))
|
||||
events.sort((a, b) => a.event.startDate.compare(b.event.startDate));
|
||||
|
||||
this.notify("upcoming");
|
||||
this.notify("num-upcoming");
|
||||
|
||||
this.setReminders();
|
||||
}
|
||||
|
||||
#notifyEvent(event: ical.Event, calendar: string) {
|
||||
#notifyEvent(event: IEvent) {
|
||||
const start = GLib.DateTime.new_from_unix_local(event.startDate.toUnixTime());
|
||||
const end = GLib.DateTime.new_from_unix_local(event.endDate.toUnixTime());
|
||||
const time = `${start.format(`%A, %-d %B`)} • Now — ${end.format("%-I:%M%P")}`;
|
||||
const locIfExists = event.location ? ` ${event.location}\n` : "";
|
||||
const descIfExists = event.description ? ` ${event.description}\n` : "";
|
||||
const locIfExists = event.event.location ? ` ${event.event.location}\n` : "";
|
||||
const descIfExists = event.event.description ? ` ${event.event.description}\n` : "";
|
||||
|
||||
notify({
|
||||
summary: ` ${event.summary} `,
|
||||
body: `${time}\n${locIfExists}${descIfExists} ${calendar}`,
|
||||
summary: ` ${event.event.summary} `,
|
||||
body: `${time}\n${locIfExists}${descIfExists} ${event.calendar}`,
|
||||
}).catch(console.error);
|
||||
}
|
||||
|
||||
#createReminder(event: ical.Event, calendar: string, next: ical.Time) {
|
||||
const diff = next.toUnixTime() - ical.Time.now().toUnixTime();
|
||||
if (diff > 0) this.#reminders.push(timeout(diff * 1000, () => this.#notifyEvent(event, calendar)));
|
||||
#createReminder(event: IEvent) {
|
||||
const diff = event.startDate.toJSDate().getTime() - ical.Time.now().toJSDate().getTime();
|
||||
if (diff > 0) this.#reminders.push(timeout(diff, () => this.#notifyEvent(event)));
|
||||
}
|
||||
|
||||
setReminders() {
|
||||
|
|
@ -161,25 +194,7 @@ export default class Calendar extends GObject.Object {
|
|||
|
||||
if (!config.notify.get()) return;
|
||||
|
||||
const today = ical.Time.now();
|
||||
const upcoming = ical.Time.now().adjust(config.upcomingDays.get(), 0, 0, 0);
|
||||
for (const [name, cal] of Object.entries(this.#calendars)) {
|
||||
for (const e of cal.getAllSubcomponents()) {
|
||||
const event = new ical.Event(e);
|
||||
|
||||
// Skip invalid events
|
||||
if (!event.startDate) continue;
|
||||
|
||||
if (event.isRecurring()) {
|
||||
// Recurring events
|
||||
const iter = event.iterator();
|
||||
for (let next = iter.next(); next && next.compare(upcoming) <= 0; next = iter.next())
|
||||
if (next.compare(today) >= 0) this.#createReminder(event, name, next);
|
||||
} else if (event.startDate.compare(today) >= 0 && event.startDate.compare(upcoming) <= 0)
|
||||
// Create reminder if in upcoming range
|
||||
this.#createReminder(event, name, event.startDate);
|
||||
}
|
||||
}
|
||||
for (const events of Object.values(this.#upcoming)) for (const event of events) this.#createReminder(event);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue