Linting...

This commit is contained in:
root 2020-04-06 19:12:00 +00:00
parent 60792cd382
commit e61d4187f2
10 changed files with 329 additions and 381 deletions

View File

@ -31,6 +31,7 @@
"editor.formatOnPaste": false,
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true
"files.trimTrailingWhitespace": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@ -2,9 +2,7 @@ import typescript from 'rollup-plugin-typescript2';
import commonjs from '@rollup/plugin-commonjs';
import nodeResolve from '@rollup/plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import {
terser
} from 'rollup-plugin-terser';
import { terser } from 'rollup-plugin-terser';
import serve from 'rollup-plugin-serve';
import json from '@rollup/plugin-json';
@ -21,25 +19,30 @@ const serveopts = {
};
const plugins = [
nodeResolve({}),
commonjs(),
typescript(),
json(),
babel({
exclude: 'node_modules/**',
}),
dev && serve(serveopts),
!dev && terser({
nodeResolve({}),
commonjs(),
typescript(),
json(),
babel({
exclude: 'node_modules/**',
}),
dev && serve(serveopts),
!dev &&
terser({
mangle: {
safari10: true,
}), ];
export default [{
input: 'src/button-card.ts',
output: {
dir: './dist',
format: 'es',
sourcemap: dev ? true : false,
},
plugins: [...plugins],
}, ];
}),
];
export default [
{
input: 'src/button-card.ts',
output: {
dir: './dist',
format: 'es',
sourcemap: dev ? true : false,
},
plugins: [...plugins],
},
];

View File

@ -4,9 +4,7 @@ import { directive, PropertyPart } from 'lit-html';
import { Ripple } from '@material/mwc-ripple';
import { myFireEvent } from './my-fire-event';
const isTouch = 'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
const isTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
interface ActionHandler extends HTMLElement {
holdTime: number;
@ -25,7 +23,7 @@ declare global {
interface ActionHandlerOptions {
hasHold?: boolean;
hasDoubleClick?: boolean;
repeat?: Number;
repeat?: number;
}
interface ActionHandlerDetail {
@ -45,14 +43,14 @@ class ActionHandler extends HTMLElement implements ActionHandler {
private repeatTimeout: NodeJS.Timeout | undefined;
private isRepeating: boolean = false;
private isRepeating = false;
constructor() {
super();
this.ripple = document.createElement('mwc-ripple');
}
public connectedCallback() {
public connectedCallback(): void {
Object.assign(this.style, {
position: 'absolute',
width: isTouch ? '100px' : '50px',
@ -64,15 +62,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
this.appendChild(this.ripple);
this.ripple.primary = true;
[
'touchcancel',
'mouseout',
'mouseup',
'touchmove',
'mousewheel',
'wheel',
'scroll',
].forEach((ev) => {
['touchcancel', 'mouseout', 'mouseup', 'touchmove', 'mousewheel', 'wheel', 'scroll'].forEach(ev => {
document.addEventListener(
ev,
() => {
@ -80,12 +70,12 @@ class ActionHandler extends HTMLElement implements ActionHandler {
this.stopAnimation();
this.timer = undefined;
},
{ passive: true }
{ passive: true },
);
});
}
public bind(element: ActionHandlerElement, options) {
public bind(element: ActionHandlerElement, options): void {
if (element.actionHandler) {
return;
}
@ -104,7 +94,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
return false;
});
const start = (ev: Event) => {
const start = (ev: Event): void => {
this.held = false;
let x;
let y;
@ -123,25 +113,23 @@ class ActionHandler extends HTMLElement implements ActionHandler {
this.isRepeating = true;
this.repeatTimeout = setInterval(() => {
myFireEvent(element, 'action', { action: 'hold' });
}, options.repeat)
}, options.repeat);
}
}, this.holdTime);
};
const handleEnter = (ev: KeyboardEvent) => {
const handleEnter = (ev: KeyboardEvent): void => {
if (ev.keyCode !== 13) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
end(ev);
};
const end = (ev: Event) => {
const end = (ev: Event): void => {
// Prevent mouse event if touch event
ev.preventDefault();
if (
['touchend', 'touchcancel'].includes(ev.type) &&
this.timer === undefined
) {
if (['touchend', 'touchcancel'].includes(ev.type) && this.timer === undefined) {
if (this.isRepeating && this.repeatTimeout) {
clearInterval(this.repeatTimeout);
this.isRepeating = false;
@ -160,10 +148,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
myFireEvent(element, 'action', { action: 'hold' });
}
} else if (options.hasDoubleClick) {
if (
(ev.type === 'click' && (ev as MouseEvent).detail < 2) ||
!this.dblClickTimeout
) {
if ((ev.type === 'click' && (ev as MouseEvent).detail < 2) || !this.dblClickTimeout) {
this.dblClickTimeout = window.setTimeout(() => {
this.dblClickTimeout = undefined;
myFireEvent(element, 'action', { action: 'tap' });
@ -188,7 +173,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
element.addEventListener('keyup', handleEnter);
}
private startAnimation(x: number, y: number) {
private startAnimation(x: number, y: number): void {
Object.assign(this.style, {
left: `${x}px`,
top: `${y}px`,
@ -199,7 +184,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
this.ripple.unbounded = true;
}
private stopAnimation() {
private stopAnimation(): void {
this.ripple.active = false;
this.ripple.disabled = true;
this.style.display = 'none';
@ -220,10 +205,7 @@ const getActionHandler = (): ActionHandler => {
return actionhandler as ActionHandler;
};
export const actionHandlerBind = (
element: ActionHandlerElement,
options: ActionHandlerOptions,
) => {
export const actionHandlerBind = (element: ActionHandlerElement, options: ActionHandlerOptions): void => {
const actionhandler: ActionHandler = getActionHandler();
if (!actionhandler) {
return;
@ -231,8 +213,6 @@ export const actionHandlerBind = (
actionhandler.bind(element, options);
};
export const actionHandler = directive(
(options: ActionHandlerOptions = {}) => (part: PropertyPart) => {
actionHandlerBind(part.committer.element as ActionHandlerElement, options);
}
);
export const actionHandler = directive((options: ActionHandlerOptions = {}) => (part: PropertyPart): void => {
actionHandlerBind(part.committer.element as ActionHandlerElement, options);
});

View File

@ -1,18 +1,10 @@
import {
LitElement,
html,
customElement,
property,
TemplateResult,
CSSResult,
PropertyValues,
} from 'lit-element';
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { LitElement, html, customElement, property, TemplateResult, CSSResult, PropertyValues } from 'lit-element';
import { styleMap, StyleInfo } from 'lit-html/directives/style-map';
import { unsafeHTML } from 'lit-html/directives/unsafe-html';
import { classMap, ClassInfo } from 'lit-html/directives/class-map';
import {
HassEntity,
} from 'home-assistant-js-websocket';
import { HassEntity } from 'home-assistant-js-websocket';
import {
stateIcon,
HomeAssistant,
@ -25,13 +17,7 @@ import {
fireEvent,
DOMAINS_TOGGLE,
} from 'custom-card-helpers';
import { BUTTON_CARD_VERSION } from './version-const';
import {
ButtonCardConfig,
StateConfig,
ExemptionUserConfig,
ExemptionUsernameConfig,
} from './types';
import { ButtonCardConfig, StateConfig, ExemptionUserConfig, ExemptionUsernameConfig } from './types';
import { actionHandler } from './action-handler';
import {
computeDomain,
@ -47,9 +33,10 @@ import {
} from './helpers';
import { styles } from './styles';
import myComputeStateDisplay from './compute_state_display';
import * as pjson from '../package.json';
let helpers = (window as any).cardHelpers;
const helperPromise = new Promise(async (resolve) => {
const helperPromise = new Promise(async resolve => {
if (helpers) resolve();
if ((window as any).loadCardHelpers) {
helpers = await (window as any).loadCardHelpers();
@ -60,7 +47,7 @@ const helperPromise = new Promise(async (resolve) => {
/* eslint no-console: 0 */
console.info(
`%c BUTTON-CARD \n%c Version ${BUTTON_CARD_VERSION} `,
`%c BUTTON-CARD \n%c Version ${pjson.version} `,
'color: orange; font-weight: bold; background: black',
'color: white; font-weight: bold; background: dimgray',
);
@ -88,19 +75,14 @@ class ButtonCard extends LitElement {
public connectedCallback(): void {
super.connectedCallback();
if (
this.config
&& this.config.entity
&& computeDomain(this.config.entity) === 'timer'
) {
if (this.config && this.config.entity && computeDomain(this.config.entity) === 'timer') {
const stateObj = this.hass!.states[this.config.entity];
this._startInterval(stateObj);
}
}
private _createCard(config: any): any {
if (helpers)
return helpers.createCardElement(config);
if (helpers) return helpers.createCardElement(config);
else {
const element = createThing(config);
helperPromise.then(() => {
@ -126,26 +108,22 @@ class ButtonCard extends LitElement {
}
protected shouldUpdate(changedProps: PropertyValues): boolean {
const forceUpdate = this._hasTemplate
|| changedProps.has('_timeRemaining')
? true : false;
const forceUpdate = this._hasTemplate || changedProps.has('_timeRemaining') ? true : false;
return forceUpdate || myHasConfigOrEntityChanged(this, changedProps);
}
protected updated(changedProps: PropertyValues) {
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
if (
this.config
&& this.config.entity
&& computeDomain(this.config.entity) === 'timer'
&& changedProps.has('hass')
this.config &&
this.config.entity &&
computeDomain(this.config.entity) === 'timer' &&
changedProps.has('hass')
) {
const stateObj = this.hass!.states[this.config.entity];
const oldHass = changedProps.get('hass') as this['hass'];
const oldStateObj = oldHass
? oldHass.states[this.config.entity]
: undefined;
const oldStateObj = oldHass ? oldHass.states[this.config.entity] : undefined;
if (oldStateObj !== stateObj) {
this._startInterval(stateObj);
@ -167,10 +145,7 @@ class ButtonCard extends LitElement {
this._calculateRemaining(stateObj);
if (stateObj.state === 'active') {
this._interval = window.setInterval(
() => this._calculateRemaining(stateObj),
1000,
);
this._interval = window.setInterval(() => this._calculateRemaining(stateObj), 1000);
}
}
@ -183,42 +158,37 @@ class ButtonCard extends LitElement {
return undefined;
}
return secondsToDuration(
this._timeRemaining || durationToSeconds(stateObj.attributes.duration),
);
return secondsToDuration(this._timeRemaining || durationToSeconds(stateObj.attributes.duration));
}
private _getMatchingConfigState(state: HassEntity | undefined): StateConfig | undefined {
if (!this.config!.state) {
return undefined;
}
const hasTemplate = this.config!.state.find(
elt => elt.operator === 'template',
);
const hasTemplate = this.config!.state.find(elt => elt.operator === 'template');
if (!state && !hasTemplate) {
return undefined;
}
let def: StateConfig | undefined;
const retval = this.config!.state.find((elt) => {
const retval = this.config!.state.find(elt => {
if (elt.operator) {
switch (elt.operator) {
case '==':
/* eslint eqeqeq: 0 */
return (state && state.state == this._getTemplateOrValue(state, elt.value));
return state && state.state == this._getTemplateOrValue(state, elt.value);
case '<=':
return (state && state.state <= this._getTemplateOrValue(state, elt.value));
return state && state.state <= this._getTemplateOrValue(state, elt.value);
case '<':
return (state && state.state < this._getTemplateOrValue(state, elt.value));
return state && state.state < this._getTemplateOrValue(state, elt.value);
case '>=':
return (state && state.state >= this._getTemplateOrValue(state, elt.value));
return state && state.state >= this._getTemplateOrValue(state, elt.value);
case '>':
return (state && state.state > this._getTemplateOrValue(state, elt.value));
return state && state.state > this._getTemplateOrValue(state, elt.value);
case '!=':
return (state && state.state != this._getTemplateOrValue(state, elt.value));
return state && state.state != this._getTemplateOrValue(state, elt.value);
case 'regex': {
/* eslint no-unneeded-ternary: 0 */
const matches = state
&& state.state.match(this._getTemplateOrValue(state, elt.value)) ? true : false;
const matches = state && state.state.match(this._getTemplateOrValue(state, elt.value)) ? true : false;
return matches;
}
case 'template': {
@ -231,7 +201,7 @@ class ButtonCard extends LitElement {
return false;
}
} else {
return state && (this._getTemplateOrValue(state, elt.value) == state.state);
return state && this._getTemplateOrValue(state, elt.value) == state.state;
}
});
if (!retval && def) {
@ -242,37 +212,33 @@ class ButtonCard extends LitElement {
private _evalTemplate(state: HassEntity | undefined, func: any): any {
/* eslint no-new-func: 0 */
return new Function('states', 'entity', 'user', 'hass', 'variables', 'html',
`'use strict'; ${func}`)
.call(this, this.hass!.states, state, this.hass!.user, this.hass,
this._evaledVariables, html);
return new Function('states', 'entity', 'user', 'hass', 'variables', 'html', `'use strict'; ${func}`).call(
this,
this.hass!.states,
state,
this.hass!.user,
this.hass,
this._evaledVariables,
html,
);
}
private _objectEvalTemplate(
state: HassEntity | undefined,
obj: any | undefined,
) {
private _objectEvalTemplate(state: HassEntity | undefined, obj: any | undefined): any {
const objClone = JSON.parse(JSON.stringify(obj));
return this._getTemplateOrValue(state, objClone);
}
private _getTemplateOrValue(
state: HassEntity | undefined,
value: any | undefined,
): any | undefined {
private _getTemplateOrValue(state: HassEntity | undefined, value: any | undefined): any | undefined {
if (['number', 'boolean'].includes(typeof value)) return value;
if (!value) return value;
if (['object'].includes(typeof value)) {
Object.keys(value).forEach((key) => {
Object.keys(value).forEach(key => {
value[key] = this._getTemplateOrValue(state, value[key]);
});
return value;
}
const trimmed = value.trim();
if (
trimmed.substring(0, 3) === '[[['
&& trimmed.slice(-3) === ']]]'
) {
if (trimmed.substring(0, 3) === '[[[' && trimmed.slice(-3) === ']]]') {
return this._evalTemplate(state, trimmed.slice(3, -3));
} else {
return value;
@ -290,10 +256,7 @@ class ButtonCard extends LitElement {
}
}
private _getColorForLightEntity(
state: HassEntity | undefined,
useTemperature: boolean,
): string {
private _getColorForLightEntity(state: HassEntity | undefined, useTemperature: boolean): string {
let color: string = this.config!.default_color;
if (state) {
if (state.attributes.rgb_color) {
@ -301,10 +264,12 @@ class ButtonCard extends LitElement {
if (state.attributes.brightness) {
color = applyBrightnessToColor(color, (state.attributes.brightness + 245) / 5);
}
} else if (useTemperature
&& state.attributes.color_temp
&& state.attributes.min_mireds
&& state.attributes.max_mireds) {
} else if (
useTemperature &&
state.attributes.color_temp &&
state.attributes.min_mireds &&
state.attributes.max_mireds
) {
color = getLightColorBasedOnTemperature(
state.attributes.color_temp,
state.attributes.min_mireds,
@ -314,9 +279,7 @@ class ButtonCard extends LitElement {
color = applyBrightnessToColor(color, (state.attributes.brightness + 245) / 5);
}
} else if (state.attributes.brightness) {
color = applyBrightnessToColor(
this._getDefaultColorForState(state), (state.attributes.brightness + 245) / 5,
);
color = applyBrightnessToColor(this._getDefaultColorForState(state), (state.attributes.brightness + 245) / 5);
} else {
color = this._getDefaultColorForState(state);
}
@ -324,10 +287,8 @@ class ButtonCard extends LitElement {
return color;
}
private _buildCssColorAttribute(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string {
let colorValue: string = '';
private _buildCssColorAttribute(state: HassEntity | undefined, configState: StateConfig | undefined): string {
let colorValue = '';
let color: undefined | string;
if (configState && configState.color) {
colorValue = configState.color;
@ -348,9 +309,7 @@ class ButtonCard extends LitElement {
return color;
}
private _buildIcon(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string | undefined {
private _buildIcon(state: HassEntity | undefined, configState: StateConfig | undefined): string | undefined {
if (!this.config!.show_icon) {
return undefined;
}
@ -360,18 +319,14 @@ class ButtonCard extends LitElement {
} else if (this.config!.icon) {
icon = this.config!.icon;
} else {
if (!state)
return undefined;
if (!state) return undefined;
icon = stateIcon(state);
}
return this._getTemplateOrValue(state, icon);
}
private _buildEntityPicture(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string | undefined {
if (!this.config!.show_entity_picture
|| !state && !configState && !this.config!.entity_picture) {
private _buildEntityPicture(state: HassEntity | undefined, configState: StateConfig | undefined): string | undefined {
if (!this.config!.show_entity_picture || (!state && !configState && !this.config!.entity_picture)) {
return undefined;
}
let entityPicture: string | undefined;
@ -381,8 +336,7 @@ class ButtonCard extends LitElement {
} else if (this.config!.entity_picture) {
entityPicture = this.config!.entity_picture;
} else if (state) {
entityPicture = state.attributes && state.attributes.entity_picture
? state.attributes.entity_picture : undefined;
entityPicture = state.attributes && state.attributes.entity_picture ? state.attributes.entity_picture : undefined;
}
return this._getTemplateOrValue(state, entityPicture);
}
@ -404,7 +358,7 @@ class ButtonCard extends LitElement {
...configStateStyle,
};
}
Object.keys(style).forEach((key) => {
Object.keys(style).forEach(key => {
style[key] = this._getTemplateOrValue(state, style[key]);
});
return style;
@ -416,35 +370,29 @@ class ButtonCard extends LitElement {
styleType: string,
): StyleInfo {
let style: any = {};
if (this.config!.styles
&& this.config!.styles.custom_fields
&& this.config!.styles.custom_fields[styleType]
) {
if (this.config!.styles && this.config!.styles.custom_fields && this.config!.styles.custom_fields[styleType]) {
style = Object.assign(style, ...this.config!.styles.custom_fields[styleType]);
}
if (configState && configState.styles
&& configState.styles.custom_fields
&& configState.styles.custom_fields[styleType]
if (
configState &&
configState.styles &&
configState.styles.custom_fields &&
configState.styles.custom_fields[styleType]
) {
let configStateStyle: StyleInfo = {};
configStateStyle = Object.assign(
configStateStyle,
...configState.styles.custom_fields[styleType],
);
configStateStyle = Object.assign(configStateStyle, ...configState.styles.custom_fields[styleType]);
style = {
...style,
...configStateStyle,
};
}
Object.keys(style).forEach((key) => {
Object.keys(style).forEach(key => {
style[key] = this._getTemplateOrValue(state, style[key]);
});
return style;
}
private _buildName(
state: HassEntity | undefined, configState: StateConfig | undefined,
): string | undefined {
private _buildName(state: HassEntity | undefined, configState: StateConfig | undefined): string | undefined {
if (this.config!.show_name === false) {
return undefined;
}
@ -455,10 +403,11 @@ class ButtonCard extends LitElement {
} else if (this.config!.name) {
name = this.config!.name;
} else if (state) {
name = state.attributes && state.attributes.friendly_name
? state.attributes.friendly_name : computeEntity(state.entity_id);
name =
state.attributes && state.attributes.friendly_name
? state.attributes.friendly_name
: computeEntity(state.entity_id);
}
return this._getTemplateOrValue(state, name);
}
@ -499,25 +448,21 @@ class ButtonCard extends LitElement {
return units;
}
private _buildLastChanged(
state: HassEntity | undefined,
style: StyleInfo,
): TemplateResult | undefined {
private _buildLastChanged(state: HassEntity | undefined, style: StyleInfo): TemplateResult | undefined {
return this.config!.show_last_changed && state
? html`
<ha-relative-time
id="label"
class="ellipsis"
.hass="${this.hass}"
.datetime="${state.last_changed}"
style=${styleMap(style)}
></ha-relative-time>` : undefined;
<ha-relative-time
id="label"
class="ellipsis"
.hass="${this.hass}"
.datetime="${state.last_changed}"
style=${styleMap(style)}
></ha-relative-time>
`
: undefined;
}
private _buildLabel(
state: HassEntity | undefined,
configState: StateConfig | undefined,
): string | undefined {
private _buildLabel(state: HassEntity | undefined, configState: StateConfig | undefined): string | undefined {
if (!this.config!.show_label) {
return undefined;
}
@ -532,15 +477,12 @@ class ButtonCard extends LitElement {
return this._getTemplateOrValue(state, label);
}
private _buildCustomFields(
state: HassEntity | undefined,
configState: StateConfig | undefined,
): TemplateResult {
private _buildCustomFields(state: HassEntity | undefined, configState: StateConfig | undefined): TemplateResult {
let result = html``;
const fields: any = {};
const cards: any = {};
if (this.config!.custom_fields) {
Object.keys(this.config!.custom_fields).forEach((key) => {
Object.keys(this.config!.custom_fields).forEach(key => {
const value = this.config!.custom_fields![key];
if (!value.card) {
fields[key] = this._getTemplateOrValue(state, value);
@ -550,7 +492,7 @@ class ButtonCard extends LitElement {
});
}
if (configState && configState.custom_fields) {
Object.keys(configState.custom_fields).forEach((key) => {
Object.keys(configState.custom_fields).forEach(key => {
const value = configState!.custom_fields![key];
if (!value!.card) {
fields[key] = this._getTemplateOrValue(state, value);
@ -559,22 +501,21 @@ class ButtonCard extends LitElement {
}
});
}
Object.keys(fields).forEach((key) => {
Object.keys(fields).forEach(key => {
if (fields[key] != undefined) {
const customStyle: StyleInfo = {
...this._buildCustomStyleGeneric(state, configState, key),
'grid-area': key,
};
result = html`${result}
<div id=${key}
class="ellipsis"
style=${styleMap(customStyle)}
>
${fields[key] && (fields[key] as any).type === 'html' ? fields[key] : unsafeHTML(fields[key])}
</div>`;
result = html`
${result}
<div id=${key} class="ellipsis" style=${styleMap(customStyle)}>
${fields[key] && (fields[key] as any).type === 'html' ? fields[key] : unsafeHTML(fields[key])}
</div>
`;
}
});
Object.keys(cards).forEach((key) => {
Object.keys(cards).forEach(key => {
if (cards[key] != undefined) {
const customStyle: StyleInfo = {
...this._buildCustomStyleGeneric(state, configState, key),
@ -582,15 +523,18 @@ class ButtonCard extends LitElement {
};
const thing = this._createCard(cards[key]);
thing.hass = this.hass;
result = html`${result}
<div id=${key}
class="ellipsis"
@click=${this._stopPropagation}
@touchstart=${this._stopPropagation}
style=${styleMap(customStyle)}
>
${thing}
</div>`;
result = html`
${result}
<div
id=${key}
class="ellipsis"
@click=${this._stopPropagation}
@touchstart=${this._stopPropagation}
style=${styleMap(customStyle)}
>
${thing}
</div>
`;
}
});
return result;
@ -601,11 +545,7 @@ class ButtonCard extends LitElement {
const tap_action = this._getTemplateOrValue(state, this.config!.tap_action!.action);
const hold_action = this._getTemplateOrValue(state, this.config!.hold_action!.action);
const double_tap_action = this._getTemplateOrValue(state, this.config!.double_tap_action!.action);
if (
tap_action != 'none'
|| hold_action != 'none'
|| double_tap_action != 'none'
) {
if (tap_action != 'none' || hold_action != 'none' || double_tap_action != 'none') {
clickable = true;
} else {
clickable = false;
@ -617,9 +557,7 @@ class ButtonCard extends LitElement {
return configState && configState.spin ? true : false;
}
private _blankCardColoredHtml(
cardStyle: StyleInfo,
): TemplateResult {
private _blankCardColoredHtml(cardStyle: StyleInfo): TemplateResult {
const blankCardStyle = {
background: 'none',
'box-shadow': 'none',
@ -629,7 +567,7 @@ class ButtonCard extends LitElement {
<ha-card class="disabled" style=${styleMap(blankCardStyle)}>
<div></div>
</ha-card>
`;
`;
}
private _cardHtml(): TemplateResult {
@ -673,45 +611,54 @@ class ButtonCard extends LitElement {
aspectRatio.display = 'inline';
}
this.style.setProperty('--button-card-light-color', this._getColorForLightEntity(this._stateObj, true));
this.style.setProperty('--button-card-light-color-no-temperature', this._getColorForLightEntity(this._stateObj, false));
this.style.setProperty(
'--button-card-light-color-no-temperature',
this._getColorForLightEntity(this._stateObj, false),
);
lockStyle = { ...lockStyle, ...lockStyleFromConfig };
const extraStyles = document.createElement('style');
extraStyles.innerHTML = this.config!.extra_styles ? this._getTemplateOrValue(this._stateObj, this.config!.extra_styles) : '';
extraStyles.innerHTML = this.config!.extra_styles
? this._getTemplateOrValue(this._stateObj, this.config!.extra_styles)
: '';
return html`
${extraStyles}
<div id="aspect-ratio" style=${styleMap(aspectRatio)}>
<ha-card
id="card"
class=${classMap(classList)}
style=${styleMap(cardStyle)}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasDoubleClick: this.config!.double_tap_action!.action !== 'none',
hasHold: this.config!.hold_action!.action !== 'none',
repeat: this.config!.hold_action!.repeat,
})}
.config="${this.config}"
>
${this._buttonContent(this._stateObj, configState, buttonColor)}
${this.config!.lock
&& this._getTemplateOrValue(this._stateObj, this.config!.lock.enabled) ? '' : html`<mwc-ripple id="ripple"></mwc-ripple>`}
</ha-card>
</div>
${this._getLock(lockStyle)}
<div id="aspect-ratio" style=${styleMap(aspectRatio)}>
<ha-card
id="card"
class=${classMap(classList)}
style=${styleMap(cardStyle)}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasDoubleClick: this.config!.double_tap_action!.action !== 'none',
hasHold: this.config!.hold_action!.action !== 'none',
repeat: this.config!.hold_action!.repeat,
})}
.config="${this.config}"
>
${this._buttonContent(this._stateObj, configState, buttonColor)}
${this.config!.lock && this._getTemplateOrValue(this._stateObj, this.config!.lock.enabled)
? ''
: html`
<mwc-ripple id="ripple"></mwc-ripple>
`}
</ha-card>
</div>
${this._getLock(lockStyle)}
`;
}
private _getLock(lockStyle: StyleInfo): TemplateResult {
if (this.config!.lock
&& this._getTemplateOrValue(this._stateObj, this.config!.lock.enabled)) {
if (this.config!.lock && this._getTemplateOrValue(this._stateObj, this.config!.lock.enabled)) {
return html`
<div id="overlay" style=${styleMap(lockStyle)}
<div
id="overlay"
style=${styleMap(lockStyle)}
@action=${this._handleUnlockType}
.actionHandler=${actionHandler({
hasDoubleClick: this.config!.lock!.unlock === 'double_tap',
hasHold: this.config!.lock!.unlock === 'hold',
})}
hasDoubleClick: this.config!.lock!.unlock === 'double_tap',
hasHold: this.config!.lock!.unlock === 'hold',
})}
.config="${this.config}"
>
<ha-icon id="lock" icon="mdi:lock-outline"></ha-icon>
@ -727,20 +674,19 @@ class ButtonCard extends LitElement {
color: string,
): TemplateResult {
const name = this._buildName(state, configState);
const stateDisplay = this.config!.show_state && this.config!.state_display
? this._getTemplateOrValue(state, this.config!.state_display)
: undefined;
const stateDisplay =
this.config!.show_state && this.config!.state_display
? this._getTemplateOrValue(state, this.config!.state_display)
: undefined;
const stateString = stateDisplay ? stateDisplay : this._buildStateString(state);
const nameStateString = buildNameStateConcat(name, stateString);
switch (this.config!.layout) {
case 'icon_name_state':
case 'name_state':
return this._gridHtml(state, configState, this.config!.layout, color,
nameStateString, undefined);
return this._gridHtml(state, configState, this.config!.layout, color, nameStateString, undefined);
default:
return this._gridHtml(state, configState, this.config!.layout, color,
name, stateString);
return this._gridHtml(state, configState, this.config!.layout, color, name, stateString);
}
}
@ -768,11 +714,28 @@ class ButtonCard extends LitElement {
return html`
<div id="container" class=${itemClass.join(' ')} style=${styleMap(gridStyleFromConfig)}>
${iconTemplate ? iconTemplate : ''}
${name ? html`<div id="name" class="ellipsis" style=${styleMap(nameStyleFromConfig)}>${(name as any).type === 'html' ? name : unsafeHTML(name)}</div>` : ''}
${stateString ? html`<div id="state" class="ellipsis" style=${styleMap(stateStyleFromConfig)}>${(stateString as any).type === 'html' ? stateString : unsafeHTML(stateString)}</div>` : ''}
${label && !lastChangedTemplate ? html`<div id="label" class="ellipsis" style=${styleMap(labelStyleFromConfig)}>${(label as any).type === 'html' ? label : unsafeHTML(label)}</div>` : ''}
${lastChangedTemplate ? lastChangedTemplate : ''}
${this._buildCustomFields(state, configState)}
${name
? html`
<div id="name" class="ellipsis" style=${styleMap(nameStyleFromConfig)}>
${(name as any).type === 'html' ? name : unsafeHTML(name)}
</div>
`
: ''}
${stateString
? html`
<div id="state" class="ellipsis" style=${styleMap(stateStyleFromConfig)}>
${(stateString as any).type === 'html' ? stateString : unsafeHTML(stateString)}
</div>
`
: ''}
${label && !lastChangedTemplate
? html`
<div id="label" class="ellipsis" style=${styleMap(labelStyleFromConfig)}>
${(label as any).type === 'html' ? label : unsafeHTML(label)}
</div>
`
: ''}
${lastChangedTemplate ? lastChangedTemplate : ''} ${this._buildCustomFields(state, configState)}
</div>
`;
}
@ -804,11 +767,27 @@ class ButtonCard extends LitElement {
if (icon || entityPicture) {
return html`
<div id="img-cell" style=${styleMap(imgCellStyleFromConfig)}>
${icon && !entityPicture && !liveStream ? html`<ha-icon style=${styleMap(haIconStyle)}
.icon="${icon}" id="icon" ?rotating=${this._rotate(configState)}></ha-icon>` : ''}
${icon && !entityPicture && !liveStream
? html`
<ha-icon
style=${styleMap(haIconStyle)}
.icon="${icon}"
id="icon"
?rotating=${this._rotate(configState)}
></ha-icon>
`
: ''}
${liveStream ? liveStream : ''}
${entityPicture && !liveStream ? html`<img src="${entityPicture}" style=${styleMap(entityPictureStyle)}
id="icon" ?rotating=${this._rotate(configState)} />` : ''}
${entityPicture && !liveStream
? html`
<img
src="${entityPicture}"
style=${styleMap(entityPictureStyle)}
id="icon"
?rotating=${this._rotate(configState)}
/>
`
: ''}
</div>
`;
} else {
@ -823,7 +802,7 @@ class ButtonCard extends LitElement {
.hass=${this.hass}
.cameraImage=${this.config!.entity}
.entity=${this.config!.entity}
cameraView='live'
cameraView="live"
style=${styleMap(style)}
></hui-image>
`;
@ -908,7 +887,7 @@ class ButtonCard extends LitElement {
if (!configEval) {
return configEval;
}
Object.keys(configEval).forEach((key) => {
Object.keys(configEval).forEach(key => {
if (typeof configEval[key] === 'object') {
configEval[key] = __evalObject(configEval[key]);
} else {
@ -924,7 +903,7 @@ class ButtonCard extends LitElement {
return configDuplicate;
}
private _handleAction(ev: any) {
private _handleAction(ev: any): void {
if (ev.detail && ev.detail.action) {
switch (ev.detail.action) {
case 'tap':
@ -960,7 +939,7 @@ class ButtonCard extends LitElement {
handleClick(this, this.hass!, this._evalActions(config, 'double_tap_action'), false, true);
}
private _handleUnlockType(ev) {
private _handleUnlockType(ev): void {
const config = ev.target.config as ButtonCardConfig;
if (!config) return;
if (config.lock.unlock === ev.detail.action) {
@ -974,9 +953,11 @@ class ButtonCard extends LitElement {
if (this.config!.lock!.exemptions) {
if (!this.hass!.user.name || !this.hass!.user.id) return;
let matched = false;
this.config!.lock!.exemptions.forEach((e) => {
if (!matched && (e as ExemptionUserConfig).user === this.hass!.user.id
|| (e as ExemptionUsernameConfig).username === this.hass!.user.name) {
this.config!.lock!.exemptions.forEach(e => {
if (
(!matched && (e as ExemptionUserConfig).user === this.hass!.user.id) ||
(e as ExemptionUsernameConfig).username === this.hass!.user.name
) {
matched = true;
}
});
@ -1014,7 +995,7 @@ class ButtonCard extends LitElement {
}, this.config!.lock!.duration! * 1000);
}
private _stopPropagation(ev) {
private _stopPropagation(ev): void {
ev.stopPropagation();
}
}

View File

@ -2,36 +2,24 @@ import { HassEntity } from 'home-assistant-js-websocket';
import { LocalizeFunc } from 'custom-card-helpers';
import { computeDomain } from './helpers';
export default (
localize: LocalizeFunc,
stateObj: HassEntity,
): string | undefined => {
export default (localize: LocalizeFunc, stateObj: HassEntity): string | undefined => {
let display: string | undefined;
const domain = computeDomain(stateObj.entity_id);
if (domain === 'binary_sensor') {
// Try device class translation, then default binary sensor translation
if (stateObj.attributes.device_class) {
display = localize(
`state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}`,
);
display = localize(`state.${domain}.${stateObj.attributes.device_class}.${stateObj.state}`);
}
if (!display) {
display = localize(`state.${domain}.default.${stateObj.state}`);
}
} else if (
stateObj.attributes.unit_of_measurement
&& !['unknown', 'unavailable'].includes(stateObj.state)
) {
} else if (stateObj.attributes.unit_of_measurement && !['unknown', 'unavailable'].includes(stateObj.state)) {
display = stateObj.state;
} else if (domain === 'zwave') {
if (['initializing', 'dead'].includes(stateObj.state)) {
display = localize(
`state.zwave.query_stage.${stateObj.state}`,
'query_stage',
stateObj.attributes.query_stage,
);
display = localize(`state.zwave.query_stage.${stateObj.state}`, 'query_stage', stateObj.attributes.query_stage);
} else {
display = localize(`state.zwave.default.${stateObj.state}`);
}
@ -41,9 +29,10 @@ export default (
// Fall back to default, component backend translation, or raw state if nothing else matches.
if (!display) {
display = localize(`state.default.${stateObj.state}`)
|| localize(`component.${domain}.state.${stateObj.state}`)
|| stateObj.state;
display =
localize(`state.default.${stateObj.state}`) ||
localize(`component.${domain}.state.${stateObj.state}`) ||
stateObj.state;
}
return display;

View File

@ -13,8 +13,10 @@ export function computeEntity(entityId: string): string {
export function getColorFromVariable(color: string): string {
if (color.substring(0, 3) === 'var') {
return window.getComputedStyle(document.documentElement)
.getPropertyValue(color.substring(4).slice(0, -1)).trim();
return window
.getComputedStyle(document.documentElement)
.getPropertyValue(color.substring(4).slice(0, -1))
.trim();
}
return color;
}
@ -24,29 +26,27 @@ export function getFontColorBasedOnBackgroundColor(backgroundColor: string): str
if (colorObj.isValid && colorObj.getLuminance() > 0.5) {
return 'rgb(62, 62, 62)'; // bright colors - black font
} else {
return 'rgb(234, 234, 234)';// dark colors - white font
return 'rgb(234, 234, 234)'; // dark colors - white font
}
}
export function getLightColorBasedOnTemperature(
current: number,
min: number,
max: number,
): string {
export function getLightColorBasedOnTemperature(current: number, min: number, max: number): string {
const high = new TinyColor('rgb(255, 160, 0)'); // orange-ish
const low = new TinyColor('rgb(166, 209, 255)'); // blue-ish
const middle = new TinyColor('white');
const mixAmount = (current - min) / (max - min) * 100;
const mixAmount = ((current - min) / (max - min)) * 100;
if (mixAmount < 50) {
return tinycolor(low).mix(middle, mixAmount * 2).toRgbString();
return tinycolor(low)
.mix(middle, mixAmount * 2)
.toRgbString();
} else {
return tinycolor(middle).mix(high, (mixAmount - 50) * 2).toRgbString();
return tinycolor(middle)
.mix(high, (mixAmount - 50) * 2)
.toRgbString();
}
}
export function buildNameStateConcat(
name: string | undefined, stateString: string | undefined,
): string | undefined {
export function buildNameStateConcat(name: string | undefined, stateString: string | undefined): string | undefined {
if (!name && !stateString) {
return undefined;
}
@ -63,9 +63,7 @@ export function buildNameStateConcat(
return nameStateString;
}
export function applyBrightnessToColor(
color: string, brightness: number,
): string {
export function applyBrightnessToColor(color: string, brightness: number): string {
const colorObj = new TinyColor(getColorFromVariable(color));
if (colorObj.isValid) {
const validColor = colorObj.mix('black', 100 - brightness).toString();
@ -75,10 +73,7 @@ export function applyBrightnessToColor(
}
// Check if config or Entity changed
export function myHasConfigOrEntityChanged(
element: any,
changedProps: PropertyValues,
): boolean {
export function myHasConfigOrEntityChanged(element: any, changedProps: PropertyValues): boolean {
if (changedProps.has('config')) {
return true;
}
@ -87,10 +82,8 @@ export function myHasConfigOrEntityChanged(
const oldHass = changedProps.get('hass') as HomeAssistant | undefined;
if (oldHass) {
return (
oldHass.states[element.config!.entity]
!== element.hass!.states[element.config!.entity]
|| oldHass.states[element.config!.entity].attributes
!== element.hass!.states[element.config!.entity].attributes
oldHass.states[element.config!.entity] !== element.hass!.states[element.config!.entity] ||
oldHass.states[element.config!.entity].attributes !== element.hass!.states[element.config!.entity].attributes
);
}
return true;
@ -100,17 +93,17 @@ export function myHasConfigOrEntityChanged(
}
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation and filtering.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation and filtering.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
export function mergeDeep(...objects: any): any {
const isObject = (obj: any) => obj && typeof obj === 'object';
return objects.reduce((prev: any, obj: any) => {
Object.keys(obj).forEach((key) => {
Object.keys(obj).forEach(key => {
const pVal = prev[key];
const oVal = obj[key];
@ -134,10 +127,10 @@ export function mergeStatesById(
): StateConfig[] {
let resultStateConfigs: StateConfig[] = [];
if (intoStates) {
intoStates.forEach((intoState) => {
intoStates.forEach(intoState => {
let localState = intoState;
if (fromStates) {
fromStates.forEach((fromState) => {
fromStates.forEach(fromState => {
if (fromState.id && intoState.id && fromState.id == intoState.id)
localState = mergeDeep(localState, fromState);
});
@ -148,17 +141,13 @@ export function mergeStatesById(
if (fromStates) {
/* eslint eqeqeq: 0 no-confusing-arrow: 0 */
resultStateConfigs = resultStateConfigs.concat(
fromStates.filter(
x => !intoStates
? true
: !intoStates.find(y => y.id && x.id ? y.id == x.id : false),
),
fromStates.filter(x => (!intoStates ? true : !intoStates.find(y => (y.id && x.id ? y.id == x.id : false)))),
);
}
return resultStateConfigs;
}
export function getLovelaceCast() {
export function getLovelaceCast(): any {
let root: any = document.querySelector('hc-main');
root = root && root.shadowRoot;
root = root && root.querySelector('hc-lovelace');

View File

@ -29,7 +29,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
declare global {
// tslint:disable-next-line
// eslint-disable-next-line
interface HASSDomEvents { }
}
@ -62,10 +62,9 @@ export const myFireEvent = <HassEvent extends ValidHassDomEvent>(
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
) => {
},
): any => {
options = options || {};
// @ts-ignore
detail = detail === null || detail === undefined ? {} : detail;
const event = new Event(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,

View File

@ -73,9 +73,15 @@ export const styles = css`
transition: visibility 0s 1s, opacity 1s linear;
}
@keyframes blink {
0%{opacity:0;}
50%{opacity:1;}
100%{opacity:0;}
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@-webkit-keyframes rotating /* Safari and Chrome */ {
from {
@ -170,13 +176,13 @@ export const styles = css`
}
#container.vertical {
grid-template-areas: "i" "n" "s" "l";
grid-template-areas: 'i' 'n' 's' 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content min-content min-content;
}
/* Vertical No Icon */
#container.vertical.no-icon {
grid-template-areas: "n" "s" "l";
grid-template-areas: 'n' 's' 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content 1fr;
}
@ -192,7 +198,7 @@ export const styles = css`
/* Vertical No Icon No Name */
#container.vertical.no-icon.no-name {
grid-template-areas: "s" "l";
grid-template-areas: 's' 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
@ -205,7 +211,7 @@ export const styles = css`
/* Vertical No Icon No State */
#container.vertical.no-icon.no-state {
grid-template-areas: "n" "l";
grid-template-areas: 'n' 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
@ -218,7 +224,7 @@ export const styles = css`
/* Vertical No Icon No Label */
#container.vertical.no-icon.no-label {
grid-template-areas: "n" "s";
grid-template-areas: 'n' 's';
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
@ -231,7 +237,7 @@ export const styles = css`
/* Vertical No Icon No Label No Name */
#container.vertical.no-icon.no-label.no-name {
grid-template-areas: "s";
grid-template-areas: 's';
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
@ -240,7 +246,7 @@ export const styles = css`
}
/* Vertical No Icon No Label No State */
#container.vertical.no-icon.no-label.no-state {
grid-template-areas: "n";
grid-template-areas: 'n';
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
@ -250,7 +256,7 @@ export const styles = css`
/* Vertical No Icon No Name No State */
#container.vertical.no-icon.no-name.no-state {
grid-template-areas: "l";
grid-template-areas: 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
@ -259,52 +265,52 @@ export const styles = css`
}
#container.icon_name_state {
grid-template-areas: "i n" "l l";
grid-template-areas: 'i n' 'l l';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content;
}
#container.icon_name {
grid-template-areas: "i n" "s s" "l l";
grid-template-areas: 'i n' 's s' 'l l';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content min-content;
}
#container.icon_state {
grid-template-areas: "i s" "n n" "l l";
grid-template-areas: 'i s' 'n n' 'l l';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content min-content;
}
#container.name_state {
grid-template-areas: "i" "n" "l";
grid-template-areas: 'i' 'n' 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr min-content min-content;
}
#container.name_state.no-icon {
grid-template-areas: "n" "l";
grid-template-areas: 'n' 'l';
grid-template-columns: 1fr;
grid-template-rows: 1fr 1fr;
}
#container.name_state.no-icon #name {
align-self: end
align-self: end;
}
#container.name_state.no-icon #label {
align-self: start
align-self: start;
}
#container.name_state.no-icon.no-label {
grid-template-areas: "n";
grid-template-areas: 'n';
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
#container.name_state.no-icon.no-label #name {
align-self: center
align-self: center;
}
/* icon_name_state2nd default */
#container.icon_name_state2nd {
grid-template-areas: "i n" "i s" "i l";
grid-template-areas: 'i n' 'i s' 'i l';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content 1fr;
}
@ -320,7 +326,7 @@ export const styles = css`
/* icon_name_state2nd No Label */
#container.icon_name_state2nd.no-label {
grid-template-areas: "i n" "i s";
grid-template-areas: 'i n' 'i s';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr 1fr;
}
@ -333,7 +339,7 @@ export const styles = css`
/* icon_state_name2nd Default */
#container.icon_state_name2nd {
grid-template-areas: "i s" "i n" "i l";
grid-template-areas: 'i s' 'i n' 'i l';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content 1fr;
}
@ -349,7 +355,7 @@ export const styles = css`
/* icon_state_name2nd No Label */
#container.icon_state_name2nd.no-label {
grid-template-areas: "i s" "i n";
grid-template-areas: 'i s' 'i n';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr 1fr;
}
@ -361,27 +367,27 @@ export const styles = css`
}
#container.icon_label {
grid-template-areas: "i l" "n n" "s s";
grid-template-areas: 'i l' 'n n' 's s';
grid-template-columns: 40% 1fr;
grid-template-rows: 1fr min-content min-content;
}
[style*="--aspect-ratio"] > :first-child {
[style*='--aspect-ratio'] > :first-child {
width: 100%;
}
[style*="--aspect-ratio"] > img {
[style*='--aspect-ratio'] > img {
height: auto;
}
@supports (--custom:property) {
[style*="--aspect-ratio"] {
@supports (--custom: property) {
[style*='--aspect-ratio'] {
position: relative;
}
[style*="--aspect-ratio"]::before {
content: "";
[style*='--aspect-ratio']::before {
content: '';
display: block;
padding-bottom: calc(100% / (var(--aspect-ratio)));
}
[style*="--aspect-ratio"] > :first-child {
[style*='--aspect-ratio'] > :first-child {
position: absolute;
top: 0;
left: 0;

View File

@ -39,7 +39,8 @@ export interface ButtonCardConfig {
extra_styles?: string;
}
export type Layout = 'vertical'
export type Layout =
| 'vertical'
| 'icon_name_state'
| 'name_state'
| 'icon_name'

View File

@ -1 +0,0 @@
export const BUTTON_CARD_VERSION = '3.2.3';