HACS - lovelace añadidos plugins
This commit is contained in:
1923
.storage/lovelace
1923
.storage/lovelace
File diff suppressed because it is too large
Load Diff
125
www/community/entity-attributes-card/entity-attributes-card.js
Normal file
125
www/community/entity-attributes-card/entity-attributes-card.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
class EntityAttributesCard extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
}
|
||||||
|
|
||||||
|
_getAttributes(hass, filters) {
|
||||||
|
function _filterName(stateObj, pattern) {
|
||||||
|
let parts;
|
||||||
|
let attr_id;
|
||||||
|
let attribute;
|
||||||
|
if (typeof (pattern) === "object") {
|
||||||
|
parts = pattern["key"].split(".");
|
||||||
|
attribute = pattern["key"];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
parts = pattern.split(".");
|
||||||
|
attribute = pattern;
|
||||||
|
}
|
||||||
|
attr_id = parts[2];
|
||||||
|
if (attr_id.indexOf('*') === -1) {
|
||||||
|
return stateObj == attribute;
|
||||||
|
}
|
||||||
|
const regEx = new RegExp(`^${attribute.replace(/\*/g, '.*')}$`, 'i');
|
||||||
|
return stateObj.search(regEx) === 0;
|
||||||
|
}
|
||||||
|
const attributes = new Map();
|
||||||
|
filters.forEach((filter) => {
|
||||||
|
const filters = [];
|
||||||
|
filters.push(stateObj => _filterName(stateObj, filter));
|
||||||
|
Object.keys(hass.states).sort().forEach(key => {
|
||||||
|
Object.keys(hass.states[key].attributes).sort().forEach(attr_key => {
|
||||||
|
if (filters.every(filterFunc => filterFunc(`${key}.${attr_key}`))) {
|
||||||
|
attributes.set(`${key}.${attr_key}`, {
|
||||||
|
name: `${filter.name?filter.name:attr_key.replace(/_/g, ' ')}`,
|
||||||
|
value: `${hass.states[key].attributes[attr_key]} ${filter.unit||''}`.trim(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return Array.from(attributes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
if (!config.filter.include || !Array.isArray(config.filter.include)) {
|
||||||
|
throw new Error('Please define filters');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.heading_name) config.heading_name = 'Attributes';
|
||||||
|
if (!config.heading_state) config.heading_state = 'States';
|
||||||
|
|
||||||
|
const root = this.shadowRoot;
|
||||||
|
if (root.lastChild) root.removeChild(root.lastChild);
|
||||||
|
|
||||||
|
const cardConfig = Object.assign({}, config);
|
||||||
|
const card = document.createElement('ha-card');
|
||||||
|
card.header = config.title;
|
||||||
|
const content = document.createElement('div');
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
thead th {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(odd) {
|
||||||
|
background-color: var(--paper-card-background-color);
|
||||||
|
}
|
||||||
|
tbody tr:nth-child(even) {
|
||||||
|
background-color: var(--secondary-background-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
content.innerHTML = `
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>${config.heading_name}</th>
|
||||||
|
<th>${config.heading_state}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id='attributes'>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
card.appendChild(style);
|
||||||
|
card.appendChild(content);
|
||||||
|
root.appendChild(card)
|
||||||
|
this._config = cardConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateContent(element, attributes) {
|
||||||
|
element.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
${attributes.map((attribute) => `
|
||||||
|
<tr>
|
||||||
|
<td>${attribute.name}</td>
|
||||||
|
<td>${attribute.value}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('')}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
const config = this._config;
|
||||||
|
const root = this.shadowRoot;
|
||||||
|
|
||||||
|
let attributes = this._getAttributes(hass, config.filter.include);
|
||||||
|
if (config.filter.exclude) {
|
||||||
|
const excludeAttributes = this._getAttributes(hass, config.filter.exclude).map(attr => attr.name);
|
||||||
|
attributes = attributes.filter(attr => {
|
||||||
|
return !excludeAttributes.includes(attr.name)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this._updateContent(root.getElementById('attributes'), attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardSize() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('entity-attributes-card', EntityAttributesCard);
|
||||||
Binary file not shown.
225
www/community/lovelace-auto-entities/auto-entities.js
Normal file
225
www/community/lovelace-auto-entities/auto-entities.js
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
customElements.whenDefined('card-tools').then(() => {
|
||||||
|
class AutoEntities extends cardTools.LitElement {
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
if(!config || !config.card)
|
||||||
|
throw new Error("Invalid configuration");
|
||||||
|
|
||||||
|
this._config = config;
|
||||||
|
this.data = {};
|
||||||
|
|
||||||
|
this.entities = this.get_entities() || [];
|
||||||
|
this.card = cardTools.createCard(Object.assign({entities: this.entities}, config.card));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
match(pattern, str){
|
||||||
|
if (typeof(str) === "string" && typeof(pattern) === "string") {
|
||||||
|
if((pattern.startsWith('/') && pattern.endsWith('/')) || pattern.indexOf('*') !== -1) {
|
||||||
|
if(pattern[0] !== '/') {
|
||||||
|
pattern = pattern.replace(/\./g, '\.');
|
||||||
|
pattern = pattern.replace(/\*/g, '.*');
|
||||||
|
pattern = `/^${pattern}$/`;
|
||||||
|
}
|
||||||
|
var regex = new RegExp(pattern.substr(1).slice(0,-1));
|
||||||
|
return regex.test(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(typeof(pattern) === "string") {
|
||||||
|
if(pattern.indexOf(":") !== -1 && typeof(str) === "object") {
|
||||||
|
while(pattern.indexOf(":") !== -1)
|
||||||
|
{
|
||||||
|
str = str[pattern.split(":")[0]];
|
||||||
|
pattern = pattern.substr(pattern.indexOf(":")+1, pattern.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pattern.startsWith("<=")) return parseFloat(str) <= parseFloat(pattern.substr(2));
|
||||||
|
if(pattern.startsWith(">=")) return parseFloat(str) >= parseFloat(pattern.substr(2));
|
||||||
|
if(pattern.startsWith("<")) return parseFloat(str) < parseFloat(pattern.substr(1));
|
||||||
|
if(pattern.startsWith(">")) return parseFloat(str) > parseFloat(pattern.substr(1));
|
||||||
|
if(pattern.startsWith("!")) return parseFloat(str) != parseFloat(pattern.substr(1));
|
||||||
|
if(pattern.startsWith("=")) return parseFloat(str) == parseFloat(pattern.substr(1));
|
||||||
|
}
|
||||||
|
return str === pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
match_filter(hass, entities, filter) {
|
||||||
|
let retval = [];
|
||||||
|
let count = -1;
|
||||||
|
entities.forEach((i) => {
|
||||||
|
count++;
|
||||||
|
if(!hass.states) return;
|
||||||
|
const e = (typeof(i) === "string")?hass.states[i]:hass.states[i.entity];
|
||||||
|
if(!e) return;
|
||||||
|
|
||||||
|
let unmatched = false;
|
||||||
|
Object.keys(filter).forEach((filterKey) => {
|
||||||
|
const key = filterKey.split(" ")[0];
|
||||||
|
const value = filter[filterKey];
|
||||||
|
switch(key) {
|
||||||
|
case "options":
|
||||||
|
break;
|
||||||
|
case "domain":
|
||||||
|
if(!this.match(value, e.entity_id.split('.')[0]))
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "state":
|
||||||
|
if(!this.match(value, e.state))
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "entity_id":
|
||||||
|
if(!this.match(value, e.entity_id))
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
if(!e.attributes.friendly_name
|
||||||
|
|| !this.match(value, e.attributes.friendly_name)
|
||||||
|
)
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "area":
|
||||||
|
let found = false;
|
||||||
|
this.data.areas.forEach((a) => {
|
||||||
|
if(found) return;
|
||||||
|
if(this.match(value, a.name)) {
|
||||||
|
this.data.devices.forEach((d) => {
|
||||||
|
if(found) return;
|
||||||
|
if(d.area_id && d.area_id === a.area_id) {
|
||||||
|
this.data.entities.forEach((en) => {
|
||||||
|
if(found) return;
|
||||||
|
if(en.device_id === d.id && en.entity_id === e.entity_id) {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(!found) unmatched = true;
|
||||||
|
break;
|
||||||
|
case "group":
|
||||||
|
if(!value.startsWith("group.")
|
||||||
|
|| !hass.states[value]
|
||||||
|
|| !hass.states[value].attributes.entity_id
|
||||||
|
|| !hass.states[value].attributes.entity_id.includes(e.entity_id)
|
||||||
|
)
|
||||||
|
unmatched = true;
|
||||||
|
break;
|
||||||
|
case "attributes":
|
||||||
|
Object.keys(value).forEach((entityKey) => {
|
||||||
|
const k = entityKey.split(" ")[0];
|
||||||
|
const v = value[entityKey];
|
||||||
|
if(!e.attributes[k]
|
||||||
|
|| !this.match(v, e.attributes[k])
|
||||||
|
)
|
||||||
|
unmatched = true;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
unmatched = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(!unmatched) retval.push(count);
|
||||||
|
});
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
get_entities()
|
||||||
|
{
|
||||||
|
let entities = [];
|
||||||
|
if(this._config.entities)
|
||||||
|
this._config.entities.forEach((e) => entities.push(e));
|
||||||
|
|
||||||
|
if(this._hass && this._config.filter) {
|
||||||
|
|
||||||
|
if(this._config.filter.include){
|
||||||
|
this._config.filter.include.forEach((f) => {
|
||||||
|
const add = this.match_filter(this._hass, Object.keys(this._hass.states), f);
|
||||||
|
let toAdd = [];
|
||||||
|
add.forEach((i) => {
|
||||||
|
toAdd.push(Object.assign({entity: Object.keys(this._hass.states)[i]}, f.options));
|
||||||
|
});
|
||||||
|
toAdd.sort((a,b) => {
|
||||||
|
if (a.entity < b.entity) return -1;
|
||||||
|
if (a.entity > b.entity) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
toAdd.forEach((i) => entities.push(i));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this._config.filter.exclude) {
|
||||||
|
this._config.filter.exclude.forEach((f) => {
|
||||||
|
const remove = this.match_filter(this._hass, entities, f);
|
||||||
|
for(var i = remove.length-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
entities.splice(remove[i],1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
createRenderRoot() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if(this.entities.length === 0 && this._config.show_empty === false)
|
||||||
|
return cardTools.LitHtml``;
|
||||||
|
return cardTools.LitHtml`
|
||||||
|
<div id="root">${this.card}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_data(hass) {
|
||||||
|
try {
|
||||||
|
this.data.areas = await hass.callWS({type: "config/area_registry/list"});
|
||||||
|
this.data.devices = await hass.callWS({type: "config/device_registry/list"});
|
||||||
|
this.data.entities = await hass.callWS({type: "config/entity_registry/list"});
|
||||||
|
} catch (err) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_compare_arrays(a,b) {
|
||||||
|
if(a === b) return true;
|
||||||
|
if(a == null || b == null) return false;
|
||||||
|
if(a.length != b.length) return false;
|
||||||
|
for(var i = 0; i < a.length; i++) {
|
||||||
|
if(a[i] !== b[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
this._hass = hass;
|
||||||
|
this.get_data(hass).then(() => {
|
||||||
|
if(this.card)
|
||||||
|
{
|
||||||
|
this.card.hass = this._hass;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldEntities = this.entities.map((e) => e.entity);
|
||||||
|
this.entities = this.get_entities() || [];
|
||||||
|
const newEntities = this.entities.map((e) => e.entity);
|
||||||
|
|
||||||
|
if(!this._compare_arrays(oldEntities, newEntities)) {
|
||||||
|
this.card.setConfig(Object.assign({entities: this.entities}, this._config.card));
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('auto-entities', AutoEntities);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if(customElements.get('card-tools')) return;
|
||||||
|
customElements.define('auto-entities', class extends HTMLElement{
|
||||||
|
setConfig() { throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
BIN
www/community/lovelace-auto-entities/auto-entities.js.gz
Normal file
BIN
www/community/lovelace-auto-entities/auto-entities.js.gz
Normal file
Binary file not shown.
428
www/community/lovelace-card-tools/card-tools.js
Normal file
428
www/community/lovelace-card-tools/card-tools.js
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
customElements.define('card-tools',
|
||||||
|
class {
|
||||||
|
static get CUSTOM_TYPE_PREFIX() { return "custom:"}
|
||||||
|
static get version() { return "0.4"}
|
||||||
|
|
||||||
|
static checkVersion(v) {
|
||||||
|
if (this.version < v) {
|
||||||
|
throw new Error(`Old version of card-tools found. Get the latest version of card-tools.js from https://github.com/thomasloven/lovelace-card-tools`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static deprecationWarning() {
|
||||||
|
if(window.cardTools_deprecationWarning) return;
|
||||||
|
console.warn("One or more of your lovelace plugins are using the functions cardTools.litElement(), cardTools.litHtml() or cardTools.hass(). Those are replaced with better alternatives and will be removed a some point in the future.")
|
||||||
|
console.warn("If you are a plugin developer, make sure you are using the new functions (see documentation).");
|
||||||
|
console.warn("If you are a plugin user, feel free to ignore this warning (or poke the developer of your plugins - not me though, I already know about this).")
|
||||||
|
console.warn("Best regards / thomasloven - " + (document.currentScript && document.currentScript.src));
|
||||||
|
window.cardTools_deprecationWarning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get LitElement() {
|
||||||
|
if(customElements.get('home-assistant-main'))
|
||||||
|
return Object.getPrototypeOf(customElements.get('home-assistant-main'));
|
||||||
|
return Object.getPrototypeOf(customElements.get('hui-view'));
|
||||||
|
}
|
||||||
|
static litElement() { // Backwards compatibility - deprecated
|
||||||
|
this.deprecationWarning();
|
||||||
|
return this.LitElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get LitHtml() {
|
||||||
|
return this.LitElement.prototype.html;
|
||||||
|
}
|
||||||
|
static litHtml() { // Backwards compatibility - deprecated
|
||||||
|
this.deprecationWarning();
|
||||||
|
return this.LitHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get LitCSS() {
|
||||||
|
return this.LitElement.prototype.css;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get hass() {
|
||||||
|
var hass = function() { // Backwards compatibility - deprecated
|
||||||
|
this.deprecationWarning();
|
||||||
|
return hass;
|
||||||
|
}
|
||||||
|
for (var k in document.querySelector('home-assistant').hass)
|
||||||
|
hass[k] = document.querySelector('home-assistant').hass[k];
|
||||||
|
hass.original = document.querySelector('home-assistant').hass;
|
||||||
|
return hass;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fireEvent(ev, detail, entity=null) {
|
||||||
|
ev = new Event(ev, {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: false,
|
||||||
|
composed: true,
|
||||||
|
});
|
||||||
|
ev.detail = detail || {};
|
||||||
|
if(entity) {
|
||||||
|
entity.dispatchEvent(ev);
|
||||||
|
} else {
|
||||||
|
var root = document.querySelector("home-assistant");
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("home-assistant-main");
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("app-drawer-layout partial-panel-resolver");
|
||||||
|
root = root && root.shadowRoot || root;
|
||||||
|
root = root && root.querySelector("ha-panel-lovelace");
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("hui-root");
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("ha-app-layout #view");
|
||||||
|
root = root && root.firstElementChild;
|
||||||
|
if (root) root.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get lovelace() {
|
||||||
|
var root = document.querySelector("home-assistant");
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("home-assistant-main");
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("app-drawer-layout partial-panel-resolver");
|
||||||
|
root = root && root.shadowRoot || root;
|
||||||
|
root = root && root.querySelector("ha-panel-lovelace")
|
||||||
|
root = root && root.shadowRoot;
|
||||||
|
root = root && root.querySelector("hui-root")
|
||||||
|
if (root) {
|
||||||
|
var ll = root.lovelace
|
||||||
|
ll.current_view = root.___curView;
|
||||||
|
return ll;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createThing(thing, config) {
|
||||||
|
const _createThing = (tag, config) => {
|
||||||
|
const element = document.createElement(tag);
|
||||||
|
try {
|
||||||
|
element.setConfig(config);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(tag, err);
|
||||||
|
return _createError(err.message, config);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _createError = (error, config) => {
|
||||||
|
return _createThing("hui-error-card", {
|
||||||
|
type: "error",
|
||||||
|
error,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!config || typeof config !== "object" || !config.type)
|
||||||
|
return _createError(`No ${thing} type configured`, config);
|
||||||
|
let tag = config.type;
|
||||||
|
if(config.error) {
|
||||||
|
const err = config.error;
|
||||||
|
delete config.error;
|
||||||
|
return _createError(err, config);
|
||||||
|
}
|
||||||
|
if(tag.startsWith(this.CUSTOM_TYPE_PREFIX))
|
||||||
|
tag = tag.substr(this.CUSTOM_TYPE_PREFIX.length);
|
||||||
|
else
|
||||||
|
tag = `hui-${tag}-${thing}`;
|
||||||
|
|
||||||
|
if(customElements.get(tag))
|
||||||
|
return _createThing(tag, config);
|
||||||
|
|
||||||
|
// If element doesn't exist (yet) create an error
|
||||||
|
const element = _createError(
|
||||||
|
`Custom element doesn't exist: ${tag}.`,
|
||||||
|
config
|
||||||
|
);
|
||||||
|
element.style.display = "None";
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
element.style.display = "";
|
||||||
|
}, 2000);
|
||||||
|
// Remove error if element is defined later
|
||||||
|
customElements.whenDefined(tag).then(() => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
this.fireEvent("ll-rebuild", {}, element);
|
||||||
|
});
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
static createCard(config) {
|
||||||
|
return this.createThing("card", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createElement(config) {
|
||||||
|
return this.createThing("element", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createEntityRow(config) {
|
||||||
|
const SPECIAL_TYPES = new Set([
|
||||||
|
"call-service",
|
||||||
|
"divider",
|
||||||
|
"section",
|
||||||
|
"weblink",
|
||||||
|
]);
|
||||||
|
const DEFAULT_ROWS = {
|
||||||
|
alert: "toggle",
|
||||||
|
automation: "toggle",
|
||||||
|
climate: "climate",
|
||||||
|
cover: "cover",
|
||||||
|
fan: "toggle",
|
||||||
|
group: "group",
|
||||||
|
input_boolean: "toggle",
|
||||||
|
input_number: "input-number",
|
||||||
|
input_select: "input-select",
|
||||||
|
input_text: "input-text",
|
||||||
|
light: "toggle",
|
||||||
|
media_player: "media-player",
|
||||||
|
lock: "lock",
|
||||||
|
remote: "toggle",
|
||||||
|
scene: "scene",
|
||||||
|
script: "script",
|
||||||
|
sensor: "sensor",
|
||||||
|
timer: "timer",
|
||||||
|
switch: "toggle",
|
||||||
|
vacuum: "toggle",
|
||||||
|
water_heater: "climate",
|
||||||
|
input_datetime: "input-datetime",
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!config || typeof config !== "object" || (!config.entity && !config.type)) {
|
||||||
|
Object.assign(config, {error: "Invalid config given"});
|
||||||
|
return this.createThing("", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = config.type || "default";
|
||||||
|
if(SPECIAL_TYPES.has(type) || type.startsWith(this.CUSTOM_TYPE_PREFIX))
|
||||||
|
return this.createThing("row", config);
|
||||||
|
|
||||||
|
const domain = config.entity.split(".", 1)[0];
|
||||||
|
Object.assign(config, {type: DEFAULT_ROWS[domain] || "text"});
|
||||||
|
return this.createThing("entity-row", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get deviceID() {
|
||||||
|
const ID_STORAGE_KEY = 'lovelace-player-device-id';
|
||||||
|
if(window['fully'] && typeof fully.getDeviceId === "function")
|
||||||
|
return fully.getDeviceId();
|
||||||
|
if(!localStorage[ID_STORAGE_KEY])
|
||||||
|
{
|
||||||
|
const s4 = () => {
|
||||||
|
return Math.floor((1+Math.random())*100000).toString(16).substring(1);
|
||||||
|
}
|
||||||
|
localStorage[ID_STORAGE_KEY] = `${s4()}${s4()}-${s4()}${s4()}`;
|
||||||
|
}
|
||||||
|
return localStorage[ID_STORAGE_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
static moreInfo(entity) {
|
||||||
|
this.fireEvent("hass-more-info", {entityId: entity});
|
||||||
|
}
|
||||||
|
|
||||||
|
static longpress(element) {
|
||||||
|
customElements.whenDefined("long-press").then(() => {
|
||||||
|
const longpress = document.body.querySelector("long-press");
|
||||||
|
longpress.bind(element);
|
||||||
|
});
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
static hasTemplate(text) {
|
||||||
|
return /\[\[\s+.*\s+\]\]/.test(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseTemplateString(str, specialData = {}) {
|
||||||
|
if(typeof(str) !== "string") return text;
|
||||||
|
const FUNCTION = /^[a-zA-Z0-9_]+\(.*\)$/;
|
||||||
|
const EXPR = /([^=<>!]+)\s*(==|!=|<|>|<=|>=)\s*([^=<>!]+)/;
|
||||||
|
const SPECIAL = /^\{.+\}$/;
|
||||||
|
const STRING = /^"[^"]*"|'[^']*'$/;
|
||||||
|
|
||||||
|
if(typeof(specialData) === "string") specialData = {};
|
||||||
|
specialData = Object.assign({
|
||||||
|
user: this.hass.user.name,
|
||||||
|
browser: this.deviceID,
|
||||||
|
hash: location.hash.substr(1) || ' ',
|
||||||
|
}, specialData);
|
||||||
|
|
||||||
|
const _parse_function = (str) => {
|
||||||
|
let args = [str.substr(0, str.indexOf('(')).trim()]
|
||||||
|
str = str.substr(str.indexOf('(')+1);
|
||||||
|
while(str) {
|
||||||
|
let index = 0;
|
||||||
|
let parens = 0;
|
||||||
|
let quote = false;
|
||||||
|
while(str[index]) {
|
||||||
|
let c = str[index++];
|
||||||
|
|
||||||
|
if(c === quote && index > 1 && str[index-2] !== "\\")
|
||||||
|
quote = false;
|
||||||
|
else if(`"'`.includes(c))
|
||||||
|
quote = c;
|
||||||
|
if(quote) continue;
|
||||||
|
|
||||||
|
if(c === '(')
|
||||||
|
parens = parens + 1;
|
||||||
|
else if(c === ')') {
|
||||||
|
parens = parens - 1;
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if(parens > 0) continue;
|
||||||
|
|
||||||
|
if(",)".includes(c)) break;
|
||||||
|
}
|
||||||
|
args.push(str.substr(0, index-1).trim());
|
||||||
|
str = str.substr(index);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _parse_special = (str) => {
|
||||||
|
str = str.substr(1, str.length - 2);
|
||||||
|
return specialData[str] || `{${str}}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _parse_entity = (str) => {
|
||||||
|
str = str.split(".");
|
||||||
|
let v;
|
||||||
|
if(str[0].match(SPECIAL)) {
|
||||||
|
v = _parse_special(str.shift());
|
||||||
|
v = this.hass.states[v] || v;
|
||||||
|
} else {
|
||||||
|
v = this.hass.states[`${str.shift()}.${str.shift()}`];
|
||||||
|
if(!str.length) return v['state'];
|
||||||
|
}
|
||||||
|
str.forEach(item => v=v[item]);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _eval_expr = (str) => {
|
||||||
|
str = EXPR.exec(str);
|
||||||
|
if(str === null) return false;
|
||||||
|
const lhs = this.parseTemplateString(str[1]);
|
||||||
|
const rhs = this.parseTemplateString(str[3]);
|
||||||
|
var expr = ''
|
||||||
|
if(parseFloat(lhs) != lhs)
|
||||||
|
expr = `"${lhs}" ${str[2]} "${rhs}"`;
|
||||||
|
else
|
||||||
|
expr = `${parseFloat(lhs)} ${str[2]} ${parseFloat(rhs)}`
|
||||||
|
return eval(expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _eval_function = (args) => {
|
||||||
|
if(args[0] === "if") {
|
||||||
|
if(_eval_expr(args[1]))
|
||||||
|
return this.parseTemplateString(args[2]);
|
||||||
|
return this.parseTemplateString(args[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
str = str.trim();
|
||||||
|
if(str.match(STRING))
|
||||||
|
return str.substr(1, str.length - 2);
|
||||||
|
if(str.match(SPECIAL))
|
||||||
|
return _parse_special(str);
|
||||||
|
if(str.match(FUNCTION))
|
||||||
|
return _eval_function(_parse_function(str));
|
||||||
|
if(str.includes("."))
|
||||||
|
return _parse_entity(str);
|
||||||
|
return str;
|
||||||
|
} catch (err) {
|
||||||
|
return `[[ Template matching failed: ${str} ]]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static parseTemplate(text, data = {}) {
|
||||||
|
if(typeof(text) !== "string") return text;
|
||||||
|
// Note: .*? is javascript regex syntax for NON-greedy matching
|
||||||
|
var RE_template = /\[\[\s(.*?)\s\]\]/g;
|
||||||
|
text = text.replace(RE_template, (str, p1, offset, s) => this.parseTemplateString(p1, data));
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static args(script=null) {
|
||||||
|
script = script || document.currentScript;
|
||||||
|
var url = script.src;
|
||||||
|
url = url.substr(url.indexOf("?")+1)
|
||||||
|
let args = {};
|
||||||
|
url.split("&").forEach((a) => {
|
||||||
|
if(a.indexOf("=")) {
|
||||||
|
let parts = a.split("=");
|
||||||
|
args[parts[0]] = parts[1]
|
||||||
|
} else {
|
||||||
|
args[a] = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
static localize(key, def="") {
|
||||||
|
const language = this.hass.language;
|
||||||
|
if(this.hass.resources[language] && this.hass.resources[language][key])
|
||||||
|
return this.hass.resources[language][key];
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
static popUp(title, message, large=false) {
|
||||||
|
let popup = document.createElement('div');
|
||||||
|
popup.innerHTML = `
|
||||||
|
<style>
|
||||||
|
app-toolbar {
|
||||||
|
color: var(--more-info-header-color);
|
||||||
|
background-color: var(--more-info-header-background);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<app-toolbar>
|
||||||
|
<paper-icon-button
|
||||||
|
icon="hass:close"
|
||||||
|
dialog-dismiss=""
|
||||||
|
></paper-icon-button>
|
||||||
|
<div class="main-title" main-title="">
|
||||||
|
${title}
|
||||||
|
</div>
|
||||||
|
</app-toolbar>
|
||||||
|
`;
|
||||||
|
popup.appendChild(message);
|
||||||
|
this.moreInfo(Object.keys(this.hass.states)[0]);
|
||||||
|
let moreInfo = document.querySelector("home-assistant")._moreInfoEl;
|
||||||
|
moreInfo._page = "none";
|
||||||
|
moreInfo.shadowRoot.appendChild(popup);
|
||||||
|
moreInfo.large = large;
|
||||||
|
document.querySelector("home-assistant").provideHass(message);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
let interval = setInterval(() => {
|
||||||
|
if (moreInfo.getAttribute('aria-hidden')) {
|
||||||
|
popup.parentNode.removeChild(popup);
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
}, 1000);
|
||||||
|
return moreInfo;
|
||||||
|
}
|
||||||
|
static closePopUp() {
|
||||||
|
let moreInfo = document.querySelector("home-assistant")._moreInfoEl;
|
||||||
|
if (moreInfo) moreInfo.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
static logger(message, script=null) {
|
||||||
|
if(!('debug' in this.args(script))) return;
|
||||||
|
|
||||||
|
if(typeof message !== "string")
|
||||||
|
message = JSON.stringify(message);
|
||||||
|
console.log(`%cDEBUG:%c ${message}`,
|
||||||
|
"color: blue; font-weight: bold", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global definition of cardTools
|
||||||
|
var cardTools = customElements.get('card-tools');
|
||||||
|
|
||||||
|
console.info(`%cCARD-TOOLS IS INSTALLED
|
||||||
|
%cDeviceID: ${customElements.get('card-tools').deviceID}`,
|
||||||
|
"color: green; font-weight: bold",
|
||||||
|
"");
|
||||||
BIN
www/community/lovelace-card-tools/card-tools.js.gz
Normal file
BIN
www/community/lovelace-card-tools/card-tools.js.gz
Normal file
Binary file not shown.
194
www/community/lovelace-layout-card/layout-card.js
Normal file
194
www/community/lovelace-layout-card/layout-card.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
customElements.whenDefined('card-tools').then(() => {
|
||||||
|
let cardTools = customElements.get('card-tools');
|
||||||
|
class LayoutCard extends cardTools.LitElement {
|
||||||
|
|
||||||
|
async setConfig(config) {
|
||||||
|
this.config = config;
|
||||||
|
this.layout = config.layout || 'auto';
|
||||||
|
this.minCols = config.column_num || 1;
|
||||||
|
this.maxCols = config.max_columns || 100;
|
||||||
|
this.colWidth = config.column_width || 400;
|
||||||
|
this.maxWidth = config.max_width || 500;
|
||||||
|
this.minHeight = config.min_height || 5;
|
||||||
|
this.rtl = config.rtl || false;
|
||||||
|
this.cardSize = 1;
|
||||||
|
|
||||||
|
this.make_cards();
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => this.build());
|
||||||
|
window.addEventListener('hass-open-menu', () => setTimeout(() => this.build(), 100));
|
||||||
|
window.addEventListener('hass-close-menu', () => setTimeout(() => this.build(), 100));
|
||||||
|
window.addEventListener('location-changed', () => {
|
||||||
|
if(location.hash === "") setTimeout(() =>
|
||||||
|
this.build(), 100)
|
||||||
|
});
|
||||||
|
if(config.rebuild)
|
||||||
|
window.setTimeout(() => this.build(), config.rebuild);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return cardTools.LitHtml`
|
||||||
|
<div id="columns"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
if(this.parentElement && this.parentElement.id !== "view")
|
||||||
|
{
|
||||||
|
this.style.padding = "0";
|
||||||
|
}
|
||||||
|
if(this.rtl)
|
||||||
|
this.shadowRoot.querySelector("#columns").style.flexDirection = 'row-reverse';
|
||||||
|
this.build();
|
||||||
|
this._cardModder = {
|
||||||
|
target: this,
|
||||||
|
styles: this.shadowRoot.querySelector("style")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return cardTools.LitCSS`
|
||||||
|
:host {
|
||||||
|
padding: 8px 4px 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#columns {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex-basis: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column > * {
|
||||||
|
display: block;
|
||||||
|
margin: 4px 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column > *:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_cards() {
|
||||||
|
this._cards = this.config.cards.map((c) => {
|
||||||
|
if (typeof c === 'string') return c;
|
||||||
|
const card = cardTools.createCard(c);
|
||||||
|
if(this._hass) card.hass = this._hass;
|
||||||
|
this.appendChild(card); // Place card in DOM to get size
|
||||||
|
return card;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update_columns() {
|
||||||
|
const width = (this.shadowRoot && this.shadowRoot.querySelector("#columns").clientWidth) || (this.parentElement && this.parentElement.clientWidth);
|
||||||
|
this.colNum = Math.floor(width / this.colWidth) || 1;
|
||||||
|
this.colNum = Math.max(this.colNum, this.minCols);
|
||||||
|
this.colNum = Math.min(this.colNum, this.maxCols);
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
if (this.offsetParent === null) return;
|
||||||
|
const root = this.shadowRoot.querySelector("#columns");
|
||||||
|
while(root.lastChild) {
|
||||||
|
root.removeChild(root.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update_columns();
|
||||||
|
|
||||||
|
if(!this._cards) this.make_cards();
|
||||||
|
|
||||||
|
let cols = [];
|
||||||
|
let colSize = [];
|
||||||
|
for(let i = 0; i < this.colNum; i++) {
|
||||||
|
cols.push([]);
|
||||||
|
colSize.push(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortestCol = () => {
|
||||||
|
let i = 0;
|
||||||
|
for(let j = 0; j < this.colNum; j++) {
|
||||||
|
if(colSize[j] < this.min_height)
|
||||||
|
return j;
|
||||||
|
if(colSize[j] < colSize[i])
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
this._cards.forEach((c) => {
|
||||||
|
const isBreak = (typeof(c) === 'string');
|
||||||
|
const sz = c.getCardSize ? c.getCardSize() : 1;
|
||||||
|
|
||||||
|
switch(this.layout) {
|
||||||
|
case 'horizontal':
|
||||||
|
if(i >= this.colNum) i = 0;
|
||||||
|
i += 1;
|
||||||
|
if(isBreak) break;
|
||||||
|
cols[i-1].push(c);
|
||||||
|
colSize[i-1] += sz;
|
||||||
|
break;
|
||||||
|
case 'vertical':
|
||||||
|
if(isBreak){
|
||||||
|
i += 1;
|
||||||
|
if(i >= this.colNum)
|
||||||
|
i = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cols[i].push(c);
|
||||||
|
colSize[i] += sz;
|
||||||
|
break;
|
||||||
|
case 'auto':
|
||||||
|
default:
|
||||||
|
if(isBreak) break;
|
||||||
|
cols[shortestCol()].push(c);
|
||||||
|
colSize[shortestCol()] += sz;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cols = cols.filter((c) => c.length > 0);
|
||||||
|
cols.forEach((c, i) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('column');
|
||||||
|
c.forEach((e) => div.appendChild(e));
|
||||||
|
root.appendChild(div);
|
||||||
|
if(cols.length > 1 && typeof(this.maxWidth) === 'object') {
|
||||||
|
div.style.setProperty('max-width', this.maxWidth[i]);
|
||||||
|
} else {
|
||||||
|
div.style.setProperty('max-width', this.maxWidth+'px');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cardSize = Math.max.apply(null, colSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
this._hass = hass;
|
||||||
|
if(this._cards)
|
||||||
|
this._cards
|
||||||
|
.filter((c) => typeof(c) !== 'string')
|
||||||
|
.forEach((c) => c.hass = hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardSize() {
|
||||||
|
return this.cardSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('layout-card', LayoutCard);
|
||||||
|
});
|
||||||
|
window.setTimeout(() => {
|
||||||
|
if(customElements.get('card-tools')) return;
|
||||||
|
customElements.define('layout-card', class extends HTMLElement{
|
||||||
|
setConfig() { throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
BIN
www/community/lovelace-layout-card/layout-card.js.gz
Normal file
BIN
www/community/lovelace-layout-card/layout-card.js.gz
Normal file
Binary file not shown.
@@ -0,0 +1,203 @@
|
|||||||
|
class MultipleEntityRow extends Polymer.Element {
|
||||||
|
|
||||||
|
static get template() {
|
||||||
|
return Polymer.html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 16px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
flex: 1 0 60px;
|
||||||
|
}
|
||||||
|
.info, .info > * {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.flex ::slotted(*) {
|
||||||
|
margin-left: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.flex ::slotted([slot="secondary"]) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.secondary, ha-relative-time {
|
||||||
|
display: block;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
state-badge {
|
||||||
|
flex: 0 0 40px;
|
||||||
|
}
|
||||||
|
.entity {
|
||||||
|
margin-right: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.entity span {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.entity:last-of-type {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
.state {
|
||||||
|
min-width: 45px;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
.toggle {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<state-badge state-obj="[[_config.stateObj]]" override-icon="[[_config.icon]]"></state-badge>
|
||||||
|
<div class="flex">
|
||||||
|
<div class="info">
|
||||||
|
[[entityName(_config)]]
|
||||||
|
<div class="secondary">
|
||||||
|
<template is="dom-if" if="{{displayInfo}}">
|
||||||
|
[[entityName(info)]] [[entityState(info)]]
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{displayLastChanged}}">
|
||||||
|
<ha-relative-time datetime="[[_config.stateObj.last_changed]]" hass="[[_hass]]"></ha-relative-time>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template is="dom-if" if="{{displayPrimary}}">
|
||||||
|
<div class="entity" on-click="primaryMoreInfo">
|
||||||
|
<span>[[entityName(primary)]]</span>
|
||||||
|
<div>[[entityState(primary)]]</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{displaySecondary}}">
|
||||||
|
<div class="entity" on-click="secondaryMoreInfo">
|
||||||
|
<span>[[entityName(secondary)]]</span>
|
||||||
|
<div>[[entityState(secondary)]]</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{displayValue}}">
|
||||||
|
<div class="state">
|
||||||
|
[[entityState(_config)]]
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{displayToggle}}">
|
||||||
|
<div class="toggle">
|
||||||
|
<ha-entity-toggle state-obj="[[_config.stateObj]]" hass="[[_hass]]"></ha-entity-toggle>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryMoreInfo(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.fireEvent(this._config.primary.entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryMoreInfo(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.fireEvent(this._config.secondary.entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
entityName(data) {
|
||||||
|
return data && data.stateObj && data.name !== false ? this.computeStateName(data.stateObj, data.name) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
entityState(data) {
|
||||||
|
if (!data || !data.stateObj) return this._hass.localize('state.default.unavailable');
|
||||||
|
return data.attribute
|
||||||
|
? data.stateObj.attributes[data.attribute]
|
||||||
|
? `${data.stateObj.attributes[data.attribute]} ${data.unit ? data.unit : ''}`
|
||||||
|
: this._hass.localize('state.default.unavailable')
|
||||||
|
: this.computeStateValue(data.stateObj, data.unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
computeStateName(stateObj, name) {
|
||||||
|
return name || (stateObj.attributes.friendly_name === undefined
|
||||||
|
? stateObj.entity_id.substr(stateObj.entity_id.indexOf('.') + 1).replace(/_/g, ' ')
|
||||||
|
: stateObj.attributes.friendly_name || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
computeStateValue(stateObj, unit) {
|
||||||
|
let display;
|
||||||
|
const domain = stateObj.entity_id.substr(0, stateObj.entity_id.indexOf("."));
|
||||||
|
|
||||||
|
if (domain === "binary_sensor") {
|
||||||
|
if (stateObj.attributes.device_class) {
|
||||||
|
display = this._hass.localize(`state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}`);
|
||||||
|
}
|
||||||
|
if (!display) {
|
||||||
|
display = this._hass.localize(`state.${domain}.default.${stateObj.state}`);
|
||||||
|
}
|
||||||
|
} else if ((unit || stateObj.attributes.unit_of_measurement) && !["unknown", "unavailable"].includes(stateObj.state)) {
|
||||||
|
display = `${stateObj.state} ${stateObj.attributes.unit_of_measurement}`;
|
||||||
|
} else if (domain === "zwave") {
|
||||||
|
display = ["initializing", "dead"].includes(stateObj.state)
|
||||||
|
? this._hass.localize(`state.zwave.query_stage.${stateObj.state}`, 'query_stage', stateObj.attributes.query_stage)
|
||||||
|
: this._hass.localize(`state.zwave.default.${stateObj.state}`);
|
||||||
|
} else {
|
||||||
|
display = this._hass.localize(`state.${domain}.${stateObj.state}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return display ||
|
||||||
|
this._hass.localize(`state.default.${stateObj.state}`) ||
|
||||||
|
this._hass.localize(`component.${domain}.state.${stateObj.state}`) ||
|
||||||
|
stateObj.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
if (!config.entity) throw new Error('Please define an entity.');
|
||||||
|
if (config.primary && !config.primary.entity) throw new Error('Please define a primary entity.');
|
||||||
|
if (config.secondary && !config.secondary.entity) throw new Error('Please define a secondary entity.');
|
||||||
|
|
||||||
|
this._config = config;
|
||||||
|
this.displayToggle = config.toggle === true;
|
||||||
|
this.displayValue = !this.displayToggle && !config.hide_state;
|
||||||
|
this.displayPrimary = config.primary && config.primary.entity;
|
||||||
|
this.displaySecondary = config.secondary && config.secondary.entity;
|
||||||
|
this.displayInfo = config.info && config.info.entity;
|
||||||
|
this.displayLastChanged = !this.displayInfo && config.secondary_info === 'last-changed';
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
this._hass = hass;
|
||||||
|
|
||||||
|
if (hass && this._config) {
|
||||||
|
const stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null;
|
||||||
|
if (stateObj) {
|
||||||
|
this._config.stateObj = stateObj;
|
||||||
|
|
||||||
|
this.primary = Object.assign({}, this._config.primary, {
|
||||||
|
stateObj: this.displayPrimary && this._config.primary.entity in hass.states ?
|
||||||
|
hass.states[this._config.primary.entity] : null
|
||||||
|
});
|
||||||
|
this.secondary = Object.assign({}, this._config.secondary, {
|
||||||
|
stateObj: this.displaySecondary && this._config.secondary.entity in hass.states ?
|
||||||
|
hass.states[this._config.secondary.entity] : null
|
||||||
|
});
|
||||||
|
this.info = Object.assign({}, this._config.info, {
|
||||||
|
stateObj: this.displayInfo && this._config.info.entity in hass.states ?
|
||||||
|
hass.states[this._config.info.entity] : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(entity, options = {}) {
|
||||||
|
const event = new Event('hass-more-info', {
|
||||||
|
bubbles: options.bubbles || true,
|
||||||
|
cancelable: options.cancelable || true,
|
||||||
|
composed: options.composed || true,
|
||||||
|
});
|
||||||
|
event.detail = {entityId: entity};
|
||||||
|
this.shadowRoot.dispatchEvent(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('multiple-entity-row', MultipleEntityRow);
|
||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
www/community/lovelace-slider-entity-row/slider-entity-row.js.gz
Normal file
BIN
www/community/lovelace-slider-entity-row/slider-entity-row.js.gz
Normal file
Binary file not shown.
221
www/community/lovelace-xiaomi-vacuum-card/xiaomi-vacuum-card.js
Normal file
221
www/community/lovelace-xiaomi-vacuum-card/xiaomi-vacuum-card.js
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
class XiaomiVacuumCard extends Polymer.Element {
|
||||||
|
|
||||||
|
static get template() {
|
||||||
|
return Polymer.html`
|
||||||
|
<style>
|
||||||
|
.background {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 16px 16px 0;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-right: 2px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<ha-card hass="[[_hass]]" config="[[_config]]" class="background" style="[[backgroundImage]]">
|
||||||
|
<template is="dom-if" if="{{name}}">
|
||||||
|
<div class="title" style="[[contentText]]">[[name]]</div>
|
||||||
|
</template>
|
||||||
|
<div class="content grid" style="[[contentStyle]]" on-click="moreInfo">
|
||||||
|
<div class="grid-content grid-left">
|
||||||
|
<div>[[_config.labels.status]]: [[stateObj.attributes.status]]</div>
|
||||||
|
<div>[[_config.labels.battery]]: [[stateObj.attributes.battery_level]] %</div>
|
||||||
|
<div>[[_config.labels.mode]]: [[stateObj.attributes.fan_speed]]</div>
|
||||||
|
</div>
|
||||||
|
<template is="dom-if" if="{{showDetails}}">
|
||||||
|
<div class="grid-content grid-right" >
|
||||||
|
<div>[[_config.labels.main_brush]]: [[stateObj.attributes.main_brush_left]] [[_config.labels.hours]]</div>
|
||||||
|
<div>[[_config.labels.side_brush]]: [[stateObj.attributes.side_brush_left]] [[_config.labels.hours]]</div>
|
||||||
|
<div>[[_config.labels.filter]]: [[stateObj.attributes.filter_left]] [[_config.labels.hours]]</div>
|
||||||
|
<div>[[_config.labels.sensor]]: [[stateObj.attributes.sensor_dirty_left]] [[_config.labels.hours]]</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<template is="dom-if" if="{{showButtons}}">
|
||||||
|
<div class="flex" style="[[contentText]]">
|
||||||
|
<template is="dom-if" if="{{_config.buttons.start}}">
|
||||||
|
<div class="button" on-tap="startVaccum">
|
||||||
|
<ha-icon icon="mdi:play"></ha-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{_config.buttons.pause}}">
|
||||||
|
<div class="button" on-tap="pauseVacuum">
|
||||||
|
<ha-icon icon="mdi:pause"></ha-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{_config.buttons.stop}}">
|
||||||
|
<div class="button" on-tap="stopVacuum">
|
||||||
|
<ha-icon icon="mdi:stop"></ha-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{_config.buttons.spot}}">
|
||||||
|
<div class="button" on-tap="cleanSpot">
|
||||||
|
<ha-icon icon="mdi:broom"></ha-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{_config.buttons.locate}}">
|
||||||
|
<div class="button" on-tap="locateVacuum">
|
||||||
|
<ha-icon icon="mdi:map-marker"></ha-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="{{_config.buttons.return}}">
|
||||||
|
<div class="button" on-tap="returnVacuum">
|
||||||
|
<ha-icon icon="mdi:home-map-marker"></ha-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
moreInfo() { this.fireEvent('hass-more-info'); }
|
||||||
|
startVaccum() { this.callService(this._config.service.start); }
|
||||||
|
pauseVacuum() { this.callService(this._config.service.pause); }
|
||||||
|
stopVacuum() { this.callService(this._config.service.stop); }
|
||||||
|
locateVacuum() { this.callService(this._config.service.locate); }
|
||||||
|
returnVacuum() { this.callService(this._config.service.return); }
|
||||||
|
cleanSpot() { this.callService(this._config.service.spot); }
|
||||||
|
|
||||||
|
callService(service) {
|
||||||
|
this._hass.callService('vacuum', service, {entity_id: this._config.entity});
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(type, options = {}) {
|
||||||
|
const event = new Event(type, {
|
||||||
|
bubbles: options.bubbles || true,
|
||||||
|
cancelable: options.cancelable || true,
|
||||||
|
composed: options.composed || true,
|
||||||
|
});
|
||||||
|
event.detail = {entityId: this._config.entity};
|
||||||
|
this.shadowRoot.dispatchEvent(event);
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardSize() {
|
||||||
|
if (this.name && this.showButtons) return 5;
|
||||||
|
if (this.name || this.showButtons) return 4;
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
const labels = {
|
||||||
|
status: 'Status',
|
||||||
|
battery: 'Battery',
|
||||||
|
mode: 'Mode',
|
||||||
|
main_brush: 'Main Brush',
|
||||||
|
side_brush: 'Side Brush',
|
||||||
|
filter: 'Filter',
|
||||||
|
sensor: 'Sensor',
|
||||||
|
hours: 'h',
|
||||||
|
};
|
||||||
|
|
||||||
|
const services = {
|
||||||
|
start: 'start',
|
||||||
|
pause: 'pause',
|
||||||
|
stop: 'stop',
|
||||||
|
locate: 'locate',
|
||||||
|
return: 'return_to_base',
|
||||||
|
spot: 'clean_spot',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttons = {
|
||||||
|
start: true,
|
||||||
|
pause: true,
|
||||||
|
stop: true,
|
||||||
|
spot: false,
|
||||||
|
locate: true,
|
||||||
|
return: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const vendors = {
|
||||||
|
xiaomi: {
|
||||||
|
image: '/local/img/vacuum.png',
|
||||||
|
details: true,
|
||||||
|
},
|
||||||
|
ecovacs: {
|
||||||
|
image: '/local/img/vacuum_ecovacs.png',
|
||||||
|
details: false,
|
||||||
|
buttons: {
|
||||||
|
stop: false,
|
||||||
|
spot: true,
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
start: 'turn_on',
|
||||||
|
pause: 'stop',
|
||||||
|
stop: 'turn_off',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!config.entity) throw new Error('Please define an entity.');
|
||||||
|
if (config.entity.split('.')[0] !== 'vacuum') throw new Error('Please define a vacuum entity.');
|
||||||
|
if (config.vendor && !config.vendor in vendors) throw new Error('Please define a valid vendor.');
|
||||||
|
|
||||||
|
const vendor = vendors[config.vendor] || vendors.xiaomi;
|
||||||
|
|
||||||
|
this.showDetails = vendor.details;
|
||||||
|
this.showButtons = config.buttons !== false;
|
||||||
|
|
||||||
|
config.service = Object.assign({}, services, vendor.service);
|
||||||
|
config.buttons = Object.assign({}, buttons, vendor.buttons, config.buttons);
|
||||||
|
config.labels = Object.assign({}, labels, config.labels);
|
||||||
|
|
||||||
|
this.contentText = `color: ${config.image !== false ? 'white; text-shadow: 0 0 10px black;' : 'var(--primary-text-color)'}`;
|
||||||
|
this.contentStyle = `padding: ${this.showButtons ? '16px 16px 4px' : '16px'}; ${this.contentText}`;
|
||||||
|
this.backgroundImage = config.image !== false ? `background-image: url('${config.image || vendor.image}')` : '';
|
||||||
|
|
||||||
|
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.name = this._config.name !== false && (this._config.name || this.stateObj.attributes.friendly_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('xiaomi-vacuum-card', XiaomiVacuumCard);
|
||||||
Binary file not shown.
1
www/community/mini-media-player/mini-media-player.js
Normal file
1
www/community/mini-media-player/mini-media-player.js
Normal file
File diff suppressed because one or more lines are too long
BIN
www/community/mini-media-player/mini-media-player.js.gz
Normal file
BIN
www/community/mini-media-player/mini-media-player.js.gz
Normal file
Binary file not shown.
@@ -0,0 +1,56 @@
|
|||||||
|
customElements.whenDefined('card-tools').then(() => {
|
||||||
|
|
||||||
|
class SecondaryInfoEntityRow extends cardTools.LitElement {
|
||||||
|
version() { return "0.3.1"; }
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return cardTools.LitHtml`
|
||||||
|
${this._wrappedElement}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
cardTools.checkVersion(0.4);
|
||||||
|
this._config = config;
|
||||||
|
this._wrappedElement = this._createElement(config);
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
this._hass = hass;
|
||||||
|
this._stateObj = this._config.entity in hass.states ? hass.states[this._config.entity] : null;
|
||||||
|
this._updateElement(this._wrappedElement, hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
_createElement(config) {
|
||||||
|
// Override the custom row type in order to create the 'standard' row for this entity
|
||||||
|
let defaultRowConfig = Object.assign(config, {type: "default"});
|
||||||
|
const element = cardTools.createEntityRow(defaultRowConfig);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _updateElement(wrappedElement, hass) {
|
||||||
|
if (!wrappedElement) return;
|
||||||
|
|
||||||
|
this._wrappedElement.hass = hass;
|
||||||
|
await this._wrappedElement.updateComplete;
|
||||||
|
await this._wrappedElement.shadowRoot.querySelector("hui-generic-entity-row");
|
||||||
|
let secondaryInfoDiv = this._wrappedElement.shadowRoot.querySelector("hui-generic-entity-row").shadowRoot.querySelector(".secondary");
|
||||||
|
if (secondaryInfoDiv && this._config.secondary_info) {
|
||||||
|
let text = window.cardTools.parseTemplate(this._config.secondary_info, {entity: this._config.entity});
|
||||||
|
secondaryInfoDiv.innerHTML = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customElements.define('secondaryinfo-entity-row', SecondaryInfoEntityRow);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (customElements.get('card-tools')) return;
|
||||||
|
customElements.define('secondaryinfo-entity-row', class extends HTMLElement {
|
||||||
|
setConfig() {
|
||||||
|
throw new Error("Can't find card-tools. See https://github.com/thomasloven/lovelace-card-tools");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 2000);
|
||||||
Binary file not shown.
176
www/community/vertical-stack-in-card/vertical-stack-in-card.js
Normal file
176
www/community/vertical-stack-in-card/vertical-stack-in-card.js
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
class VerticalStackInCard extends HTMLElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
// Make use of shadowRoot to avoid conflicts when reusing
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
}
|
||||||
|
|
||||||
|
setConfig(config) {
|
||||||
|
if (!config || !config.cards || !Array.isArray(config.cards)) {
|
||||||
|
throw new Error('Card config incorrect');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.style.boxShadow = "var(--ha-card-box-shadow, 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2))";
|
||||||
|
this.style.borderRadius = "var(--ha-card-border-radius, 2px)";
|
||||||
|
this.style.background = "var(--paper-card-background-color)";
|
||||||
|
this.style.display = "block";
|
||||||
|
|
||||||
|
const root = this.shadowRoot;
|
||||||
|
while (root.hasChildNodes()) {
|
||||||
|
root.removeChild(root.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._refCards = [];
|
||||||
|
if (config.title) {
|
||||||
|
const title = document.createElement("div");
|
||||||
|
title.className = "header";
|
||||||
|
title.style = "font-family: var(--paper-font-headline_-_font-family); -webkit-font-smoothing: var(--paper-font-headline_-_-webkit-font-smoothing); font-size: var(--paper-font-headline_-_font-size); font-weight: var(--paper-font-headline_-_font-weight); letter-spacing: var(--paper-font-headline_-_letter-spacing); line-height: var(--paper-font-headline_-_line-height);text-rendering: var(--paper-font-common-expensive-kerning_-_text-rendering);opacity: var(--dark-primary-opacity);padding: 24px 16px 0px 16px";
|
||||||
|
title.innerHTML = '<div class="name">' + config.title + '</div>';
|
||||||
|
root.appendChild(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
const _createThing = (tag, config) => {
|
||||||
|
const element = document.createElement(tag);
|
||||||
|
try {
|
||||||
|
element.setConfig(config);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(tag, err);
|
||||||
|
return _createError(err.message, config);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _createError = (error, config) => {
|
||||||
|
return _createThing("hui-error-card", {
|
||||||
|
type: "error",
|
||||||
|
error,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const _fireEvent = (ev, detail, entity=null) => {
|
||||||
|
ev = new Event(ev, {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: false,
|
||||||
|
composed: true,
|
||||||
|
});
|
||||||
|
ev.detail = detail || {};
|
||||||
|
|
||||||
|
if (entity) {
|
||||||
|
entity.dispatchEvent(ev);
|
||||||
|
} else {
|
||||||
|
document
|
||||||
|
.querySelector("home-assistant")
|
||||||
|
.shadowRoot.querySelector("home-assistant-main")
|
||||||
|
.shadowRoot.querySelector("app-drawer-layout partial-panel-resolver")
|
||||||
|
.shadowRoot.querySelector("ha-panel-lovelace")
|
||||||
|
.shadowRoot.querySelector("hui-root")
|
||||||
|
.shadowRoot.querySelector("ha-app-layout #view")
|
||||||
|
.firstElementChild
|
||||||
|
.dispatchEvent(ev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.cards.forEach((item) => {
|
||||||
|
let tag = item.type;
|
||||||
|
|
||||||
|
if (tag.startsWith("divider")) {
|
||||||
|
tag = `hui-divider-row`;
|
||||||
|
} else if (tag.startsWith("custom:")) {
|
||||||
|
tag = tag.substr("custom:".length);
|
||||||
|
} else {
|
||||||
|
tag = `hui-${tag}-card`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customElements.get(tag)) {
|
||||||
|
const element = _createThing(tag, item);
|
||||||
|
root.appendChild(element);
|
||||||
|
this._refCards.push(element);
|
||||||
|
} else {
|
||||||
|
// If element doesn't exist (yet) create an error
|
||||||
|
const element = _createError(
|
||||||
|
`Custom element doesn't exist: ${tag}.`,
|
||||||
|
item
|
||||||
|
);
|
||||||
|
element.style.display = "None";
|
||||||
|
|
||||||
|
const time = setTimeout(() => {
|
||||||
|
element.style.display = "";
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
// Remove error if element is defined later
|
||||||
|
customElements.whenDefined(tag).then(() => {
|
||||||
|
clearTimeout(time);
|
||||||
|
_fireEvent("ll-rebuild", {}, element);
|
||||||
|
});
|
||||||
|
|
||||||
|
root.appendChild(element);
|
||||||
|
this._refCards.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
set hass(hass) {
|
||||||
|
if (this._refCards) {
|
||||||
|
this._refCards.forEach((card) => {
|
||||||
|
card.hass = hass;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this._refCards.forEach((element) => {
|
||||||
|
let fn = () => {
|
||||||
|
this._card(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
if(element.updateComplete) {
|
||||||
|
element.updateComplete.then(fn);
|
||||||
|
} else {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_card(element) {
|
||||||
|
if (element.shadowRoot) {
|
||||||
|
if (!element.shadowRoot.querySelector('ha-card')) {
|
||||||
|
let searchEles = element.shadowRoot.getElementById("root");
|
||||||
|
if (!searchEles) {
|
||||||
|
searchEles = element.shadowRoot.getElementById("card");
|
||||||
|
}
|
||||||
|
if (!searchEles) return;
|
||||||
|
searchEles = searchEles.childNodes;
|
||||||
|
for (let i = 0; i < searchEles.length; i++) {
|
||||||
|
if(searchEles[i].style !== undefined){
|
||||||
|
searchEles[i].style.margin = "0px";
|
||||||
|
}
|
||||||
|
this._card(searchEles[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
element.shadowRoot.querySelector('ha-card').style.boxShadow = 'none';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (typeof element.querySelector === 'function' && element.querySelector('ha-card')) {
|
||||||
|
element.querySelector('ha-card').style.boxShadow = 'none';
|
||||||
|
}
|
||||||
|
let searchEles = element.childNodes;
|
||||||
|
for (let i = 0; i < searchEles.length; i++) {
|
||||||
|
if (searchEles[i] && searchEles[i].style) {
|
||||||
|
searchEles[i].style.margin = "0px";
|
||||||
|
}
|
||||||
|
this._card(searchEles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCardSize() {
|
||||||
|
let totalSize = 0;
|
||||||
|
this._refCards.forEach((element) => {
|
||||||
|
totalSize += typeof element.getCardSize === 'function' ? element.getCardSize() : 1;
|
||||||
|
});
|
||||||
|
return totalSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('vertical-stack-in-card', VerticalStackInCard);
|
||||||
Binary file not shown.
BIN
www/lovelace/home/xiaomi_vacuum_02.jpg
Normal file
BIN
www/lovelace/home/xiaomi_vacuum_02.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
Submodule www/plugins/lovelace-auto-entities deleted from be13234a43
Submodule www/plugins/lovelace-card-modder deleted from f3d96f73f1
Submodule www/plugins/lovelace-card-tools deleted from deb8e38741
Submodule www/plugins/lovelace-fold-entity-row deleted from 6d11d3cc65
Submodule www/plugins/lovelace-layout-card deleted from e0ec2f21ad
Reference in New Issue
Block a user