import { LitElement, html, css } from "https://unpkg.com/lit-element@2.0.1/lit-element.js?module"; class LovelaceRoomba5Card extends LitElement { static get properties() { return { _hass: {}, _config: {}, stateObj: {}, state: {}, style: {} } } static get styles() { return css` .background { background-repeat: no-repeat; background-position: center center; background-size: cover; } .title-left { font-size: 20px; padding: 16px 16px 4px 16px; text-align: left; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; color: var(--primary-text-color); text-shadow: none; font-weight: 700; } .content { cursor: pointer; color: var(--primary-text-color); text-shadow: none; } .flex { display: flex; align-items: center; justify-content: space-evenly; color: var(--primary-text-color); text-shadow: none; } .button { cursor: pointer; padding: 16px; } .button:hover { font-weight: 600; } .button-blank { cursor: pointer; padding: 28px; } .grid { display: grid; grid-template-columns: repeat(2, auto); } .grid-content { display: grid; align-content: space-between; grid-row-gap: 6px; } .grid-left { text-align: left; font-size: 110%; padding-left: 10px; border-left: 2px solid var(--primary-color); } .grid-right { text-align: right; font-size: 110%; padding-right: 10px; border-right: 2px solid var(--primary-color); } .tabactive { display: grid; } .tabpassive { display: none; } .totals { border-right: 2px solid var(--primary-color) } .job { border-right: 2px solid var(--accent-color) } `; } render() { return html` <ha-card .hass="${this._hass}" .config="${this._config}" class="background" style="${this.style.background}"> ${this.state.showTitle ? html`<div> ${this.state.name ? html`<div class="title-left" style="${this.style.text}" @click="${() => this.fireEvent('hass-more-info')}">${this.state.name}</div>` : html`<div class="title-left" style="${this.style.text}" @click="${() => this.fireEvent('hass-more-info')}"></div>`} </div>` : null } ${this.state.showLabels ? html` <div class="content grid" style="${this.style.content + this.style.text}" > <div class="grid-content grid-left" @click="${() => this.fireEvent('hass-more-info')}"> <div>${this.getState('status')}</div> <div>${this.getValue('mode')}</div> <div>${this.getValue('battery')}</div> <div>${this.getValue('bin')}</div> </div> <div id="total" class="${this.state.hideRightGrid ? "grid-content " : "grid-content grid-right totals"} ${this.state.defaultTotals ? "tabactive" : "tabpassive"}" @click="${() => this.tabSwap('last')}"> ${this.state.showTotals ? html` <div style="font-weight: 600;">${this.getValue('total_header')}</div> <div>${this.getValue('total_time')}</div> <div>${this.getValue('total_jobs')}</div> <div>${this.getValue('dirt_events')}</div> <div>${null}</div>` : null} </div> <div id="last" class="${this.state.hideRightGrid ? "grid-content " : "grid-content grid-right job"} ${this.state.defaultTotals ? "tabpassive" : "tabactive"}" @click="${() => this.tabSwap('total')}"> ${this.state.showJob ? html` <div style="font-weight: 600;">${this.getValue('job_header')}</div> <div>${this.getValue('job_initiator')}</div> <div>${this.getValue('job_time')}</div> <div>${this.getValue('battery')}</div> <div>${null}</div>` : null} </div> </div>` : null} ${this.state.showButtons ? html` <div class="flex" style="${this.style.text}"> ${Object.keys(this.state.buttons).map(this.renderButton.bind(this))} </div>` : null} </ha-card>`; } renderButton(key) { if (((key == "stop") && (this.stateObj.state == this.state.vac_states.ready)) || (this.stateObj.state == this.state.vac_states.pending) || (this.stateObj.state == this.state.vac_states.empty)) { return this.state.buttons[key] ? html`<div class="button-blank" style="cursor:default" @click="${() => this.tabSwap('total')}"></div>` : null; } else if (key != "blank") { if(this.getButton(key,"icon") !== ''){ return this.state.buttons[key] ? html`<div class="button" @tap="${() => this.callService(key)}"><ha-icon style="padding-right: 5px" icon="${this.getButton(key,"icon")}"></ha-icon> ${this.getButton(key,"label")}</div>` : null; } else { return null; } } else { return this.state.buttons[key] ? html`<div class="button" style="cursor:default" @click="${() => this.tabSwap('total')}"></div>` : null; } } getValue(field) { if((this.state.attributes[field] === 'bin_missing') || (this.state.attributes[field] === 'full_clean') || (this.state.attributes[field] === 'resume_clean') || (this.state.attributes[field] === 'pause_clean') || (this.state.attributes[field] === 'stop_clean') || (this.state.attributes[field] === 'dock')){ return `${this.state.labels[field]}`; } if((this.state.attributes[field] === 'total_header') || (this.state.attributes[field] === 'job_header')){ return `${this.state.labels[field]}:`; } if ((this.state.attributes[field] === 'bin')) { const bin_check = this.state.attributes.bin_present; const value = (this.stateObj && this.state.attributes[bin_check] in this.stateObj.attributes) ? this.stateObj.attributes[this.state.attributes[bin_check]] : (this._hass ? this._hass.localize('state.default.unavailable') : 'Unavailable'); if (value === 'No') { return `${this.state.labels[field]}: ${this.getValue('bin_missing')}`; }; }; const value = (this.stateObj && this.state.attributes[field] in this.stateObj.attributes) ? this.stateObj.attributes[this.state.attributes[field]] : (this._hass ? this._hass.localize('state.default.unavailable') : 'Unavailable'); return `${this.state.labels[field]}: ${value}`; }; getState(field) { const value = this.stateObj.state; if (this.state.autoSwitch) { if (value !== this.state.vac_states.ready ? this.tabSwap('last') : this.tabSwap('total')); } return `${this.state.labels[field]}: ${value}`; }; getButton(index, field) { switch(index) { case "startstop": if (this.stateObj.state === this.state.vac_states.ready) { // Full Clean switch(field) { case "label": return `${this.getValue('full_clean')}`; case "icon": return `mdi:play-circle`; case "action": return `start`; } } else if ((this.stateObj.attributes['phase'] === this.state.vac_states.paused) || (this.stateObj.attributes['phase'] === this.state.vac_states.stuck) || (this.stateObj.attributes['phase'] === this.state.vac_states.charge)) { // Resume switch(field) { case "label": return `${this.getValue('resume_clean')}`; case "icon": return `mdi:play`; case "action": return `resume`; } } else { // Pause switch(field) { case "label": return `${this.getValue('pause_clean')}`; case "icon": return `mdi:pause`; case "action": return `pause`; } } case "dock": if ((this.stateObj.attributes['phase'] === this.state.vac_states.charge) || (this.stateObj.attributes['phase'] === this.state.vac_states.idle)) { // Resume switch(field) { case "label": return ``; case "icon": return ``; case "action": return ``; } } else { // Pause switch(field) { case "label": return `${this.getValue('dock')}`; case "icon": return `mdi:home-minus`; case "action": return `dock`; } } case "stop": // Stop if (this.stateObj.state === this.state.vac_states.ready) { // Blank switch(field) { case "label": return ``; case "icon": return ``; case "action": return ``; } } else { // Stop switch(field) { case "label": return `${this.getValue('stop_clean')}`; case "icon": return `mdi:stop`; case "action": return `stop`; } } } }; tabSwap(tab) { // Swap Tabs switch(tab) { case "last": if (!this.state.showJob) { return; } var tabLast = this.shadowRoot.getElementById("total"); if (tabLast !== null) { tabLast.style.display = "none" }; var tabTotal = this.shadowRoot.getElementById("last"); if (tabTotal !== null) { tabTotal.style.display = "grid" }; break; case "total": if (!this.state.showTotals) { return; } var tabLast = this.shadowRoot.getElementById("last"); if (tabLast !== null) { tabLast.style.display = "none" }; var tabTotal = this.shadowRoot.getElementById("total"); if (tabTotal !== null) { tabTotal.style.display = "grid" }; } }; callService(service) { this._hass.callService('rest_command', 'vacuum_action', {command: this.getButton(service,"action")}); } fireEvent(type, options = {}) { const event = new Event(type, { bubbles: options.bubbles || true, cancelable: options.cancelable || true, composed: options.composed || true, }); event.detail = {entityId: this.stateObj.entity_id}; this.dispatchEvent(event); } getCardSize() { if (this.state.name && this.state.showButtons) return 5; if (this.state.name || this.state.showButtons) return 4; return 3; } setConfig(config) { const labels = { status: 'Status', mode: 'Mode', battery: 'Battery', bin: 'Bin', total_time: 'Time', total_jobs: 'Jobs', dirt_events: 'Dirts', job_initiator: 'Source', job_time: 'Time', total_header: 'Total', job_header: 'Job', full_clean: 'Full Clean', resume_clean: 'Resume', pause_clean: 'Pause', stop_clean: 'Stop', dock: 'Dock', bin_missing: 'Missing!' }; const vac_states = { ready: 'Ready', stuck: 'Stuck', pending: 'Pending', charge: 'Charge', idle: 'Idle', empty: 'Empty', paused: 'Paused' }; const attributes = { status: 'state', battery: 'battery', mode: 'phase', bin: 'bin', bin_present: 'bin_present', total_time: 'total_time', total_jobs: 'total_jobs', dirt_events: 'dirt_events', job_initiator: 'job_initiator', job_time: 'job_time', total_header: 'total_header', job_header: 'job_header', full_clean: 'full_clean', resume_clean: 'resume_clean', pause_clean: 'pause_clean', stop_clean: 'stop_clean', dock: 'dock', bin_missing: 'bin_missing' }; const buttons = { startstop: true, blank: false, stop: false, dock: true, }; if (!config.entity) throw new Error('Please define an entity.'); if (config.entity.split('.')[0] !== 'sensor') throw new Error('Please define a sensor entity.'); this.state = { showTotals: config.totals !== false, showJob: config.job !== false, showButtons: config.buttons !== false, showLabels: config.labels !== false, showName: config.name !== false, showTitle: config.name !== false, defaultTotals: config.defaultjob !== true ? (config.totals !== false ? true : false) : (config.job !== false ? false : true), hideRightGrid: (config.totals === false && config.job === false), autoSwitch: config.autoswitch !== false, buttons: Object.assign({}, buttons, config.buttons), attributes: Object.assign({}, attributes, config.attributes), vac_states: Object.assign({}, vac_states, config.vac_states), labels: Object.assign({}, labels, config.labels), }; this.style = { text: `cursor: pointer;`, content: `padding: ${config.showButtons ? '16px 16px 4px' : '16px'};`, background: config.image !== false ? `background-image: url('${config.image || '/hacsfiles/lovelace-roomba-e5-card/vacuum.png'}')` : '' }; this._config = config; } set hass(hass) { this._hass = hass; if (hass && this._config) { this.stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null; if (this.stateObj && this.state.showName) { this.state.name = this._config.name || this.stateObj.attributes.friendly_name; } } } } customElements.define('lovelace-roomba-e5-card', LovelaceRoomba5Card);