diff --git a/src/components/header-slide.js b/src/components/header-slide.js deleted file mode 100644 index 0fccc37..0000000 --- a/src/components/header-slide.js +++ /dev/null @@ -1,113 +0,0 @@ -import { LitElement, css, html } from 'lit'; -import Swiper from 'swiper'; -import { Pagination } from 'swiper/modules'; -import swipercss from '../css/swiper-bundle.css'; - -export class HeaderSlide extends LitElement { - static get properties() { - return { - images: { Type: Array }, - swiper: { Type: Object }, - }; - } - - static get styles() { - return [ - swipercss, - css` - :host { - --swiper-pagination-bottom: 0px; - --swiper-theme-color: var(--primary-text-color); - } - section { - display: block; - padding: 1rem 0; - } - .swiper-container { - width: 100%; - height: 100%; - display: block; - } - .swiper-slide { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - max-height: 125px; - } - .swiper-slide:active { - scale: 1.02; - } - - .swiper-slide img { - width: 100%; - height: 100%; - max-height: 150px; - object-fit: scale-down; - } - .swiper-pagination-bullet { - background-color: var(--swiper-theme-color); - transition: all 0.3s ease-in-out !important; - } - .swiper-pagination-bullet-active { - width: 18px !important; - border-radius: 1rem !important; - opacity: 0.7; - } - `, - ]; - } - - firstUpdated() { - if (!this.swiper) { - this.initSwiper(); - } - } - - initSwiper() { - const swiperCon = this.shadowRoot.querySelector('.swiper-container'); - if (!swiperCon) return; - const paginationEl = swiperCon.querySelector('.swiper-pagination'); - this.swiper = new Swiper(swiperCon, { - modules: [Pagination], - centeredSlides: true, - grabCursor: true, - speed: 500, - roundLengths: true, - keyboard: { - enabled: true, - onlyInViewport: true, - }, - loop: true, - slidesPerView: 1, - pagination: { - el: paginationEl, - clickable: true, - }, - }); - } - - render() { - const images = this.images; - if (!images || images.length === 0) { - return html``; - } - return html` -
-
-
- ${images.map( - (image) => html` -
- -
- `, - )} -
-
-
-
- `; - } -} -customElements.define('header-slide', HeaderSlide); diff --git a/src/components/header-slide.ts b/src/components/header-slide.ts new file mode 100644 index 0000000..a6354d0 --- /dev/null +++ b/src/components/header-slide.ts @@ -0,0 +1,110 @@ +import { LitElement, css, html, TemplateResult } from 'lit'; +import { customElement, property } from 'lit/decorators'; +import Swiper from 'swiper'; +import { Pagination } from 'swiper/modules'; +import swipercss from '../css/swiper-bundle.css'; + +@customElement('header-slide') +export class HeaderSlide extends LitElement { + @property({ type: Array }) + images: string[] = []; + + @property({ type: Object }) + swiper: Swiper | null = null; + + static styles = [ + swipercss, + css` + :host { + --swiper-pagination-bottom: 0px; + --swiper-theme-color: var(--primary-text-color); + } + section { + display: block; + padding: 1rem 0; + } + .swiper-container { + width: 100%; + height: 100%; + display: block; + } + .swiper-slide { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + max-height: 125px; + } + .swiper-slide:active { + scale: 1.02; + } + .swiper-slide img { + width: 100%; + height: 100%; + max-height: 150px; + object-fit: scale-down; + } + .swiper-pagination-bullet { + background-color: var(--swiper-theme-color); + transition: all 0.3s ease-in-out !important; + } + .swiper-pagination-bullet-active { + width: 18px !important; + border-radius: 1rem !important; + opacity: 0.7; + } + `, + ]; + + firstUpdated(): void { + if (!this.swiper) { + this.initSwiper(); + } + } + + initSwiper(): void { + const swiperCon = this.shadowRoot?.querySelector('.swiper-container'); + if (!swiperCon) return; + const paginationEl = swiperCon.querySelector('.swiper-pagination') as HTMLElement; + this.swiper = new Swiper(swiperCon as HTMLElement, { + modules: [Pagination], + centeredSlides: true, + grabCursor: true, + speed: 500, + roundLengths: true, + keyboard: { + enabled: true, + onlyInViewport: true, + }, + loop: true, + slidesPerView: 1, + pagination: { + el: paginationEl, + clickable: true, + }, + }); + } + + render(): TemplateResult { + const images = this.images; + if (!images || images.length === 0) { + return html``; + } + return html` +
+
+
+ ${images.map( + (image) => html` +
+ +
+ `, + )} +
+
+
+
+ `; + } +} diff --git a/src/components/map-card.js b/src/components/map-card.ts similarity index 75% rename from src/components/map-card.js rename to src/components/map-card.ts index 54383b1..b7d243d 100644 --- a/src/components/map-card.js +++ b/src/components/map-card.ts @@ -1,68 +1,63 @@ -import { LitElement, html, css } from 'lit'; +import { LitElement, html, css, TemplateResult, CSSResultGroup } from 'lit'; +import { customElement, property, state } from 'lit/decorators.js'; + +import { HomeAssistant } from 'custom-card-helpers'; import L from 'leaflet'; import 'leaflet-providers/leaflet-providers.js'; - import mapstyle from '../css/leaflet.css'; -export class VehicleMap extends LitElement { - static get properties() { - return { - hass: {}, - deviceTracker: { Type: String }, - lat: { Type: Number }, - lon: { Type: Number }, - zoom: { Type: Number }, - picture: { Type: String }, - map: { Type: Object }, - marker: { Type: Object }, - address: { Type: Object }, - darkMode: { Type: Boolean }, - state: { Type: String }, - apiKey: { Type: String }, - popup: { Type: Boolean }, - enableAdress: { Type: Boolean }, - }; - } - constructor() { - super(); - this.lat = 0; - this.lon = 0; - this.zoom = 16; - this.picture = ''; - this.state = ''; - this.address = {}; - this.darkMode = false; - this.popup = false; - this.enableAdress = false; - } +interface Address { + streetNumber: string; + streetName: string; + sublocality: string; + city: string; + state: string; + country: string; + postcode: string; +} - firstUpdated() { +@customElement('vehicle-map') +export class VehicleMap extends LitElement { + @property({ attribute: false }) hass!: HomeAssistant; + @property({ type: String }) deviceTracker = ''; + @property({ type: Boolean }) darkMode = false; + @property({ type: Boolean }) popup = false; + + @state() private map: L.Map | null = null; + @state() private marker: L.Marker | null = null; + @state() private lat = 0; + @state() private lon = 0; + @state() private zoom = 15; + @state() private state = ''; + @state() private address: Partial
= {}; + @state() private enableAdress = false; + @state() private apiKey = ''; + + firstUpdated(): void { this.setEntityAttribute(); } - updated(changedProperties) { + updated(changedProperties: Map): void { if (changedProperties.has('darkMode')) { this.updateCSSVariables(); } } - setEntityAttribute() { + setEntityAttribute(): void { const deviceTracker = this.hass.states[this.deviceTracker]; if (deviceTracker) { this.lat = deviceTracker.attributes.latitude; this.lon = deviceTracker.attributes.longitude; - this.picture = deviceTracker.attributes.entity_picture; this.state = deviceTracker.state; - this.getAddressData(this.lat, this.lon); - this.darkMode = this.hass.themes.darkMode; + this.getAddress(this.lat, this.lon); } setTimeout(() => { this.initMap(); }, 100); } - updateCSSVariables() { + updateCSSVariables(): void { if (this.darkMode) { this.style.setProperty('--map-marker-color', 'var(--accent-color)'); } else { @@ -70,7 +65,20 @@ export class VehicleMap extends LitElement { } } - static get styles() { + async getAddress(lat: number, lon: number): Promise { + let address: Partial
| null = null; + if (this.apiKey !== '') { + address = await this.getAddressFromGoggle(lat, lon); + } else { + address = await this.getAddressFromOpenStreet(lat, lon); + } + if (address) { + this.address = address; + this.enableAdress = true; + } + } + + static get styles(): CSSResultGroup { return [ mapstyle, css` @@ -84,7 +92,6 @@ export class VehicleMap extends LitElement { width: 100%; height: 100%; } - #map { height: 100%; width: 100%; @@ -101,7 +108,6 @@ export class VehicleMap extends LitElement { .marker.dark { filter: brightness(0.5); } - .dot { position: absolute; width: 14px; @@ -129,7 +135,6 @@ export class VehicleMap extends LitElement { .marker:hover .dot { opacity: 1; } - .leaflet-control-container { display: none; } @@ -172,14 +177,17 @@ export class VehicleMap extends LitElement { ]; } - initMap() { + initMap(): void { const mapOptions = { dragging: true, zoomControl: false, scrollWheelZoom: true, }; - this.map = L.map(this.shadowRoot.getElementById('map'), mapOptions).setView([this.lat, this.lon], this.zoom); + this.map = L.map(this.shadowRoot?.getElementById('map') as HTMLElement, mapOptions).setView( + [this.lat, this.lon], + this.zoom, + ); const tileLayer = this.darkMode ? 'CartoDB.DarkMatter' : 'CartoDB.Positron'; L.tileLayer.provider(tileLayer).addTo(this.map); @@ -208,7 +216,7 @@ export class VehicleMap extends LitElement { this.updateMap(); } - togglePopup() { + togglePopup(): void { const event = new CustomEvent('toggle-map-popup', { detail: {}, bubbles: true, @@ -217,13 +225,26 @@ export class VehicleMap extends LitElement { this.dispatchEvent(event); } - updateMap() { - const offset = this.calculateLatLngOffset(this.map, this.lat, this.lon, this.map.getSize().x / 5, 3); + private updateMap(): void { + if (!this.map || !this.marker) return; + const offset: [number, number] = this.calculateLatLngOffset( + this.map, + this.lat, + this.lon, + this.map.getSize().x / 5, + 3, + ); this.map.setView(offset, this.zoom); this.marker.setLatLng([this.lat, this.lon]); } - calculateLatLngOffset(map, lat, lng, xOffset, yOffset) { + private calculateLatLngOffset( + map: L.Map, + lat: number, + lng: number, + xOffset: number, + yOffset: number, + ): [number, number] { // Convert the lat/lng to a point const point = map.latLngToContainerPoint([lat, lng]); // Apply the offset @@ -233,17 +254,19 @@ export class VehicleMap extends LitElement { return [newLatLng.lat, newLatLng.lng]; } - render() { - return html`
-
-
- + render(): TemplateResult { + return html` +
+
+
+ +
+ ${this._renderAddress()}
- ${this._renderAddress()} -
`; + `; } - _renderAddress() { + private _renderAddress() { if (!this.enableAdress) return html``; return html`
@@ -260,9 +283,9 @@ export class VehicleMap extends LitElement { `; } - async getAddressFromOpenStreet(lat, lon) { - const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lon}&format=jsonv2`; - + private async getAddressFromOpenStreet(lat: number, lng: number): Promise | null> { + const url = `https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=jsonv2`; + console.log(url); try { const response = await fetch(url); const data = await response.json(); @@ -284,11 +307,12 @@ export class VehicleMap extends LitElement { throw new Error('Failed to fetch address'); } } catch (error) { + console.error('Error fetching address:', error); return null; } } - async getAddressFromGoggle(lat, lng) { + private async getAddressFromGoggle(lat: number, lng: number): Promise | null> { const apiKey = this.apiKey; // Replace with your API key const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${apiKey}`; @@ -340,22 +364,4 @@ export class VehicleMap extends LitElement { return null; } } - - async getAddressData(lat, lon) { - let address; - if (this.apiKey) { - address = await this.getAddressFromGoggle(lat, lon); - } else { - address = await this.getAddressFromOpenStreet(lat, lon); - } - if (address) { - this.address = address; - this.enableAdress = true; - this.requestUpdate(); - } else { - this.enableAdress = false; - console.log('Could not retrieve address'); - } - } } -customElements.define('vehicle-map', VehicleMap); diff --git a/src/types.ts b/src/types.ts index 97512d7..7dc0178 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,7 +26,7 @@ export interface VehicleCardConfig extends LovelaceCardConfig { entity?: string; device_tracker?: string; google_api_key?: string; - images?: string | string[]; + images?: string[]; show_slides?: boolean; show_map?: boolean; show_buttons?: boolean; diff --git a/src/vehicle-info-card.ts b/src/vehicle-info-card.ts index 3324970..27585c6 100644 --- a/src/vehicle-info-card.ts +++ b/src/vehicle-info-card.ts @@ -29,7 +29,7 @@ import { tapFeedback } from './utils/tap-action.js'; import styles from './css/styles.css'; import { amgBlack, amgWhite } from './utils/imgconst'; import './components/map-card.js'; -import './components/header-slide.js'; +import './components/header-slide'; import './components/eco-chart'; declare global { interface Window { @@ -325,9 +325,12 @@ export class VehicleCard extends LitElement { } } - private _renderHeaderSlides(): TemplateResult | void { - if (!this.config.images || !this.config.show_slides) return; - return html` `; + private _renderHeaderSlides(): TemplateResult { + if (!this.config.images || !this.config.show_slides) return html``; + + const images: string[] = this.config.images; + + return html``; } private _renderMap(): TemplateResult | void { @@ -338,13 +341,15 @@ export class VehicleCard extends LitElement { if (!config.device_tracker && config.show_map) { return this._showWarning('No device_tracker entity provided.'); } + const darkMode = this.isDark(); return html`
`;