diff --git a/src/components/editor/panel-images.ts b/src/components/editor/panel-images.ts
index ec6d123..7185c2d 100644
--- a/src/components/editor/panel-images.ts
+++ b/src/components/editor/panel-images.ts
@@ -10,6 +10,7 @@ import Sortable from 'sortablejs';
import { VehicleCardEditor } from '../../editor';
import { ImageConfig, VehicleCardConfig } from '../../types';
import { imageInputChange, handleFilePicked } from '../../utils/editor-image-handler';
+import { Picker } from '../../utils/create';
import editorcss from '../../css/editor.css';
@@ -76,11 +77,98 @@ export class PanelImages extends LitElement {
this.toggleUpload()} class="upload-btn">
${this.editor.hass.localize('ui.components.selectors.image.upload')}
+ this.toggleSwiperConfig()} class="swiper-btn"> Swiper Config
`;
return urlInput;
}
+ private _renderSwiperConfig(): TemplateResult {
+ const image = this.config?.extra_configs?.images_swipe || {};
+ const sharedConfig = {
+ component: this,
+ configType: 'images_swipe',
+ };
+
+ const swiperConfig = [
+ {
+ value: image.max_height || 150,
+ configValue: 'max_height',
+ label: 'Max Height (px)',
+ options: { selector: { number: { min: 100, max: 500, mode: 'slider', step: 1 } } },
+ pickerType: 'number' as 'number',
+ },
+ {
+ value: image.max_width || 450,
+ configValue: 'max_width',
+ label: 'Max Width (px)',
+ options: { selector: { number: { min: 100, max: 500, mode: 'slider', step: 1 } } },
+ pickerType: 'number' as 'number',
+ },
+
+ {
+ value: image.delay || 3000,
+ configValue: 'delay',
+ label: 'Delay (ms)',
+ options: { selector: { number: { min: 500, max: 10000, mode: 'slider', step: 50 } } },
+ pickerType: 'number' as 'number',
+ },
+ {
+ value: image.speed || 500,
+ configValue: 'speed',
+ label: 'Speed (ms)',
+ options: { selector: { number: { min: 100, max: 5000, mode: 'slider', step: 50 } } },
+ pickerType: 'number' as 'number',
+ },
+ {
+ value: image.effect || 'slide',
+ configValue: 'effect',
+ label: 'Effect',
+ items: [
+ {
+ value: 'slide',
+ label: 'Slide',
+ },
+ {
+ value: 'fade',
+ label: 'Fade',
+ },
+ {
+ value: 'coverflow',
+ label: 'Coverflow',
+ },
+ ],
+ pickerType: 'attribute' as 'attribute',
+ },
+ ];
+ const swiperBooleanConfig = [
+ {
+ value: image.autoplay || false,
+ configValue: 'autoplay',
+ label: 'Autoplay',
+ pickerType: 'selectorBoolean' as 'selectorBoolean',
+ },
+ {
+ value: image.loop || true,
+ configValue: 'loop',
+ label: 'Loop',
+ pickerType: 'selectorBoolean' as 'selectorBoolean',
+ },
+ ];
+
+ return html`
+
+
+
${swiperConfig.map((config) => this.generateItemPicker({ ...config, ...sharedConfig }))}
+
+
+ ${swiperBooleanConfig.map((config) => this.generateItemPicker({ ...config, ...sharedConfig }), 'sub-content')}
+
+
`;
+ }
+
private _imageList(): TemplateResult {
if (this._reindexImages) {
return html`Loading...`;
@@ -141,6 +229,7 @@ export class PanelImages extends LitElement {
return html`${dropArea}${imageList}`;
}
+
private _renderDropArea(): TemplateResult {
const errorMsg = this.editor.localize('card.common.toastImageError');
@@ -185,8 +274,9 @@ export class PanelImages extends LitElement {
protected render(): TemplateResult {
const imageList = this._imageList();
const addNewImage = this._renderUploadAddNewImage();
+ const swiperConfig = this._renderSwiperConfig();
- const content = html`${imageList}${addNewImage}`;
+ const content = html`${imageList}${addNewImage} ${swiperConfig}`;
return content;
}
@@ -195,6 +285,25 @@ export class PanelImages extends LitElement {
fireEvent(this.editor, 'config-changed', { config: this.config });
}
+ private toggleSwiperConfig(): void {
+ const swiperConfig = this.shadowRoot?.querySelector('.image-swiper-config') as HTMLElement;
+ const imageList = this.shadowRoot?.getElementById('images-list') as HTMLElement;
+ const swiperBtn = this.shadowRoot?.querySelector('.swiper-btn') as HTMLElement;
+ const uploadBtn = this.shadowRoot?.querySelector('.upload-btn') as HTMLElement;
+ const isHidden = swiperConfig?.style.display === 'none';
+ if (isHidden) {
+ swiperConfig.style.display = 'block';
+ imageList.style.display = 'none';
+ uploadBtn.style.visibility = 'hidden';
+ swiperBtn.innerHTML = 'Cancel';
+ } else {
+ swiperConfig.style.display = 'none';
+ uploadBtn.style.visibility = 'visible';
+ imageList.style.display = 'block';
+ swiperBtn.innerHTML = 'Swiper Config';
+ }
+ }
+
private toggleUpload(): void {
const dropArea = this.shadowRoot?.getElementById('drop-area') as HTMLElement;
const imageList = this.shadowRoot?.getElementById('images-list') as HTMLElement;
@@ -210,6 +319,17 @@ export class PanelImages extends LitElement {
addImageBtn.innerHTML = 'Add Image';
}
}
+
+ private generateItemPicker(config: any, wrapperClass = 'item-content'): TemplateResult {
+ return html`
+
+ ${Picker({
+ ...config,
+ })}
+
+ `;
+ }
+
private _handleDragOver(event: DragEvent) {
event.preventDefault();
this.isDragging = true;
@@ -218,6 +338,7 @@ export class PanelImages extends LitElement {
private _handleDragLeave() {
this.isDragging = false;
}
+
private _handleDrop(event: DragEvent): void {
event.preventDefault();
this.isDragging = false;
@@ -353,4 +474,28 @@ export class PanelImages extends LitElement {
this._newImageUrl = '';
this._debouncedConfigChanged();
}
+
+ _valueChanged(ev: any): void {
+ ev.stopPropagation();
+ if (!this.config) return;
+
+ const target = ev.target;
+ const configValue = target.configValue;
+ const configType = target.configType;
+ let newValue: any = ev.detail.value;
+
+ const updates: Partial = {};
+
+ if (configType === 'images_swipe') {
+ let imagesSwipe = this.config.extra_configs?.images_swipe || {};
+ imagesSwipe = { ...imagesSwipe, [configValue]: newValue };
+ updates.extra_configs = { ...this.config.extra_configs, images_swipe: imagesSwipe };
+ this.config = { ...this.config, ...updates };
+ this._debouncedConfigChanged();
+ } else {
+ updates[configValue] = newValue;
+ this.config = { ...this.config, ...updates };
+ this._debouncedConfigChanged();
+ }
+ }
}
diff --git a/src/css/styles.css b/src/css/styles.css
index 3853531..bd6c3ec 100644
--- a/src/css/styles.css
+++ b/src/css/styles.css
@@ -238,7 +238,6 @@ header h1 {
}
}
-
.grid-container {
display: grid;
grid-template-columns: repeat(2, minmax(140px, 1fr));
@@ -251,13 +250,9 @@ header h1 {
.grid-item {
display: flex;
position: relative;
- gap: var(--vic-card-padding);
padding: var(--vic-gutter-gap) var(--vic-card-padding);
- height: 100%;
- flex-direction: row;
- align-items: center;
background: var(--secondary-background-color, var(--card-background-color, #fff));
- box-shadow: var(--ha-card-box-shadow, none);
+ box-shadow: var(--ha-card-box-shadow);
box-sizing: border-box;
border-radius: var(--ha-card-border-radius, 12px);
border-width: var(--ha-card-border-width, 1px);
@@ -266,18 +261,22 @@ header h1 {
transition: all 0.3s ease-out;
opacity: 1;
cursor: pointer;
+}
- &:hover {
- box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.3);
- }
+.grid-item:hover {
+ box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.3);
}
-/* @media screen and (max-width: 768px) {
- .grid-item {
- padding: 8px;
- gap: 0.5rem;
- }
-} */
+
+.grid-item .click-container {
+ display: flex;
+ height: 100%;
+ flex-direction: row;
+ align-items: center;
+ gap: var(--vic-card-padding);
+}
+
+
.grid-item .item-notify {
display: flex;
position: absolute;
@@ -293,12 +292,6 @@ header h1 {
display: none;
}
-/* .grid-item .item-icon {
- display: inline-block;
- border-radius: 50% 50%;
- background-color: var(--chip-background-color);
- padding: 0.5rem;
-} */
.grid-item .item-icon {
position: relative;
@@ -326,23 +319,23 @@ header h1 {
flex-direction: column;
min-width: 0;
overflow: hidden;
+}
- .primary {
- font-weight: 500;
- font-size: 1rem;
- white-space: nowrap;
- position: relative;
- text-overflow: ellipsis;
- overflow: hidden;
- }
+.grid-item .item-content .primary {
+ font-weight: 500;
+ font-size: 1rem;
+ white-space: nowrap;
+ position: relative;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
- .secondary {
- color: var(--secondary-text-color);
- /* text-transform: capitalize; */
- letter-spacing: 0.5px;
- font-size: smaller;
- text-wrap: nowrap;
- }
+.grid-item .item-content .secondary {
+ color: var(--secondary-text-color);
+ /* text-transform: capitalize; */
+ letter-spacing: 0.5px;
+ font-size: smaller;
+ text-wrap: nowrap;
}
.primary.title-wrap {
@@ -515,6 +508,7 @@ header h1 {
transition: all 0.3s ease-out;
/* margin-bottom: 1rem; */
position: relative;
+ overflow: hidden;
}
.default-card:not(:first-child) {
@@ -701,10 +695,10 @@ dialog::backdrop {
opacity: 0.5;
cursor: pointer;
transition: opacity 0.3s;
+}
- &:hover {
- opacity: 1;
- }
+.tyre-toggle-btn:hover {
+ opacity: 1;
}
/* TYRE WRAP ROTATED */
@@ -718,13 +712,13 @@ dialog::backdrop {
.tyre-wrapper .background {
position: absolute;
- width: 100%;
- height: 100%;
+ width: var(--vic-tire-size, 100%);
+ height: var(--vic-tire-size, 100%);
z-index: 0;
- top: 50%;
- left: 50%;
+ top: var(--vic-tire-top, 50%);
+ left: var(--vic-tire-left, 50%);
transform: translate(-50%, -50%);
- background-size: cover;
+ background-size: contain;
background-repeat: no-repeat;
overflow: hidden;
filter: drop-shadow(2px 4px 1rem #000000d8);
@@ -732,8 +726,8 @@ dialog::backdrop {
.tyre-wrapper .tyre-box {
position: absolute;
- width: 30%;
- height: 60%;
+ width: 35%;
+ height: 50%;
z-index: 1;
display: flex;
align-items: center;
@@ -742,6 +736,7 @@ dialog::backdrop {
gap: 0.5rem;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.3);
transition: all 400ms cubic-bezier(0.3, 0.0, 0.8, 0.15);
+ scale: var(--vic-tire-value-size);
}
.tyre-value {
@@ -753,11 +748,11 @@ dialog::backdrop {
.tyre-name {
color: var(--secondary-text-color);
- text-align: center;
+ text-align: left;
margin: 0;
text-transform: uppercase;
letter-spacing: 1.5px;
- text-wrap: pretty;
+ text-wrap: nowrap;
}
.tyre-info {
diff --git a/src/editor.ts b/src/editor.ts
index 49e1537..444f7c2 100644
--- a/src/editor.ts
+++ b/src/editor.ts
@@ -27,6 +27,7 @@ import { uploadImage } from './utils/editor-image-handler';
import { handleFirstUpdated, deepMerge } from './utils/ha-helpers';
import { compareVersions } from './utils/helpers';
import { loadHaComponents, stickyPreview } from './utils/loader';
+import { Create } from './utils';
// Import the custom card components
import './components/editor';
@@ -42,14 +43,15 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
@property() private baseCardTypes: CardTypeConfig[] = [];
@state() private _activeSubcardType: string | null = null;
- @state() private _confirmDeleteType: string | null = null;
@state() private _yamlConfig: { [key: string]: any } = {};
+ @state() private _confirmDeleteType: string | null = null;
@state() private _customBtns: { [key: string]: BaseButtonConfig } = {};
@state() private _selectedLanguage: string = 'system';
@state() _latestRelease: string = '';
@state() private _visiblePanel: Set = new Set();
@state() private _newCardType: Map = new Map();
+ @state() private _isTirePreview: boolean = false;
@query('panel-images') private _panelImages!: PanelImages;
@@ -73,9 +75,9 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
}
private _cleanConfig(): void {
- if (['btn_preview', 'card_preview'].some((key) => this._config[key])) {
+ if (['btn_preview', 'card_preview', 'tire_preview'].some((key) => this._config[key])) {
console.log('Cleaning config of preview keys');
- this._config = { ...this._config, btn_preview: null, card_preview: null };
+ this._config = { ...this._config, btn_preview: null, card_preview: null, tire_preview: null };
this.configChanged();
}
}
@@ -90,7 +92,7 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
- if (!this._btnPreview && !this._cardPreview) {
+ if (!this._btnPreview && !this._cardPreview && !this._isTirePreview) {
this._cleanConfig();
}
}
@@ -741,13 +743,13 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
- ${this._config.extra_configs?.tire_background
- ? html` this._removeTireBackground()}> Remove image `
+ ${this._config.extra_configs?.tire_card_custom.background
+ ? html` this._removeTireBackground()}> Use Defaut image `
: html` this.shadowRoot?.getElementById('file-upload-new')?.click()}>
Upload image
@@ -758,10 +760,63 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
@change=${(ev: any) => this._handleTireBackgroundUpload(ev)}
accept="image/*"
/>`}
+ this._toggleTirePreview()}>
+ ${this._isTirePreview ? 'Close preview' : 'Preview'}
`;
- return this.panelTemplate('customTireBackground', 'customTireBackground', 'mdi:car-tire-alert', urlInput);
+ const tireCard = this._config.extra_configs?.tire_card_custom || {};
+
+ const imageSizeDirection = [
+ {
+ value: tireCard.image_size || 100,
+ label: 'Base image size',
+ configValue: 'image_size',
+ pickerType: 'number' as 'number',
+ options: { selector: { number: { max: 200, min: 0, mode: 'slider', step: 1 } } },
+ },
+ {
+ value: tireCard.value_size || 100,
+ label: 'Name & Value size',
+ configValue: 'value_size',
+ pickerType: 'number' as 'number',
+ options: { selector: { number: { max: 150, min: 50, mode: 'slider', step: 1 } } },
+ },
+ {
+ value: tireCard.top || 50,
+ label: `${tireCard.horizontal ? 'Horizontal' : 'Vertical'} position`,
+ configValue: 'top',
+ pickerType: 'number' as 'number',
+ options: { selector: { number: { max: 100, min: 0, mode: 'slider', step: 1 } } },
+ },
+ {
+ value: tireCard.left || 50,
+ label: `${tireCard.horizontal ? 'Vertical' : 'Horizontal'} position`,
+ configValue: 'left',
+ pickerType: 'number' as 'number',
+ options: { selector: { number: { max: 100, min: 0, mode: 'slider', step: 1 } } },
+ },
+ {
+ value: tireCard.horizontal || false,
+ label: 'Horizontal layout',
+ configValue: 'horizontal',
+ pickerType: 'selectorBoolean' as 'selectorBoolean',
+ },
+ ];
+
+ const imageSizeWrapper = html`
+ ${imageSizeDirection.map((config) =>
+ this.generateItemPicker({ ...config, configIndex: 'extra_configs', configType: 'tire_card_custom' })
+ )}
+ this.resetTireImageSizes()}
+ >Reset
+
`;
+
+ const content = html`${urlInput} ${imageSizeWrapper}
`;
+
+ return this.panelTemplate('customTireBackground', 'customTireBackground', 'mdi:car-tire-alert', content);
}
/* ---------------------------- TEMPLATE HELPERS ---------------------------- */
@@ -833,8 +888,37 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
`;
}
+ private generateItemPicker(config: any, wrapperClass = 'item-content'): TemplateResult {
+ return html`
+
+ ${Create.Picker({
+ ...config,
+ component: this,
+ })}
+
+ `;
+ }
+
/* ----------------------------- EVENT HANDLERS ----------------------------- */
+ private resetTireImageSizes(): void {
+ this._config = {
+ ...this._config,
+ extra_configs: {
+ ...this._config.extra_configs,
+ tire_card_custom: {
+ ...this._config.extra_configs?.tire_card_custom,
+ image_size: 100,
+ value_size: 100,
+ top: 50,
+ left: 50,
+ horizontal: false,
+ },
+ },
+ };
+ this.configChanged();
+ }
+
private async _handleTireBackgroundUpload(ev: any): Promise {
if (!ev.target.files || ev.target.files.length === 0) {
return;
@@ -843,7 +927,13 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
const file = ev.target.files[0];
const url = await uploadImage(this.hass, file);
if (url) {
- this._config = { ...this._config, extra_configs: { ...this._config.extra_configs, tire_background: url } };
+ this._config = {
+ ...this._config,
+ extra_configs: {
+ ...this._config.extra_configs,
+ tire_card_custom: { ...this._config.extra_configs?.tire_card_custom, background: url },
+ },
+ };
this.configChanged();
} else {
return;
@@ -851,7 +941,13 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
}
private _removeTireBackground(): void {
- this._config = { ...this._config, extra_configs: { ...this._config.extra_configs, tire_background: '' } };
+ this._config = {
+ ...this._config,
+ extra_configs: {
+ ...this._config.extra_configs,
+ tire_card_custom: { ...this._config.extra_configs?.tire_card_custom, background: '' },
+ },
+ };
this.configChanged();
}
@@ -1169,6 +1265,7 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
const target = ev.target;
const configValue = target.configValue;
const configBtnType = target.configBtnType;
+ const configIndex = target.configIndex;
let newValue: any = target.value;
const updates: Partial = {};
@@ -1204,6 +1301,16 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
[key]: parseInt(newValue) || newValue,
};
console.log('Button grid config changed:', key, newValue);
+ } else if (configIndex === 'extra_configs') {
+ newValue = ev.detail.value;
+ const key = configValue as keyof VehicleCardConfig['extra_configs']['tire_card_custom'];
+ updates.extra_configs = {
+ ...this._config.extra_configs,
+ tire_card_custom: {
+ ...this._config.extra_configs?.tire_card_custom,
+ [key]: parseInt(newValue) || newValue,
+ },
+ };
} else {
newValue = target.checked !== undefined ? target.checked : ev.detail.value;
updates[configValue] = newValue;
@@ -1346,6 +1453,46 @@ export class VehicleCardEditor extends LitElement implements LovelaceCardEditor
}
}
+ public _toggleTirePreview(): void {
+ if (this._cardPreview) {
+ this._dispatchCardEvent('close_card_preview');
+ this._cardPreview = false;
+ }
+
+ if (this._isTirePreview) {
+ console.log('Closing tire preview');
+ if (this._config) {
+ this._config = {
+ ...this._config,
+ tire_preview: null,
+ };
+ }
+
+ this._isTirePreview = false;
+ this.configChanged();
+ setTimeout(() => {
+ this._dispatchCardEvent('close_preview');
+ }, 50);
+ } else {
+ let tireConfig = this._config.extra_configs?.tire_card_custom;
+ console.log('Setting tire preview');
+ if (this._config) {
+ this._config = {
+ ...this._config,
+ tire_preview: {
+ ...tireConfig,
+ },
+ };
+ }
+
+ this._isTirePreview = true;
+ this.configChanged();
+ setTimeout(() => {
+ this._dispatchCardEvent('toggle_preview_tire');
+ }, 50);
+ }
+ }
+
private _closeSubCardEditor(card: CardTypeConfig): void {
const resetState = () => {
this._activeSubcardType = null;
diff --git a/src/types/config.ts b/src/types/config.ts
index 2231016..a5c4b8b 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -130,7 +130,23 @@ export interface ShowOptionsConfig extends VehicleCardConfig {
}
export type ExtraConfigs = {
- tire_background: string;
+ tire_card_custom: {
+ background: string;
+ horizontal: boolean;
+ image_size: number;
+ value_size: number;
+ top: number;
+ left: number;
+ };
+ images_swipe: {
+ max_height: number;
+ max_width: number;
+ autoplay: boolean;
+ loop: boolean;
+ delay: number;
+ speed: number;
+ effect: 'slide' | 'fade' | 'coverflow';
+ };
};
export interface VehicleCardConfig extends LovelaceCardConfig {
@@ -216,7 +232,23 @@ export const defaultConfig: Partial = {
mode: THEME_MODE.Auto,
},
extra_configs: {
- tire_background: '',
+ tire_card_custom: {
+ background: '',
+ horizontal: false,
+ image_size: 100,
+ value_size: 100,
+ top: 50,
+ left: 50,
+ },
+ images_swipe: {
+ max_height: 150,
+ max_width: 450,
+ autoplay: false,
+ loop: true,
+ delay: 5000,
+ speed: 500,
+ effect: 'slide',
+ },
},
button_grid: {
use_swiper: false,
diff --git a/src/vehicle-info-card.ts b/src/vehicle-info-card.ts
index 0dd3752..fd9fc05 100644
--- a/src/vehicle-info-card.ts
+++ b/src/vehicle-info-card.ts
@@ -13,6 +13,7 @@ import {
import { LitElement, html, TemplateResult, PropertyValues, CSSResultGroup, nothing } from 'lit';
import { customElement, property, state, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
+import { styleMap } from 'lit-html/directives/style-map.js';
import './components/cards';
import { VehicleButtons } from './components/cards';
@@ -55,14 +56,14 @@ export class VehicleCard extends LitElement implements LovelaceCard {
@state() public _entityNotFound: boolean = false;
// Active card type
- @state() private _currentCardType: string | null = null;
+ @state() public _currentCardType: string | null = null;
@state() private _activeSubCard: Set = new Set();
@state() private _mapPopupLovelace: LovelaceCardConfig[] = [];
@state() private chargingInfoVisible!: boolean;
@state() private isTyreHorizontal!: boolean;
// Preview states
- @state() private _currentPreviewType: 'button' | 'card' | null = null;
+ @state() private _currentPreviewType: 'button' | 'card' | 'tire' | null = null;
@state() private _cardPreviewElement: LovelaceCardConfig[] = [];
@state() private _buttonEntityPreview: Partial = {};
@@ -161,9 +162,6 @@ export class VehicleCard extends LitElement implements LovelaceCard {
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
- if (changedProps.has('_buttonReady')) {
- console.log('Button ready:', this._buttonReady);
- }
if (
changedProps.has('activeCardType') &&
this._currentCardType !== 'mapDialog' &&
@@ -293,6 +291,8 @@ export class VehicleCard extends LitElement implements LovelaceCard {
this._currentPreviewType = 'card';
} else if (!this._currentPreviewType && this.config?.btn_preview) {
this._currentPreviewType = 'button';
+ } else if (!this._currentPreviewType && this.config?.tire_preview) {
+ this._currentPreviewType = 'tire';
}
if (this._currentPreviewType !== null) {
@@ -302,7 +302,7 @@ export class VehicleCard extends LitElement implements LovelaceCard {
}
}
- private async _configurePreview(cardType: 'button' | 'card' | null): Promise {
+ private async _configurePreview(cardType: 'button' | 'card' | 'tire' | null): Promise {
if (!cardType && !this.isEditorPreview) return;
let cardConfig: LovelaceCardConfig[] | BaseButtonConfig = [];
@@ -321,11 +321,14 @@ export class VehicleCard extends LitElement implements LovelaceCard {
cardElement = await createCardElement(this._hass, cardConfig as LovelaceCardConfig[]);
this._cardPreviewElement = cardElement;
break;
+ case 'tire':
+ this._currentPreviewType = 'tire';
+ break;
default:
return;
}
- if (!cardElement) {
+ if (cardType === null) {
this._resetCardPreview(); // Reset preview
return;
}
@@ -394,6 +397,7 @@ export class VehicleCard extends LitElement implements LovelaceCard {
const typeMap = {
button: this._renderBtnPreview(this._buttonEntityPreview as CustomButtonEntity),
card: html`${this._cardPreviewElement}`,
+ tire: this._renderDefaultTyreCard(),
};
return typeMap[type];
@@ -808,9 +812,22 @@ export class VehicleCard extends LitElement implements LovelaceCard {
}
private _renderDefaultTyreCard(): TemplateResult {
- const customTyreBg = this.config.extra_configs?.tire_background
- ? this.config.extra_configs.tire_background
- : IMG.tyreBg;
+ const tireConfig = this.config.extra_configs.tire_card_custom;
+ const customTyreBg = tireConfig?.background || IMG.tyreBg;
+ const isHorizontal = tireConfig?.horizontal ?? false;
+ const tireImageSize = tireConfig?.image_size ?? 100;
+ const tireValueSize = tireConfig?.value_size ?? 100;
+ const tireTop = tireConfig?.top ?? 50;
+ const tireLeft = tireConfig?.left ?? 50;
+
+ const sizeStyle = {
+ '--vic-tire-top': `${tireTop}%`,
+ '--vic-tire-left': `${tireLeft}%`,
+ '--vic-tire-size': `${tireImageSize}%`,
+ '--vic-tire-value-size': tireValueSize / 100,
+ };
+ const directionClass = isHorizontal ? 'rotated' : '';
+
const lang = this.userLang;
const isPressureWarning = this.getBooleanState(this.vehicleEntities.tirePressureWarning?.entity_id);
@@ -821,22 +838,17 @@ export class VehicleCard extends LitElement implements LovelaceCard {
const tyreInfo = isPressureWarning ? tireWarningProblem : tireWarningOk;
const infoClass = isPressureWarning ? 'warning' : '';
- const isHorizontal = this.isTyreHorizontal ? 'rotated' : '';
- const toggleHorizontal = () => {
- this.isTyreHorizontal = !this.isTyreHorizontal;
- };
-
return html`
-
+
this.toggleTireDirection(ev)}>
-
+
${DataKeys.tyrePressures(lang).map(
(tyre) =>
- html`
+ html`
${this.getStateDisplay(this.vehicleEntities[tyre.key]?.entity_id)}
${tyre.name}
`
@@ -849,6 +861,21 @@ export class VehicleCard extends LitElement implements LovelaceCard {
`;
}
+ private toggleTireDirection(ev: Event): void {
+ ev.stopPropagation();
+ const target = ev.target as HTMLElement;
+ const tyreWrapper = target.closest('.default-card')?.querySelector('.tyre-wrapper');
+ const tyreBoxex = tyreWrapper?.querySelectorAll('.tyre-box');
+ if (!tyreWrapper || !tyreBoxex) return;
+
+ const isHorizontal = tyreWrapper.classList.contains('rotated');
+
+ tyreWrapper.classList.toggle('rotated', !isHorizontal);
+ tyreBoxex.forEach((el) => {
+ el.classList.toggle('rotated', !isHorizontal);
+ });
+ }
+
private _renderServiceControl(): TemplateResult | void {
const hass = this._hass;
const serviceControl = this.config.services;