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
+44
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;
}
}
+15
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 {}
}
+46 -30
View File
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Io
import "../Helpers/QtObj2JS.js" as QtObj2JS
import qs.Commons
import qs.Commons.Migrations
import qs.Modules.OSD
import qs.Services.UI
@@ -21,7 +22,7 @@ Singleton {
- Default cache directory: ~/.cache/noctalia
*/
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 string shellName: "noctalia"
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) {
Logger.i("Settings", "Settings loaded");
// -----------------
// Run versioned migrations from MigrationRegistry
runVersionedMigrations();
upgradeSettingsData();
root.isLoaded = true;
@@ -267,11 +272,11 @@ Singleton {
property JsonObject calendar: JsonObject {
property list<var> cards: [
{
"id": "banner-card",
"id": "calendar-header-card",
"enabled": true
},
{
"id": "calendar-card",
"id": "calendar-month-card",
"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 upgradeWidget(widget) {
@@ -677,31 +717,7 @@ Singleton {
const sections = ["left", "center", "right"];
// -----------------
// 1st. convert old widget id to new id
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
// 1. remove any non existing widget type
var removedWidget = false;
for (var s = 0; s < sections.length; 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++) {
const sectionName = sections[s];
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 (removedWidget) {
var gotControlCenter = false;