From 1bab9c6c84e8eb91ac878b802aa2eadd428c4fc5 Mon Sep 17 00:00:00 2001 From: Viet Ngoc <96962827+ngocjohn@users.noreply.github.com> Date: Sat, 24 Aug 2024 15:57:46 +0200 Subject: [PATCH] Add font customization options and styles (#13) * chore: Add font customization options * add Font options withi UI Editor * feat: Add font options for UI Editor * chore: Update font customization options and styles * chore: Add utility function for deep merging objects * chore: Update background and font customization options * chore: Update font customization options and styles --- src/const.ts | 18 ++ src/css/editor.css | 32 ++- src/css/style.css | 60 ++--- src/editor.ts | 557 ++++++++++++++++++++++----------------- src/languages/ca.json | 20 ++ src/languages/cs.json | 20 ++ src/languages/da.json | 20 ++ src/languages/de.json | 20 ++ src/languages/en.json | 49 ++++ src/languages/fr.json | 20 ++ src/languages/id.json | 20 ++ src/languages/pt.json | 20 ++ src/localize/string.json | 20 ++ src/lunar-phase-card.ts | 28 +- src/types.ts | 23 ++ src/utils/ha-helper.ts | 34 +++ 16 files changed, 676 insertions(+), 285 deletions(-) create mode 100644 src/utils/ha-helper.ts diff --git a/src/const.ts b/src/const.ts index 08ad140..ad5324f 100644 --- a/src/const.ts +++ b/src/const.ts @@ -1,4 +1,5 @@ import { version, repository } from '../package.json'; +import { FontSizeOptions, FontTextTransformOptions, FontCustomStyles } from './types'; export const REPOSITORY = repository.repo; export const CARD_VERSION = `v${version}`; @@ -42,3 +43,20 @@ export const MOON_IMAGES = [ moon_phase_14, moon_phase_15, ]; + +export const FONTSIZES: FontSizeOptions[] = ['auto', 'small', 'medium', 'large', 'x-large', 'xx-large']; +export const FONTSTYLES: FontTextTransformOptions[] = ['none', 'capitalize', 'uppercase', 'lowercase']; + +export const FONTCOLORS: string[] = [ + 'white', + 'black', + 'red', + 'green', + 'blue', + 'yellow', + 'orange', + 'purple', + 'pink', + 'brown', + 'gray', +]; diff --git a/src/css/editor.css b/src/css/editor.css index d25792e..b871181 100644 --- a/src/css/editor.css +++ b/src/css/editor.css @@ -10,20 +10,40 @@ display: flex; } -ha-select, ha-textfield { display: block; width: 100%; +} + + +ha-select { + width: 100%; +} + + +.font-config-content { + display: grid; + grid-gap: 8px; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); margin-bottom: 8px; + align-items: center; +} + +.font-config-type { + color: var(--secondary-text-color); + display: flex; + padding: 8px; + justify-content: space-between; + align-items: center; } -ha-formfield { - padding-bottom: 8px; - width: max-content; +.font-config-type .title { + font-weight: 500; + text-transform: uppercase; } -ha-expansion-panel .container { - padding: 0px 1rem !important; +.font-config-type .desc { + font-weight: 400; } .right-icon { diff --git a/src/css/style.css b/src/css/style.css index fb19939..b5b9b22 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -1,16 +1,12 @@ :host { - font-family: - system-ui, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - Oxygen, - Ubuntu, - Cantarell, - 'Open Sans', - 'Helvetica Neue', - sans-serif; + --swiper-pagination-bullet-inactive-color: var(--secondary-text-color); + --swiper-pagination-bottom: 0; + --lunar-card-padding: 12px; + --lunar-card-header-font-color: var(--lunar-card-header-font-color, #212121); + --lunar-card-label-font-color: var(--lunar-card-label-font-color, #212121); + --lunar-card-header-text-transform: capitalize; + --lunar-card-header-font-size: x-large; + --lunar-card-label-font-size: 14px; } *:focus { @@ -25,27 +21,20 @@ ha-card { height: auto; background-color: var(--card-background-color); letter-spacing: 0.5px; - padding: 1rem; - --swiper-pagination-bullet-inactive-color: var(--secondary-text-color); - --swiper-pagination-bottom: 0; + padding: var(--lunar-card-padding, 12px); + --primary-text-color: var(--lunar-card-label-font-color, #212121); + --swiper-theme-color: var(--primary-text-color); } -/* @media screen and (max-width: 800px) { - .moon-image { - width: 100%; - max-width: 100px !important; - max-height: 100px !important; - } -} */ - ha-card.--background { background-size: cover; background-position: bottom; background-repeat: no-repeat; background-image: var(--lunar-background-image); transition: all 0.5s ease; - --primary-text-color: #e1e1e1; + --primary-text-color: var(--lunar-card-label-font-color, #e1e1e1); --swiper-theme-color: var(--primary-text-color); + } .lunar-card-header { @@ -64,27 +53,31 @@ ha-card.--background { padding: 0; } +ha-card.--background h1 { + color: var(--lunar-card-header-font-color, #e1e1e1); +} + h1 { - color: var(--ha-card-header-color, --primary-text-color); + color: var(--lunar-card-header-font-color, var(--primary-text-color)); font-family: var(--ha-card-header-font-family, inherit); display: block; margin-block-start: 0px; margin-block-end: 0px; font-weight: 400; - text-transform: capitalize; - font-size: x-large; + text-transform: var(--lunar-card-header-text-transform, capitalize); + font-size: var(--lunar-card-header-font-size, x-large); } .btn-calendar { display: block; - color: var(--disabled-text-color); + color: var(--lunar-card-label-font-color); cursor: pointer; - transition: all 0.3s ease; + opacity: 0.4; } .btn-calendar:hover { - color: var(--primary-text-color); - opacity: 1; + color: var(--lunar-card-label-font-color); + opacity: 0.8; } .click-shrink { @@ -101,6 +94,8 @@ h1 { justify-content: space-between; align-items: center; gap: 1.5rem; + font-size: var(--lunar-card-label-font-size, 14px); + text-transform: var(--lunar-card-label-text-transform, none); } .lunar-card-content.flex-col { @@ -343,5 +338,6 @@ button.date-input-btn:hover { } .compact-item .value { - color: var(--secondary-text-color); + color: var(--lunar-card-label-font-color); + opacity: 0.8; } \ No newline at end of file diff --git a/src/editor.ts b/src/editor.ts index a860caf..b0846b7 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -5,30 +5,27 @@ import { customElement, property, state } from 'lit/decorators'; // Custom card helpers import { fireEvent, LovelaceCardEditor } from 'custom-card-helpers'; -import { HomeAssistantExtended as HomeAssistant, LunarPhaseCardConfig } from './types'; +import { HomeAssistantExtended as HomeAssistant, LunarPhaseCardConfig, FontCustomStyles, defaultConfig } from './types'; import { languageOptions, localize } from './localize/localize'; import { loadHaComponents, fetchLatestReleaseTag } from './utils/loader'; import { compareVersions } from './utils/helpers'; +import { deepMerge, InitializeDefaultConfig } from './utils/ha-helper'; -import { CARD_VERSION } from './const'; +import { CARD_VERSION, FONTCOLORS, FONTSTYLES, FONTSIZES } from './const'; import editorcss from './css/editor.css'; @customElement('lunar-phase-card-editor') export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEditor { @property({ attribute: false }) public hass!: HomeAssistant; - @state() private _config?: LunarPhaseCardConfig; - @property({ type: String }) selectedOption: string = 'url'; - - @state() private _newLatitude: number | string = ''; - @state() private _newLongitude: number | string = ''; + @state() private _config!: LunarPhaseCardConfig; @state() private _latestRelease = ''; private _systemLanguage = this.hass?.language; - public setConfig(config: LunarPhaseCardConfig): void { - this._config = config; + public async setConfig(config: LunarPhaseCardConfig): Promise { + this._config = this._config ? config : deepMerge(InitializeDefaultConfig(), config); } protected firstUpdated(changedProps: PropertyValues): void { @@ -50,18 +47,6 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit return localize(string, this.selectedLanguage, search, replace); }; - get _show_background(): boolean { - return this._config?.show_background || false; - } - - get _compact_view(): boolean { - return this._config?.compact_view || false; - } - - get _custom_background(): string { - return this._config?.custom_background || ''; - } - connectedCallback() { super.connectedCallback(); void loadHaComponents(); @@ -77,6 +62,8 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit super.disconnectedCallback(); } + /* --------------------------------- RENDERS -------------------------------- */ + protected render(): TemplateResult { if (!this.hass) { return html``; @@ -84,59 +71,21 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit return html` ${this._renderToast()} -
${this._renderBaseConfigSelector()} ${this.renderViewConfiguration()}
+
${this._renderBaseConfigSelector()} ${this._renderViewConfiguration()} + ${this._renderFontConfiguration()}
+
${CARD_VERSION === this._latestRelease - ? CARD_VERSION - : html`version: ${CARD_VERSION} -> ${this._latestRelease}`}${ + CARD_VERSION === this._latestRelease + ? CARD_VERSION + : html`version: ${CARD_VERSION} -> ${this._latestRelease}` + }
`; } - private _handleRadioChange(ev): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target; - const configValue = target.value; - - if (this._config[configValue] === true) { - return; - } - - const updates: Partial = {}; - const radiosOptions = this._getBaseConfigSelector().options.map((item) => item.key); - radiosOptions.forEach((item) => { - if (item === configValue) { - updates[item] = true; - } else { - updates[item] = false; - } - }); - - if (configValue === 'use_custom') { - updates.entity = ''; - } - if (configValue === 'use_entity') { - this._newLatitude = ''; - this._newLongitude = ''; - } - - if (configValue === 'use_default') { - updates.entity = ''; - updates.latitude = this.hass.config.latitude; - updates.longitude = this.hass.config.longitude; - } - - if (Object.keys(updates).length > 0) { - this._config = { ...this._config, ...updates }; - console.log('updates', updates); - fireEvent(this, 'config-changed', { config: this._config }); - } - } - private _renderBaseConfigSelector(): TemplateResult { const radiosOptions = this._getBaseConfigSelector().options; @@ -152,52 +101,68 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit `; }); - const customLatLongForms = html` - - - `; - const contentWrapp = html`
${radios}
- ${this._config?.use_default ? this._renderUseDefault() : ''} - ${this._config?.use_custom ? customLatLongForms : ''} - ${this._config?.use_entity ? this._renderEntityPicker() : ''} + ${this._config?.use_default + ? this._renderUseDefault() + : this._config?.use_custom + ? this._renderCustomLatLong() + : this._config?.use_entity + ? this._renderEntityPicker() + : ''}
`; - return this.panelTemplate('baseConfig', 'baseConfig', 'mdi:cog', contentWrapp); + + return this.panelTemplate('baseConfig', 'baseConfig', 'mdi:cog', contentWrapp, true); } private _renderUseDefault(): TemplateResult { - const latitude = this._config?.latitude || this.hass.config.latitude; - const longitude = this._config?.longitude || this.hass.config.longitude; - const content = html` - - - `; - return content; + const latLong = [ + { label: 'Latitude', value: this._config?.latitude }, + { label: 'Longitude', value: this._config?.longitude }, + ]; + + return html`
+ ${latLong.map((item) => { + return html` `; + })} +
`; + } + + private _renderCustomLatLong(): TemplateResult { + const latLong = [ + { label: this.localize('editor.placeHolder.latitude'), configKey: 'latitude' }, + { label: this.localize('editor.placeHolder.longitude'), configKey: 'longitude' }, + ]; + + return html`
+ ${latLong.map((item) => { + return html` + + `; + })} +
`; } private _renderEntityPicker(): TemplateResult { + // Filter entities with moon_phase const entities = Object.keys(this.hass.states) .filter((entity) => entity.startsWith('sensor') && entity.endsWith('_moon_phase')) - .filter((entity) => entity.startsWith('sensor') && !entity.endsWith('next_moon_phase')) - .sort(); + .filter((entity) => entity.startsWith('sensor') && !entity.endsWith('next_moon_phase')); + + // Filter entities with latitude and longitude + const entitiesWithLatLong = Object.keys(this.hass.states).filter( + (entity) => this.hass.states[entity].attributes.latitude && this.hass.states[entity].attributes.longitude, + ); + + const combinedEntities = [...entities, 'separator', ...entitiesWithLatLong]; + const entityPicker = html` `; + const entitySelected = this._config?.entity ? true : false; const entityLatLong = this._getEntityLatLong(); - const entityContent = entityLatLong.map((item) => { - return html` `; - }); - const content = html` ${entityPicker} ${entitySelected ? entityContent : ''} `; - return content; - } - - private _getEntityLatLong(): { label: string; value: number | string }[] { - if (!this._config?.entity) { - return []; - } + const entityContent = html`
+ ${entityLatLong.map((item) => { + return html` `; + })} +
`; - const entity = this.hass.states[this._config.entity]; - if (!entity || !entity.attributes) { - return []; - } + const content = html` ${entityPicker} ${entitySelected ? entityContent : ''} `; - return [ - { - label: 'City', - value: entity.attributes.location.name, - }, - { - label: 'Latitude', - value: entity.attributes.location.latitude, - }, - { - label: 'Longitude', - value: entity.attributes.location.longitude, - }, - ]; + return content; } - private renderViewConfiguration(): TemplateResult { + private _renderViewConfiguration(): TemplateResult { const langOpts = [...languageOptions.sort((a, b) => a.name.localeCompare(b.name))]; + // Map langOpts to the format expected by _haComboBox + const itemsLang = langOpts.map((lang) => ({ + value: lang.key, + label: `${lang.name} (${lang.nativeName})`, + })); + + const viewItemMap = [ + { label: 'compactView', configValue: 'compact_view' }, + { label: 'showBackground', configValue: 'show_background' }, + { label: 'timeFormat', configValue: '12hr_format' }, + ]; const viewOptions = html`
- - - - - - - - + ${viewItemMap.map((item) => this._tempCheckBox(item.label, item.configValue, item.configValue))}
`; - const languageSelect = html` - ev.stopPropagation()} - > - ${langOpts.map( - (lang) => - html`${lang.nativeName ? lang.nativeName : lang.name} `, - )} - + + // Create the ha-combo-box using the _haComboBox method + const langComboBox = html` + ${this._haComboBox( + itemsLang, // Passing the mapped language options + 'placeHolder.language', // Localization key for the label + this._config?.selected_language || this._systemLanguage, // Currently selected language + 'selected_language', // Config value key + false, // Allow custom value + )} `; const textFormInput = html` @@ -293,10 +226,10 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit - ${!this._custom_background + ${!this._config?.custom_background ? html` this.shadowRoot?.getElementById('file-upload-new')?.click()}> Upload @@ -316,16 +249,83 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit `} `; - const content = html` ${viewOptions} ${languageSelect} ${textFormInput} `; + const content = html` ${viewOptions} ${langComboBox} ${textFormInput} `; return this.panelTemplate('viewConfig', 'viewConfig', 'mdi:image', content); } - private panelTemplate(title: string, secondary: string, icon: string, content: TemplateResult): TemplateResult { + private _renderFontConfiguration(): TemplateResult { + const _fontPrefix = 'fontOptions'; + + // Helper function for localization + const localizeKey = (key: string) => this.localize(`editor.${_fontPrefix}.${key}`); + + const createFontConfigRow = (prefix: 'header' | 'label') => { + // Function to generate the ha-combo-box elements + const createComboBox = (type: 'size' | 'style' | 'color', allowCustomValue = true) => { + const configKey = `${prefix}_font_${type}`; + + // Ensure font_customize is defined, fallback to defaults if undefined + const fontCustomize = this._config?.font_customize ?? defaultConfig.font_customize; + + const items = + type === 'color' + ? FONTCOLORS.map((color) => ({ value: color, label: color })) + : type === 'size' + ? FONTSIZES.map((size) => ({ value: size, label: size })) + : FONTSTYLES.map((style) => ({ value: style, label: style })); + + return this._haComboBox( + items, + `${_fontPrefix}.${prefix}Font${type.charAt(0).toUpperCase() + type.slice(1)}`, + fontCustomize[configKey] || (type === 'size' ? 'auto' : type === 'style' ? 'none' : ''), + configKey, + allowCustomValue, + ); + }; + + return html` ${createComboBox('size')} ${createComboBox('style', false)} ${createComboBox('color')} `; + }; + + const hideLabelCompactView = this._config?.compact_view + ? this._tempCheckBox('fontOptions.hideLabel', 'font_customize.hide_label', 'hide_label') + : ''; + + return this.panelTemplate( + 'fontOptions', + 'fontOptions', + 'mdi:format-font', + html` +
+
+ ${localizeKey('headerFontConfig.title')} + ${localizeKey('headerFontConfig.description')} +
+
${createFontConfigRow('header')}
+
+ +
+
+ ${localizeKey('labelFontConfig.title')} + ${localizeKey('labelFontConfig.description')} +
+
${createFontConfigRow('label')} ${hideLabelCompactView}
+
+ `, + ); + } + + private panelTemplate( + title: string, + secondary: string, + icon: string, + content: TemplateResult, + expanded: boolean = false, + ): TemplateResult { const localTitle = this.localize(`editor.${title}.title`); const localDesc = this.localize(`editor.${secondary}.description`); return html`
- +
@@ -367,6 +367,147 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit `; } + /* ---------------------------- RENDER TEMPLATES ---------------------------- */ + + private _haComboBox = ( + items: { value: string; label: string }[], + labelKey: string, + valueKey: string, + configValue: string, + allowCustomValue = true, + ): TemplateResult => { + return html` + + `; + }; + + private _tempCheckBox = (labelKey: string, checkedValue, configValueKey: string): TemplateResult => { + return html` + + `; + }; + + /* ----------------------------- HANDLER METHODS ---------------------------- */ + + private _handleValueChange(ev) { + if (!this._config || !this.hass) { + return; + } + + const target = ev.target as any; + const configValue = target?.configValue; + + // Safely access the value, add a fallback to an empty string if undefined + const value = target?.checked !== undefined ? target.checked : ev.detail.value; + + const updates: Partial = {}; + + // Define default values for FontCustomStyles + const defaultFontCustomStyles: FontCustomStyles = { + header_font_size: 'x-large', + header_font_style: 'capitalize', + header_font_color: '', + label_font_size: 'auto', + label_font_style: 'none', + label_font_color: '', + hide_label: false, + }; + + // Check if the configValue is a key of FontCustomStyles + if (configValue in this._config.font_customize) { + const key = configValue as keyof FontCustomStyles; + + // If the current value is undefined, use the default value + const updatedValue = value !== undefined ? value : defaultFontCustomStyles[key]; + + updates.font_customize = { + ...this._config.font_customize, + [key]: updatedValue, + }; + } else { + // Update the main configuration object + updates[configValue] = value; + } + + if (Object.keys(updates).length > 0) { + this._config = { ...this._config, ...updates }; + fireEvent(this, 'config-changed', { config: this._config }); + } + } + + private _getEntityLatLong(): { label: string; value: number | string }[] { + if (!this._config?.entity) { + return []; + } + + const entity = this.hass.states[this._config.entity]; + if (!entity || !entity.attributes) { + return []; + } + + return [ + { + label: 'Latitude', + value: entity.attributes.latitude ?? entity.attributes.location.latitude, + }, + { + label: 'Longitude', + value: entity.attributes.longitude ?? entity.attributes.location.longitude, + }, + ]; + } + + private _handleRadioChange(ev): void { + if (!this._config || !this.hass) { + return; + } + const target = ev.target; + const configValue = target.value; + + if (this._config[configValue] === true) { + return; + } + + const updates: Partial = {}; + const radiosOptions = this._getBaseConfigSelector().options.map((item) => item.key); + radiosOptions.forEach((item) => { + if (item === configValue) { + updates[item] = true; + } else { + updates[item] = false; + } + }); + + if (configValue === 'use_custom') { + updates.entity = ''; + } + + if (configValue === 'use_default') { + updates.entity = ''; + updates.latitude = this.hass.config.latitude; + updates.longitude = this.hass.config.longitude; + } + + if (Object.keys(updates).length > 0) { + this._config = { ...this._config, ...updates }; + console.log('updates', updates); + fireEvent(this, 'config-changed', { config: this._config }); + } + } + private _handleAlertDismissed(): void { const toast = this.shadowRoot?.getElementById('toast') as HTMLElement; const version = this.shadowRoot?.querySelector('.version') as HTMLElement; @@ -458,85 +599,19 @@ export class LunarPhaseCardEditor extends LitElement implements LovelaceCardEdit return; } const entity = ev.detail.value; - if (this._config.entity === entity) { + if (this._config.entity === entity || !entity) { return; } - const location = this.hass.states[entity].attributes.location; - const latitude = location.latitude; - const longitude = location.longitude; + const attribute = this.hass.states[entity].attributes; + + const latitude = attribute.latitude ?? attribute.location.latitude; + const longitude = attribute.longitude ?? attribute.location.longitude; const updates: Partial = { entity, latitude, longitude }; this._config = { ...this._config, ...updates }; fireEvent(this, 'config-changed', { config: this._config }); } - private _customLatLongChanged(ev): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target; - const configValue = target.configValue; - - if (this[`${configValue}`] === target.value) { - return; - } - - if (configValue === 'latitude') { - this._newLatitude = target.value; - } - if (configValue === 'longitude') { - this._newLongitude = target.value; - } - - this._config = { - ...this._config, - [configValue]: parseFloat(target.value), - }; - console.log(configValue, target.value); - fireEvent(this, 'config-changed', { config: this._config }); - } - - private _valueChanged(ev): void { - if (!this._config || !this.hass) { - return; - } - const target = ev.target; - const configValue = target.configValue; - - if (this[`${configValue}`] === target.value) { - return; - } - - let newValue: any; - - if (['custom_latitude', 'custom_longitude'].includes(configValue)) { - newValue = Number(target.value); - this._config = { - ...this._config, - [configValue]: newValue, - }; - } else if (newValue && newValue.length === 0) { - // Check for an empty array - const tmpConfig = { ...this._config }; - delete tmpConfig[configValue]; - this._config = tmpConfig; - } else { - newValue = target.checked !== undefined ? target.checked : target.value; - this._config = { - ...this._config, - [configValue]: newValue, - }; - } - - if (newValue && newValue.length === 0) { - // Check for an empty array - const tmpConfig = { ...this._config }; - delete tmpConfig[configValue]; - this._config = tmpConfig; - } - fireEvent(this, 'config-changed', { config: this._config }); - } - static get styles(): CSSResultGroup { return css` ${editorcss} diff --git a/src/languages/ca.json b/src/languages/ca.json index d8c70c2..4174dd3 100644 --- a/src/languages/ca.json +++ b/src/languages/ca.json @@ -46,6 +46,26 @@ "useEntity": "Fer servir l'entitat", "useCustom": "Personalitza" }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Vista compacta", "showBackground": "Mostra el fons", "timeFormat": "Format del temps de 12 hores", diff --git a/src/languages/cs.json b/src/languages/cs.json index dba9593..e163ee1 100644 --- a/src/languages/cs.json +++ b/src/languages/cs.json @@ -46,6 +46,26 @@ "useEntity": "Použít entitu", "useCustom": "Použít vlastní" }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Kompaktní zobrazení", "showBackground": "Zobrazit pozadí", "timeFormat": "12-hodinový formát času", diff --git a/src/languages/da.json b/src/languages/da.json index b9cc41c..0e1eb84 100644 --- a/src/languages/da.json +++ b/src/languages/da.json @@ -46,6 +46,26 @@ "useEntity": "Brug enhed", "useCustom": "Brug brugerdefineret" }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Kompakt udsigt", "showBackground": "Vis baggrund", "timeFormat": "12-timers tidsformat", diff --git a/src/languages/de.json b/src/languages/de.json index f97fac1..5eccfe1 100644 --- a/src/languages/de.json +++ b/src/languages/de.json @@ -46,6 +46,26 @@ "useEntity": "Entität verwenden", "useCustom": "Benutzerdefinierte Einstellungen verwenden " }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Kompakte Ansicht", "showBackground": "Hintergrund anzeigen", "timeFormat": "12-Stunden-Zeitformat", diff --git a/src/languages/en.json b/src/languages/en.json index bdcaac0..297844e 100644 --- a/src/languages/en.json +++ b/src/languages/en.json @@ -46,6 +46,55 @@ "useEntity": "Use entity", "useCustom": "Use custom" }, + "fontOptions": { + "title": "Font customization", + "description": "Set a configuration for font size and color", + "hideLabel": "Hide label", + "headerFontConfig": { + "title": "Header font", + "description": "Set a configuration for header font" + }, + "labelFontConfig": { + "title": "Label font", + "description": "Set a configuration for label font" + }, + "headerFontSize": "Header font size", + "headerFontColor": "Header font color", + "headerFontStyle": "Header font style", + "labelFontSize": "Label font size", + "labelFontColor": "Label font color", + "labelFontStyle": "Label font style", + "valueFontSize": "Value font size", + "fontSize": "Font size", + "fontColor": "Font color", + "fontTextTransform": "Text transform", + "textTransformOptions": { + "none": "None", + "capitalize": "Capitalize", + "uppercase": "Uppercase", + "lowercase": "Lowercase" + }, + "fontSizes": { + "auto": "Auto", + "small": "Small", + "medium": "Medium", + "large": "Large", + "x-large": "X-Large", + "xx-large": "XX-Large" + }, + "fontStyles": { + "none": "None", + "capitalize": "Capitalize", + "uppercase": "Uppercase", + "lowercase": "Lowercase" + }, + "header_font_size": "Header font size", + "header_font_color": "Header font color", + "header_font_style": "Header font style", + "label_font_size": "Label font size", + "label_font_color": "Label font color", + "label_font_style": "Label font style" + }, "compactView": "Compact view", "showBackground": "Show background", "timeFormat": "12-hour time format", diff --git a/src/languages/fr.json b/src/languages/fr.json index 0f55d00..43e5254 100644 --- a/src/languages/fr.json +++ b/src/languages/fr.json @@ -46,6 +46,26 @@ "useEntity": "Entitée", "useCustom": "Custom" }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Affichage compact", "showBackground": "Afficher fond d'écran", "timeFormat": "Format 12h", diff --git a/src/languages/id.json b/src/languages/id.json index b1c12b4..bf75b27 100644 --- a/src/languages/id.json +++ b/src/languages/id.json @@ -46,6 +46,26 @@ "useEntity": "Gunakan entitas", "useCustom": "Gunakan pengaturan khusus" }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Tampilan kompak", "showBackground": "Tampilkan gambar latar belakang", "timeFormat": "Format waktu 12 jam", diff --git a/src/languages/pt.json b/src/languages/pt.json index 06720f8..57b6b7e 100644 --- a/src/languages/pt.json +++ b/src/languages/pt.json @@ -46,6 +46,26 @@ "useEntity": "Usar entidade", "useCustom": "Usar personalizado" }, + "fontOptions": { + "title": "", + "description": "", + "hideLabel": "", + "headerFontConfig": { + "title": "", + "description": "" + }, + "labelFontConfig": { + "title": "", + "description": "" + }, + "headerFontSize": "", + "headerFontColor": "", + "headerFontStyle": "", + "labelFontSize": "", + "labelFontColor": "", + "labelFontStyle": "", + "valueFontSize": "" + }, "compactView": "Visualização compacta", "showBackground": "Mostrar plano de fundo", "timeFormat": "Formato de 12 horas", diff --git a/src/localize/string.json b/src/localize/string.json index bdcaac0..dbe604c 100644 --- a/src/localize/string.json +++ b/src/localize/string.json @@ -46,6 +46,26 @@ "useEntity": "Use entity", "useCustom": "Use custom" }, + "fontOptions": { + "title": "Font customization", + "description": "Set a configuration for font size and color", + "hideLabel": "Hide label", + "headerFontConfig": { + "title": "Header font", + "description": "Set a configuration for header font" + }, + "labelFontConfig": { + "title": "Label font", + "description": "Set a configuration for label font" + }, + "headerFontSize": "Header font size", + "headerFontColor": "Header font color", + "headerFontStyle": "Header font style", + "labelFontSize": "Label font size", + "labelFontColor": "Label font color", + "labelFontStyle": "Label font style", + "valueFontSize": "Value font size" + }, "compactView": "Compact view", "showBackground": "Show background", "timeFormat": "12-hour time format", diff --git a/src/lunar-phase-card.ts b/src/lunar-phase-card.ts index a4db9f9..55bbb01 100644 --- a/src/lunar-phase-card.ts +++ b/src/lunar-phase-card.ts @@ -77,13 +77,16 @@ export class LunarPhaseCard extends LitElement { protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); - this._setBackgroundCss(); // Initialize Swiper only if the parent element does not have the class 'preview' if (this.parentElement && !this.parentElement.classList.contains('preview')) { setTimeout(() => { this.fetchBaseMoonData(); }, 300); } + this._setBackgroundCss(); + if (this.config.font_customize) { + this._setCustomVars(); + } } connectedCallback(): void { @@ -96,9 +99,6 @@ export class LunarPhaseCard extends LitElement { } disconnectedCallback(): void { - if (process.env.ROLLUP_WATCH === 'true' && window.LunarCard === this) { - window.LunarCard = undefined; - } this.clearRefreshInterval(); this._connected = false; super.disconnectedCallback(); @@ -279,7 +279,7 @@ export class LunarPhaseCard extends LitElement { ${value} ${unit}
- ${label} + ${!this.config.font_customize.hide_label ? html` ${label}` : nothing} `; }; @@ -454,6 +454,22 @@ export class LunarPhaseCard extends LitElement { this.style.setProperty('--lunar-background-image', `url(${background})`); } + private _setCustomVars() { + const fontOptions = this.config.font_customize; + if (!fontOptions) return; + const varCss = { + '--lunar-card-header-font-size': fontOptions.header_font_size, + '--lunar-card-header-text-transform': fontOptions.header_font_style, + '--lunar-card-header-font-color': fontOptions.header_font_color, + '--lunar-card-label-font-size': fontOptions.label_font_size, + '--lunar-card-label-text-transform': fontOptions.label_font_style, + '--lunar-card-label-font-color': fontOptions.label_font_color, + }; + Object.entries(varCss).forEach(([key, value]) => { + this.style.setProperty(key, value); + }); + } + // https://lit.dev/docs/components/styles/ public static get styles(): CSSResultGroup { return [style]; @@ -470,7 +486,7 @@ export class LunarPhaseCard extends LitElement { declare global { interface Window { - LunarCard: LunarPhaseCard | undefined; + LunarCard: LunarPhaseCard; } interface HTMLElementTagNameMap { 'lunar-phase-card': LunarPhaseCard; diff --git a/src/types.ts b/src/types.ts index fa71ef9..af5508b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,6 +31,19 @@ export type HomeAssistantExtended = HomeAssistant & { formatEntityAttributeValue: (entityId: string, attribute: string) => string; }; +export type FontSizeOptions = 'auto' | 'small' | 'medium' | 'large' | 'x-large' | 'xx-large'; +export type FontTextTransformOptions = 'none' | 'capitalize' | 'uppercase' | 'lowercase'; + +export interface FontCustomStyles { + header_font_size: FontSizeOptions; + header_font_style: FontTextTransformOptions; + header_font_color: string; + label_font_size: FontSizeOptions; + label_font_style: FontTextTransformOptions; + label_font_color: string; + hide_label: boolean; +} + export interface LunarPhaseCardConfig extends LovelaceCardConfig { type: string; entity?: string; @@ -44,6 +57,7 @@ export interface LunarPhaseCardConfig extends LovelaceCardConfig { selected_language?: string | null; latitude: number; longitude: number; + font_customize: FontCustomStyles; } export const defaultConfig: Partial = { @@ -56,6 +70,15 @@ export const defaultConfig: Partial = { selected_language: 'en', compact_view: true, '12hr_format': false, + font_customize: { + header_font_size: 'x-large', + header_font_style: 'capitalize', + header_font_color: '', + label_font_size: 'auto', + label_font_style: 'none', + label_font_color: '', + hide_label: false, + }, }; export interface LunarPhaseData { diff --git a/src/utils/ha-helper.ts b/src/utils/ha-helper.ts new file mode 100644 index 0000000..4f2fd8a --- /dev/null +++ b/src/utils/ha-helper.ts @@ -0,0 +1,34 @@ +import { HomeAssistantExtended as HomeAssistant, LunarPhaseCardConfig } from '../types'; + +export function deepMerge(target: any, source: any): any { + for (const key of Object.keys(source)) { + if (source[key] instanceof Object && key in target) { + Object.assign(source[key], deepMerge(target[key], source[key])); + } + } + return { ...target, ...source }; +} + +export function InitializeDefaultConfig(): Record { + const defaultConfig: Partial = { + type: 'custom:lunar-phase-card', + entity: '', + use_default: true, + use_custom: false, + use_entity: false, + show_background: true, + selected_language: 'en', + compact_view: true, + '12hr_format': false, + font_customize: { + header_font_size: 'x-large', + header_font_style: 'capitalize', + header_font_color: '', + label_font_size: 'auto', + label_font_style: 'none', + label_font_color: '', + hide_label: false, + }, + }; + return defaultConfig; +}