NEWS
Light-Mode / Dark-Mode
-
Hallo Zusammen
Ich habe mich in den letzten Tag ein wenig mit dem automatischen switch von Light-Mode zu Dark-Mode beschäftigt.
Würde gerne mein Stand mit euch Teilen und freue mich über Anregungen oder Verbesserungen.Ich nutze vorwiegend @Scrounger Material Design Widgets.
Als erstes habe ich mir im JS-Adapter ein Script erstellt, welches anhand der Tageszeit ein Datenpunkt befüllt
// imports NPM Module const moment = require("moment"); moment.locale("de"); // Skript Einstellungen ************************************************************************************************************************************************* let debug = true; // Debug Meldungen anzeigen let strukturChannel = '0_userdata.0.vis.Scripts.Control'; // Übergeordnete Id, wo die Datenpunkte angelegt werden sollen let idColorDatenpunkt = `${strukturChannel}.datapoint_color` createControlDatapoints(); schedule({hour: 08, minute: 00}, lightmode ); schedule({hour: 19, minute: 30}, darkmode ); function darkmode() { setState(idColorDatenpunkt, 'darkmode'); } function lightmode() { setState(idColorDatenpunkt, 'lightmode'); } function createControlDatapoints() { if (!existsState(idColorDatenpunkt)) { if (debug) console.debug(`[createControlDatapoints]: create datapoint '${idColorDatenpunkt}'`); mySetState(idColorDatenpunkt, 'lightmode', 'string', `Light or Dark Mode`); } } function mySetState(id, val, type, name) { if (existsState(id)) { if (debug) console.debug(`[mySetState]: history colore data stored to '${id}'`); setState(id, val, true); } else { if (debug) console.debug(`[mySetState]: create datapoint '${id}'`); createUserStates(false, [ id, { 'name': name, 'type': type, 'read': true, 'write': true } ], function () { setState(id, val, true); }); } } /** * Create states under 0_userdata.0 or javascript.x * Current Version: https://github.com/Mic-M/iobroker.createUserStates * Support: https://forum.iobroker.net/topic/26839/ * Autor: Mic (ioBroker) | Mic-M (github) * Version: 1.1 (26 January 2020) * Example: see https://github.com/Mic-M/iobroker.createUserStates#beispiel * ----------------------------------------------- * PLEASE NOTE: Per https://github.com/ioBroker/ioBroker.javascript/issues/474, the used function setObject() * executes the callback PRIOR to completing the state creation. Therefore, we use a setTimeout and counter. * ----------------------------------------------- * @param {boolean} force Force state creation (overwrite), if state is existing. * @param {array} statesToCreate State(s) to create. single array or array of arrays * @param {object} [callback] Optional: a callback function -- This provided function will be executed after all states are created. */ function createUserStates(force, statesToCreate, callback = undefined) { const where = '0_userdata.0' const WARN = false; // Only for 0_userdata.0: Throws warning in log, if state is already existing and force=false. Default is false, so no warning in log, if state exists. const LOG_DEBUG = false; // To debug this function, set to true // Per issue #474 (https://github.com/ioBroker/ioBroker.javascript/issues/474), the used function setObject() executes the callback // before the state is actual created. Therefore, we use a setTimeout and counter as a workaround. const DELAY = 50; // Delay in milliseconds (ms). Increase this to 100, if it is not working. // Validate "where" if (where.endsWith('.')) where = where.slice(0, -1); // Remove trailing dot if ((where.match(/^((javascript\.([1-9][0-9]|[0-9]))$|0_userdata\.0$)/) == null)) { log('This script does not support to create states under [' + where + ']', 'error'); return; } // Prepare "statesToCreate" since we also allow a single state to create if (!Array.isArray(statesToCreate[0])) statesToCreate = [statesToCreate]; // wrap into array, if just one array and not inside an array // Add "where" to STATES_TO_CREATE for (let i = 0; i < statesToCreate.length; i++) { let lpPath = statesToCreate[i][0].replace(/\.*\./g, '.'); // replace all multiple dots like '..', '...' with a single '.' lpPath = lpPath.replace(/^((javascript\.([1-9][0-9]|[0-9])\.)|0_userdata\.0\.)/, '') // remove any javascript.x. / 0_userdata.0. from beginning lpPath = where + '.' + lpPath; // add where to beginning of string statesToCreate[i][0] = lpPath; } if (where != '0_userdata.0') { // Create States under javascript.x let numStates = statesToCreate.length; statesToCreate.forEach(function (loopParam) { if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']'); let loopInit = (loopParam[1]['def'] == undefined) ? null : loopParam[1]['def']; // mimic same behavior as createState if no init value is provided createState(loopParam[0], loopInit, force, loopParam[1], function () { numStates--; if (numStates === 0) { if (LOG_DEBUG) log('[Debug] All states processed.'); if (typeof callback === 'function') { // execute if a function was provided to parameter callback if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided'); return callback(); } else { return; } } }); }); } else { // Create States under 0_userdata.0 let numStates = statesToCreate.length; let counter = -1; statesToCreate.forEach(function (loopParam) { counter += 1; if (LOG_DEBUG) log('[Debug] Currently processing following state: [' + loopParam[0] + ']'); if (($(loopParam[0]).length > 0) && (existsState(loopParam[0]))) { // Workaround due to https://github.com/ioBroker/ioBroker.javascript/issues/478 // State is existing. if (WARN && !force) log('State [' + loopParam[0] + '] is already existing and will no longer be created.', 'warn'); if (!WARN && LOG_DEBUG) log('[Debug] State [' + loopParam[0] + '] is already existing. Option force (=overwrite) is set to [' + force + '].'); if (!force) { // State exists and shall not be overwritten since force=false // So, we do not proceed. numStates--; if (numStates === 0) { if (LOG_DEBUG) log('[Debug] All states successfully processed!'); if (typeof callback === 'function') { // execute if a function was provided to parameter callback if (LOG_DEBUG) log('[Debug] An optional callback function was provided, which we are going to execute now.'); return callback(); } } else { // We need to go out and continue with next element in loop. return; // https://stackoverflow.com/questions/18452920/continue-in-cursor-foreach } } // if(!force) } // State is not existing or force = true, so we are continuing to create the state through setObject(). let obj = {}; obj.type = 'state'; obj.native = {}; obj.common = loopParam[1]; setObject(loopParam[0], obj, function (err) { if (err) { log('Cannot write object for state [' + loopParam[0] + ']: ' + err); } else { if (LOG_DEBUG) log('[Debug] Now we are creating new state [' + loopParam[0] + ']') let init = null; if (loopParam[1].def === undefined) { if (loopParam[1].type === 'number') init = 0; if (loopParam[1].type === 'boolean') init = false; if (loopParam[1].type === 'string') init = ''; } else { init = loopParam[1].def; } setTimeout(function () { setState(loopParam[0], init, true, function () { if (LOG_DEBUG) log('[Debug] setState durchgeführt: ' + loopParam[0]); numStates--; if (numStates === 0) { if (LOG_DEBUG) log('[Debug] All states processed.'); if (typeof callback === 'function') { // execute if a function was provided to parameter callback if (LOG_DEBUG) log('[Debug] Function to callback parameter was provided'); return callback(); } } }); }, DELAY + (20 * counter)); } }); }); } }
Dieser Datenpunkt wird verwendet um z.B den Zustand auch den Charts übergeben zu können, da diese nicht vollständig übers CSS angepasst werden können. Im Editor hab ich folgendes hinterlegt:
{wert:0_userdata.0.vis.Scripts.Control.datapoint_color;(wert =="lightmode") ? "#505050" : (wert =="darkmode") ? "#ffffff" : "#ffffff"}
Da leider der JS-Adapter nicht direkt auf das CSS zugreifen kann, nutze ich das Register Skript im Editor für einen zusätzlichen Workaround:
vis.registerOnChange(status_callback); vis.subscribing.IDs.push("0_userdata.0.vis.Scripts.Control.datapoint_color"); function status_callback(arg, id, val, ack) { if (val=="lightmode") document.documentElement.setAttribute('data-theme', 'light'); localStorage.setItem('theme', 'light'); if (val=="darkmode") document.documentElement.setAttribute('data-theme', 'dark'); localStorage.setItem('theme', 'dark'); console.log('Status', 'Id:' + id+', Val:'+val); } let objID = '0_userdata.0.vis.Scripts.Control.datapoint_color'; servConn.getStates(objID, (error, states) => { let stateValue = states[objID].val; if (stateValue=="lightmode") document.documentElement.setAttribute('data-theme', 'light'); localStorage.setItem('theme', 'light'); if (stateValue=="darkmode") document.documentElement.setAttribute('data-theme', 'dark'); localStorage.setItem('theme', 'dark'); console.log('Status', 'Id:' + id+', Val:'+val); });
Das CSS sieht wie folgt aus:
/* Design-time light-Theme */ :root[data-theme="light"] { --main-background: #ffffff; --content-background: #f5f5f5; --content-background-hover: #505050; --design-font-color: #505050; --design-font-color-hover: #ffffff; --design-fontsize : 0.9em; --filter-svg:; --filter-svg-hover: brightness(0) invert(1); --border-color: #e0e0e0; } /* Design-time dark-Theme */ :root[data-theme="dark"] { --main-background: #000000; --content-background: #505050; --content-background-hover: #808080; --design-font-color: #ffffff; --design-font-color-hover: #505050; --design-fontsize : 0.9em; --filter-svg: brightness(0) invert(1); --filter-svg-hover: ; --border-color: #404040; }
Ich habe den grössten Teil Design-Teil im CSS und mache nur das nötigste im Editor (dies vorallem, da ich aktuell auf Rund 180 Seiten bin und bei kleinen Änderungen nicht alle Seiten öffnen möchte für Anpassungen.
Bei mir Funktioniert dies wunderbar.Hier mal noch ein grosses Lob an @Scrounger und seine Widgets --> Hammer!!
Viel spass beim ausprobieren und für allfällige Feedbacks...
-
coole Geschichte! Der Mode wird aktuell gemäss einer fixen Zeit aus dem schedule erstellt. Eine Idee wäre auch, den Status des "dark modes" direkt aus dem Browser auszulesen, was meinst du?