Dämmerungsgrafik gefixt
This commit is contained in:
@@ -22,3 +22,24 @@ Wenn ihr das später wirklich über HACS verwenden wollt, legt ihr für beide Or
|
||||
## Hinweis
|
||||
|
||||
Die Integration läuft bereits lokal. Für eine spätere Veröffentlichung sollten in `manifest.json` noch echte `documentation`- und `issue_tracker`-Links auf das spätere Repository gesetzt werden.
|
||||
|
||||
## Test-Home-Assistant
|
||||
|
||||
Für Tests der Lovelace-Card gibt es eine Test-Home-Assistant-Instanz mit einer SMB-Freigabe:
|
||||
|
||||
- Freigabe: `\\192.168.1.71\config\www`
|
||||
- Benutzer: `Eskimue`
|
||||
- Passwort: `Preside`
|
||||
|
||||
In dieser Freigabe liegen die Card-Dateien, die Home Assistant direkt als Dashboard-Ressourcen lädt:
|
||||
|
||||
- `\\192.168.1.71\config\www\sternwarte-card.js`
|
||||
- `\\192.168.1.71\config\www\sternwarte-card-retro.js`
|
||||
- `\\192.168.1.71\config\www\astronomy.browser.min.js`
|
||||
- `\\192.168.1.71\config\www\moon_small.png`
|
||||
|
||||
Arbeitsweise:
|
||||
|
||||
- Änderungen an der Card werden nach dem Anpassen direkt auf diese Test-Instanz kopiert.
|
||||
- Relevant sind dafür in der Regel die Dateien aus `sternwarte-homeassistant-card/dist/`.
|
||||
- Nach dem Kopieren muss das Home-Assistant-Frontend hart neu geladen werden, damit kein alter Browser-Cache im Weg ist.
|
||||
|
||||
+44
-10
@@ -404,7 +404,8 @@ class SternwarteCardRetro extends HTMLElement {
|
||||
const tw = this._calcTwilight(now);
|
||||
const off = this._isMESZ(now) ? 2 : 1;
|
||||
const shift = (h) => ((h - 12 + 24) % 24);
|
||||
const localize = (e) => e ? { dawn: (e.dawn + off + 24) % 24, dusk: (e.dusk + off + 24) % 24 } : null;
|
||||
const localizeHour = (h) => (h === null || h === undefined ? null : (h + off + 24) % 24);
|
||||
const localize = (e) => e ? { dawn: localizeHour(e.dawn), dusk: localizeHour(e.dusk) } : null;
|
||||
const lt = { sun: localize(tw.sun), civil: localize(tw.civil), naut: localize(tw.naut), astro: localize(tw.astro) };
|
||||
const nowL = shift(now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600);
|
||||
const segs = [];
|
||||
@@ -416,15 +417,43 @@ class SternwarteCardRetro extends HTMLElement {
|
||||
if (ee <= ss) return;
|
||||
segs.push({ start: ss, end: ee, color: colors[k] });
|
||||
};
|
||||
add(0, lt.astro?.dawn, "night");
|
||||
add(lt.astro?.dawn, lt.naut?.dawn, "astro");
|
||||
add(lt.naut?.dawn, lt.civil?.dawn, "naut");
|
||||
add(lt.civil?.dawn, lt.sun?.dawn, "civil");
|
||||
add(lt.sun?.dawn, lt.sun?.dusk, "day");
|
||||
add(lt.sun?.dusk, lt.civil?.dusk, "civil");
|
||||
add(lt.civil?.dusk, lt.naut?.dusk, "naut");
|
||||
add(lt.naut?.dusk, lt.astro?.dusk, "astro");
|
||||
add(lt.astro?.dusk, 24, "night");
|
||||
const hasAstroNight = lt.astro?.dawn !== null && lt.astro?.dawn !== undefined
|
||||
&& lt.astro?.dusk !== null && lt.astro?.dusk !== undefined;
|
||||
const hasNautNight = lt.naut?.dawn !== null && lt.naut?.dawn !== undefined
|
||||
&& lt.naut?.dusk !== null && lt.naut?.dusk !== undefined;
|
||||
const hasCivilNight = lt.civil?.dawn !== null && lt.civil?.dawn !== undefined
|
||||
&& lt.civil?.dusk !== null && lt.civil?.dusk !== undefined;
|
||||
const hasSunCycle = lt.sun?.dawn !== null && lt.sun?.dawn !== undefined
|
||||
&& lt.sun?.dusk !== null && lt.sun?.dusk !== undefined;
|
||||
if (hasAstroNight) {
|
||||
add(0, lt.astro.dawn, "night");
|
||||
add(lt.astro.dawn, lt.naut?.dawn, "astro");
|
||||
add(lt.naut?.dawn, lt.civil?.dawn, "naut");
|
||||
add(lt.civil?.dawn, lt.sun?.dawn, "civil");
|
||||
add(lt.sun?.dawn, lt.sun?.dusk, "day");
|
||||
add(lt.sun?.dusk, lt.civil?.dusk, "civil");
|
||||
add(lt.civil?.dusk, lt.naut?.dusk, "naut");
|
||||
add(lt.naut?.dusk, lt.astro.dusk, "astro");
|
||||
add(lt.astro.dusk, 24, "night");
|
||||
} else if (hasNautNight) {
|
||||
add(0, lt.naut.dawn, "astro");
|
||||
add(lt.naut.dawn, lt.civil?.dawn, "naut");
|
||||
add(lt.civil?.dawn, lt.sun?.dawn, "civil");
|
||||
add(lt.sun?.dawn, lt.sun?.dusk, "day");
|
||||
add(lt.sun?.dusk, lt.civil?.dusk, "civil");
|
||||
add(lt.civil?.dusk, lt.naut.dusk, "naut");
|
||||
add(lt.naut.dusk, 24, "astro");
|
||||
} else if (hasCivilNight) {
|
||||
add(0, lt.civil.dawn, "naut");
|
||||
add(lt.civil.dawn, lt.sun?.dawn, "civil");
|
||||
add(lt.sun?.dawn, lt.sun?.dusk, "day");
|
||||
add(lt.sun?.dusk, lt.civil.dusk, "civil");
|
||||
add(lt.civil.dusk, 24, "naut");
|
||||
} else if (hasSunCycle) {
|
||||
add(0, lt.sun.dawn, "civil");
|
||||
add(lt.sun.dawn, lt.sun.dusk, "day");
|
||||
add(lt.sun.dusk, 24, "civil");
|
||||
}
|
||||
if (!segs.length) add(0, 24, "night");
|
||||
segs.sort((a, b) => a.start - b.start);
|
||||
const stops = segs.flatMap((s) => {
|
||||
@@ -439,6 +468,11 @@ class SternwarteCardRetro extends HTMLElement {
|
||||
};
|
||||
return {
|
||||
gradient: grad,
|
||||
phaseSegments: segs.map((s) => ({
|
||||
left: `${(s.start / 24) * 100}%`,
|
||||
width: `${Math.max(((s.end - s.start) / 24) * 100, 0.35)}%`,
|
||||
color: s.color,
|
||||
})),
|
||||
markerLeft: `${(nowL / 24) * 100}%`,
|
||||
openMarkerLeft: toLocal(this._state(this._scheduleOpenEntityId())),
|
||||
closeMarkerLeft: toLocal(this._state(this._scheduleCloseEntityId())),
|
||||
|
||||
+87
-65
@@ -571,6 +571,31 @@ class SternwarteCard extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
_sunAltitudeDegAt(date) {
|
||||
const astronomy = this._astronomy();
|
||||
const observer = this._astroObserver();
|
||||
if (!astronomy || !observer) return Number.NEGATIVE_INFINITY;
|
||||
|
||||
try {
|
||||
const bodySun = astronomy.Body?.Sun ?? "Sun";
|
||||
const equator = astronomy.Equator(bodySun, date, observer, true, true);
|
||||
const horizon = astronomy.Horizon(date, observer, equator.ra, equator.dec, "normal");
|
||||
return Number(horizon?.altitude);
|
||||
} catch (error) {
|
||||
return Number.NEGATIVE_INFINITY;
|
||||
}
|
||||
}
|
||||
|
||||
_astroPhaseAt(date) {
|
||||
const altitude = this._sunAltitudeDegAt(date);
|
||||
if (!Number.isFinite(altitude)) return "night";
|
||||
if (altitude >= -0.833) return "day";
|
||||
if (altitude >= -6) return "civil";
|
||||
if (altitude >= -12) return "naut";
|
||||
if (altitude >= -18) return "astro";
|
||||
return "night";
|
||||
}
|
||||
|
||||
_moonVisualState() {
|
||||
const moon = this._moonStateAt(new Date());
|
||||
const phaseAngle = moon.phaseAngle ?? 0;
|
||||
@@ -695,7 +720,7 @@ class SternwarteCard extends HTMLElement {
|
||||
}
|
||||
|
||||
return {
|
||||
opacity: "0.72",
|
||||
opacity: "0.38",
|
||||
segments,
|
||||
};
|
||||
}
|
||||
@@ -710,20 +735,12 @@ class SternwarteCard extends HTMLElement {
|
||||
|
||||
_astroState() {
|
||||
const now = new Date();
|
||||
const tw = this._calcTwilight(now);
|
||||
const offset = this._isMESZ(now) ? 2 : 1;
|
||||
const shiftHour = (hourValue) => ((hourValue - 12 + 24) % 24);
|
||||
const localize = (event) => event ? {
|
||||
dawn: (event.dawn + offset + 24) % 24,
|
||||
dusk: (event.dusk + offset + 24) % 24,
|
||||
} : null;
|
||||
const localTw = {
|
||||
sun: localize(tw.sun),
|
||||
civil: localize(tw.civil),
|
||||
naut: localize(tw.naut),
|
||||
astro: localize(tw.astro),
|
||||
};
|
||||
const nowLocal = shiftHour(now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600);
|
||||
const windowStart = new Date(now);
|
||||
windowStart.setHours(12, 0, 0, 0);
|
||||
if (now < windowStart) {
|
||||
windowStart.setDate(windowStart.getDate() - 1);
|
||||
}
|
||||
const nowLocal = (now.getTime() - windowStart.getTime()) / 3600000;
|
||||
const segments = [];
|
||||
const colors = {
|
||||
night: "#16202f",
|
||||
@@ -732,65 +749,59 @@ class SternwarteCard extends HTMLElement {
|
||||
civil: "#c68c50",
|
||||
day: "#eed06f",
|
||||
};
|
||||
const addSegment = (start, end, className) => {
|
||||
if (start === null || end === null) return;
|
||||
const shiftedStart = shiftHour(start);
|
||||
const shiftedEnd = shiftHour(end);
|
||||
if (shiftedEnd < shiftedStart) {
|
||||
segments.push({
|
||||
start: shiftedStart,
|
||||
end: 24,
|
||||
color: colors[className],
|
||||
});
|
||||
segments.push({
|
||||
start: 0,
|
||||
end: shiftedEnd,
|
||||
color: colors[className],
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (shiftedEnd <= shiftedStart) return;
|
||||
const sampleMinutes = 5;
|
||||
const totalSamples = Math.floor((24 * 60) / sampleMinutes);
|
||||
let activePhase = null;
|
||||
let segmentStart = 0;
|
||||
const pushSegment = (startHour, endHour, phaseName) => {
|
||||
if (!phaseName || endHour <= startHour) return;
|
||||
segments.push({
|
||||
start: shiftedStart,
|
||||
end: shiftedEnd,
|
||||
color: colors[className],
|
||||
start: startHour,
|
||||
end: endHour,
|
||||
color: colors[phaseName],
|
||||
});
|
||||
};
|
||||
|
||||
addSegment(0, localTw.astro?.dawn ?? null, "night");
|
||||
addSegment(localTw.astro?.dawn ?? null, localTw.naut?.dawn ?? null, "astro");
|
||||
addSegment(localTw.naut?.dawn ?? null, localTw.civil?.dawn ?? null, "naut");
|
||||
addSegment(localTw.civil?.dawn ?? null, localTw.sun?.dawn ?? null, "civil");
|
||||
addSegment(localTw.sun?.dawn ?? null, localTw.sun?.dusk ?? null, "day");
|
||||
addSegment(localTw.sun?.dusk ?? null, localTw.civil?.dusk ?? null, "civil");
|
||||
addSegment(localTw.civil?.dusk ?? null, localTw.naut?.dusk ?? null, "naut");
|
||||
addSegment(localTw.naut?.dusk ?? null, localTw.astro?.dusk ?? null, "astro");
|
||||
addSegment(localTw.astro?.dusk ?? null, 24, "night");
|
||||
if (!segments.length) addSegment(0, 24, "night");
|
||||
for (let index = 0; index <= totalSamples; index += 1) {
|
||||
const minutesFromStart = Math.min(index * sampleMinutes, 24 * 60);
|
||||
const sampleDate = new Date(windowStart.getTime() + minutesFromStart * 60000);
|
||||
const phase = this._astroPhaseAt(sampleDate);
|
||||
if (activePhase === null) {
|
||||
activePhase = phase;
|
||||
segmentStart = minutesFromStart / 60;
|
||||
continue;
|
||||
}
|
||||
if (phase !== activePhase) {
|
||||
pushSegment(segmentStart, minutesFromStart / 60, activePhase);
|
||||
activePhase = phase;
|
||||
segmentStart = minutesFromStart / 60;
|
||||
}
|
||||
}
|
||||
if (activePhase !== null) {
|
||||
pushSegment(segmentStart, 24, activePhase);
|
||||
}
|
||||
|
||||
segments.sort((a, b) => a.start - b.start);
|
||||
|
||||
const gradientStops = segments.flatMap((segment) => {
|
||||
const start = `${(segment.start / 24) * 100}%`;
|
||||
const end = `${(segment.end / 24) * 100}%`;
|
||||
return [`${segment.color} ${start}`, `${segment.color} ${end}`];
|
||||
});
|
||||
const gradient = gradientStops.length
|
||||
? `linear-gradient(90deg, ${gradientStops.join(", ")})`
|
||||
: colors.night;
|
||||
const toLocalHour = (state) => {
|
||||
const toWindowHour = (state) => {
|
||||
if (!state?.state || state.state === "unknown" || state.state === "unavailable") return null;
|
||||
const value = new Date(state.state);
|
||||
if (Number.isNaN(value.getTime())) return null;
|
||||
return shiftHour(value.getHours() + value.getMinutes() / 60);
|
||||
const hour = (value.getTime() - windowStart.getTime()) / 3600000;
|
||||
if (hour < 0 || hour > 24) return null;
|
||||
return hour;
|
||||
};
|
||||
|
||||
return {
|
||||
now: now.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }),
|
||||
gradient,
|
||||
phaseSegments: segments.map((segment) => ({
|
||||
left: `${(segment.start / 24) * 100}%`,
|
||||
width: `${Math.max(((segment.end - segment.start) / 24) * 100, 0.35)}%`,
|
||||
color: segment.color,
|
||||
})),
|
||||
markerLeft: `${(nowLocal / 24) * 100}%`,
|
||||
openMarkerLeft: toLocalHour(this._state(this._scheduleOpenEntityId())),
|
||||
closeMarkerLeft: toLocalHour(this._state(this._scheduleCloseEntityId())),
|
||||
openMarkerLeft: toWindowHour(this._state(this._scheduleOpenEntityId())),
|
||||
closeMarkerLeft: toWindowHour(this._state(this._scheduleCloseEntityId())),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1035,8 +1046,18 @@ class SternwarteCard extends HTMLElement {
|
||||
height: 32px;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
background: rgba(255,255,255,0.06);
|
||||
background: #16202f;
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
box-shadow: inset 0 0 0 1px rgba(255,255,255,0.03);
|
||||
}
|
||||
.astro-phase {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
box-shadow:
|
||||
inset -1px 0 0 rgba(255,255,255,0.14),
|
||||
inset 0 1px 0 rgba(255,255,255,0.08),
|
||||
inset 0 -10px 18px rgba(0,0,0,0.14);
|
||||
}
|
||||
.astro-tick {
|
||||
position: absolute;
|
||||
@@ -1085,14 +1106,14 @@ class SternwarteCard extends HTMLElement {
|
||||
.astro-moon-track {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
height: 8px;
|
||||
top: 2px;
|
||||
height: 6px;
|
||||
top: 3px;
|
||||
bottom: auto;
|
||||
margin-left: 0;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(12, 18, 28, 0.85);
|
||||
border: 1px solid rgba(12, 18, 28, 0.78);
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 8px rgba(255, 255, 255, 0.45);
|
||||
box-shadow: 0 0 6px rgba(255, 255, 255, 0.24);
|
||||
z-index: 2;
|
||||
}
|
||||
.badges {
|
||||
@@ -1449,7 +1470,8 @@ class SternwarteCard extends HTMLElement {
|
||||
<div class="astro-tick-label" style="left:75%">6</div>
|
||||
<div class="astro-tick" style="left:100%"></div>
|
||||
<div class="astro-tick-label" style="left:100%">12</div>
|
||||
<div class="astro-bar" style="background:${astro.gradient}">
|
||||
<div class="astro-bar">
|
||||
${astro.phaseSegments.map((segment) => `<div class="astro-phase" style="left:${segment.left};width:${segment.width};background:${segment.color}"></div>`).join("")}
|
||||
${moonTrack.segments.map((segment) => `<div class="astro-moon-track" style="left:${segment.left};width:${segment.width};opacity:${moonTrack.opacity}"></div>`).join("")}
|
||||
${astro.openMarkerLeft === null ? "" : `<div class="astro-marker open" style="left:${(astro.openMarkerLeft / 24) * 100}%"></div>`}
|
||||
${astro.closeMarkerLeft === null ? "" : `<div class="astro-marker close" style="left:${(astro.closeMarkerLeft / 24) * 100}%"></div>`}
|
||||
|
||||
Reference in New Issue
Block a user