Cards & Settings refactoring

- All cards now live in Modules/Cards
- CalendarPanel is now called ClockPanel
- Added a way to ease settings migration in separate QML files
This commit is contained in:
ItsLemmy
2025-11-30 14:26:09 -05:00
parent 087c9b4ced
commit e972e1f7aa
20 changed files with 738 additions and 686 deletions

View File

@@ -0,0 +1,44 @@
import QtQuick
QtObject {
id: root
// Migrate from version < 26 to version 26
// Replaces old calendar-card and banner-card with calendar-header-card and calendar-month-card
function migrate(adapter, logger) {
logger.i("Settings", "Migrating settings to v26");
// Replace old calendar-card and banner-card with calendar-header-card and calendar-month-card
if (adapter.calendar !== undefined && adapter.calendar.cards !== undefined) {
const oldCards = adapter.calendar.cards;
const newCards = [];
let anyCalendarEnabled = false;
// Check if any calendar-related card was enabled
for (var i = 0; i < oldCards.length; i++) {
const card = oldCards[i];
if ((card.id === "banner-card" || card.id === "calendar-card") && card.enabled) {
anyCalendarEnabled = true;
} else if (card.id !== "banner-card" && card.id !== "calendar-card") {
// Keep other cards as-is (timer, weather)
newCards.push(card);
}
}
// Add new split cards at the beginning (enabled if any old calendar card was enabled)
newCards.unshift({
"id": "calendar-month-card",
"enabled": anyCalendarEnabled
});
newCards.unshift({
"id": "calendar-header-card",
"enabled": anyCalendarEnabled
});
adapter.calendar.cards = newCards;
logger.i("Settings", "Replaced old calendar cards with calendar-header-card + calendar-month-card");
}
return true;
}
}

View File

@@ -0,0 +1,15 @@
pragma Singleton
import QtQuick
QtObject {
id: root
// Map of version number to migration component
readonly property var migrations: ({
26: migration26Component
})
// Migration components
property Component migration26Component: Migration26 {}
}

View File

@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import "../Helpers/QtObj2JS.js" as QtObj2JS import "../Helpers/QtObj2JS.js" as QtObj2JS
import qs.Commons import qs.Commons
import qs.Commons.Migrations
import qs.Modules.OSD import qs.Modules.OSD
import qs.Services.UI import qs.Services.UI
@@ -21,7 +22,7 @@ Singleton {
- Default cache directory: ~/.cache/noctalia - Default cache directory: ~/.cache/noctalia
*/ */
readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy readonly property alias data: adapter // Used to access via Settings.data.xxx.yyy
readonly property int settingsVersion: 25 readonly property int settingsVersion: 26
readonly property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1" readonly property bool isDebug: Quickshell.env("NOCTALIA_DEBUG") === "1"
readonly property string shellName: "noctalia" readonly property string shellName: "noctalia"
readonly property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/" readonly property string configDir: Quickshell.env("NOCTALIA_CONFIG_DIR") || (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/"
@@ -100,6 +101,10 @@ Singleton {
if (!isLoaded) { if (!isLoaded) {
Logger.i("Settings", "Settings loaded"); Logger.i("Settings", "Settings loaded");
// -----------------
// Run versioned migrations from MigrationRegistry
runVersionedMigrations();
upgradeSettingsData(); upgradeSettingsData();
root.isLoaded = true; root.isLoaded = true;
@@ -267,11 +272,11 @@ Singleton {
property JsonObject calendar: JsonObject { property JsonObject calendar: JsonObject {
property list<var> cards: [ property list<var> cards: [
{ {
"id": "banner-card", "id": "calendar-header-card",
"enabled": true "enabled": true
}, },
{ {
"id": "calendar-card", "id": "calendar-month-card",
"enabled": true "enabled": true
}, },
{ {
@@ -627,6 +632,41 @@ Singleton {
} }
} }
// -----------------------------------------------------
// Run versioned migrations using MigrationRegistry
function runVersionedMigrations() {
const currentVersion = adapter.settingsVersion;
const migrations = MigrationRegistry.migrations;
// Get all migration versions and sort them
const versions = Object.keys(migrations).map(v => parseInt(v)).sort((a, b) => a - b);
// Run migrations in order for versions newer than current
for (var i = 0; i < versions.length; i++) {
const version = versions[i];
if (currentVersion < version) {
// Create migration instance and run it
const migrationComponent = migrations[version];
const migration = migrationComponent.createObject(root);
if (migration && typeof migration.migrate === "function") {
const success = migration.migrate(adapter, Logger);
if (!success) {
Logger.e("Settings", "Migration to v" + version + " failed");
}
} else {
Logger.e("Settings", "Invalid migration for v" + version);
}
// Clean up migration instance
if (migration) {
migration.destroy();
}
}
}
}
// ----------------------------------------------------- // -----------------------------------------------------
// Function to clean up deprecated user/custom bar widgets settings // Function to clean up deprecated user/custom bar widgets settings
function upgradeWidget(widget) { function upgradeWidget(widget) {
@@ -677,31 +717,7 @@ Singleton {
const sections = ["left", "center", "right"]; const sections = ["left", "center", "right"];
// ----------------- // -----------------
// 1st. convert old widget id to new id // 1. remove any non existing widget type
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i];
switch (widget.id) {
case "DarkModeToggle":
widget.id = "DarkMode";
break;
case "PowerToggle":
widget.id = "SessionMenu";
break;
case "ScreenRecorderIndicator":
widget.id = "ScreenRecorder";
break;
case "SidePanelToggle":
widget.id = "ControlCenter";
break;
}
}
}
// -----------------
// 2nd. remove any non existing widget type
var removedWidget = false; var removedWidget = false;
for (var s = 0; s < sections.length; s++) { for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s]; const sectionName = sections[s];
@@ -718,7 +734,7 @@ Singleton {
} }
// ----------------- // -----------------
// 3nd. upgrade widget settings // 2. upgrade user widget settings
for (var s = 0; s < sections.length; s++) { for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s]; const sectionName = sections[s];
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) { for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
@@ -737,7 +753,7 @@ Singleton {
} }
// ----------------- // -----------------
// 4th. safety check // 3. safety check
// if a widget was deleted, ensure we still have a control center // if a widget was deleted, ensure we still have a control center
if (removedWidget) { if (removedWidget) {
var gotControlCenter = false; var gotControlCenter = false;

View File

@@ -140,7 +140,7 @@ Rectangle {
} }
if (action === "open-calendar") { if (action === "open-calendar") {
PanelService.getPanel("calendarPanel", screen)?.toggle(root); PanelService.getPanel("clockPanel", screen)?.toggle(root);
} else if (action === "widget-settings") { } else if (action === "widget-settings") {
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings); BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
} }
@@ -154,7 +154,7 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: { onEntered: {
if (!PanelService.getPanel("calendarPanel", screen)?.active) { if (!PanelService.getPanel("clockPanel", screen)?.active) {
TooltipService.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection()); TooltipService.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection());
} }
} }
@@ -171,7 +171,7 @@ Rectangle {
contextMenu.openAtItem(root, pos.x, pos.y); contextMenu.openAtItem(root, pos.x, pos.y);
} }
} else { } else {
PanelService.getPanel("calendarPanel", screen)?.toggle(this); PanelService.getPanel("clockPanel", screen)?.toggle(this);
} }
} }
} }

View File

@@ -0,0 +1,132 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Location
import qs.Widgets
// Calendar header with date, month/year, location, and clock
Rectangle {
id: root
Layout.fillWidth: true
Layout.minimumHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
Layout.preferredHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
implicitHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
radius: Style.radiusL
color: Color.mPrimary
// Internal state
readonly property var now: Time.now
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
// Expose current month/year for potential synchronization with CalendarMonthCard
readonly property int currentMonth: now.getMonth()
readonly property int currentYear: now.getFullYear()
ColumnLayout {
id: capsuleColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: Style.marginM
anchors.bottomMargin: Style.marginM
anchors.rightMargin: clockLoader.width + (Style.marginXL * 2)
anchors.leftMargin: Style.marginXL
spacing: 0
// Combined layout for date, month year, location and time-zone
RowLayout {
Layout.fillWidth: true
height: 60 * Style.uiScaleRatio
clip: true
spacing: Style.marginS
// Today day number
NText {
Layout.preferredWidth: implicitWidth
elide: Text.ElideNone
clip: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: root.now.getDate()
pointSize: Style.fontSizeXXXL * 1.5
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
}
// Month, year, location
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.bottomMargin: Style.marginXXS
Layout.topMargin: -Style.marginXXS
spacing: -Style.marginXS
RowLayout {
spacing: Style.marginS
NText {
text: I18n.locale.monthName(root.currentMonth, Locale.LongFormat).toUpperCase()
pointSize: Style.fontSizeXL * 1.1
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Layout.alignment: Qt.AlignBaseline
elide: Text.ElideRight
}
NText {
text: `${root.currentYear}`
pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: Qt.alpha(Color.mOnPrimary, 0.7)
Layout.alignment: Qt.AlignBaseline
}
}
RowLayout {
spacing: 0
NText {
text: {
if (!Settings.data.location.weatherEnabled)
return "";
if (!root.weatherReady)
return I18n.tr("calendar.weather.loading");
const chunks = Settings.data.location.name.split(",");
return chunks[0];
}
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnPrimary
Layout.maximumWidth: 150
elide: Text.ElideRight
}
NText {
text: root.weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
pointSize: Style.fontSizeXS
font.weight: Style.fontWeightMedium
color: Qt.alpha(Color.mOnPrimary, 0.7)
}
}
}
// Spacer
Item {
Layout.fillWidth: true
}
}
}
// Analog/Digital clock
NClock {
id: clockLoader
anchors.right: parent.right
anchors.rightMargin: Style.marginXL
anchors.verticalCenter: parent.verticalCenter
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
progressColor: Color.mOnPrimary
Layout.alignment: Qt.AlignVCenter
now: root.now
}
}

View File

@@ -0,0 +1,396 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.Commons
import qs.Services.Location
import qs.Services.System
import qs.Services.UI
import qs.Widgets
// Calendar month grid with navigation
NBox {
id: root
Layout.fillWidth: true
implicitHeight: calendarContent.implicitHeight + Style.marginM * 2
// Internal state - independent from header
readonly property var now: Time.now
property int calendarMonth: now.getMonth()
property int calendarYear: now.getFullYear()
readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek
// Helper function to calculate ISO week number
function getISOWeekNumber(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4);
const diff = target - firstThursday;
const oneWeek = 1000 * 60 * 60 * 24 * 7;
const weekNumber = 1 + Math.round(diff / oneWeek);
return weekNumber;
}
// Helper function to check if an event is all-day
function isAllDayEvent(event) {
const duration = event.end - event.start;
const startDate = new Date(event.start * 1000);
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
return duration === 86400 && isAtMidnight;
}
ColumnLayout {
id: calendarContent
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginS
// Navigation row
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NDivider {
Layout.fillWidth: true
}
NIconButton {
icon: "chevron-left"
onClicked: {
let newDate = new Date(root.calendarYear, root.calendarMonth - 1, 1);
root.calendarYear = newDate.getFullYear();
root.calendarMonth = newDate.getMonth();
const now = new Date();
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
NIconButton {
icon: "calendar"
onClicked: {
root.calendarMonth = root.now.getMonth();
root.calendarYear = root.now.getFullYear();
CalendarService.loadEvents();
}
}
NIconButton {
icon: "chevron-right"
onClicked: {
let newDate = new Date(root.calendarYear, root.calendarMonth + 1, 1);
root.calendarYear = newDate.getFullYear();
root.calendarMonth = newDate.getMonth();
const now = new Date();
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
}
// Day names header
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
}
GridLayout {
Layout.fillWidth: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.fontSizeS * 2
NText {
anchors.centerIn: parent
text: {
let dayIndex = (root.firstDayOfWeek + index) % 7;
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
return dayName.substring(0, 2).toUpperCase();
}
color: Color.mPrimary
pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
// Calendar grid with week numbers
RowLayout {
Layout.fillWidth: true
spacing: 0
// Helper functions
function hasEventsOnDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return false;
const targetDate = new Date(year, month, day);
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
const targetEnd = targetStart + 86400;
return CalendarService.events.some(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
function getEventsForDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return [];
const targetDate = new Date(year, month, day);
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
const targetEnd = targetStart + 86400;
return CalendarService.events.filter(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
function isMultiDayEvent(event) {
if (root.isAllDayEvent(event)) {
return false;
}
const startDate = new Date(event.start * 1000);
const endDate = new Date(event.end * 1000);
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
return startDateOnly.getTime() !== endDateOnly.getTime();
}
function getEventColor(event, isToday) {
if (isMultiDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mTertiary;
} else if (root.isAllDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mSecondary;
} else {
return isToday ? Color.mOnSecondary : Color.mPrimary;
}
}
// Week numbers column
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
Layout.alignment: Qt.AlignTop
spacing: Style.marginXXS
property var weekNumbers: {
if (!grid.daysModel || grid.daysModel.length === 0)
return [];
const weeks = [];
const numWeeks = Math.ceil(grid.daysModel.length / 7);
for (var i = 0; i < numWeeks; i++) {
const dayIndex = i * 7;
if (dayIndex < grid.daysModel.length) {
const weekDay = grid.daysModel[dayIndex];
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
let thursday = new Date(date);
if (root.firstDayOfWeek === 0) {
thursday.setDate(date.getDate() + 4);
} else if (root.firstDayOfWeek === 1) {
thursday.setDate(date.getDate() + 3);
} else {
let daysToThursday = (4 - root.firstDayOfWeek + 7) % 7;
thursday.setDate(date.getDate() + daysToThursday);
}
weeks.push(root.getISOWeekNumber(thursday));
}
}
return weeks;
}
Repeater {
model: parent.weekNumbers
Item {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.9
NText {
anchors.centerIn: parent
color: Qt.alpha(Color.mPrimary, 0.7)
pointSize: Style.fontSizeXXS
font.weight: Style.fontWeightMedium
text: modelData
}
}
}
}
// Calendar grid
GridLayout {
id: grid
Layout.fillWidth: true
columns: 7
columnSpacing: Style.marginXXS
rowSpacing: Style.marginXXS
property int month: root.calendarMonth
property int year: root.calendarYear
property var daysModel: {
const firstOfMonth = new Date(year, month, 1);
const lastOfMonth = new Date(year, month + 1, 0);
const daysInMonth = lastOfMonth.getDate();
const firstDayOfWeek = root.firstDayOfWeek;
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
const days = [];
const today = new Date();
// Previous month days
const prevMonth = new Date(year, month, 0);
const prevMonthDays = prevMonth.getDate();
for (var i = daysBefore - 1; i >= 0; i--) {
const day = prevMonthDays - i;
days.push({
"day": day,
"month": month - 1,
"year": month === 0 ? year - 1 : year,
"today": false,
"currentMonth": false
});
}
// Current month days
for (var day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
days.push({
"day": day,
"month": month,
"year": year,
"today": isToday,
"currentMonth": true
});
}
// Next month days
for (var i = 1; i <= daysAfter; i++) {
days.push({
"day": i,
"month": month + 1,
"year": month === 11 ? year + 1 : year,
"today": false,
"currentMonth": false
});
}
return days;
}
Repeater {
model: grid.daysModel
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.9
Rectangle {
width: Style.baseWidgetSize * 0.9
height: Style.baseWidgetSize * 0.9
anchors.centerIn: parent
radius: Style.radiusM
color: modelData.today ? Color.mSecondary : Color.transparent
NText {
anchors.centerIn: parent
text: modelData.day
color: {
if (modelData.today)
return Color.mOnSecondary;
if (modelData.currentMonth)
return Color.mOnSurface;
return Color.mOnSurfaceVariant;
}
opacity: modelData.currentMonth ? 1.0 : 0.4
pointSize: Style.fontSizeM
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
}
// Event indicator dots
Row {
visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day)
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.marginXS
Repeater {
model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
Rectangle {
width: 4
height: width
radius: width / 2
color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today)
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: Settings.data.location.showCalendarEvents
onEntered: {
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
if (events.length > 0) {
const summaries = events.map(event => {
if (root.isAllDayEvent(event)) {
return event.summary;
} else {
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
const start = new Date(event.start * 1000);
const startFormatted = I18n.locale.toString(start, timeFormat);
const end = new Date(event.end * 1000);
const endFormatted = I18n.locale.toString(end, timeFormat);
return `${startFormatted}-${endFormatted} ${event.summary}`;
}
}).join('\n');
TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
}
}
onClicked: {
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
if (ProgramCheckerService.gnomeCalendarAvailable) {
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
}
}
onExited: {
TooltipService.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
}
}

View File

@@ -5,7 +5,6 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Widgets import Quickshell.Widgets
import qs.Commons import qs.Commons
import qs.Modules.Panels.ControlCenter.Cards
import qs.Modules.Panels.Settings import qs.Modules.Panels.Settings
import qs.Services.System import qs.Services.System
import qs.Services.UI import qs.Services.UI

View File

@@ -4,7 +4,6 @@ import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Modules.Panels.ControlCenter import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.ControlCenter.Cards
import qs.Widgets import qs.Widgets
RowLayout { RowLayout {

View File

@@ -90,9 +90,9 @@ Item {
backgroundColor: panelBackgroundColor backgroundColor: panelBackgroundColor
} }
// Calendar // Clock
PanelBackground { PanelBackground {
panel: root.windowRoot.calendarPanelPlaceholder panel: root.windowRoot.clockPanelPlaceholder
shapeContainer: backgroundsShape shapeContainer: backgroundsShape
backgroundColor: panelBackgroundColor backgroundColor: panelBackgroundColor
} }

View File

@@ -14,8 +14,8 @@ import qs.Modules.Panels.Audio
import qs.Modules.Panels.Battery import qs.Modules.Panels.Battery
import qs.Modules.Panels.Bluetooth import qs.Modules.Panels.Bluetooth
import qs.Modules.Panels.Brightness import qs.Modules.Panels.Brightness
import qs.Modules.Panels.Calendar
import qs.Modules.Panels.Changelog import qs.Modules.Panels.Changelog
import qs.Modules.Panels.Clock
import qs.Modules.Panels.ControlCenter import qs.Modules.Panels.ControlCenter
import qs.Modules.Panels.Launcher import qs.Modules.Panels.Launcher
import qs.Modules.Panels.NotificationHistory import qs.Modules.Panels.NotificationHistory
@@ -39,7 +39,7 @@ PanelWindow {
readonly property alias batteryPanel: batteryPanel readonly property alias batteryPanel: batteryPanel
readonly property alias bluetoothPanel: bluetoothPanel readonly property alias bluetoothPanel: bluetoothPanel
readonly property alias brightnessPanel: brightnessPanel readonly property alias brightnessPanel: brightnessPanel
readonly property alias calendarPanel: calendarPanel readonly property alias clockPanel: clockPanel
readonly property alias changelogPanel: changelogPanel readonly property alias changelogPanel: changelogPanel
readonly property alias controlCenterPanel: controlCenterPanel readonly property alias controlCenterPanel: controlCenterPanel
readonly property alias launcherPanel: launcherPanel readonly property alias launcherPanel: launcherPanel
@@ -56,7 +56,7 @@ PanelWindow {
readonly property var batteryPanelPlaceholder: batteryPanel.panelRegion readonly property var batteryPanelPlaceholder: batteryPanel.panelRegion
readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelRegion readonly property var bluetoothPanelPlaceholder: bluetoothPanel.panelRegion
readonly property var brightnessPanelPlaceholder: brightnessPanel.panelRegion readonly property var brightnessPanelPlaceholder: brightnessPanel.panelRegion
readonly property var calendarPanelPlaceholder: calendarPanel.panelRegion readonly property var clockPanelPlaceholder: clockPanel.panelRegion
readonly property var changelogPanelPlaceholder: changelogPanel.panelRegion readonly property var changelogPanelPlaceholder: changelogPanel.panelRegion
readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelRegion readonly property var controlCenterPanelPlaceholder: controlCenterPanel.panelRegion
readonly property var launcherPanelPlaceholder: launcherPanel.panelRegion readonly property var launcherPanelPlaceholder: launcherPanel.panelRegion
@@ -240,9 +240,9 @@ PanelWindow {
z: 50 z: 50
} }
CalendarPanel { ClockPanel {
id: calendarPanel id: clockPanel
objectName: "calendarPanel-" + (root.screen?.name || "unknown") objectName: "clockPanel-" + (root.screen?.name || "unknown")
screen: root.screen screen: root.screen
z: 50 z: 50
} }

View File

@@ -1,636 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import "."
import qs.Commons
import qs.Modules.MainScreen
import qs.Modules.Panels.ControlCenter.Cards
import qs.Services.Location
import qs.Services.System
import qs.Services.UI
import qs.Widgets
SmartPanel {
id: root
readonly property var now: Time.now
// Calculate width based on settings
preferredWidth: Math.round((Settings.data.location.showWeekNumberInCalendar ? 460 : 440) * Style.uiScaleRatio)
// Use a reasonable fixed height that accommodates most layouts
preferredHeight: Math.round(700 * Style.uiScaleRatio)
// Helper function to calculate ISO week number
function getISOWeekNumber(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = new Date(target.getFullYear(), 0, 4);
const diff = target - firstThursday;
const oneWeek = 1000 * 60 * 60 * 24 * 7;
const weekNumber = 1 + Math.round(diff / oneWeek);
return weekNumber;
}
// Helper function to check if an event is all-day
function isAllDayEvent(event) {
const duration = event.end - event.start;
const startDate = new Date(event.start * 1000);
const isAtMidnight = startDate.getHours() === 0 && startDate.getMinutes() === 0;
return duration === 86400 && isAtMidnight;
}
// Shared calendar state (month/year) accessible by all components
property int calendarMonth: now.getMonth()
property int calendarYear: now.getFullYear()
panelContent: Item {
anchors.fill: parent
// Dynamic height based on actual content height
property real contentPreferredHeight: content.implicitHeight + Style.marginL * 2
ColumnLayout {
id: content
x: Style.marginL
y: Style.marginL
width: parent.width - (Style.marginL * 2)
spacing: Style.marginL
readonly property int firstDayOfWeek: Settings.data.location.firstDayOfWeek === -1 ? I18n.locale.firstDayOfWeek : Settings.data.location.firstDayOfWeek
property bool isCurrentMonth: true
readonly property bool weatherReady: Settings.data.location.weatherEnabled && (LocationService.data.weather !== null)
function checkIsCurrentMonth() {
return (now.getMonth() === root.calendarMonth) && (now.getFullYear() === root.calendarYear);
}
Component.onCompleted: {
isCurrentMonth = checkIsCurrentMonth();
}
Connections {
target: Time
function onNowChanged() {
content.isCurrentMonth = content.checkIsCurrentMonth();
}
}
Connections {
target: I18n
function onLanguageChanged() {
// Force update by toggling month
root.calendarMonth = root.calendarMonth;
}
}
// All calendar items (Banner, Calendar, Timer, Weather, etc.)
Repeater {
model: Settings.data.calendar.cards
Loader {
active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled)
visible: active
Layout.fillWidth: true
Layout.topMargin: 0
Layout.bottomMargin: 0
sourceComponent: {
switch (modelData.id) {
case "banner-card":
return bannerCard;
case "calendar-card":
return calendarCard;
case "timer-card":
return timerCard;
case "weather-card":
return weatherCard;
default:
return null;
}
}
}
}
}
Component {
id: bannerCard
Rectangle {
id: banner
Layout.fillWidth: true
Layout.minimumHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
Layout.preferredHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
implicitHeight: (60 * Style.uiScaleRatio) + (Style.marginM * 2)
radius: Style.radiusL
color: Color.mPrimary
// Access parent properties
readonly property var now: root.now
readonly property bool isCurrentMonth: content.isCurrentMonth
readonly property bool weatherReady: content.weatherReady
ColumnLayout {
id: capsuleColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.topMargin: Style.marginM
anchors.bottomMargin: Style.marginM
anchors.rightMargin: clockLoader.width + (Style.marginXL * 2)
anchors.leftMargin: Style.marginXL
spacing: 0
// Combined layout for date, month year, location and time-zone
RowLayout {
Layout.fillWidth: true
height: 60 * Style.uiScaleRatio
clip: true
spacing: Style.marginS
// Today day number
NText {
opacity: banner.isCurrentMonth ? 1.0 : 0.0
Layout.preferredWidth: banner.isCurrentMonth ? implicitWidth : 0
elide: Text.ElideNone
clip: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
text: banner.now.getDate()
pointSize: Style.fontSizeXXXL * 1.5
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
}
}
Behavior on Layout.preferredWidth {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutQuad
}
}
}
// Month, year, location
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.bottomMargin: Style.marginXXS
Layout.topMargin: -Style.marginXXS
spacing: -Style.marginXS
RowLayout {
spacing: Style.marginS
NText {
text: I18n.locale.monthName(root.calendarMonth, Locale.LongFormat).toUpperCase()
pointSize: Style.fontSizeXL * 1.1
font.weight: Style.fontWeightBold
color: Color.mOnPrimary
Layout.alignment: Qt.AlignBaseline
elide: Text.ElideRight
}
NText {
text: `${root.calendarYear}`
pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
color: Qt.alpha(Color.mOnPrimary, 0.7)
Layout.alignment: Qt.AlignBaseline
}
}
RowLayout {
spacing: 0
NText {
text: {
if (!Settings.data.location.weatherEnabled)
return "";
if (!banner.weatherReady)
return I18n.tr("calendar.weather.loading");
const chunks = Settings.data.location.name.split(",");
return chunks[0];
}
pointSize: Style.fontSizeM
font.weight: Style.fontWeightMedium
color: Color.mOnPrimary
Layout.maximumWidth: 150
elide: Text.ElideRight
}
NText {
text: banner.weatherReady ? ` (${LocationService.data.weather.timezone_abbreviation})` : ""
pointSize: Style.fontSizeXS
font.weight: Style.fontWeightMedium
color: Qt.alpha(Color.mOnPrimary, 0.7)
}
}
}
// Spacer
Item {
Layout.fillWidth: true
}
}
}
// Analog clock
NClock {
id: clockLoader
anchors.right: parent.right
anchors.rightMargin: Style.marginXL
anchors.verticalCenter: parent.verticalCenter
clockStyle: Settings.data.location.analogClockInCalendar ? "analog" : "digital"
progressColor: Color.mOnPrimary
Layout.alignment: Qt.AlignVCenter
now: parent.now
}
}
}
Component {
id: calendarCard
NBox {
Layout.fillWidth: true
implicitHeight: calendarContent.implicitHeight + Style.marginM * 2
ColumnLayout {
id: calendarContent
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginS
// Navigation row
RowLayout {
Layout.fillWidth: true
spacing: Style.marginS
NDivider {
Layout.fillWidth: true
}
NIconButton {
icon: "chevron-left"
onClicked: {
let newDate = new Date(root.calendarYear, root.calendarMonth - 1, 1);
root.calendarYear = newDate.getFullYear();
root.calendarMonth = newDate.getMonth();
content.isCurrentMonth = content.checkIsCurrentMonth();
const now = new Date();
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
NIconButton {
icon: "calendar"
onClicked: {
root.calendarMonth = now.getMonth();
root.calendarYear = now.getFullYear();
content.isCurrentMonth = true;
CalendarService.loadEvents();
}
}
NIconButton {
icon: "chevron-right"
onClicked: {
let newDate = new Date(root.calendarYear, root.calendarMonth + 1, 1);
root.calendarYear = newDate.getFullYear();
root.calendarMonth = newDate.getMonth();
content.isCurrentMonth = content.checkIsCurrentMonth();
const now = new Date();
const monthStart = new Date(root.calendarYear, root.calendarMonth, 1);
const monthEnd = new Date(root.calendarYear, root.calendarMonth + 1, 0);
const daysBehind = Math.max(0, Math.ceil((now - monthStart) / (24 * 60 * 60 * 1000)));
const daysAhead = Math.max(0, Math.ceil((monthEnd - now) / (24 * 60 * 60 * 1000)));
CalendarService.loadEvents(daysAhead + 30, daysBehind + 30);
}
}
}
// Day names header
RowLayout {
Layout.fillWidth: true
spacing: 0
Item {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
}
GridLayout {
Layout.fillWidth: true
columns: 7
rows: 1
columnSpacing: 0
rowSpacing: 0
Repeater {
model: 7
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.fontSizeS * 2
NText {
anchors.centerIn: parent
text: {
let dayIndex = (content.firstDayOfWeek + index) % 7;
const dayName = I18n.locale.dayName(dayIndex, Locale.ShortFormat);
return dayName.substring(0, 2).toUpperCase();
}
color: Color.mPrimary
pointSize: Style.fontSizeS
font.weight: Style.fontWeightBold
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
// Calendar grid with week numbers
RowLayout {
Layout.fillWidth: true
spacing: 0
// Helper functions
function hasEventsOnDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return false;
const targetDate = new Date(year, month, day);
const targetStart = new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000;
const targetEnd = targetStart + 86400;
return CalendarService.events.some(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
function getEventsForDate(year, month, day) {
if (!CalendarService.available || CalendarService.events.length === 0)
return [];
const targetDate = new Date(year, month, day);
const targetStart = Math.floor(new Date(targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()).getTime() / 1000);
const targetEnd = targetStart + 86400;
return CalendarService.events.filter(event => {
return (event.start >= targetStart && event.start < targetEnd) || (event.end > targetStart && event.end <= targetEnd) || (event.start < targetStart && event.end > targetEnd);
});
}
function isMultiDayEvent(event) {
if (root.isAllDayEvent(event)) {
return false;
}
const startDate = new Date(event.start * 1000);
const endDate = new Date(event.end * 1000);
const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
return startDateOnly.getTime() !== endDateOnly.getTime();
}
function getEventColor(event, isToday) {
if (isMultiDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mTertiary;
} else if (root.isAllDayEvent(event)) {
return isToday ? Color.mOnSecondary : Color.mSecondary;
} else {
return isToday ? Color.mOnSecondary : Color.mPrimary;
}
}
// Week numbers column
ColumnLayout {
visible: Settings.data.location.showWeekNumberInCalendar
Layout.preferredWidth: visible ? Style.baseWidgetSize * 0.7 : 0
Layout.alignment: Qt.AlignTop
spacing: Style.marginXXS
property var weekNumbers: {
if (!grid.daysModel || grid.daysModel.length === 0)
return [];
const weeks = [];
const numWeeks = Math.ceil(grid.daysModel.length / 7);
for (var i = 0; i < numWeeks; i++) {
const dayIndex = i * 7;
if (dayIndex < grid.daysModel.length) {
const weekDay = grid.daysModel[dayIndex];
const date = new Date(weekDay.year, weekDay.month, weekDay.day);
let thursday = new Date(date);
if (content.firstDayOfWeek === 0) {
thursday.setDate(date.getDate() + 4);
} else if (content.firstDayOfWeek === 1) {
thursday.setDate(date.getDate() + 3);
} else {
let daysToThursday = (4 - content.firstDayOfWeek + 7) % 7;
thursday.setDate(date.getDate() + daysToThursday);
}
weeks.push(root.getISOWeekNumber(thursday));
}
}
return weeks;
}
Repeater {
model: parent.weekNumbers
Item {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.9
NText {
anchors.centerIn: parent
color: Qt.alpha(Color.mPrimary, 0.7)
pointSize: Style.fontSizeXXS
font.weight: Style.fontWeightMedium
text: modelData
}
}
}
}
// Calendar grid
GridLayout {
id: grid
Layout.fillWidth: true
columns: 7
columnSpacing: Style.marginXXS
rowSpacing: Style.marginXXS
property int month: root.calendarMonth
property int year: root.calendarYear
property var daysModel: {
const firstOfMonth = new Date(year, month, 1);
const lastOfMonth = new Date(year, month + 1, 0);
const daysInMonth = lastOfMonth.getDate();
const firstDayOfWeek = content.firstDayOfWeek;
const firstOfMonthDayOfWeek = firstOfMonth.getDay();
let daysBefore = (firstOfMonthDayOfWeek - firstDayOfWeek + 7) % 7;
const lastOfMonthDayOfWeek = lastOfMonth.getDay();
const daysAfter = (firstDayOfWeek - lastOfMonthDayOfWeek - 1 + 7) % 7;
const days = [];
const today = new Date();
// Previous month days
const prevMonth = new Date(year, month, 0);
const prevMonthDays = prevMonth.getDate();
for (var i = daysBefore - 1; i >= 0; i--) {
const day = prevMonthDays - i;
days.push({
"day": day,
"month": month - 1,
"year": month === 0 ? year - 1 : year,
"today": false,
"currentMonth": false
});
}
// Current month days
for (var day = 1; day <= daysInMonth; day++) {
const date = new Date(year, month, day);
const isToday = date.getFullYear() === today.getFullYear() && date.getMonth() === today.getMonth() && date.getDate() === today.getDate();
days.push({
"day": day,
"month": month,
"year": year,
"today": isToday,
"currentMonth": true
});
}
// Next month days
for (var i = 1; i <= daysAfter; i++) {
days.push({
"day": i,
"month": month + 1,
"year": month === 11 ? year + 1 : year,
"today": false,
"currentMonth": false
});
}
return days;
}
Repeater {
model: grid.daysModel
Item {
Layout.fillWidth: true
Layout.preferredHeight: Style.baseWidgetSize * 0.9
Rectangle {
width: Style.baseWidgetSize * 0.9
height: Style.baseWidgetSize * 0.9
anchors.centerIn: parent
radius: Style.radiusM
color: modelData.today ? Color.mSecondary : Color.transparent
NText {
anchors.centerIn: parent
text: modelData.day
color: {
if (modelData.today)
return Color.mOnSecondary;
if (modelData.currentMonth)
return Color.mOnSurface;
return Color.mOnSurfaceVariant;
}
opacity: modelData.currentMonth ? 1.0 : 0.4
pointSize: Style.fontSizeM
font.weight: modelData.today ? Style.fontWeightBold : Style.fontWeightMedium
}
// Event indicator dots
Row {
visible: Settings.data.location.showCalendarEvents && parent.parent.parent.parent.hasEventsOnDate(modelData.year, modelData.month, modelData.day)
spacing: 2
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.marginXS
Repeater {
model: parent.parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day)
Rectangle {
width: 4
height: width
radius: width / 2
color: parent.parent.parent.parent.parent.getEventColor(modelData, modelData.today)
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
enabled: Settings.data.location.showCalendarEvents
onEntered: {
const events = parent.parent.parent.parent.getEventsForDate(modelData.year, modelData.month, modelData.day);
if (events.length > 0) {
const summaries = events.map(event => {
if (root.isAllDayEvent(event)) {
return event.summary;
} else {
const timeFormat = Settings.data.location.use12hourFormat ? "hh:mm AP" : "HH:mm";
const start = new Date(event.start * 1000);
const startFormatted = I18n.locale.toString(start, timeFormat);
const end = new Date(event.end * 1000);
const endFormatted = I18n.locale.toString(end, timeFormat);
return `${startFormatted}-${endFormatted} ${event.summary}`;
}
}).join('\n');
TooltipService.show(parent, summaries, "auto", Style.tooltipDelay, Settings.data.ui.fontFixed);
}
}
onClicked: {
const dateWithSlashes = `${(modelData.month + 1).toString().padStart(2, '0')}/${modelData.day.toString().padStart(2, '0')}/${modelData.year.toString().substring(2)}`;
if (ProgramCheckerService.gnomeCalendarAvailable) {
Quickshell.execDetached(["gnome-calendar", "--date", dateWithSlashes]);
root.close();
}
}
onExited: {
TooltipService.hide();
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
}
}
}
}
}
}
}
Component {
id: timerCard
TimerCard {
Layout.fillWidth: true
}
}
Component {
id: weatherCard
WeatherCard {
Layout.fillWidth: true
forecastDays: 5
showLocation: false
}
}
}
}

View File

@@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Modules.Cards
import qs.Modules.MainScreen
import qs.Services.Location
import qs.Services.UI
import qs.Widgets
SmartPanel {
id: root
// Calculate width based on settings
preferredWidth: Math.round((Settings.data.location.showWeekNumberInCalendar ? 460 : 440) * Style.uiScaleRatio)
panelContent: Item {
anchors.fill: parent
// SmartPanel uses this to calculate panel height dynamically
readonly property real contentPreferredHeight: content.implicitHeight + (Style.marginL * 2)
ColumnLayout {
id: content
x: Style.marginL
y: Style.marginL
width: parent.width - (Style.marginL * 2)
spacing: Style.marginL
// All clock panel cards
Repeater {
model: Settings.data.calendar.cards
Loader {
active: modelData.enabled && (modelData.id !== "weather-card" || Settings.data.location.weatherEnabled)
visible: active
Layout.fillWidth: true
sourceComponent: {
switch (modelData.id) {
case "calendar-header-card":
return calendarHeaderCard;
case "calendar-month-card":
return calendarMonthCard;
case "timer-card":
return timerCard;
case "weather-card":
return weatherCard;
default:
return null;
}
}
}
}
}
}
Component {
id: calendarHeaderCard
CalendarHeaderCard {
Layout.fillWidth: true
}
}
Component {
id: calendarMonthCard
CalendarMonthCard {
Layout.fillWidth: true
}
}
Component {
id: timerCard
TimerCard {
Layout.fillWidth: true
}
}
Component {
id: weatherCard
WeatherCard {
Layout.fillWidth: true
forecastDays: 5
showLocation: false
}
}
}

View File

@@ -3,8 +3,8 @@ import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell import Quickshell
import qs.Commons import qs.Commons
import qs.Modules.Cards
import qs.Modules.MainScreen import qs.Modules.MainScreen
import qs.Modules.Panels.ControlCenter.Cards
import qs.Services.Media import qs.Services.Media
import qs.Services.UI import qs.Services.UI
import qs.Widgets import qs.Widgets

View File

@@ -12,14 +12,14 @@ ColumnLayout {
property list<var> cardsModel: [] property list<var> cardsModel: []
property list<var> cardsDefault: [ property list<var> cardsDefault: [
{ {
"id": "banner-card", "id": "calendar-header-card",
"text": I18n.tr("settings.location.calendar.banner.label"), "text": I18n.tr("settings.location.calendar.header.label"),
"enabled": true, "enabled": true,
"required": false "required": false
}, },
{ {
"id": "calendar-card", "id": "calendar-month-card",
"text": I18n.tr("settings.location.calendar.calendar.label"), "text": I18n.tr("settings.location.calendar.month.label"),
"enabled": true, "enabled": true,
"required": true "required": true
}, },

View File

@@ -46,8 +46,8 @@ Item {
target: "calendar" target: "calendar"
function toggle() { function toggle() {
root.withTargetScreen(screen => { root.withTargetScreen(screen => {
var calendarPanel = PanelService.getPanel("calendarPanel", screen); var clockPanel = PanelService.getPanel("clockPanel", screen);
calendarPanel?.toggle(null, "Clock"); clockPanel?.toggle(null, "Clock");
}); });
} }
} }