Metainformationen zur Seite
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen RevisionVorhergehende Überarbeitung | |||
| iobroker:countdown_script_fuer_vis1 [12.10.2026] – gelöscht kolja | iobroker:countdown_script_fuer_vis1 [12.10.2026] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1 | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| + | 🔔 Countdown-Widget (ioBroker VIS + MagicMirror) | ||
| + | Dieses Widget zeigt automatisch die zwei nächsten anstehenden Countdowns an | ||
| + | (z. B. Weihnachten, | ||
| + | |||
| + | Abgelaufene Countdowns werden automatisch ausgeblendet. | ||
| + | |||
| + | ⚠️ WICHTIGE HINWEISE (bitte lesen!) | ||
| + | |||
| + | JEDER Countdown muss im versteckten Subscribe-Block stehen | ||
| + | |||
| + | KEINE Einzeiler mit { ... } im JavaScript | ||
| + | |||
| + | Kein Array / kein Objekt-Literal | ||
| + | |||
| + | Code-Formatierung NICHT verändern | ||
| + | |||
| + | Sonst erscheint: | ||
| + | |||
| + | SyntaxError: | ||
| + | |||
| + | 🧩 1. Subscribe-Block (Pflicht!) | ||
| + | |||
| + | Hier meldet VIS alle benötigten Datenpunkte an. | ||
| + | |||
| + | <div style=" | ||
| + | {countdown.0.countdowns.Weihnachten.fullJson} | ||
| + | {countdown.0.countdowns.Silvester.fullJson} | ||
| + | {countdown.0.countdowns.SommerurlaubItalien.fullJson} | ||
| + | </ | ||
| + | |||
| + | |||
| + | 👉 Für jeden neuen Countdown MUSS hier eine Zeile ergänzt werden! | ||
| + | |||
| + | 🖼️ 2. Anzeige-HTML (2 Slots) | ||
| + | <div style=" | ||
| + | <div id=" | ||
| + | Lade... | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | |||
| + | <div id=" | ||
| + | Lade... | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | |||
| + | Die IDs bleiben absichtlich so – sie sind MagicMirror-erprobt. | ||
| + | |||
| + | 🧠 3. JavaScript – Finale Version | ||
| + | |||
| + | ⚠️ NICHT kürzen, NICHT umformatieren | ||
| + | < | ||
| + | |||
| + | <!-- ========================================================== | ||
| + | VIS Subscribe-Block (Pflicht!) | ||
| + | Jede OID, die du unten nutzt, MUSS hier stehen. | ||
| + | | ||
| + | <div style=" | ||
| + | {countdown.0.countdowns.Weihnachten.fullJson} | ||
| + | {countdown.0.countdowns.Silvester.fullJson} | ||
| + | {countdown.0.countdowns.SommerurlaubItalien.fullJson} | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | <div id=" | ||
| + | Lade... | ||
| + | </ | ||
| + | |||
| + | <div style=" | ||
| + | |||
| + | <div id=" | ||
| + | Lade... | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | (function () | ||
| + | { | ||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | |||
| + | const EMPTY_TEXT = " | ||
| + | |||
| + | // Wenn TRUE: Stunden werden ausgeblendet, | ||
| + | // (für days === 1 werden Stunden weiterhin angezeigt) | ||
| + | const HIDE_HOURS_IF_MORE_THAN_ONE_DAY = true; | ||
| + | |||
| + | // Wieviele Events sind AKTIV konfiguriert (max 8) | ||
| + | const EVENT_COUNT = 3; | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | TYPE: | ||
| + | 1 = Weihnachten (Speziallogik) | ||
| + | 2 = Silvester | ||
| + | 3 = Generisch | ||
| + | | ||
| + | |||
| + | // ---------------- Event 1 ---------------- | ||
| + | const E1_OID | ||
| + | const E1_TYPE | ||
| + | const E1_TITLE = ""; | ||
| + | const E1_EMOJI = ""; | ||
| + | |||
| + | // ---------------- Event 2 ---------------- | ||
| + | const E2_OID | ||
| + | const E2_TYPE | ||
| + | const E2_TITLE = ""; | ||
| + | const E2_EMOJI = ""; | ||
| + | |||
| + | // ---------------- Event 3 ---------------- | ||
| + | const E3_OID | ||
| + | const E3_TYPE | ||
| + | const E3_TITLE = " | ||
| + | const E3_EMOJI = " | ||
| + | |||
| + | // ---------------- Event 4..8 (Vorlagen) ---------------- | ||
| + | const E4_OID | ||
| + | const E4_TYPE | ||
| + | const E4_TITLE = ""; | ||
| + | const E4_EMOJI = ""; | ||
| + | |||
| + | const E5_OID | ||
| + | const E5_TYPE | ||
| + | const E5_TITLE = ""; | ||
| + | const E5_EMOJI = ""; | ||
| + | |||
| + | const E6_OID | ||
| + | const E6_TYPE | ||
| + | const E6_TITLE = ""; | ||
| + | const E6_EMOJI = ""; | ||
| + | |||
| + | const E7_OID | ||
| + | const E7_TYPE | ||
| + | const E7_TITLE = ""; | ||
| + | const E7_EMOJI = ""; | ||
| + | |||
| + | const E8_OID | ||
| + | const E8_TYPE | ||
| + | const E8_TITLE = ""; | ||
| + | const E8_EMOJI = ""; | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | |||
| + | function safeNum(x, fallback) | ||
| + | { | ||
| + | const n = Number(x); | ||
| + | |||
| + | if (Number.isFinite(n)) | ||
| + | { | ||
| + | return n; | ||
| + | } | ||
| + | |||
| + | return fallback; | ||
| + | } | ||
| + | |||
| + | function parseFullJson(val) | ||
| + | { | ||
| + | if (val === null || val === undefined || val === "" | ||
| + | { | ||
| + | return null; | ||
| + | } | ||
| + | |||
| + | try | ||
| + | { | ||
| + | let t = val; | ||
| + | |||
| + | if (typeof t === " | ||
| + | { | ||
| + | t = t.trim(); | ||
| + | |||
| + | if (t.startsWith('"' | ||
| + | { | ||
| + | t = JSON.parse(t); | ||
| + | } | ||
| + | |||
| + | return JSON.parse(t); | ||
| + | } | ||
| + | |||
| + | return t; | ||
| + | } | ||
| + | catch (e) | ||
| + | { | ||
| + | return null; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | function subline(s) | ||
| + | { | ||
| + | return "< | ||
| + | } | ||
| + | |||
| + | function dayWord(n) | ||
| + | { | ||
| + | if (n === 1) | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | |||
| + | return " | ||
| + | } | ||
| + | |||
| + | function hourWord(n) | ||
| + | { | ||
| + | if (n === 1) | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | |||
| + | return " | ||
| + | } | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | |||
| + | function getOid(i) | ||
| + | { | ||
| + | if (i === 1) return E1_OID; | ||
| + | if (i === 2) return E2_OID; | ||
| + | if (i === 3) return E3_OID; | ||
| + | if (i === 4) return E4_OID; | ||
| + | if (i === 5) return E5_OID; | ||
| + | if (i === 6) return E6_OID; | ||
| + | if (i === 7) return E7_OID; | ||
| + | if (i === 8) return E8_OID; | ||
| + | |||
| + | return ""; | ||
| + | } | ||
| + | |||
| + | function getType(i) | ||
| + | { | ||
| + | if (i === 1) return E1_TYPE; | ||
| + | if (i === 2) return E2_TYPE; | ||
| + | if (i === 3) return E3_TYPE; | ||
| + | if (i === 4) return E4_TYPE; | ||
| + | if (i === 5) return E5_TYPE; | ||
| + | if (i === 6) return E6_TYPE; | ||
| + | if (i === 7) return E7_TYPE; | ||
| + | if (i === 8) return E8_TYPE; | ||
| + | |||
| + | return 0; | ||
| + | } | ||
| + | |||
| + | function getTitle(i) | ||
| + | { | ||
| + | if (i === 1) return E1_TITLE; | ||
| + | if (i === 2) return E2_TITLE; | ||
| + | if (i === 3) return E3_TITLE; | ||
| + | if (i === 4) return E4_TITLE; | ||
| + | if (i === 5) return E5_TITLE; | ||
| + | if (i === 6) return E6_TITLE; | ||
| + | if (i === 7) return E7_TITLE; | ||
| + | if (i === 8) return E8_TITLE; | ||
| + | |||
| + | return ""; | ||
| + | } | ||
| + | |||
| + | function getEmoji(i) | ||
| + | { | ||
| + | if (i === 1) return E1_EMOJI; | ||
| + | if (i === 2) return E2_EMOJI; | ||
| + | if (i === 3) return E3_EMOJI; | ||
| + | if (i === 4) return E4_EMOJI; | ||
| + | if (i === 5) return E5_EMOJI; | ||
| + | if (i === 6) return E6_EMOJI; | ||
| + | if (i === 7) return E7_EMOJI; | ||
| + | if (i === 8) return E8_EMOJI; | ||
| + | |||
| + | return ""; | ||
| + | } | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | |||
| + | function remainingSeconds(val) | ||
| + | { | ||
| + | const d = parseFullJson(val); | ||
| + | |||
| + | if (!d) | ||
| + | { | ||
| + | return 999999999; | ||
| + | } | ||
| + | |||
| + | if (d.total && d.total.seconds !== undefined) | ||
| + | { | ||
| + | return safeNum(d.total.seconds, | ||
| + | } | ||
| + | |||
| + | const days = safeNum((d.total && d.total.days | ||
| + | const hours = safeNum((d.total && d.total.hours !== undefined) ? d.total.hours : d.hours, 0); | ||
| + | const mins = safeNum((d.total && d.total.minutes !== undefined) ? d.total.minutes : d.minutes, 0); | ||
| + | const secs = safeNum((d.total && d.total.seconds !== undefined) ? d.total.seconds : d.seconds, 0); | ||
| + | |||
| + | return (days * 86400) + (hours * 3600) + (mins * 60) + secs; | ||
| + | } | ||
| + | |||
| + | function isExpired(val) | ||
| + | { | ||
| + | return (remainingSeconds(val) <= 0); | ||
| + | } | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | Gibt nur den " | ||
| + | - "3 Tagen" (wenn days>1 und hideHours aktiv) | ||
| + | - "1 Tag und 5 Stunden" | ||
| + | | ||
| + | |||
| + | function formatDaysHours(days, | ||
| + | { | ||
| + | // Wenn konfiguriert: | ||
| + | if (HIDE_HOURS_IF_MORE_THAN_ONE_DAY) | ||
| + | { | ||
| + | if (days > 1) | ||
| + | { | ||
| + | return String(days) + " " + dayWord(days); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Standard: Tage + Stunden | ||
| + | return String(days) + " " + dayWord(days) + " und " + String(hours) + " " + hourWord(hours); | ||
| + | } | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | |||
| + | function renderWeihnachten(val) | ||
| + | { | ||
| + | const data = parseFullJson(val); | ||
| + | const now = new Date(); | ||
| + | |||
| + | if (now.getMonth() === 11 && now.getDate() >= 24 && now.getDate() <= 26) | ||
| + | { | ||
| + | return "🎄 Frohe Weihnachten 🎄< | ||
| + | } | ||
| + | |||
| + | if (!data) | ||
| + | { | ||
| + | return EMPTY_TEXT; | ||
| + | } | ||
| + | |||
| + | const days = safeNum((data.total && data.total.days !== undefined) ? data.total.days : data.days, 0); | ||
| + | const hours = safeNum((data.hours !== undefined) ? data.hours : (data.total ? data.total.hours : undefined), 0); | ||
| + | const words = (data.inWords && data.inWords.long) ? data.inWords.long : ""; | ||
| + | |||
| + | if (now.getMonth() === 11 && now.getDate() === 23) | ||
| + | { | ||
| + | return "🎅 Weihnachten morgen" | ||
| + | } | ||
| + | |||
| + | if (days >= 1) | ||
| + | { | ||
| + | return "🎁 Weihnachten in " + formatDaysHours(days, | ||
| + | } | ||
| + | |||
| + | return "🎄 Frohe Weihnachten 🎄"; | ||
| + | } | ||
| + | |||
| + | function renderSilvester(val) | ||
| + | { | ||
| + | const data = parseFullJson(val); | ||
| + | const now = new Date(); | ||
| + | |||
| + | if (now.getMonth() === 11 && now.getDate() === 31) | ||
| + | { | ||
| + | return "🎇 Silvester heute"; | ||
| + | } | ||
| + | |||
| + | if (now.getMonth() === 0 && now.getDate() === 1) | ||
| + | { | ||
| + | return " | ||
| + | } | ||
| + | |||
| + | if (!data) | ||
| + | { | ||
| + | return EMPTY_TEXT; | ||
| + | } | ||
| + | |||
| + | const days = safeNum((data.total && data.total.days !== undefined) ? data.total.days : data.days, 0); | ||
| + | const hours = safeNum((data.hours !== undefined) ? data.hours : (data.total ? data.total.hours : undefined), 0); | ||
| + | const words = (data.inWords && data.inWords.long) ? data.inWords.long : ""; | ||
| + | |||
| + | if (now.getMonth() === 11 && now.getDate() === 30) | ||
| + | { | ||
| + | return "🎆 Silvester morgen" | ||
| + | } | ||
| + | |||
| + | if (days >= 1) | ||
| + | { | ||
| + | return "🕛 Silvester in " + formatDaysHours(days, | ||
| + | } | ||
| + | |||
| + | return "🎇 Silvester heute" + subline(words); | ||
| + | } | ||
| + | |||
| + | function renderGeneric(val, | ||
| + | { | ||
| + | const data = parseFullJson(val); | ||
| + | |||
| + | if (!data) | ||
| + | { | ||
| + | return EMPTY_TEXT; | ||
| + | } | ||
| + | |||
| + | const days = safeNum((data.total && data.total.days !== undefined) ? data.total.days : data.days, 0); | ||
| + | const hours = safeNum((data.hours !== undefined) ? data.hours : (data.total ? data.total.hours : undefined), 0); | ||
| + | const words = (data.inWords && data.inWords.long) ? data.inWords.long : ""; | ||
| + | |||
| + | if (days === 1) | ||
| + | { | ||
| + | return emoji + " " + title + " morgen" | ||
| + | } | ||
| + | |||
| + | if (days >= 1) | ||
| + | { | ||
| + | return emoji + " " + title + " in " + formatDaysHours(days, | ||
| + | } | ||
| + | |||
| + | if (words) | ||
| + | { | ||
| + | return emoji + " " + title + subline(words); | ||
| + | } | ||
| + | |||
| + | return emoji + " " + title + " bald"; | ||
| + | } | ||
| + | |||
| + | function renderByIndex(i, | ||
| + | { | ||
| + | const t = getType(i); | ||
| + | |||
| + | if (t === 1) | ||
| + | { | ||
| + | return renderWeihnachten(val); | ||
| + | } | ||
| + | |||
| + | if (t === 2) | ||
| + | { | ||
| + | return renderSilvester(val); | ||
| + | } | ||
| + | |||
| + | return renderGeneric(val, | ||
| + | } | ||
| + | |||
| + | /* ========================================================== | ||
| + | | ||
| + | | ||
| + | |||
| + | function pickTwoUpcoming() | ||
| + | { | ||
| + | let best1_i = -1; | ||
| + | let best1_r = 999999999; | ||
| + | |||
| + | let best2_i = -1; | ||
| + | let best2_r = 999999999; | ||
| + | |||
| + | let i = 1; | ||
| + | |||
| + | while (i <= EVENT_COUNT) | ||
| + | { | ||
| + | const oid = getOid(i); | ||
| + | |||
| + | if (oid) | ||
| + | { | ||
| + | const val = vis.states.attr(oid + " | ||
| + | |||
| + | if (!isExpired(val)) | ||
| + | { | ||
| + | const r = remainingSeconds(val); | ||
| + | |||
| + | if (r < best1_r) | ||
| + | { | ||
| + | best2_i = best1_i; | ||
| + | best2_r = best1_r; | ||
| + | |||
| + | best1_i = i; | ||
| + | best1_r = r; | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | if (r < best2_r) | ||
| + | { | ||
| + | best2_i = i; | ||
| + | best2_r = r; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | i = i + 1; | ||
| + | } | ||
| + | |||
| + | return String(best1_i) + " | ||
| + | } | ||
| + | |||
| + | function renderSlot(slotIndex, | ||
| + | { | ||
| + | const picks = pickTwoUpcoming().split(" | ||
| + | let idx = -1; | ||
| + | |||
| + | if (slotIndex === 1) | ||
| + | { | ||
| + | idx = Number(picks[0]); | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | idx = Number(picks[1]); | ||
| + | } | ||
| + | |||
| + | if (idx < 1) | ||
| + | { | ||
| + | el.innerHTML = EMPTY_TEXT; | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | const oid = getOid(idx); | ||
| + | const val = vis.states.attr(oid + " | ||
| + | |||
| + | el.innerHTML = renderByIndex(idx, | ||
| + | } | ||
| + | |||
| + | /* ========================================================== | ||
| + | START & BINDING | ||
| + | | ||
| + | |||
| + | function start() | ||
| + | { | ||
| + | const el1 = document.getElementById(" | ||
| + | const el2 = document.getElementById(" | ||
| + | |||
| + | if (typeof vis !== " | ||
| + | { | ||
| + | function updateSlot1() | ||
| + | { | ||
| + | renderSlot(1, | ||
| + | } | ||
| + | |||
| + | function updateSlot2() | ||
| + | { | ||
| + | renderSlot(2, | ||
| + | } | ||
| + | |||
| + | if (E1_OID) vis.states.bind(E1_OID + " | ||
| + | if (E2_OID) vis.states.bind(E2_OID + " | ||
| + | if (E3_OID) vis.states.bind(E3_OID + " | ||
| + | if (E4_OID) vis.states.bind(E4_OID + " | ||
| + | if (E5_OID) vis.states.bind(E5_OID + " | ||
| + | if (E6_OID) vis.states.bind(E6_OID + " | ||
| + | if (E7_OID) vis.states.bind(E7_OID + " | ||
| + | if (E8_OID) vis.states.bind(E8_OID + " | ||
| + | |||
| + | if (E1_OID) vis.states.bind(E1_OID + " | ||
| + | if (E2_OID) vis.states.bind(E2_OID + " | ||
| + | if (E3_OID) vis.states.bind(E3_OID + " | ||
| + | if (E4_OID) vis.states.bind(E4_OID + " | ||
| + | if (E5_OID) vis.states.bind(E5_OID + " | ||
| + | if (E6_OID) vis.states.bind(E6_OID + " | ||
| + | if (E7_OID) vis.states.bind(E7_OID + " | ||
| + | if (E8_OID) vis.states.bind(E8_OID + " | ||
| + | |||
| + | updateSlot1(); | ||
| + | updateSlot2(); | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | setTimeout(start, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | if (document.readyState === " | ||
| + | { | ||
| + | start(); | ||
| + | } | ||
| + | else | ||
| + | { | ||
| + | window.addEventListener(" | ||
| + | } | ||
| + | |||
| + | })(); | ||
| + | </ | ||
| + | |||
| + | |||
| + | </ | ||
| + | |||
| + | ➕ Neuen Countdown hinzufügen (Checkliste) | ||
| + | Beispiel: countdown.0.countdowns.Herbsturlaub.fullJson | ||
| + | ✅ Schritt 1 – Subscribe-Block | ||
| + | {countdown.0.countdowns.Herbsturlaub.fullJson} | ||
| + | |||
| + | ✅ Schritt 2 – OID definieren | ||
| + | const OID_4 = " | ||
| + | |||
| + | ✅ Schritt 3 – Renderfunktion ergänzen | ||
| + | if (i === 4) return renderGeneric(val, | ||
| + | |||
| + | ✅ Schritt 4 – remainingSeconds / pickTwo erweitern | ||
| + | |||
| + | (analog zu Event 3) | ||