From edaaa00038474f3a612469289e1adfa4cb261f82 Mon Sep 17 00:00:00 2001 From: Aidan Timson Date: Wed, 27 Aug 2025 10:15:06 +0100 Subject: [PATCH] Analog style for clock card (#26557) Co-authored-by: Norbert Rittel Co-authored-by: Bram Kragten --- .../cards/clock/hui-clock-card-analog.ts | 300 ++++++++++++++++++ .../cards/clock/hui-clock-card-digital.ts | 196 ++++++++++++ src/panels/lovelace/cards/hui-clock-card.ts | 259 +++++---------- src/panels/lovelace/cards/types.ts | 4 + .../config-elements/hui-clock-card-editor.ts | 154 ++++++++- src/translations/en.json | 31 +- 6 files changed, 739 insertions(+), 205 deletions(-) create mode 100644 src/panels/lovelace/cards/clock/hui-clock-card-analog.ts create mode 100644 src/panels/lovelace/cards/clock/hui-clock-card-digital.ts diff --git a/src/panels/lovelace/cards/clock/hui-clock-card-analog.ts b/src/panels/lovelace/cards/clock/hui-clock-card-analog.ts new file mode 100644 index 0000000000..9abad06c37 --- /dev/null +++ b/src/panels/lovelace/cards/clock/hui-clock-card-analog.ts @@ -0,0 +1,300 @@ +import { css, html, LitElement, nothing } from "lit"; +import type { PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import type { ClockCardConfig } from "../types"; +import type { HomeAssistant } from "../../../../types"; +import { INTERVAL } from "../hui-clock-card"; +import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone"; + +@customElement("hui-clock-card-analog") +export class HuiClockCardAnalog extends LitElement { + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public config?: ClockCardConfig; + + @state() private _dateTimeFormat?: Intl.DateTimeFormat; + + @state() private _hourDeg?: number; + + @state() private _minuteDeg?: number; + + @state() private _secondDeg?: number; + + private _tickInterval?: undefined | number; + + private _initDate() { + if (!this.config || !this.hass) { + return; + } + + let locale = this.hass.locale; + if (this.config.time_format) { + locale = { ...locale, time_format: this.config.time_format }; + } + + this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hourCycle: "h12", + timeZone: + this.config.time_zone || + resolveTimeZone(locale.time_zone, this.hass.config?.time_zone), + }); + + this._tick(); + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("hass")) { + const oldHass = changedProps.get("hass") as HomeAssistant | undefined; + if (!oldHass || oldHass.locale !== this.hass?.locale) { + this._initDate(); + } + } + } + + public connectedCallback() { + super.connectedCallback(); + this._startTick(); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._stopTick(); + } + + private _startTick() { + this._tickInterval = window.setInterval(() => this._tick(), INTERVAL); + this._tick(); + } + + private _stopTick() { + if (this._tickInterval) { + clearInterval(this._tickInterval); + this._tickInterval = undefined; + } + } + + private _tick() { + if (!this._dateTimeFormat) return; + + const parts = this._dateTimeFormat.formatToParts(); + const hourStr = parts.find((p) => p.type === "hour")?.value; + const minuteStr = parts.find((p) => p.type === "minute")?.value; + const secondStr = parts.find((p) => p.type === "second")?.value; + + const hour = hourStr ? parseInt(hourStr, 10) : 0; + const minute = minuteStr ? parseInt(minuteStr, 10) : 0; + const second = secondStr ? parseInt(secondStr, 10) : 0; + + this._hourDeg = hour * 30 + minute * 0.5; // 30deg per hour + 0.5deg per minute + this._minuteDeg = minute * 6 + second * 0.1; // 6deg per minute + 0.1deg per second + this._secondDeg = this.config?.show_seconds ? second * 6 : undefined; // 6deg per second + } + + render() { + if (!this.config) return nothing; + + const sizeClass = this.config.clock_size + ? `size-${this.config.clock_size}` + : ""; + + return html` + + `; + } + + static styles = css` + :host { + display: flex; + align-items: center; + justify-content: center; + } + + .analog-clock { + --clock-size: 100px; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--clock-size); + height: var(--clock-size); + } + + .analog-clock.size-medium { + --clock-size: 160px; + } + + .analog-clock.size-large { + --clock-size: 220px; + } + + .dial { + position: relative; + width: 100%; + height: 100%; + box-sizing: border-box; + } + + .dial-border { + border: 2px solid var(--divider-color); + border-radius: 50%; + } + + .tick { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + transform: rotate(var(--tick-rotation)); + pointer-events: none; + z-index: 0; + } + + .tick .line { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 1px; + height: calc(var(--clock-size) * 0.04); + background: var(--primary-text-color); + opacity: 0.5; + border-radius: 1px; + } + + .tick.hour .line { + width: 2px; + height: calc(var(--clock-size) * 0.07); + opacity: 0.8; + } + + .center-dot { + position: absolute; + top: 50%; + left: 50%; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--primary-text-color); + transform: translate(-50%, -50%); + z-index: 3; + } + + .hand { + position: absolute; + left: 50%; + bottom: 50%; + transform-origin: 50% 100%; + transform: translate(-50%, 0) rotate(var(--hand-rotation, 0deg)); + background: var(--primary-text-color); + border-radius: 2px; + will-change: transform; + } + + .hand.hour { + width: 4px; + height: calc(var(--clock-size) * 0.25); /* 25% of the clock size */ + background: var(--primary-text-color); + box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.2); + z-index: 1; + } + + .hand.minute { + width: 3px; + height: calc(var(--clock-size) * 0.35); /* 35% of the clock size */ + background: var(--primary-text-color); + box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.2); + opacity: 0.9; + z-index: 3; + } + + .hand.second { + width: 2px; + height: calc(var(--clock-size) * 0.42); /* 42% of the clock size */ + background: var(--ha-color-border-danger-normal); + box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2); + opacity: 0.8; + z-index: 2; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-clock-card-analog": HuiClockCardAnalog; + } +} diff --git a/src/panels/lovelace/cards/clock/hui-clock-card-digital.ts b/src/panels/lovelace/cards/clock/hui-clock-card-digital.ts new file mode 100644 index 0000000000..3d171f20e7 --- /dev/null +++ b/src/panels/lovelace/cards/clock/hui-clock-card-digital.ts @@ -0,0 +1,196 @@ +import { css, html, LitElement, nothing } from "lit"; +import type { PropertyValues } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import type { ClockCardConfig } from "../types"; +import type { HomeAssistant } from "../../../../types"; +import { INTERVAL } from "../hui-clock-card"; +import { useAmPm } from "../../../../common/datetime/use_am_pm"; +import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone"; + +@customElement("hui-clock-card-digital") +export class HuiClockCardDigital extends LitElement { + @property({ attribute: false }) public hass?: HomeAssistant; + + @property({ attribute: false }) public config?: ClockCardConfig; + + @state() private _dateTimeFormat?: Intl.DateTimeFormat; + + @state() private _timeHour?: string; + + @state() private _timeMinute?: string; + + @state() private _timeSecond?: string; + + @state() private _timeAmPm?: string; + + private _tickInterval?: undefined | number; + + private _initDate() { + if (!this.config || !this.hass) { + return; + } + + let locale = this.hass?.locale; + + if (this.config?.time_format) { + locale = { ...locale, time_format: this.config.time_format }; + } + + this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, { + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hourCycle: useAmPm(locale) ? "h12" : "h23", + timeZone: + this.config?.time_zone || + resolveTimeZone(locale.time_zone, this.hass.config?.time_zone), + }); + + this._tick(); + } + + protected updated(changedProps: PropertyValues) { + if (changedProps.has("hass")) { + const oldHass = changedProps.get("hass"); + if (!oldHass || oldHass.locale !== this.hass?.locale) { + this._initDate(); + } + } + } + + public connectedCallback() { + super.connectedCallback(); + this._startTick(); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._stopTick(); + } + + private _startTick() { + this._tickInterval = window.setInterval(() => this._tick(), INTERVAL); + this._tick(); + } + + private _stopTick() { + if (this._tickInterval) { + clearInterval(this._tickInterval); + this._tickInterval = undefined; + } + } + + private _tick() { + if (!this._dateTimeFormat) return; + + const parts = this._dateTimeFormat.formatToParts(); + + this._timeHour = parts.find((part) => part.type === "hour")?.value; + this._timeMinute = parts.find((part) => part.type === "minute")?.value; + this._timeSecond = this.config?.show_seconds + ? parts.find((part) => part.type === "second")?.value + : undefined; + this._timeAmPm = parts.find((part) => part.type === "dayPeriod")?.value; + } + + render() { + if (!this.config) return nothing; + + const sizeClass = this.config.clock_size + ? `size-${this.config.clock_size}` + : ""; + + return html` +
+
${this._timeHour}
+
${this._timeMinute}
+ ${this._timeSecond !== undefined + ? html`
${this._timeSecond}
` + : nothing} + ${this._timeAmPm !== undefined + ? html`
${this._timeAmPm}
` + : nothing} +
+ `; + } + + static styles = css` + :host { + display: block; + } + + .time-parts { + align-items: center; + display: grid; + grid-template-areas: + "hour minute second" + "hour minute am-pm"; + + font-size: 1.5rem; + font-weight: var(--ha-font-weight-medium); + line-height: 0.8; + direction: ltr; + } + + .time-title + .time-parts { + font-size: 1.5rem; + } + + .time-parts.size-medium { + font-size: 3rem; + } + + .time-parts.size-large { + font-size: 4rem; + } + + .time-parts.size-medium .time-part.second, + .time-parts.size-medium .time-part.am-pm { + font-size: var(--ha-font-size-l); + margin-left: 6px; + } + + .time-parts.size-large .time-part.second, + .time-parts.size-large .time-part.am-pm { + font-size: var(--ha-font-size-2xl); + margin-left: 8px; + } + + .time-parts .time-part.hour { + grid-area: hour; + } + + .time-parts .time-part.minute { + grid-area: minute; + } + + .time-parts .time-part.second { + grid-area: second; + line-height: 0.9; + opacity: 0.4; + } + + .time-parts .time-part.am-pm { + grid-area: am-pm; + line-height: 0.9; + opacity: 0.6; + } + + .time-parts .time-part.second, + .time-parts .time-part.am-pm { + font-size: var(--ha-font-size-xs); + margin-left: 4px; + } + + .time-parts .time-part.hour:after { + content: ":"; + margin: 0 2px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "hui-clock-card-digital": HuiClockCardDigital; + } +} diff --git a/src/panels/lovelace/cards/hui-clock-card.ts b/src/panels/lovelace/cards/hui-clock-card.ts index 7d3a69cb84..0287faf42a 100644 --- a/src/panels/lovelace/cards/hui-clock-card.ts +++ b/src/panels/lovelace/cards/hui-clock-card.ts @@ -1,4 +1,3 @@ -import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; @@ -11,10 +10,8 @@ import type { LovelaceGridOptions, } from "../types"; import type { ClockCardConfig } from "./types"; -import { useAmPm } from "../../../common/datetime/use_am_pm"; -import { resolveTimeZone } from "../../../common/datetime/resolve-time-zone"; -const INTERVAL = 1000; +export const INTERVAL = 1000; @customElement("hui-clock-card") export class HuiClockCard extends LitElement implements LovelaceCard { @@ -33,45 +30,14 @@ export class HuiClockCard extends LitElement implements LovelaceCard { @state() private _config?: ClockCardConfig; - @state() private _dateTimeFormat?: Intl.DateTimeFormat; - - @state() private _timeHour?: string; - - @state() private _timeMinute?: string; - - @state() private _timeSecond?: string; - - @state() private _timeAmPm?: string; - - private _tickInterval?: undefined | number; - public setConfig(config: ClockCardConfig): void { this._config = config; - this._initDate(); - } - - private _initDate() { - if (!this._config || !this.hass) { - return; + // Dynamically import the clock type based on the configuration + if (config.clock_style === "analog") { + import("./clock/hui-clock-card-analog"); + } else { + import("./clock/hui-clock-card-digital"); } - - let locale = this.hass?.locale; - - if (this._config?.time_format) { - locale = { ...locale, time_format: this._config.time_format }; - } - - this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, { - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hourCycle: useAmPm(locale) ? "h12" : "h23", - timeZone: - this._config?.time_zone || - resolveTimeZone(locale.time_zone, this.hass.config?.time_zone), - }); - - this._tick(); } public getCardSize(): number { @@ -80,77 +46,59 @@ export class HuiClockCard extends LitElement implements LovelaceCard { } public getGridOptions(): LovelaceGridOptions { - if (this._config?.clock_size === "medium") { - return { - min_rows: this._config?.title ? 2 : 1, - rows: 2, - max_rows: 4, - min_columns: 4, - columns: 6, - }; + switch (this._config?.clock_style) { + case "analog": + switch (this._config?.clock_size) { + case "medium": + return { + min_rows: this._config?.title ? 4 : 3, + rows: 3, + min_columns: 5, + columns: 6, + }; + case "large": + return { + min_rows: this._config?.title ? 5 : 4, + rows: 4, + min_columns: 6, + columns: 6, + }; + default: + return { + min_rows: this._config?.title ? 3 : 2, + rows: 2, + min_columns: 2, + columns: 6, + }; + } + default: + switch (this._config?.clock_size) { + case "medium": + return { + min_rows: this._config?.title ? 2 : 1, + rows: 2, + max_rows: 4, + min_columns: 4, + columns: 6, + }; + case "large": + return { + min_rows: 2, + rows: 2, + max_rows: 4, + min_columns: 6, + columns: 6, + }; + default: + return { + min_rows: 1, + rows: 1, + max_rows: 4, + min_columns: 3, + columns: 6, + }; + } } - - if (this._config?.clock_size === "large") { - return { - min_rows: 2, - rows: 2, - max_rows: 4, - min_columns: 6, - columns: 6, - }; - } - - return { - min_rows: 1, - rows: 1, - max_rows: 4, - min_columns: 3, - columns: 6, - }; - } - - protected updated(changedProps: PropertyValues) { - if (changedProps.has("hass")) { - const oldHass = changedProps.get("hass"); - if (!oldHass || oldHass.locale !== this.hass?.locale) { - this._initDate(); - } - } - } - - public connectedCallback() { - super.connectedCallback(); - this._startTick(); - } - - public disconnectedCallback() { - super.disconnectedCallback(); - this._stopTick(); - } - - private _startTick() { - this._tickInterval = window.setInterval(() => this._tick(), INTERVAL); - this._tick(); - } - - private _stopTick() { - if (this._tickInterval) { - clearInterval(this._tickInterval); - this._tickInterval = undefined; - } - } - - private _tick() { - if (!this._dateTimeFormat) return; - - const parts = this._dateTimeFormat.formatToParts(); - - this._timeHour = parts.find((part) => part.type === "hour")?.value; - this._timeMinute = parts.find((part) => part.type === "minute")?.value; - this._timeSecond = this._config?.show_seconds - ? parts.find((part) => part.type === "second")?.value - : undefined; - this._timeAmPm = parts.find((part) => part.type === "dayPeriod")?.value; } protected render() { @@ -170,16 +118,19 @@ export class HuiClockCard extends LitElement implements LovelaceCard { ${this._config.title !== undefined ? html`
${this._config.title}
` : nothing} -
-
${this._timeHour}
-
${this._timeMinute}
- ${this._timeSecond !== undefined - ? html`
${this._timeSecond}
` - : nothing} - ${this._timeAmPm !== undefined - ? html`
${this._timeAmPm}
` - : nothing} -
+ ${this._config.clock_style === "analog" + ? html` + + ` + : html` + + `} `; @@ -234,74 +185,6 @@ export class HuiClockCard extends LitElement implements LovelaceCard { font-size: var(--ha-font-size-2xl); line-height: var(--ha-line-height-condensed); } - - .time-parts { - align-items: center; - display: grid; - grid-template-areas: - "hour minute second" - "hour minute am-pm"; - - font-size: 2rem; - font-weight: var(--ha-font-weight-medium); - line-height: 0.8; - direction: ltr; - } - - .time-title + .time-parts { - font-size: 1.5rem; - } - - .time-wrapper.size-medium .time-parts { - font-size: 3rem; - } - - .time-wrapper.size-large .time-parts { - font-size: 4rem; - } - - .time-wrapper.size-medium .time-parts .time-part.second, - .time-wrapper.size-medium .time-parts .time-part.am-pm { - font-size: var(--ha-font-size-l); - margin-left: 6px; - } - - .time-wrapper.size-large .time-parts .time-part.second, - .time-wrapper.size-large .time-parts .time-part.am-pm { - font-size: var(--ha-font-size-2xl); - margin-left: 8px; - } - - .time-parts .time-part.hour { - grid-area: hour; - } - - .time-parts .time-part.minute { - grid-area: minute; - } - - .time-parts .time-part.second { - grid-area: second; - line-height: 0.9; - opacity: 0.4; - } - - .time-parts .time-part.am-pm { - grid-area: am-pm; - line-height: 0.9; - opacity: 0.6; - } - - .time-parts .time-part.second, - .time-parts .time-part.am-pm { - font-size: var(--ha-font-size-xs); - margin-left: 4px; - } - - .time-parts .time-part.hour:after { - content: ":"; - margin: 0 2px; - } `; } diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index bc28fa3bac..99bb70734e 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -371,11 +371,15 @@ export interface MarkdownCardConfig extends LovelaceCardConfig { export interface ClockCardConfig extends LovelaceCardConfig { type: "clock"; title?: string; + clock_style?: "digital" | "analog"; clock_size?: "small" | "medium" | "large"; show_seconds?: boolean | undefined; time_format?: TimeFormat; time_zone?: string; no_background?: boolean; + // Analog clock options + border?: boolean; + ticks?: "none" | "quarter" | "hour" | "minute"; } export interface MediaControlCardConfig extends LovelaceCardConfig { diff --git a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts index c1819febf3..8f791e992a 100644 --- a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts @@ -6,6 +6,7 @@ import { assert, assign, boolean, + defaulted, enums, literal, object, @@ -30,6 +31,7 @@ const cardConfigStruct = assign( baseLovelaceCardConfig, object({ title: optional(string()), + clock_style: optional(union([literal("digital"), literal("analog")])), clock_size: optional( union([literal("small"), literal("medium"), literal("large")]) ), @@ -37,6 +39,19 @@ const cardConfigStruct = assign( time_zone: optional(enums(Object.keys(timezones))), show_seconds: optional(boolean()), no_background: optional(boolean()), + // Analog clock options + border: optional(defaulted(boolean(), false)), + ticks: optional( + defaulted( + union([ + literal("none"), + literal("quarter"), + literal("hour"), + literal("minute"), + ]), + literal("hour") + ) + ), }) ); @@ -50,9 +65,23 @@ export class HuiClockCardEditor @state() private _config?: ClockCardConfig; private _schema = memoizeOne( - (localize: LocalizeFunc) => + (localize: LocalizeFunc, clockStyle: "digital" | "analog") => [ { name: "title", selector: { text: {} } }, + { + name: "clock_style", + selector: { + select: { + mode: "dropdown", + options: ["digital", "analog"].map((value) => ({ + value, + label: localize( + `ui.panel.lovelace.editor.card.clock.clock_styles.${value}` + ), + })), + }, + }, + }, { name: "clock_size", selector: { @@ -69,20 +98,66 @@ export class HuiClockCardEditor }, { name: "show_seconds", selector: { boolean: {} } }, { name: "no_background", selector: { boolean: {} } }, - { - name: "time_format", - selector: { - select: { - mode: "dropdown", - options: ["auto", ...Object.values(TimeFormat)].map((value) => ({ - value, - label: localize( - `ui.panel.lovelace.editor.card.clock.time_formats.${value}` - ), - })), - }, - }, - }, + ...(clockStyle === "digital" + ? ([ + { + name: "time_format", + selector: { + select: { + mode: "dropdown", + options: ["auto", ...Object.values(TimeFormat)].map( + (value) => ({ + value, + label: localize( + `ui.panel.lovelace.editor.card.clock.time_formats.${value}` + ), + }) + ), + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : clockStyle === "analog" + ? ([ + { + name: "border", + description: { + suffix: localize( + `ui.panel.lovelace.editor.card.clock.border.description` + ), + }, + default: false, + selector: { + boolean: {}, + }, + }, + { + name: "ticks", + description: { + suffix: localize( + `ui.panel.lovelace.editor.card.clock.ticks.description` + ), + }, + default: "hour", + selector: { + select: { + mode: "dropdown", + options: ["none", "quarter", "hour", "minute"].map( + (value) => ({ + value, + label: localize( + `ui.panel.lovelace.editor.card.clock.ticks.${value}.label` + ), + description: localize( + `ui.panel.lovelace.editor.card.clock.ticks.${value}.description` + ), + }) + ), + }, + }, + }, + ] as const satisfies readonly HaFormSchema[]) + : []), { name: "time_zone", selector: { @@ -107,10 +182,15 @@ export class HuiClockCardEditor ); private _data = memoizeOne((config) => ({ + clock_style: "digital", clock_size: "small", time_zone: "auto", time_format: "auto", show_seconds: false, + no_background: false, + // Analog clock options + border: false, + ticks: "hour", ...config, })); @@ -128,8 +208,13 @@ export class HuiClockCardEditor `; @@ -143,6 +228,14 @@ export class HuiClockCardEditor delete ev.detail.value.time_format; } + if (ev.detail.value.clock_style === "analog") { + ev.detail.value.border = ev.detail.value.border ?? false; + ev.detail.value.ticks = ev.detail.value.ticks ?? "hour"; + } else { + delete ev.detail.value.border; + delete ev.detail.value.ticks; + } + fireEvent(this, "config-changed", { config: ev.detail.value }); } @@ -154,6 +247,10 @@ export class HuiClockCardEditor return this.hass!.localize( "ui.panel.lovelace.editor.card.generic.title" ); + case "clock_style": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.clock.clock_style` + ); case "clock_size": return this.hass!.localize( `ui.panel.lovelace.editor.card.clock.clock_size` @@ -174,6 +271,31 @@ export class HuiClockCardEditor return this.hass!.localize( `ui.panel.lovelace.editor.card.clock.no_background` ); + case "border": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.clock.border.label` + ); + case "ticks": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.clock.ticks.label` + ); + default: + return undefined; + } + }; + + private _computeHelperCallback = ( + schema: SchemaUnion> + ) => { + switch (schema.name) { + case "border": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.clock.border.description` + ); + case "ticks": + return this.hass!.localize( + `ui.panel.lovelace.editor.card.clock.ticks.description` + ); default: return undefined; } diff --git a/src/translations/en.json b/src/translations/en.json index d1c72df25e..84a3c086ab 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7704,6 +7704,11 @@ "clock": { "name": "Clock", "description": "The Clock card displays the current time using your desired size and format.", + "clock_style": "Clock style", + "clock_styles": { + "digital": "Digital", + "analog": "Analog" + }, "clock_size": "Clock size", "clock_sizes": { "small": "Small", @@ -7723,7 +7728,31 @@ "time_zones": { "auto": "Use user settings" }, - "no_background": "No background" + "no_background": "No background", + "border": { + "label": "Border", + "description": "Whether to show a border around the clock" + }, + "ticks": { + "label": "Ticks", + "description": "Whether to show ticks (indices) on the outside of the clock", + "none": { + "label": "None", + "description": "No ticks (Hands only)" + }, + "quarter": { + "label": "Quarter", + "description": "4 ticks (Every 15 minutes)" + }, + "hour": { + "label": "Hour", + "description": "12 ticks (Every hour)" + }, + "minute": { + "label": "Minute", + "description": "60 ticks (Every minute)" + } + } }, "media-control": { "name": "Media control",