lovelace-timer-bar-card
Version:
Progress bar display for Home Assistant timers
460 lines (421 loc) • 14 kB
text/typescript
/** Styles and functions from the Mushroom Cards Project by Paul Bottein
https://github.com/piitaya/lovelace-mushroom/
Licensed under Apache 2.0
*/
import { computeDomain, HomeAssistant } from "custom-card-helpers";
import { css, html, TemplateResult } from "lit";
import { HassEntity } from "src/types";
export const defaultColorCss = css`
--default-red: 244, 67, 54;
--default-pink: 233, 30, 99;
--default-purple: 156, 39, 176;
--default-deep-purple: 103, 58, 183;
--default-indigo: 63, 81, 181;
--default-blue: 33, 150, 243;
--default-light-blue: 3, 169, 244;
--default-cyan: 0, 188, 212;
--default-teal: 0, 150, 136;
--default-green: 76, 175, 80;
--default-light-green: 139, 195, 74;
--default-lime: 205, 220, 57;
--default-yellow: 255, 235, 59;
--default-amber: 255, 193, 7;
--default-orange: 255, 152, 0;
--default-deep-orange: 255, 87, 34;
--default-brown: 121, 85, 72;
--default-grey: 158, 158, 158;
--default-blue-grey: 96, 125, 139;
--default-black: 0, 0, 0;
--default-white: 255, 255, 255;
--default-disabled: 189, 189, 189;
`;
export const defaultDarkColorCss = css`
--default-disabled: 111, 111, 111;
`;
export const themeVariables = css`
--spacing: var(--mush-spacing, 10px);
/* Title */
--title-padding: var(--mush-title-padding, 24px 12px 8px);
--title-spacing: var(--mush-title-spacing, 8px);
--title-font-size: var(--mush-title-font-size, 24px);
--title-font-weight: var(--mush-title-font-weight, normal);
--title-line-height: var(--mush-title-line-height, 32px);
--title-color: var(--mush-title-color, var(--primary-text-color));
--title-letter-spacing: var(--mush-title-letter-spacing, -0.288px);
--subtitle-font-size: var(--mush-subtitle-font-size, 16px);
--subtitle-font-weight: var(--mush-subtitle-font-weight, normal);
--subtitle-line-height: var(--mush-subtitle-line-height, 24px);
--subtitle-color: var(--mush-subtitle-color, var(--secondary-text-color));
--subtitle-letter-spacing: var(--mush-subtitle-letter-spacing, 0px);
/* Card */
--card-primary-font-size: var(--mush-card-primary-font-size, 14px);
--card-secondary-font-size: var(--mush-card-secondary-font-size, 12px);
--card-primary-font-weight: var(--mush-card-primary-font-weight, 500);
--card-secondary-font-weight: var(--mush-card-secondary-font-weight, 400);
--card-primary-line-height: var(--mush-card-primary-line-height, 20px);
--card-secondary-line-height: var(--mush-card-secondary-line-height, 16px);
--card-primary-color: var(
--mush-card-primary-color,
var(--primary-text-color)
);
--card-secondary-color: var(
--mush-card-secondary-color,
var(--primary-text-color)
);
--card-primary-letter-spacing: var(--mush-card-primary-letter-spacing, 0.1px);
--card-secondary-letter-spacing: var(
--mush-card-secondary-letter-spacing,
0.4px
);
/* Chips */
--chip-spacing: var(--mush-chip-spacing, 8px);
--chip-padding: var(--mush-chip-padding, 0 0.25em);
--chip-height: var(--mush-chip-height, 36px);
--chip-border-radius: var(--mush-chip-border-radius, 19px);
--chip-border-width: var(
--mush-chip-border-width,
var(--ha-card-border-width, 1px)
);
--chip-border-color: var(
--mush-chip-border-color,
var(--ha-card-border-color, var(--divider-color))
);
--chip-box-shadow: var(
--mush-chip-box-shadow,
var(--ha-card-box-shadow, "none")
);
--chip-font-size: var(--mush-chip-font-size, 0.3em);
--chip-font-weight: var(--mush-chip-font-weight, bold);
--chip-icon-size: var(--mush-chip-icon-size, 0.5em);
--chip-avatar-padding: var(--mush-chip-avatar-padding, 0.1em);
--chip-avatar-border-radius: var(--mush-chip-avatar-border-radius, 50%);
--chip-background: var(
--mush-chip-background,
var(--ha-card-background, var(--card-background-color, white))
);
/* Controls */
--control-border-radius: var(--mush-control-border-radius, 12px);
--control-height: var(--mush-control-height, 42px);
--control-button-ratio: var(--mush-control-button-ratio, 1);
--control-icon-size: var(--mush-control-icon-size, 0.5em);
--control-spacing: var(--mush-control-spacing, 12px);
/* Slider */
--slider-threshold: var(--mush-slider-threshold);
/* Input Number */
--input-number-debounce: var(--mush-input-number-debounce);
/* Layout */
--layout-align: var(--mush-layout-align, center);
/* Badge */
--badge-size: var(--mush-badge-size, 16px);
--badge-icon-size: var(--mush-badge-icon-size, 0.75em);
--badge-border-radius: var(--mush-badge-border-radius, 50%);
/* Icon */
--icon-border-radius: var(--mush-icon-border-radius, 50%);
--icon-size: var(--mush-icon-size, 36px);
--icon-symbol-size: var(--mush-icon-symbol-size, 0.6em);
`;
export const themeColorCss = css`
/* RGB */
/* Standard colors */
--rgb-red: var(--mush-rgb-red, var(--default-red));
--rgb-pink: var(--mush-rgb-pink, var(--default-pink));
--rgb-purple: var(--mush-rgb-purple, var(--default-purple));
--rgb-deep-purple: var(--mush-rgb-deep-purple, var(--default-deep-purple));
--rgb-indigo: var(--mush-rgb-indigo, var(--default-indigo));
--rgb-blue: var(--mush-rgb-blue, var(--default-blue));
--rgb-light-blue: var(--mush-rgb-light-blue, var(--default-light-blue));
--rgb-cyan: var(--mush-rgb-cyan, var(--default-cyan));
--rgb-teal: var(--mush-rgb-teal, var(--default-teal));
--rgb-green: var(--mush-rgb-green, var(--default-green));
--rgb-light-green: var(--mush-rgb-light-green, var(--default-light-green));
--rgb-lime: var(--mush-rgb-lime, var(--default-lime));
--rgb-yellow: var(--mush-rgb-yellow, var(--default-yellow));
--rgb-amber: var(--mush-rgb-amber, var(--default-amber));
--rgb-orange: var(--mush-rgb-orange, var(--default-orange));
--rgb-deep-orange: var(--mush-rgb-deep-orange, var(--default-deep-orange));
--rgb-brown: var(--mush-rgb-brown, var(--default-brown));
--rgb-light-grey: var(--mush-rgb-light-grey, var(--default-light-grey));
--rgb-grey: var(--mush-rgb-grey, var(--default-grey));
--rgb-dark-grey: var(--mush-rgb-dark-grey, var(--default-dark-grey));
--rgb-blue-grey: var(--mush-rgb-blue-grey, var(--default-blue-grey));
--rgb-black: var(--mush-rgb-black, var(--default-black));
--rgb-white: var(--mush-rgb-white, var(--default-white));
--rgb-disabled: var(--mush-rgb-disabled, var(--default-disabled));
/* Action colors */
--rgb-info: var(--mush-rgb-info, var(--rgb-blue));
--rgb-success: var(--mush-rgb-success, var(--rgb-green));
--rgb-warning: var(--mush-rgb-warning, var(--rgb-orange));
--rgb-danger: var(--mush-rgb-danger, var(--rgb-red));
/* State colors */
--rgb-state-vacuum: var(--mush-rgb-state-vacuum, var(--rgb-teal));
--rgb-state-fan: var(--mush-rgb-state-fan, var(--rgb-green));
--rgb-state-light: var(--mush-rgb-state-light, var(--rgb-orange));
--rgb-state-entity: var(--mush-rgb-state-entity, var(--rgb-blue));
--rgb-state-media-player: var(
--mush-rgb-state-media-player,
var(--rgb-indigo)
);
--rgb-state-lock: var(--mush-rgb-state-lock, var(--rgb-blue));
--rgb-state-number: var(--mush-rgb-state-number, var(--rgb-blue));
--rgb-state-humidifier: var(--mush-rgb-state-humidifier, var(--rgb-purple));
/* State alarm colors */
--rgb-state-alarm-disarmed: var(
--mush-rgb-state-alarm-disarmed,
var(--rgb-info)
);
--rgb-state-alarm-armed: var(
--mush-rgb-state-alarm-armed,
var(--rgb-success)
);
--rgb-state-alarm-triggered: var(
--mush-rgb-state-alarm-triggered,
var(--rgb-danger)
);
/* State person colors */
--rgb-state-person-home: var(
--mush-rgb-state-person-home,
var(--rgb-success)
);
--rgb-state-person-not-home: var(
--mush-rgb-state-person-not-home,
var(--rgb-danger)
);
--rgb-state-person-zone: var(--mush-rgb-state-person-zone, var(--rgb-info));
--rgb-state-person-unknown: var(
--mush-rgb-state-person-unknown,
var(--rgb-grey)
);
/* State update colors */
--rgb-state-update-on: var(--mush-rgb-state-update-on, var(--rgb-orange));
--rgb-state-update-off: var(--mush-rgb-update-off, var(--rgb-green));
--rgb-state-update-installing: var(
--mush-rgb-update-installing,
var(--rgb-blue)
);
/* State lock colors */
--rgb-state-lock-locked: var(--mush-rgb-state-lock-locked, var(--rgb-green));
--rgb-state-lock-unlocked: var(
--mush-rgb-state-lock-unlocked,
var(--rgb-red)
);
--rgb-state-lock-pending: var(
--mush-rgb-state-lock-pending,
var(--rgb-orange)
);
/* State cover colors */
--rgb-state-cover-open: var(--mush-rgb-state-cover-open, var(--rgb-blue));
--rgb-state-cover-closed: var(
--mush-rgb-state-cover-closed,
var(--rgb-disabled)
);
/* State climate colors */
--rgb-state-climate-auto: var(
--mush-rgb-state-climate-auto,
var(--rgb-green)
);
--rgb-state-climate-cool: var(--mush-rgb-state-climate-cool, var(--rgb-blue));
--rgb-state-climate-dry: var(--mush-rgb-state-climate-dry, var(--rgb-orange));
--rgb-state-climate-fan-only: var(
--mush-rgb-state-climate-fan-only,
var(--rgb-teal)
);
--rgb-state-climate-heat: var(
--mush-rgb-state-climate-heat,
var(--rgb-deep-orange)
);
--rgb-state-climate-heat-cool: var(
--mush-rgb-state-climate-heat-cool,
var(--rgb-green)
);
--rgb-state-climate-idle: var(
--mush-rgb-state-climate-idle,
var(--rgb-disabled)
);
--rgb-state-climate-off: var(
--mush-rgb-state-climate-off,
var(--rgb-disabled)
);
`;
export const cardStyle = css`
ha-card {
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: var(--layout-align);
height: auto;
display: flex;
flex-direction: column;
}
ha-card.fill-container {
height: 100%;
}
:host([layout="grid"]) ha-card {
height: 100%;
}
.actions {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
padding: var(--control-spacing);
padding-top: 0;
box-sizing: border-box;
gap: var(--control-spacing);
}
.actions::-webkit-scrollbar {
background: transparent; /* Chrome/Safari/Webkit */
height: 0px;
}
.unavailable {
--main-color: rgb(var(--rgb-warning));
}
.not-found {
--main-color: rgb(var(--rgb-danger));
}
mushroom-state-item[disabled] {
cursor: initial;
}
`;
export const UNAVAILABLE = "unavailable";
export const UNKNOWN = "unknown";
export const ON = "on";
export const OFF = "off";
const OFF_STATES = [UNAVAILABLE, UNKNOWN, OFF];
export function isActive(stateObj: HassEntity) {
/** https://github.com/piitaya/lovelace-mushroom/blob/f33c910b8863fa2229ee0a73e6fbf515f3bc2788/src/ha/data/entity.ts */
const domain = computeDomain(stateObj.entity_id);
const state = stateObj.state;
if (["button", "input_button", "scene"].includes(domain)) {
return state !== UNAVAILABLE;
}
if (OFF_STATES.includes(state)) {
return false;
}
// Custom cases
switch (domain) {
case "cover":
return !["closed", "closing"].includes(state);
case "device_tracker":
case "person":
return state !== "not_home";
case "media_player":
return state !== "standby";
case "vacuum":
return !["idle", "docked", "paused"].includes(state);
case "plant":
return state === "problem";
default:
return true;
}
}
export const COLORS = [
"primary",
"accent",
"red",
"pink",
"purple",
"deep-purple",
"indigo",
"blue",
"light-blue",
"cyan",
"teal",
"green",
"light-green",
"lime",
"yellow",
"amber",
"orange",
"deep-orange",
"brown",
"grey",
"blue-grey",
"black",
"white",
"disabled",
];
export function computeRgbColor(color: string): string {
if (color === "primary" || color === "accent") {
return `var(--rgb-${color}-color)`;
}
if (COLORS.includes(color)) {
return `var(--rgb-${color})`;
} else if (color.startsWith("#")) {
const b = parseInt(color.substring(1), 16);
if (isNaN(b)) return "";
return [(b >> 16) & 255, (b >> 8) & 255, b & 255].join(", ");
}
return color;
}
const TIMESTAMP_STATE_DOMAINS = ["button", "input_button", "scene"];
export const INFOS = [
"name",
"state",
"last-changed",
"last-updated",
"none",
] as const;
export type Info = (typeof INFOS)[number];
export const ICON_TYPES = ["icon", "entity-picture", "none"] as const;
export type IconType = (typeof ICON_TYPES)[number];
export function isAvailable(stateObj: HassEntity) {
return stateObj.state !== UNAVAILABLE;
}
export function isOff(stateObj: HassEntity) {
return stateObj.state === OFF;
}
export function isUnknown(stateObj: HassEntity) {
return stateObj.state === UNKNOWN;
}
export function computeInfoDisplay(
info: Info,
name: string,
state: string | TemplateResult,
stateObj: HassEntity,
hass: HomeAssistant,
) {
switch (info) {
case "name":
return name;
case "state":
const domain = stateObj.entity_id.split(".")[0];
if (
(stateObj.attributes.device_class === "timestamp" ||
TIMESTAMP_STATE_DOMAINS.includes(domain)) &&
isAvailable(stateObj) &&
!isUnknown(stateObj)
) {
return html`
<ha-relative-time
.hass=${hass}
.datetime=${stateObj.state}
capitalize
></ha-relative-time>
`;
} else {
return state;
}
case "last-changed":
return html`
<ha-relative-time
.hass=${hass}
.datetime=${stateObj.last_changed}
capitalize
></ha-relative-time>
`;
case "last-updated":
return html`
<ha-relative-time
.hass=${hass}
.datetime=${stateObj.last_updated}
capitalize
></ha-relative-time>
`;
case "none":
return undefined;
}
}