Use HA timezone on the party board and restore 600px plates

This commit is contained in:
2026-09-20 12:19:48 -04:00
parent a4e612da86
commit f42fc0fc65
2 changed files with 250 additions and 162 deletions
+79 -29
View File
@@ -26,16 +26,52 @@ type DockWeather = {
low: number | null;
};
const WEEKDAY_FORMAT = new Intl.DateTimeFormat("en-US", { weekday: "long" });
const MONTH_DAY_FORMAT = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
});
const TIME_FORMAT = new Intl.DateTimeFormat("en-US", {
hour: "numeric",
minute: "2-digit",
hour12: true,
});
const FORMATTER_CACHE = new Map<string, Intl.DateTimeFormat>();
function zonedFormatter(
timeZone: string | undefined,
options: Intl.DateTimeFormatOptions
): Intl.DateTimeFormat {
const key = `${timeZone ?? "local"}|${JSON.stringify(options)}`;
const cached = FORMATTER_CACHE.get(key);
if (cached) {
return cached;
}
let formatter: Intl.DateTimeFormat;
try {
formatter = new Intl.DateTimeFormat("en-US", {
...options,
...(timeZone ? { timeZone } : {}),
});
} catch {
formatter = new Intl.DateTimeFormat("en-US", options);
}
FORMATTER_CACHE.set(key, formatter);
return formatter;
}
function hassTimeZone(hass: HassLike | undefined): string | undefined {
const config = hass?.config;
if (!config || typeof config !== "object") {
return undefined;
}
const timeZone = (config as Record<string, unknown>).time_zone;
return typeof timeZone === "string" && timeZone.trim()
? timeZone.trim()
: undefined;
}
function formatDay(date: Date, timeZone: string | undefined): string {
return `${zonedFormatter(timeZone, { weekday: "long" }).format(date)}, ${zonedFormatter(timeZone, { month: "short", day: "numeric" }).format(date)}`;
}
function formatTime(date: Date, timeZone: string | undefined): string {
return zonedFormatter(timeZone, {
hour: "numeric",
minute: "2-digit",
hour12: true,
}).format(date);
}
const CONDITION_LABELS: Record<string, string> = {
"clear-night": "Clear",
@@ -120,10 +156,6 @@ function parseIsoDate(value: unknown): Date | null {
return Number.isNaN(date.getTime()) ? null : date;
}
function formatDay(date: Date): string {
return `${WEEKDAY_FORMAT.format(date)}, ${MONTH_DAY_FORMAT.format(date)}`;
}
function conditionLabel(condition: string): string {
const known = CONDITION_LABELS[condition];
if (known) {
@@ -160,8 +192,8 @@ const boardStyles = css`
display: flex;
flex-direction: column;
align-items: center;
height: 100vh;
padding: 48px 110px 150px;
height: 1080px;
padding: 0;
overflow: hidden;
background: var(--nq-p-parchment);
box-shadow: var(--nq-p-vignette);
@@ -186,6 +218,10 @@ const boardStyles = css`
}
.wordmark-row {
position: absolute;
top: 64px;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
@@ -233,7 +269,10 @@ const boardStyles = css`
}
.kicker {
margin: 14px 0 0;
position: absolute;
top: 154px;
left: 0;
right: 0;
font-family: var(--nq-p-font-heading);
font-size: 22px;
font-weight: 600;
@@ -245,8 +284,10 @@ const boardStyles = css`
}
.plates {
width: 100%;
margin-top: 42px;
position: absolute;
top: 262px;
left: 110px;
right: 110px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 56px;
@@ -258,9 +299,9 @@ const boardStyles = css`
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
height: 560px;
padding: 36px 28px 32px;
gap: 26px;
height: 600px;
padding: 44px 28px 40px;
border: 2px solid var(--nq-p-panel-border);
border-radius: var(--nq-p-radius-panel);
background: var(--nq-p-card-panel);
@@ -294,8 +335,8 @@ const boardStyles = css`
.crest {
position: relative;
flex: none;
width: 168px;
height: 192px;
width: 188px;
height: 214px;
padding: 4px;
clip-path: ${unsafeCSS(SHIELD_CLIP)};
background: rgba(255, 255, 255, 0.22);
@@ -393,7 +434,10 @@ const boardStyles = css`
}
.hint {
margin: 26px 0 0;
position: absolute;
top: 930px;
left: 0;
right: 0;
font-family: var(--nq-p-font-body);
font-size: 24px;
font-weight: 600;
@@ -569,7 +613,7 @@ export class NestQuestPartyBoardCard extends LitElement {
</span>
<span class="rule"></span>
</div>
<p class="kicker">The Party · ${formatDay(this._now)}</p>
<p class="kicker">The Party · ${formatDay(this._now, this._timeZone())}</p>
<div class="plates">
${this._plates().map((plate) => this._renderPlate(plate))}
</div>
@@ -580,6 +624,10 @@ export class NestQuestPartyBoardCard extends LitElement {
`;
}
private _timeZone(): string | undefined {
return hassTimeZone(this.hass);
}
private _state(entityId: string): StateObject | null {
const states = this.hass?.states as
| Record<string, StateObject>
@@ -655,7 +703,9 @@ export class NestQuestPartyBoardCard extends LitElement {
}
const returnsDate = parseIsoDate(presence?.attributes?.next_present);
const returns = returnsDate ? `Returns ${formatDay(returnsDate)}` : null;
const returns = returnsDate
? `Returns ${formatDay(returnsDate, this._timeZone())}`
: null;
return {
slug,
@@ -770,9 +820,9 @@ export class NestQuestPartyBoardCard extends LitElement {
if (!weather) {
return html`
<div class="dock">
<span class="date">${formatDay(this._now)}</span>
<span class="date">${formatDay(this._now, this._timeZone())}</span>
<span class="divider"></span>
<span class="clock">${TIME_FORMAT.format(this._now)}</span>
<span class="clock">${formatTime(this._now, this._timeZone())}</span>
</div>
`;
}