Skip to content

Commit 1d9388d

Browse files
xfalcoxclaude
andcommitted
feat: add upcoming events sidebar widget with independent configs
Add scrollable upcoming events widget to sidebar (below notifications), default on. Dashboard upcoming events default off. Split upcomingHours into dashUpcomingHours (24h) and sidebarUpcomingHours (5 days) for independent control. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fd6dde7 commit 1d9388d

7 files changed

Lines changed: 133 additions & 10 deletions

File tree

config/Config.qml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ Singleton {
225225
mediaUpdateInterval: dashboard.mediaUpdateInterval,
226226
resourceUpdateInterval: dashboard.resourceUpdateInterval,
227227
dragThreshold: dashboard.dragThreshold,
228+
showUpcoming: dashboard.showUpcoming,
228229
performance: {
229230
showBattery: dashboard.performance.showBattery,
230231
showGpu: dashboard.performance.showGpu,
@@ -366,6 +367,7 @@ Singleton {
366367
function serializeSidebar(): var {
367368
return {
368369
enabled: sidebar.enabled,
370+
showUpcoming: sidebar.showUpcoming,
369371
dragThreshold: sidebar.dragThreshold
370372
};
371373
}
@@ -390,7 +392,8 @@ Singleton {
390392
enabled: services.calendar.enabled,
391393
command: services.calendar.command,
392394
agendaDays: services.calendar.agendaDays,
393-
upcomingHours: services.calendar.upcomingHours,
395+
dashUpcomingHours: services.calendar.dashUpcomingHours,
396+
sidebarUpcomingHours: services.calendar.sidebarUpcomingHours,
394397
reminderMinutes: services.calendar.reminderMinutes,
395398
refreshInterval: services.calendar.refreshInterval
396399
}

config/DashboardConfig.qml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ JsonObject {
1010
property bool showMedia: true
1111
property bool showPerformance: true
1212
property bool showWeather: true
13+
property bool showUpcoming: false
1314
property Sizes sizes: Sizes {}
1415
property Performance performance: Performance {}
1516

config/ServiceConfig.qml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ JsonObject {
2727
property bool enabled: false // Requires gws CLI in PATH
2828
property string command: "gws" // Path or name of the gws CLI binary
2929
property int agendaDays: 30 // How many days ahead to fetch events
30-
property int upcomingHours: 24 // Hours ahead to show in upcoming list
30+
property int dashUpcomingHours: 24 // Hours ahead to show in dashboard upcoming list
31+
property int sidebarUpcomingHours: 120 // Hours ahead to show in sidebar upcoming list
3132
property int reminderMinutes: 10 // Minutes before event to send notification, 0 to disable
3233
property int refreshInterval: 900 // Refresh interval in seconds
3334
}

config/SidebarConfig.qml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Quickshell.Io
22

33
JsonObject {
44
property bool enabled: true
5+
property bool showUpcoming: true
56
property int dragThreshold: 80
67
property Sizes sizes: Sizes {}
78

modules/dashboard/Dash.qml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ GridLayout {
8888
Rect {
8989
Layout.row: 0
9090
Layout.column: 5
91-
Layout.rowSpan: GCalendar.upcoming.length > 0 ? 3 : 2
91+
Layout.rowSpan: Config.dashboard.showUpcoming && GCalendar.upcomingDash.length > 0 ? 3 : 2
9292
Layout.preferredWidth: media.implicitWidth
9393
Layout.fillHeight: true
9494

@@ -107,7 +107,7 @@ GridLayout {
107107
Layout.fillWidth: true
108108
Layout.preferredHeight: eventsCol.implicitHeight
109109

110-
visible: GCalendar.upcoming.length > 0
110+
visible: Config.dashboard.showUpcoming && GCalendar.upcomingDash.length > 0
111111
radius: Appearance.rounding.large
112112

113113
ColumnLayout {
@@ -127,7 +127,7 @@ GridLayout {
127127
}
128128

129129
Repeater {
130-
model: GCalendar.upcoming
130+
model: GCalendar.upcomingDash
131131

132132
RowLayout {
133133
id: eventRow
@@ -160,7 +160,7 @@ GridLayout {
160160
StyledText {
161161
Layout.fillWidth: true
162162
text: {
163-
let line = GCalendar.formatEventTime(eventRow.modelData);
163+
let line = GCalendar.formatEventTime(eventRow.modelData, Config.services.calendar.dashUpcomingHours);
164164
if (eventRow.modelData.location)
165165
line += ` · ${eventRow.modelData.location}`;
166166
return line;

modules/sidebar/Content.qml

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
pragma ComponentBehavior: Bound
2+
13
import QtQuick
24
import QtQuick.Layouts
35
import qs.components
6+
import qs.components.containers
7+
import qs.components.controls
48
import qs.services
59
import qs.config
610

@@ -29,6 +33,111 @@ Item {
2933
}
3034
}
3135

36+
// Upcoming events
37+
StyledRect {
38+
Layout.fillWidth: true
39+
Layout.preferredHeight: Math.min(upcomingCol.implicitHeight, 300)
40+
41+
visible: Config.sidebar.showUpcoming && GCalendar.enabled && GCalendar.upcomingSidebar.length > 0
42+
radius: Appearance.rounding.normal
43+
color: Colours.tPalette.m3surfaceContainerLow
44+
45+
ColumnLayout {
46+
id: upcomingHeader
47+
48+
anchors.top: parent.top
49+
anchors.left: parent.left
50+
anchors.right: parent.right
51+
anchors.margins: Appearance.padding.normal
52+
53+
StyledText {
54+
Layout.topMargin: Appearance.padding.small
55+
text: qsTr("Upcoming Events")
56+
color: Colours.palette.m3primary
57+
font.pointSize: Appearance.font.size.small
58+
font.weight: 600
59+
}
60+
}
61+
62+
StyledFlickable {
63+
id: upcomingView
64+
65+
clip: true
66+
anchors.top: upcomingHeader.bottom
67+
anchors.left: parent.left
68+
anchors.right: parent.right
69+
anchors.bottom: parent.bottom
70+
anchors.margins: Appearance.padding.normal
71+
anchors.topMargin: Appearance.spacing.small
72+
73+
flickableDirection: Flickable.VerticalFlick
74+
contentWidth: width
75+
contentHeight: upcomingCol.implicitHeight
76+
77+
StyledScrollBar.vertical: StyledScrollBar {
78+
flickable: upcomingView
79+
}
80+
81+
ColumnLayout {
82+
id: upcomingCol
83+
84+
width: parent.width
85+
spacing: Appearance.spacing.small
86+
87+
Repeater {
88+
model: GCalendar.upcomingSidebar
89+
90+
RowLayout {
91+
id: sidebarEventRow
92+
93+
required property var modelData
94+
95+
Layout.fillWidth: true
96+
spacing: Appearance.spacing.small
97+
98+
Rectangle {
99+
width: 3
100+
Layout.fillHeight: true
101+
radius: 1.5
102+
color: Colours.palette.m3tertiary
103+
}
104+
105+
ColumnLayout {
106+
Layout.fillWidth: true
107+
spacing: 0
108+
109+
StyledText {
110+
Layout.fillWidth: true
111+
text: sidebarEventRow.modelData.summary
112+
color: Colours.palette.m3onSurface
113+
font.pointSize: Appearance.font.size.small
114+
font.weight: 500
115+
elide: Text.ElideRight
116+
}
117+
118+
StyledText {
119+
Layout.fillWidth: true
120+
text: {
121+
let line = GCalendar.formatEventTime(sidebarEventRow.modelData, Config.services.calendar.sidebarUpcomingHours);
122+
if (sidebarEventRow.modelData.location)
123+
line += ` · ${sidebarEventRow.modelData.location}`;
124+
return line;
125+
}
126+
color: Colours.palette.m3onSurfaceVariant
127+
font.pointSize: Appearance.font.size.small * 0.9
128+
elide: Text.ElideRight
129+
}
130+
}
131+
}
132+
}
133+
134+
Item {
135+
Layout.preferredHeight: Appearance.padding.small
136+
}
137+
}
138+
}
139+
}
140+
32141
StyledRect {
33142
Layout.topMargin: Appearance.padding.large - layout.spacing
34143
Layout.fillWidth: true

services/GCalendar.qml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,17 @@ Singleton {
1818
s.add(ev.dateKey);
1919
return s;
2020
}
21-
readonly property list<var> upcoming: {
21+
readonly property list<var> upcomingDash: {
2222
const now = Date.now();
23-
const cutoff = now + Config.services.calendar.upcomingHours * 3600000;
23+
const cutoff = now + Config.services.calendar.dashUpcomingHours * 3600000;
24+
return events.filter(ev => {
25+
const t = ev.startTime;
26+
return t >= now && t < cutoff;
27+
});
28+
}
29+
readonly property list<var> upcomingSidebar: {
30+
const now = Date.now();
31+
const cutoff = now + Config.services.calendar.sidebarUpcomingHours * 3600000;
2432
return events.filter(ev => {
2533
const t = ev.startTime;
2634
return t >= now && t < cutoff;
@@ -47,9 +55,9 @@ Singleton {
4755
: Qt.formatDateTime(d, "hh:mm");
4856
}
4957

50-
function formatEventTime(ev: var): string {
58+
function formatEventTime(ev: var, upcomingHours: int): string {
5159
let parts = [];
52-
if (Config.services.calendar.upcomingHours > 24) {
60+
if (upcomingHours > 24) {
5361
const d = new Date(ev.start);
5462
const target = ev.isAllDay ? new Date(ev.start + "T00:00:00") : d;
5563
const now = new Date();

0 commit comments

Comments
 (0)