feat: extended theme switching #87

This commit is contained in:
leukipp
2025-10-07 09:17:57 +02:00
parent 01c7411ddc
commit 5d012bd637
4 changed files with 124 additions and 22 deletions
+8
View File
@@ -72,6 +72,11 @@
align-items: center;
flex: 0 0 auto;
}
li.disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
button {
width: 32px;
height: 32px;
@@ -138,6 +143,9 @@
};
const registerReceiveEvents = () => {
ipcRenderer.on("button-disabled", (e, button) => {
document.getElementById(button.id).parentNode.classList.toggle("disabled", button.disabled);
});
ipcRenderer.on("text-content", (e, text) => {
document.getElementById(text.id).textContent = text.content;
});
+10
View File
@@ -78,6 +78,16 @@
</svg>
</button>
</li>
<li>
<button id="theme">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>compare</title>
<path
d="M19,3H14V5H19V18L14,12V21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M10,18H5L10,12M10,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H10V23H12V1H10V3Z"
/>
</svg>
</button>
</li>
<li>
<button id="keyboard">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
+42 -2
View File
@@ -48,12 +48,15 @@ const init = async () => {
configuration_url: APP.homepage,
};
// Connection settings
// Init options
const masked = password === null ? null : "*".repeat(password.length);
const options = user === null || password === null ? {} : { username: user, password: password };
options.rejectUnauthorized = !("ignore_certificate_errors" in ARGS);
// Client connect
console.log("MQTT Connecting:", `${user}:${masked}@${url.toString()}`);
INTEGRATION.client = mqtt.connect(url.toString(), options);
INTEGRATION.client.setMaxListeners(20);
// Client connected
INTEGRATION.client
@@ -67,6 +70,7 @@ const init = async () => {
initReboot();
initRefresh();
initKiosk();
initTheme();
initDisplay();
initVolume();
initKeyboard();
@@ -97,6 +101,8 @@ const init = async () => {
});
// Update sensor states from events
EVENTS.on("updateStatus", updateKiosk);
EVENTS.on("updateTheme", updateTheme);
EVENTS.on("updateDisplay", updateDisplay);
EVENTS.on("updateVolume", updateVolume);
EVENTS.on("updateKeyboard", updateKeyboard);
@@ -130,7 +136,6 @@ const update = async () => {
}
// Update client sensors
updateKiosk();
updatePageNumber();
updatePageZoom();
updatePageUrl();
@@ -379,6 +384,41 @@ const updateKiosk = async () => {
publishState("kiosk", kiosk);
};
/**
* Initializes the application theme and handles the execute logic.
*/
const initTheme = () => {
const root = `${INTEGRATION.root}/theme`;
const config = {
name: "Theme",
unique_id: `${INTEGRATION.node}_theme`,
command_topic: `${root}/set`,
state_topic: `${root}/state`,
value_template: "{{ value }}",
options: ["Light", "Dark"],
icon: "mdi:compare",
device: INTEGRATION.device,
};
publishConfig("select", config)
.on("message", (topic, message) => {
if (topic === config.command_topic) {
const theme = message.toString();
console.log("Set Application Theme:", theme);
WEBVIEW.theme.set(theme);
}
})
.subscribe(config.command_topic);
updateTheme();
};
/**
* Updates the application theme via the mqtt connection.
*/
const updateTheme = async () => {
const theme = WEBVIEW.theme.get();
publishState("theme", theme.charAt(0).toUpperCase() + theme.slice(1));
};
/**
* Initializes the display status, brightness and handles the execute logic.
*/
+64 -20
View File
@@ -54,15 +54,37 @@ const init = async () => {
WEBVIEW.viewActive = 0;
WEBVIEW.viewUrls = urls;
WEBVIEW.viewZoom = zoom;
WEBVIEW.viewTheme = theme;
WEBVIEW.pagerEnabled = widget;
WEBVIEW.widgetTheme = theme;
WEBVIEW.widgetEnabled = widget;
WEBVIEW.statusTheme = theme;
WEBVIEW.statusEnabled = true;
WEBVIEW.navigationTheme = theme;
WEBVIEW.pagerEnabled = widget;
WEBVIEW.widgetEnabled = widget;
WEBVIEW.navigationEnabled = widget;
nativeTheme.themeSource = WEBVIEW.viewTheme;
// Init global theme
WEBVIEW.theme = {
set: (name) => {
nativeTheme.themeSource = name.toLowerCase();
},
get: () => {
return nativeTheme.shouldUseDarkColors ? "dark" : "light";
},
toggle: () => {
nativeTheme.themeSource = nativeTheme.shouldUseDarkColors ? "light" : "dark";
},
register: () => {
nativeTheme.on("updated", () => {
const theme = WEBVIEW.theme.get();
WEBVIEW.statusTheme = theme;
WEBVIEW.widgetTheme = theme;
WEBVIEW.navigationTheme = theme;
if (WEBVIEW.initialized) {
resizeView();
}
EVENTS.emit("updateTheme");
});
},
};
WEBVIEW.theme.register();
WEBVIEW.theme.set(theme);
// Init global root window
WEBVIEW.display = screen.getPrimaryDisplay().workAreaSize;
@@ -190,10 +212,7 @@ const updateView = () => {
// Update window title
console.log(`Update View: ${title}`);
WEBVIEW.status.webContents.send("text-content", {
id: "title",
content: title,
});
WEBVIEW.status.webContents.send("text-content", { id: "title", content: title });
WEBVIEW.window.setTitle(title);
// Hide all other webviews and show only the active one
@@ -628,7 +647,7 @@ const windowEvents = async () => {
const delta = (now - then) / 1000;
// Auto-hide navigation
if (delta > 60) {
if (delta > 60 && WEBVIEW.tracker.status !== "Terminated") {
toggleNavigation("OFF");
}
}, 1000);
@@ -675,6 +694,9 @@ const widgetEvents = async () => {
// Handle widget button click events
ipcMain.on("button-click", (e, button) => {
switch (button.id) {
case "theme":
WEBVIEW.theme.toggle();
break;
case "keyboard":
const toggle = hardware.getKeyboardVisibility() === "ON" ? "OFF" : "ON";
hardware.setKeyboardVisibility(toggle, () => {
@@ -711,14 +733,23 @@ const statusEvents = async () => {
WEBVIEW.window.setStatus("Minimized");
break;
case "terminate":
WEBVIEW.status.webContents.send("button-disabled", { id: "fullscreen", disabled: true });
WEBVIEW.status.webContents.send("button-disabled", { id: "minimize", disabled: true });
WEBVIEW.status.webContents.send("button-disabled", { id: "terminate", disabled: true });
const button = dialog.showMessageBoxSync(WEBVIEW.window, {
type: "question",
title: "Confirm",
message: `\nExit ${APP.title}?`,
buttons: ["No", "Yes"],
});
if (button === 1) {
WEBVIEW.window.setStatus("Terminated");
switch (button) {
case 1:
WEBVIEW.window.setStatus("Terminated");
break;
default:
WEBVIEW.status.webContents.send("button-disabled", { id: "fullscreen", disabled: false });
WEBVIEW.status.webContents.send("button-disabled", { id: "minimize", disabled: false });
WEBVIEW.status.webContents.send("button-disabled", { id: "terminate", disabled: false });
}
break;
}
@@ -863,7 +894,7 @@ const viewEvents = async () => {
if (WEBVIEW.viewActive === 0 && ready.length === WEBVIEW.views.length) {
nextView();
}
view.webContents.loadURL(errorHtml(code, text, url));
view.webContents.loadURL(errorHtml(code, text, url, WEBVIEW.theme.get()));
}
});
@@ -1089,9 +1120,14 @@ const loaderHtml = (size, speed, theme) => {
* @param {number} code - The error code of the response.
* @param {string} text - The error text of the response.
* @param {string} url - The url of the requested page.
* @param {string} theme - The theme used for text colors.
* @returns {string} A data string with the generated html.
*/
const errorHtml = (code, text, url) => {
const errorHtml = (code, text, url, theme) => {
const color = {
dark: { icon: "#FFA500", text: "#E5E5E5", background: "#111111" },
light: { icon: "#FFA500", text: "#1A1A1A", background: "#FAFAFA" },
}[theme];
const html = `
<html>
<head>
@@ -1099,17 +1135,25 @@ const errorHtml = (code, text, url) => {
body {
display: flex;
align-items: center;
text-align: center;
justify-content: center;
font-family: sans-serif;
text-align: center;
background-color: ${color.background};
}
.icon {
margin: 0;
font-size: 5rem;
color: orange;
color: ${color.icon};
}
.title {
margin: 0;
color: ${color.text};
}
.url {
color: ${color.text};
}
.error {
color: ${color.text};
}
</style>
</head>
@@ -1117,8 +1161,8 @@ const errorHtml = (code, text, url) => {
<div>
<p class="icon">&#9888;</p>
<h1 class="title">Whoopsie!</h1>
<p><strong>Loading:</strong> ${url}</p>
<p><strong>Error:</strong> ${text} (${code})</p>
<p class="url"><strong>Loading:</strong> ${url}</p>
<p class="error"><strong>Error:</strong> ${text} (${code})</p>
</div>
</body>
</html>`;