diff --git a/Bar/Modules/Applauncher.qml b/Bar/Modules/Applauncher.qml index 297d905..fb8e36a 100644 --- a/Bar/Modules/Applauncher.qml +++ b/Bar/Modules/Applauncher.qml @@ -427,4 +427,4 @@ PanelWithOverlay { offsetY: 0 } } -} +} \ No newline at end of file diff --git a/Bar/Modules/Calendar.qml b/Bar/Modules/Calendar.qml index 5065e1a..d090010 100644 --- a/Bar/Modules/Calendar.qml +++ b/Bar/Modules/Calendar.qml @@ -5,6 +5,7 @@ import Quickshell import qs.Components import qs.Settings import Quickshell.Wayland +import "root:/Helpers/Holidays.js" as Holidays PanelWithOverlay { id: calendarOverlay @@ -87,10 +88,38 @@ PanelWithOverlay { month: Time.date.getMonth() year: Time.date.getFullYear() + property var holidays: [] + + // Fetch holidays when calendar is opened or month/year changes + function updateHolidays() { + Holidays.getHolidaysForMonth(calendar.year, calendar.month, function(holidays) { + calendar.holidays = holidays; + }); + } + onMonthChanged: updateHolidays() + onYearChanged: updateHolidays() + Component.onCompleted: updateHolidays() + // Optionally, update when the panel becomes visible + Connections { + target: calendarOverlay + function onVisibleChanged() { + if (calendarOverlay.visible) { + calendar.month = Time.date.getMonth(); + calendar.year = Time.date.getFullYear(); + calendar.updateHolidays(); + } + } + } + delegate: Rectangle { width: 32 height: 32 radius: 8 + property var holidayInfo: calendar.holidays.filter(function(h) { + var d = new Date(h.date); + return d.getDate() === model.day && d.getMonth() === model.month && d.getFullYear() === model.year; + }) + property bool isHoliday: holidayInfo.length > 0 color: { if (model.today) return Theme.accentPrimary; @@ -99,6 +128,19 @@ PanelWithOverlay { return "transparent"; } + // Holiday dot indicator + Rectangle { + visible: isHoliday + width: 4; height: 4 + radius: 4 + color: Theme.accentTertiary + anchors.top: parent.top + anchors.right: parent.right + anchors.topMargin: 4 + anchors.rightMargin: 4 + z: 2 + } + Text { anchors.centerIn: parent text: model.day @@ -113,6 +155,16 @@ PanelWithOverlay { id: mouseArea2 anchors.fill: parent hoverEnabled: true + onEntered: { + if (isHoliday) { + holidayTooltip.text = holidayInfo.map(function(h) { + return h.localName + (h.name !== h.localName ? " (" + h.name + ")" : "") + (h.global ? " [Global]" : ""); + }).join(", "); + holidayTooltip.targetItem = parent; + holidayTooltip.tooltipVisible = true; + } + } + onExited: holidayTooltip.tooltipVisible = false } Behavior on color { @@ -120,6 +172,14 @@ PanelWithOverlay { duration: 150 } } + + StyledTooltip { + id: holidayTooltip + text: "" + tooltipVisible: false + targetItem: undefined + delay: 100 + } } } } diff --git a/Bar/Modules/Volume.qml b/Bar/Modules/Volume.qml index 8ca074a..4df0e1f 100644 --- a/Bar/Modules/Volume.qml +++ b/Bar/Modules/Volume.qml @@ -14,7 +14,7 @@ Item { PillIndicator { id: pillIndicator - icon: volume === 0 ? "volume_off" : (volume < 30 ? "volume_down" : "volume_up") + icon: shell && shell.defaultAudioSink && shell.defaultAudioSink.audio && shell.defaultAudioSink.audio.muted ? "volume_off" : (volume === 0 ? "volume_off" : (volume < 30 ? "volume_down" : "volume_up")) text: volume + "%" pillColor: Theme.surfaceVariant @@ -36,7 +36,7 @@ Item { if (shell && shell.volume !== volume) { volume = shell.volume pillIndicator.text = volume + "%" - pillIndicator.icon = volume === 0 ? "volume_off" : (volume < 30 ? "volume_down" : "volume_up") + pillIndicator.icon = shell && shell.defaultAudioSink && shell.defaultAudioSink.audio && shell.defaultAudioSink.audio.muted ? "volume_off" : (volume === 0 ? "volume_off" : (volume < 30 ? "volume_down" : "volume_up")) pillIndicator.show() } } diff --git a/Helpers/Holidays.js b/Helpers/Holidays.js new file mode 100644 index 0000000..a5879a6 --- /dev/null +++ b/Helpers/Holidays.js @@ -0,0 +1,51 @@ +var _countryCode = null; +var _holidaysCache = {}; + +function getCountryCode(callback) { + if (_countryCode) { + callback(_countryCode); + return; + } + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://ip-api.com/json/", true); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { + var response = JSON.parse(xhr.responseText); + _countryCode = response.countryCode; + callback(_countryCode); + } + } + xhr.send(); +} + +function getHolidays(year, countryCode, callback) { + var cacheKey = year + "-" + countryCode; + if (_holidaysCache[cacheKey]) { + callback(_holidaysCache[cacheKey]); + return; + } + var url = "https://date.nager.at/api/v3/PublicHolidays/" + year + "/" + countryCode; + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { + var holidays = JSON.parse(xhr.responseText); + _holidaysCache[cacheKey] = holidays; + callback(holidays); + } + } + xhr.send(); +} + +function getHolidaysForMonth(year, month, callback) { + getCountryCode(function(countryCode) { + getHolidays(year, countryCode, function(holidays) { + // 0-based months (0=Jan, 11=Dec) + var filtered = holidays.filter(function(h) { + var date = new Date(h.date); + return date.getFullYear() === year && date.getMonth() === month; + }); + callback(filtered); + }); + }); +} \ No newline at end of file diff --git a/shell.qml b/shell.qml index 42676f7..dd03cd6 100644 --- a/shell.qml +++ b/shell.qml @@ -72,7 +72,7 @@ Scope { } property var defaultAudioSink: Pipewire.defaultAudioSink - property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume ? Math.round(defaultAudioSink.audio.volume * 100) : 0 + property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume && !defaultAudioSink.audio.muted ? Math.round(defaultAudioSink.audio.volume * 100) : 0 PwObjectTracker { objects: [Pipewire.defaultAudioSink]