Metainformationen zur Seite
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen RevisionVorhergehende Überarbeitung | |||
| iobroker:countdown_script_fuer_vis1 [06.01.2026] – kolja | iobroker:countdown_script_fuer_vis1 [12.10.2026] (aktuell) – gelöscht kolja | ||
|---|---|---|---|
| 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) | ||