1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-09 06:58:29 -04:00

feat(theme): add extract dynamic theme to JSON file (#3009)

* feat(theme): add extract current theme to JSON file

Adds an 'Extract' button in Settings > Theme Color that exports the
current theme (generic/dynamic/custom/registry) to a dual-mode dark+light
JSON file via the DMS file save dialog.

* refactor(theme): simplify extract button to single DankButton

* refactor(theme): move extract button inside dynamic theme section

* fix(theme): address review feedback on extract theme save

* refactor: utilize dankActionButton

---------

(cherry picked from commit bde4decc5d)
This commit is contained in:
Huỳnh Thiện Lộc
2026-08-08 11:31:59 +07:00
committed by dms-ci[bot]
parent 0313e96e8e
commit f971b6bcff
2 changed files with 144 additions and 1 deletions
+80
View File
@@ -436,6 +436,10 @@ Singleton {
function getMatugenColor(path, fallback) { function getMatugenColor(path, fallback) {
const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark"; const colorMode = (typeof SessionData !== "undefined" && SessionData.isLightMode) ? "light" : "dark";
return getMatugenColorForMode(colorMode, path, fallback);
}
function getMatugenColorForMode(colorMode, path, fallback) {
let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode]; let cur = matugenColors && matugenColors.colors && matugenColors.colors[colorMode];
for (const part of path.split(".")) { for (const part of path.split(".")) {
if (!cur || typeof cur !== "object" || !(part in cur)) if (!cur || typeof cur !== "object" || !(part in cur))
@@ -445,6 +449,82 @@ Singleton {
return cur || fallback; return cur || fallback;
} }
function extractCurrentTheme(themeName) {
var name = themeName || "Extracted Theme";
var dark = {};
var light = {};
if (currentTheme === dynamic) {
dark = buildExtractedDynamicMode("dark", name + " Dark");
light = buildExtractedDynamicMode("light", name + " Light");
} else if (currentTheme === custom && customThemeRawData) {
var rawDark = customThemeRawData.dark || null;
var rawLight = customThemeRawData.light || null;
if (rawDark) {
dark = JSON.parse(JSON.stringify(rawDark));
if (!dark.name)
dark.name = name + " Dark";
} else if (rawLight) {
dark = buildExtractedDynamicMode("dark", name + " Dark");
} else {
dark = currentThemeData ? JSON.parse(JSON.stringify(currentThemeData)) : {};
dark.name = name + " Dark";
}
if (rawLight) {
light = JSON.parse(JSON.stringify(rawLight));
if (!light.name)
light.name = name + " Light";
} else if (rawDark) {
light = buildExtractedDynamicMode("light", name + " Light");
} else {
light = currentThemeData ? JSON.parse(JSON.stringify(currentThemeData)) : {};
light.name = name + " Light";
}
} else {
var darkTheme = StockThemes.getThemeByName(currentTheme, false);
var lightTheme = StockThemes.getThemeByName(currentTheme, true);
dark = darkTheme ? JSON.parse(JSON.stringify(darkTheme)) : {};
light = lightTheme ? JSON.parse(JSON.stringify(lightTheme)) : {};
dark.name = name + " Dark";
light.name = name + " Light";
}
return JSON.stringify({
dark: dark,
light: light
}, null, 2);
}
function buildExtractedDynamicMode(colorMode, name) {
return {
"name": name,
"primary": getMatugenColorForMode(colorMode, "primary", "#42a5f5"),
"primaryText": getMatugenColorForMode(colorMode, "on_primary", "#ffffff"),
"primaryContainer": getMatugenColorForMode(colorMode, "primary_container", "#1976d2"),
"secondary": getMatugenColorForMode(colorMode, "secondary", "#8ab4f8"),
"secondaryContainer": getMatugenColorForMode(colorMode, "secondary_container", getMatugenColorForMode(colorMode, "surface_container_high", "#292b2f")),
"tertiary": getMatugenColorForMode(colorMode, "tertiary", "#efb8c8"),
"tertiaryContainer": getMatugenColorForMode(colorMode, "tertiary_container", getMatugenColorForMode(colorMode, "surface_container_high", "#292b2f")),
"surface": getMatugenColorForMode(colorMode, "surface", "#1a1c1e"),
"surfaceText": getMatugenColorForMode(colorMode, "on_background", "#e3e8ef"),
"surfaceVariant": getMatugenColorForMode(colorMode, "surface_variant", "#44464f"),
"surfaceVariantText": getMatugenColorForMode(colorMode, "on_surface_variant", "#c4c7c5"),
"surfaceTint": getMatugenColorForMode(colorMode, "surface_tint", "#8ab4f8"),
"background": getMatugenColorForMode(colorMode, "background", "#1a1c1e"),
"backgroundText": getMatugenColorForMode(colorMode, "on_background", "#e3e8ef"),
"outline": getMatugenColorForMode(colorMode, "outline", "#8e918f"),
"surfaceContainerLowest": getMatugenColorForMode(colorMode, "surface_container_lowest", "#0e1013"),
"surfaceContainerLow": getMatugenColorForMode(colorMode, "surface_container_low", "#181a1d"),
"surfaceContainer": getMatugenColorForMode(colorMode, "surface_container", "#1e2023"),
"surfaceContainerHigh": getMatugenColorForMode(colorMode, "surface_container_high", "#292b2f"),
"surfaceContainerHighest": getMatugenColorForMode(colorMode, "surface_container_highest", "#343740"),
"error": getMatugenColorForMode(colorMode, "error", "#F2B8B5"),
"warning": "#FF9800",
"info": "#2196F3",
"success": "#4CAF50"
};
}
readonly property var currentThemeData: { readonly property var currentThemeData: {
if (currentTheme === "custom") { if (currentTheme === "custom") {
return customThemeData || StockThemes.getThemeByName("purple", isLightMode); return customThemeData || StockThemes.getThemeByName("purple", isLightMode);
+64 -1
View File
@@ -1,6 +1,7 @@
import QtCore import QtCore
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io
import Quickshell.Widgets import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modals.FileBrowser import qs.Modals.FileBrowser
@@ -13,6 +14,7 @@ Item {
id: themeColorsTab id: themeColorsTab
property var parentModal: null property var parentModal: null
property string pendingExtractJson: ""
readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive readonly property bool connectedFrameModeActive: SettingsData.connectedFrameModeActive
readonly property bool frameModeActive: SettingsData.frameEnabled readonly property bool frameModeActive: SettingsData.frameEnabled
property var cachedIconThemes: SettingsData.availableIconThemes property var cachedIconThemes: SettingsData.availableIconThemes
@@ -564,7 +566,7 @@ Item {
} }
Column { Column {
width: parent.width - 120 - Theme.spacingM width: parent.width - 120 - Theme.spacingM - 36 - Theme.spacingM
spacing: Theme.spacingS spacing: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -586,6 +588,7 @@ Item {
} }
StyledText { StyledText {
id: wallpaperPathText
text: { text: {
if (ToastService.wallpaperErrorStatus === "error") if (ToastService.wallpaperErrorStatus === "error")
return I18n.tr("Wallpaper processing failed", "wallpaper processing error"); return I18n.tr("Wallpaper processing failed", "wallpaper processing error");
@@ -603,6 +606,22 @@ Item {
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
} }
DankActionButton {
buttonSize: 36
iconName: "download"
iconSize: Theme.iconSize
backgroundColor: Theme.primaryHover
iconColor: Theme.primary
tooltipText: I18n.tr("Extract theme to JSON", "extract theme tooltip")
anchors.bottom: wallpaperPathText.bottom
onClicked: {
pendingExtractJson = Theme.extractCurrentTheme();
saveBrowserLoader.active = true;
if (saveBrowserLoader.item)
saveBrowserLoader.item.open();
}
}
} }
SettingsDropdownRow { SettingsDropdownRow {
@@ -2977,6 +2996,28 @@ Item {
} }
} }
LazyLoader {
id: saveBrowserLoader
active: false
FileBrowserSurfaceModal {
id: saveBrowser
browserTitle: I18n.tr("Save Extracted Theme", "extract theme save dialog title")
browserIcon: "download"
browserType: "default"
fileExtensions: ["*.json"]
allowStacking: true
saveMode: true
defaultFileName: "dms-extracted-theme.json"
onFileSelected: path => {
saveExtractedTheme(pendingExtractJson, Paths.strip(path));
close();
}
}
}
LazyLoader { LazyLoader {
id: themeBrowserLoader id: themeBrowserLoader
active: false active: false
@@ -2992,4 +3033,26 @@ Item {
if (themeBrowserLoader.item) if (themeBrowserLoader.item)
themeBrowserLoader.item.show(); themeBrowserLoader.item.show();
} }
FileView {
id: extractSaveFileView
blockWrites: true
preload: false
atomicWrites: true
printErrors: true
onSaved: {
ToastService.showInfo(I18n.tr("Theme extracted to: %1", "extract theme success").arg(Paths.strip(extractSaveFileView.path)));
}
onSaveFailed: error => {
ToastService.showError(I18n.tr("Failed to extract theme", "extract theme error"));
log.warn("Failed to write extracted theme to " + extractSaveFileView.path + ": " + error);
}
}
function saveExtractedTheme(json, outputPath) {
extractSaveFileView.path = outputPath;
extractSaveFileView.setText(json);
}
} }