sidebar: create upcoming module
Requires ical.js and curl
This commit is contained in:
parent
dd5b749ba1
commit
e8a40f31c9
11 changed files with 342 additions and 63 deletions
2
app.tsx
2
app.tsx
|
|
@ -5,6 +5,7 @@ import Osds from "@/modules/osds";
|
||||||
import Popdowns from "@/modules/popdowns";
|
import Popdowns from "@/modules/popdowns";
|
||||||
import Session from "@/modules/session";
|
import Session from "@/modules/session";
|
||||||
import SideBar from "@/modules/sidebar";
|
import SideBar from "@/modules/sidebar";
|
||||||
|
import Calendar from "@/services/calendar";
|
||||||
import Monitors from "@/services/monitors";
|
import Monitors from "@/services/monitors";
|
||||||
import Palette from "@/services/palette";
|
import Palette from "@/services/palette";
|
||||||
import Players from "@/services/players";
|
import Players from "@/services/players";
|
||||||
|
|
@ -82,6 +83,7 @@ App.start({
|
||||||
timeout(1000, () => {
|
timeout(1000, () => {
|
||||||
idle(() => Schemes.get_default());
|
idle(() => Schemes.get_default());
|
||||||
idle(() => Wallpapers.get_default());
|
idle(() => Wallpapers.get_default());
|
||||||
|
idle(() => Calendar.get_default());
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Caelestia started in ${Date.now() - now}ms`);
|
console.log(`Caelestia started in ${Date.now() - now}ms`);
|
||||||
|
|
|
||||||
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -6,6 +6,7 @@
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fuzzysort": "^3.1.0",
|
"fuzzysort": "^3.1.0",
|
||||||
|
"ical.js": "^2.1.0",
|
||||||
"mathjs": "^14.0.1"
|
"mathjs": "^14.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -534,6 +535,12 @@
|
||||||
"integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
|
"integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ical.js": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ical.js/-/ical.js-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-BOVfrH55xQ6kpS3muGvIXIg2l7p+eoe12/oS7R5yrO3TL/j/bLsR0PR+tYQESFbyTbvGgPHn9zQ6tI4FWyuSaQ==",
|
||||||
|
"license": "MPL-2.0"
|
||||||
|
},
|
||||||
"node_modules/javascript-natural-sort": {
|
"node_modules/javascript-natural-sort": {
|
||||||
"version": "0.7.1",
|
"version": "0.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fuzzysort": "^3.1.0",
|
"fuzzysort": "^3.1.0",
|
||||||
|
"ical.js": "^2.1.0",
|
||||||
"mathjs": "^14.0.1"
|
"mathjs": "^14.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,49 @@
|
||||||
margin: 0 lib.s(10);
|
margin: 0 lib.s(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-bar {
|
||||||
|
margin-bottom: lib.s(10);
|
||||||
|
|
||||||
|
@include lib.spacing;
|
||||||
|
|
||||||
|
& > :not(button) {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: lib.s(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
@include lib.element-decel;
|
||||||
|
@include lib.rounded(10);
|
||||||
|
|
||||||
|
padding: lib.s(3) lib.s(8);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: scheme.$overlay2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.enabled {
|
||||||
|
$-base: color.change(scheme.$base, $alpha: 1);
|
||||||
|
|
||||||
|
background-color: scheme.$primary;
|
||||||
|
color: $-base;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: color.mix(scheme.$primary, $-base, 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: color.mix(scheme.$primary, $-base, 70%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
@include lib.spacing(15);
|
@include lib.spacing(15);
|
||||||
|
|
||||||
|
|
@ -79,6 +122,7 @@
|
||||||
font-size: lib.s(64);
|
font-size: lib.s(64);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: scheme.$base;
|
background-color: scheme.$base;
|
||||||
|
color: scheme.$subtext0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
|
|
@ -133,86 +177,98 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications {
|
.notification {
|
||||||
.header-bar {
|
.wrapper {
|
||||||
margin-bottom: lib.s(10);
|
padding-bottom: lib.s(10);
|
||||||
margin-right: lib.s(-10);
|
}
|
||||||
|
|
||||||
@include lib.spacing;
|
.inner {
|
||||||
|
@include lib.rounded(20);
|
||||||
|
|
||||||
& > button {
|
background-color: color.change(scheme.$surface1, $alpha: 0.4);
|
||||||
@include lib.element-decel;
|
|
||||||
@include lib.rounded(10);
|
|
||||||
|
|
||||||
padding: lib.s(3) lib.s(8);
|
&.low {
|
||||||
|
@include notification(if(scheme.$light, scheme.$surface1, scheme.$overlay0));
|
||||||
|
}
|
||||||
|
|
||||||
&:hover,
|
&.normal {
|
||||||
&:focus {
|
@include lib.border(scheme.$primary, if(scheme.$light, 0.5, 0.3));
|
||||||
color: scheme.$subtext0;
|
@include notification(scheme.$primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&.critical {
|
||||||
color: scheme.$overlay2;
|
@include lib.border(scheme.$error, 0.8);
|
||||||
}
|
@include notification(scheme.$error);
|
||||||
|
|
||||||
&.enabled {
|
|
||||||
background-color: scheme.$primary;
|
|
||||||
color: scheme.$base;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
background-color: color.mix(scheme.$primary, scheme.$base, 80%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: color.mix(scheme.$primary, scheme.$base, 70%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification {
|
.actions {
|
||||||
.wrapper {
|
@include lib.spacing;
|
||||||
padding-bottom: lib.s(10);
|
|
||||||
|
& > button {
|
||||||
|
@include lib.rounded(10);
|
||||||
|
@include lib.element-decel;
|
||||||
|
|
||||||
|
padding: lib.s(5) lib.s(10);
|
||||||
|
background-color: color.change(scheme.$surface1, $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: color.change(scheme.$surface2, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: color.change(scheme.$overlay0, $alpha: 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.upcoming {
|
||||||
|
.list {
|
||||||
|
min-height: lib.s(300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
@include lib.spacing($vertical: true);
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: lib.s(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.date {
|
||||||
|
margin-left: lib.s(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sublabel {
|
||||||
|
font-size: lib.s(14);
|
||||||
|
color: scheme.$subtext0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.events {
|
||||||
@include lib.rounded(20);
|
@include lib.rounded(20);
|
||||||
|
|
||||||
background-color: color.change(scheme.$surface1, $alpha: 0.4);
|
background-color: color.change(scheme.$surface1, $alpha: 0.4);
|
||||||
|
padding: lib.s(10) lib.s(15);
|
||||||
|
|
||||||
&.low {
|
@include lib.spacing(10, true);
|
||||||
@include notification(if(scheme.$light, scheme.$surface1, scheme.$overlay0));
|
|
||||||
}
|
|
||||||
|
|
||||||
&.normal {
|
|
||||||
@include lib.border(scheme.$primary, if(scheme.$light, 0.5, 0.3));
|
|
||||||
@include notification(scheme.$primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.critical {
|
|
||||||
@include lib.border(scheme.$error, 0.8);
|
|
||||||
@include notification(scheme.$error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.event {
|
||||||
@include lib.spacing;
|
@include lib.spacing(8);
|
||||||
|
}
|
||||||
|
|
||||||
& > button {
|
.calendar-indicator {
|
||||||
@include lib.rounded(10);
|
@include lib.rounded(5);
|
||||||
@include lib.element-decel;
|
|
||||||
|
|
||||||
padding: lib.s(5) lib.s(10);
|
min-width: lib.s(1);
|
||||||
background-color: color.change(scheme.$surface1, $alpha: 0.5);
|
|
||||||
|
|
||||||
&:hover,
|
$-colours: scheme.$red, scheme.$sapphire, scheme.$flamingo, scheme.$maroon, scheme.$pink, scheme.$sky,
|
||||||
&:focus {
|
scheme.$peach, scheme.$yellow, scheme.$green, scheme.$rosewater, scheme.$mauve, scheme.$teal,
|
||||||
background-color: color.change(scheme.$surface2, $alpha: 0.5);
|
scheme.$blue;
|
||||||
}
|
@for $i from 1 through length($-colours) {
|
||||||
|
&.c#{$i} {
|
||||||
&:active {
|
background-color: nth($-colours, $i);
|
||||||
background-color: color.change(scheme.$overlay0, $alpha: 0.5);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,4 +143,8 @@ export default {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
calendar: {
|
||||||
|
webcals: [] as string[], // An array of urls to ICS files which you can curl
|
||||||
|
upcomingDays: 7, // Number of days which count as upcoming
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,6 @@ export const {
|
||||||
memory,
|
memory,
|
||||||
storage,
|
storage,
|
||||||
wallpapers,
|
wallpapers,
|
||||||
|
calendar,
|
||||||
} = config;
|
} = config;
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||
|
|
@ -68,4 +68,6 @@ export default {
|
||||||
"memory.interval": NUM,
|
"memory.interval": NUM,
|
||||||
"storage.interval": NUM,
|
"storage.interval": NUM,
|
||||||
"wallpapers.paths": OBJ_ARR({ recursive: BOOL, path: STR }),
|
"wallpapers.paths": OBJ_ARR({ recursive: BOOL, path: STR }),
|
||||||
|
"calendar.webcals": ARR(STR),
|
||||||
|
"calendar.upcomingDays": NUM,
|
||||||
} as { [k: string]: string | string[] | number[] };
|
} as { [k: string]: string | string[] | number[] };
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { bind, GLib, monitorFile, Variable } from "astal";
|
||||||
import { Gtk } from "astal/gtk3";
|
import { Gtk } from "astal/gtk3";
|
||||||
import AstalMpris from "gi://AstalMpris";
|
import AstalMpris from "gi://AstalMpris";
|
||||||
import Notifications from "./modules/notifications";
|
import Notifications from "./modules/notifications";
|
||||||
|
import Upcoming from "./modules/upcoming";
|
||||||
|
|
||||||
const lengthStr = (length: number) =>
|
const lengthStr = (length: number) =>
|
||||||
`${Math.floor(length / 60)}:${Math.floor(length % 60)
|
`${Math.floor(length / 60)}:${Math.floor(length % 60)
|
||||||
|
|
@ -127,5 +128,7 @@ export default () => (
|
||||||
))}
|
))}
|
||||||
<box className="separator" />
|
<box className="separator" />
|
||||||
<Notifications />
|
<Notifications />
|
||||||
|
<box className="separator" />
|
||||||
|
<Upcoming />
|
||||||
</box>
|
</box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
83
src/modules/sidebar/modules/upcoming.tsx
Normal file
83
src/modules/sidebar/modules/upcoming.tsx
Normal file
|
|
@ -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>
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import { execAsync, GObject, property, register } from "astal";
|
||||||
|
import { calendar as config } from "config";
|
||||||
|
import ical from "ical.js";
|
||||||
|
|
||||||
|
export interface IEvent {
|
||||||
|
calendar: string;
|
||||||
|
event: ical.Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@register({ GTypeName: "Calendar" })
|
||||||
|
export default class Calendar extends GObject.Object {
|
||||||
|
static instance: Calendar;
|
||||||
|
static get_default() {
|
||||||
|
if (!this.instance) this.instance = new Calendar();
|
||||||
|
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calCount = 1;
|
||||||
|
#loading = false;
|
||||||
|
#calendars: { [name: string]: ical.Component } = {};
|
||||||
|
#upcoming: { [date: string]: IEvent[] } = {};
|
||||||
|
|
||||||
|
@property(Boolean)
|
||||||
|
get loading() {
|
||||||
|
return this.#loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Object)
|
||||||
|
get calendars() {
|
||||||
|
return this.#calendars;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Object)
|
||||||
|
get upcoming() {
|
||||||
|
return this.#upcoming;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(Number)
|
||||||
|
get numUpcoming() {
|
||||||
|
return Object.values(this.#upcoming).reduce((acc, e) => acc + e.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCalendarIndex(name: string) {
|
||||||
|
return Object.keys(this.#calendars).indexOf(name) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCalendars() {
|
||||||
|
this.#loading = true;
|
||||||
|
this.notify("loading");
|
||||||
|
|
||||||
|
this.#calendars = {};
|
||||||
|
this.#calCount = 1;
|
||||||
|
|
||||||
|
const cals = await Promise.allSettled(config.webcals.get().map(c => execAsync(["curl", c])));
|
||||||
|
for (const cal of cals) {
|
||||||
|
if (cal.status === "fulfilled") {
|
||||||
|
const comp = new ical.Component(ical.parse(cal.value));
|
||||||
|
const name = (comp.getFirstPropertyValue("x-wr-calname") ?? `Calendar ${this.#calCount++}`) as string;
|
||||||
|
this.#calendars[name] = comp;
|
||||||
|
} else console.error(`Failed to get calendar: ${cal.reason}`);
|
||||||
|
}
|
||||||
|
this.notify("calendars");
|
||||||
|
|
||||||
|
this.updateUpcoming();
|
||||||
|
|
||||||
|
this.#loading = false;
|
||||||
|
this.notify("loading");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUpcoming() {
|
||||||
|
this.#upcoming = {};
|
||||||
|
|
||||||
|
for (const [name, cal] of Object.entries(this.#calendars)) {
|
||||||
|
const today = ical.Time.now();
|
||||||
|
const upcoming = ical.Time.now().adjust(config.upcomingDays.get(), 0, 0, 0);
|
||||||
|
|
||||||
|
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] = [];
|
||||||
|
this.#upcoming[date].push({ calendar: name, event });
|
||||||
|
}
|
||||||
|
} 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 (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");
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.updateCalendars().catch(console.error);
|
||||||
|
config.webcals.subscribe(() => this.updateCalendars().catch(console.error));
|
||||||
|
config.upcomingDays.subscribe(() => this.updateUpcoming());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,11 @@ import { Astal, astalify, Gtk, Widget, type ConstructProps } from "astal/gtk3";
|
||||||
import AstalHyprland from "gi://AstalHyprland";
|
import AstalHyprland from "gi://AstalHyprland";
|
||||||
import type { AstalWidget } from "./types";
|
import type { AstalWidget } from "./types";
|
||||||
|
|
||||||
export const setupCustomTooltip = (self: AstalWidget, text: string | Binding<string>) => {
|
export const setupCustomTooltip = (
|
||||||
|
self: AstalWidget,
|
||||||
|
text: string | Binding<string>,
|
||||||
|
labelProps: Widget.LabelProps = {}
|
||||||
|
) => {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
|
|
||||||
self.set_has_tooltip(true);
|
self.set_has_tooltip(true);
|
||||||
|
|
@ -15,7 +19,7 @@ export const setupCustomTooltip = (self: AstalWidget, text: string | Binding<str
|
||||||
keymode: Astal.Keymode.NONE,
|
keymode: Astal.Keymode.NONE,
|
||||||
exclusivity: Astal.Exclusivity.IGNORE,
|
exclusivity: Astal.Exclusivity.IGNORE,
|
||||||
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
|
anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT,
|
||||||
child: new Widget.Label({ className: "tooltip", label: text }),
|
child: new Widget.Label({ ...labelProps, className: "tooltip", label: text }),
|
||||||
});
|
});
|
||||||
self.set_tooltip_window(window);
|
self.set_tooltip_window(window);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue