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
|
## 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.
|
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 tw = this._calcTwilight(now);
|
||||||
const off = this._isMESZ(now) ? 2 : 1;
|
const off = this._isMESZ(now) ? 2 : 1;
|
||||||
const shift = (h) => ((h - 12 + 24) % 24);
|
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 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 nowL = shift(now.getHours() + now.getMinutes() / 60 + now.getSeconds() / 3600);
|
||||||
const segs = [];
|
const segs = [];
|
||||||
@@ -416,15 +417,43 @@ class SternwarteCardRetro extends HTMLElement {
|
|||||||
if (ee <= ss) return;
|
if (ee <= ss) return;
|
||||||
segs.push({ start: ss, end: ee, color: colors[k] });
|
segs.push({ start: ss, end: ee, color: colors[k] });
|
||||||
};
|
};
|
||||||
add(0, lt.astro?.dawn, "night");
|
const hasAstroNight = lt.astro?.dawn !== null && lt.astro?.dawn !== undefined
|
||||||
add(lt.astro?.dawn, lt.naut?.dawn, "astro");
|
&& lt.astro?.dusk !== null && lt.astro?.dusk !== undefined;
|
||||||
add(lt.naut?.dawn, lt.civil?.dawn, "naut");
|
const hasNautNight = lt.naut?.dawn !== null && lt.naut?.dawn !== undefined
|
||||||
add(lt.civil?.dawn, lt.sun?.dawn, "civil");
|
&& lt.naut?.dusk !== null && lt.naut?.dusk !== undefined;
|
||||||
add(lt.sun?.dawn, lt.sun?.dusk, "day");
|
const hasCivilNight = lt.civil?.dawn !== null && lt.civil?.dawn !== undefined
|
||||||
add(lt.sun?.dusk, lt.civil?.dusk, "civil");
|
&& lt.civil?.dusk !== null && lt.civil?.dusk !== undefined;
|
||||||
add(lt.civil?.dusk, lt.naut?.dusk, "naut");
|
const hasSunCycle = lt.sun?.dawn !== null && lt.sun?.dawn !== undefined
|
||||||
add(lt.naut?.dusk, lt.astro?.dusk, "astro");
|
&& lt.sun?.dusk !== null && lt.sun?.dusk !== undefined;
|
||||||
add(lt.astro?.dusk, 24, "night");
|
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");
|
if (!segs.length) add(0, 24, "night");
|
||||||
segs.sort((a, b) => a.start - b.start);
|
segs.sort((a, b) => a.start - b.start);
|
||||||
const stops = segs.flatMap((s) => {
|
const stops = segs.flatMap((s) => {
|
||||||
@@ -439,6 +468,11 @@ class SternwarteCardRetro extends HTMLElement {
|
|||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
gradient: grad,
|
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}%`,
|
markerLeft: `${(nowL / 24) * 100}%`,
|
||||||
openMarkerLeft: toLocal(this._state(this._scheduleOpenEntityId())),
|
openMarkerLeft: toLocal(this._state(this._scheduleOpenEntityId())),
|
||||||
closeMarkerLeft: toLocal(this._state(this._scheduleCloseEntityId())),
|
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() {
|
_moonVisualState() {
|
||||||
const moon = this._moonStateAt(new Date());
|
const moon = this._moonStateAt(new Date());
|
||||||
const phaseAngle = moon.phaseAngle ?? 0;
|
const phaseAngle = moon.phaseAngle ?? 0;
|
||||||
@@ -695,7 +720,7 @@ class SternwarteCard extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
opacity: "0.72",
|
opacity: "0.38",
|
||||||
segments,
|
segments,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -710,20 +735,12 @@ class SternwarteCard extends HTMLElement {
|
|||||||
|
|
||||||
_astroState() {
|
_astroState() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const tw = this._calcTwilight(now);
|
const windowStart = new Date(now);
|
||||||
const offset = this._isMESZ(now) ? 2 : 1;
|
windowStart.setHours(12, 0, 0, 0);
|
||||||
const shiftHour = (hourValue) => ((hourValue - 12 + 24) % 24);
|
if (now < windowStart) {
|
||||||
const localize = (event) => event ? {
|
windowStart.setDate(windowStart.getDate() - 1);
|
||||||
dawn: (event.dawn + offset + 24) % 24,
|
}
|
||||||
dusk: (event.dusk + offset + 24) % 24,
|
const nowLocal = (now.getTime() - windowStart.getTime()) / 3600000;
|
||||||
} : 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 segments = [];
|
const segments = [];
|
||||||
const colors = {
|
const colors = {
|
||||||
night: "#16202f",
|
night: "#16202f",
|
||||||
@@ -732,65 +749,59 @@ class SternwarteCard extends HTMLElement {
|
|||||||
civil: "#c68c50",
|
civil: "#c68c50",
|
||||||
day: "#eed06f",
|
day: "#eed06f",
|
||||||
};
|
};
|
||||||
const addSegment = (start, end, className) => {
|
const sampleMinutes = 5;
|
||||||
if (start === null || end === null) return;
|
const totalSamples = Math.floor((24 * 60) / sampleMinutes);
|
||||||
const shiftedStart = shiftHour(start);
|
let activePhase = null;
|
||||||
const shiftedEnd = shiftHour(end);
|
let segmentStart = 0;
|
||||||
if (shiftedEnd < shiftedStart) {
|
const pushSegment = (startHour, endHour, phaseName) => {
|
||||||
segments.push({
|
if (!phaseName || endHour <= startHour) return;
|
||||||
start: shiftedStart,
|
|
||||||
end: 24,
|
|
||||||
color: colors[className],
|
|
||||||
});
|
|
||||||
segments.push({
|
|
||||||
start: 0,
|
|
||||||
end: shiftedEnd,
|
|
||||||
color: colors[className],
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (shiftedEnd <= shiftedStart) return;
|
|
||||||
segments.push({
|
segments.push({
|
||||||
start: shiftedStart,
|
start: startHour,
|
||||||
end: shiftedEnd,
|
end: endHour,
|
||||||
color: colors[className],
|
color: colors[phaseName],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
addSegment(0, localTw.astro?.dawn ?? null, "night");
|
for (let index = 0; index <= totalSamples; index += 1) {
|
||||||
addSegment(localTw.astro?.dawn ?? null, localTw.naut?.dawn ?? null, "astro");
|
const minutesFromStart = Math.min(index * sampleMinutes, 24 * 60);
|
||||||
addSegment(localTw.naut?.dawn ?? null, localTw.civil?.dawn ?? null, "naut");
|
const sampleDate = new Date(windowStart.getTime() + minutesFromStart * 60000);
|
||||||
addSegment(localTw.civil?.dawn ?? null, localTw.sun?.dawn ?? null, "civil");
|
const phase = this._astroPhaseAt(sampleDate);
|
||||||
addSegment(localTw.sun?.dawn ?? null, localTw.sun?.dusk ?? null, "day");
|
if (activePhase === null) {
|
||||||
addSegment(localTw.sun?.dusk ?? null, localTw.civil?.dusk ?? null, "civil");
|
activePhase = phase;
|
||||||
addSegment(localTw.civil?.dusk ?? null, localTw.naut?.dusk ?? null, "naut");
|
segmentStart = minutesFromStart / 60;
|
||||||
addSegment(localTw.naut?.dusk ?? null, localTw.astro?.dusk ?? null, "astro");
|
continue;
|
||||||
addSegment(localTw.astro?.dusk ?? null, 24, "night");
|
}
|
||||||
if (!segments.length) addSegment(0, 24, "night");
|
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);
|
segments.sort((a, b) => a.start - b.start);
|
||||||
|
|
||||||
const gradientStops = segments.flatMap((segment) => {
|
const toWindowHour = (state) => {
|
||||||
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) => {
|
|
||||||
if (!state?.state || state.state === "unknown" || state.state === "unavailable") return null;
|
if (!state?.state || state.state === "unknown" || state.state === "unavailable") return null;
|
||||||
const value = new Date(state.state);
|
const value = new Date(state.state);
|
||||||
if (Number.isNaN(value.getTime())) return null;
|
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 {
|
return {
|
||||||
now: now.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }),
|
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}%`,
|
markerLeft: `${(nowLocal / 24) * 100}%`,
|
||||||
openMarkerLeft: toLocalHour(this._state(this._scheduleOpenEntityId())),
|
openMarkerLeft: toWindowHour(this._state(this._scheduleOpenEntityId())),
|
||||||
closeMarkerLeft: toLocalHour(this._state(this._scheduleCloseEntityId())),
|
closeMarkerLeft: toWindowHour(this._state(this._scheduleCloseEntityId())),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1035,8 +1046,18 @@ class SternwarteCard extends HTMLElement {
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: rgba(255,255,255,0.06);
|
background: #16202f;
|
||||||
border: 1px solid rgba(255,255,255,0.08);
|
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 {
|
.astro-tick {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -1085,14 +1106,14 @@ class SternwarteCard extends HTMLElement {
|
|||||||
.astro-moon-track {
|
.astro-moon-track {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: rgba(255, 255, 255, 0.96);
|
background: rgba(255, 255, 255, 0.96);
|
||||||
height: 8px;
|
height: 6px;
|
||||||
top: 2px;
|
top: 3px;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
border-radius: 999px;
|
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-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;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.badges {
|
.badges {
|
||||||
@@ -1449,7 +1470,8 @@ class SternwarteCard extends HTMLElement {
|
|||||||
<div class="astro-tick-label" style="left:75%">6</div>
|
<div class="astro-tick-label" style="left:75%">6</div>
|
||||||
<div class="astro-tick" style="left:100%"></div>
|
<div class="astro-tick" style="left:100%"></div>
|
||||||
<div class="astro-tick-label" style="left:100%">12</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("")}
|
${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.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>`}
|
${astro.closeMarkerLeft === null ? "" : `<div class="astro-marker close" style="left:${(astro.closeMarkerLeft / 24) * 100}%"></div>`}
|
||||||
|
|||||||
Reference in New Issue
Block a user