import QtQuick import Quickshell import qs.config import qs.services import qs.modules.quicksettings Item { id: root signal requestNotifications readonly property bool isToday: CalendarAgenda.selectedDateKey === CalendarAgenda.dateKey(clock.date) readonly property var upNext: CalendarAgenda.nextEventForSelectedDate readonly property var listedEvents: CalendarAgenda.selectedEvents.filter(event => !root.upNext || event.id !== root.upNext.id) readonly property string selectedLabel: root.isToday ? "Today" : Qt.formatDate(CalendarAgenda.selectedDate, "dddd, MMMM d") implicitHeight: content.implicitHeight SystemClock { id: clock precision: SystemClock.Minutes } Column { id: content width: parent.width spacing: 12 Column { width: parent.width spacing: 3 visible: CalendarAgenda.phase === "loading" Text { text: "CALENDAR" color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.DemiBold font.letterSpacing: 1.1 } Text { text: "Loading calendars…" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } } Column { width: parent.width spacing: 10 visible: CalendarAgenda.phase === "unavailable" Text { text: "Calendar data is unavailable" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } Text { width: parent.width wrapMode: Text.WordWrap text: "Notifications, weather, and the month grid are still available. GNOME Calendar may have more detail." color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } ActionPill { text: "Open Calendar" onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate) } } Rectangle { id: nextCard width: parent.width height: visible ? 88 : 0 visible: CalendarAgenda.available && root.isToday && root.upNext !== null radius: Theme.cardRadius border.width: 1 border.color: Theme.alpha(Theme.accent, 0.18) color: Theme.alpha(Theme.accent, 0.105) clip: true Rectangle { anchors.right: parent.right anchors.rightMargin: -24 anchors.bottom: parent.bottom anchors.bottomMargin: -34 width: 110 height: 110 radius: 55 border.width: 0 color: Theme.alpha(Theme.accentSecondary, 0.08) } Column { anchors.left: parent.left anchors.leftMargin: 14 anchors.right: nextJoin.visible ? nextJoin.left : parent.right anchors.rightMargin: nextJoin.visible ? 10 : 14 anchors.verticalCenter: parent.verticalCenter spacing: 5 Text { text: "UP NEXT · " + root.relativeStart(root.upNext) color: Theme.accent font.family: Theme.fontFamily font.features: Theme.tabularFigures font.pixelSize: Theme.fontSizeSmall font.weight: Font.Bold font.letterSpacing: 0.8 } Text { width: parent.width text: root.upNext?.summary ?? "" color: Theme.fg elide: Text.ElideRight font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } Text { width: parent.width text: root.eventDetail(root.upNext) color: Theme.fgDim elide: Text.ElideRight font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall } } ActionPill { id: nextJoin anchors.right: parent.right anchors.rightMargin: 12 anchors.top: parent.top anchors.topMargin: 12 visible: String(root.upNext?.joinUrl ?? "").startsWith("https://") text: "Join" accent: true onClicked: CalendarAgenda.join(root.upNext) } MouseArea { anchors.fill: parent z: -1 cursorShape: Qt.PointingHandCursor onClicked: CalendarAgenda.openEvent(root.upNext) } } Item { width: parent.width height: 34 visible: CalendarAgenda.available Column { anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter spacing: 2 Text { text: root.selectedLabel.toUpperCase() color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.DemiBold font.letterSpacing: 0.9 } Text { text: "Your schedule" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } } Text { anchors.right: parent.right anchors.bottom: parent.bottom text: CalendarAgenda.selectedEvents.length + (CalendarAgenda.selectedEvents.length === 1 ? " event" : " events") color: Theme.fgDim font.family: Theme.fontFamily font.features: Theme.tabularFigures font.pixelSize: Theme.fontSizeSmall } } Item { width: parent.width height: visible ? 92 : 0 visible: CalendarAgenda.available && CalendarAgenda.selectedEvents.length === 0 Column { anchors.centerIn: parent spacing: 8 Text { anchors.horizontalCenter: parent.horizontalCenter text: "Nothing scheduled" color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } ActionPill { anchors.horizontalCenter: parent.horizontalCenter text: "Open Calendar" onClicked: CalendarAgenda.openCalendar(CalendarAgenda.selectedDate) } } } ScrollColumn { width: parent.width maxHeight: 286 spacing: 0 visible: CalendarAgenda.available && root.listedEvents.length > 0 Repeater { model: root.listedEvents AgendaEvent { required property var modelData width: parent.width eventData: modelData past: Number(modelData.end) <= CalendarAgenda.nowEpoch onActivated: CalendarAgenda.openEvent(modelData) onJoinRequested: CalendarAgenda.join(modelData) } } } NotificationSummary { width: parent.width onActivated: root.requestNotifications() } } function relativeStart(event: var): string { if (!event) return ""; const seconds = Number(event.start) - CalendarAgenda.nowEpoch; if (seconds <= 0) return "NOW"; if (seconds < 3600) return "IN " + Math.ceil(seconds / 60) + " MINUTES"; return Qt.formatTime(new Date(Number(event.start) * 1000), Settings.use24Hour ? "HH:mm" : "h:mm AP"); } function eventDetail(event: var): string { if (!event) return ""; const source = CalendarAgenda.sourceForId(event.sourceId)?.name ?? "Calendar"; const location = String(event.location ?? "").trim(); return location === "" ? source : source + " · " + location; } component ActionPill: Rectangle { id: action required property string text property bool accent: false signal clicked width: actionLabel.implicitWidth + 20 height: 28 radius: Theme.pillRadius border.width: 0 color: actionMouse.containsMouse ? Theme.alpha(action.accent ? Theme.accent : Theme.fg, 0.24) : Theme.alpha(action.accent ? Theme.accent : Theme.fg, action.accent ? 0.14 : 0.07) Text { id: actionLabel anchors.centerIn: parent text: action.text color: action.accent ? Theme.accent : Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.DemiBold } MouseArea { id: actionMouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: action.clicked() } } }