mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20221212.0 (#14722)
This commit is contained in:
commit
9f8aa0b4bf
@ -5,33 +5,39 @@
|
||||
"context": ".."
|
||||
},
|
||||
"appPort": "8124:8123",
|
||||
"context": "..",
|
||||
"postCreateCommand": "script/bootstrap",
|
||||
"extensions": [
|
||||
"github.vscode-pull-request-github",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin",
|
||||
"esbenp.prettier-vscode",
|
||||
"bierner.lit-html",
|
||||
"runem.lit-plugin",
|
||||
"ms-python.vscode-pylance"
|
||||
],
|
||||
"containerEnv": {
|
||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/bash",
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"files.trimTrailingWhitespace": true
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"runem.lit-plugin",
|
||||
"github.vscode-pull-request-github",
|
||||
"eamodio.gitlens"
|
||||
],
|
||||
"settings": {
|
||||
"files.eol": "\n",
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnPaste": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnType": true,
|
||||
"editor.renderWhitespace": "boundary",
|
||||
"editor.rulers": [80],
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"terminal.integrated.shell.linux": "/usr/bin/zsh",
|
||||
"gitlens.showWelcomeOnInstall": false,
|
||||
"gitlens.showWhatsNewAfterUpgrades": false,
|
||||
"workbench.startupEditor": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
90
.github/workflows/cast_deployment.yaml
vendored
Normal file
90
.github/workflows/cast_deployment.yaml
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
name: Cast deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
env:
|
||||
NODE_VERSION: 16
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
jobs:
|
||||
deploy_dev:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy Development
|
||||
if: github.event_name != 'push'
|
||||
environment:
|
||||
name: Cast Development
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Cast
|
||||
run: ./node_modules/.bin/gulp build-cast
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=cast/dist --alias dev
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||
|
||||
deploy_master:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy Production
|
||||
if: github.event_name == 'push'
|
||||
environment:
|
||||
name: Cast Production
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Cast
|
||||
run: ./node_modules/.bin/gulp build-cast
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=cast/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
@ -1,6 +1,9 @@
|
||||
name: Demo
|
||||
name: Demo deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
@ -12,24 +15,34 @@ env:
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Demo
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Demo
|
||||
run: ./node_modules/.bin/gulp build-demo
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
run: npx netlify-cli deploy --dir=demo/dist --prod
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
45
.github/workflows/design_deployment.yaml
vendored
Normal file
45
.github/workflows/design_deployment.yaml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
name: Design deployment
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
env:
|
||||
NODE_VERSION: 16
|
||||
NODE_OPTIONS: --max_old_space_size=6144
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: Design
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Node ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: yarn
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
|
||||
- name: Build Gallery
|
||||
run: ./node_modules/.bin/gulp build-gallery
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
uses: netlify/actions/cli@master
|
||||
with:
|
||||
args: deploy --dir=gallery/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
19
.github/workflows/netflify.yml
vendored
19
.github/workflows/netflify.yml
vendored
@ -1,19 +0,0 @@
|
||||
name: Netlify
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
trigger_builds:
|
||||
name: Trigger netlify build preview
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- name: Trigger Cast build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_CAST_DEV_BUILD_HOOK }}
|
||||
|
||||
- name: Trigger Demo build
|
||||
run: curl -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_DEMO_DEV_BUILD_HOOK }}
|
||||
|
||||
- name: Trigger Design build
|
||||
run: curl -X POST -d "NIGHTLY" https://api.netlify.com/build_hooks/${{ secrets.NETLIFY_GALLERY_DEV_BUILD_HOOK }}
|
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@ -2,7 +2,8 @@
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"bierner.lit-html",
|
||||
"runem.lit-plugin"
|
||||
"runem.lit-plugin",
|
||||
"github.vscode-pull-request-github",
|
||||
"eamodio.gitlens"
|
||||
]
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
TARGET_LABEL="needs design preview"
|
||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS="true"
|
||||
|
||||
if [[ "$NETLIFY" != "true" ]]; then
|
||||
echo "This script can only be run on Netlify"
|
||||
@ -30,6 +31,4 @@ if [[ "${PULL_REQUEST}" == "true" ]]; then
|
||||
else
|
||||
createStatus "success" "Build was not requested by PR label"
|
||||
fi
|
||||
elif [[ "$INCOMING_HOOK_BODY" == "NIGHTLY" ]]; then
|
||||
gulp build-gallery
|
||||
fi
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20221208.0"
|
||||
version = "20221212.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -52,7 +52,9 @@ import {
|
||||
mdiScriptText,
|
||||
mdiSineWave,
|
||||
mdiSpeedometer,
|
||||
mdiSunWireless,
|
||||
mdiThermometer,
|
||||
mdiThermometerLines,
|
||||
mdiThermostat,
|
||||
mdiTimerOutline,
|
||||
mdiVideo,
|
||||
@ -126,6 +128,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
apparent_power: mdiFlash,
|
||||
aqi: mdiAirFilter,
|
||||
atmospheric_pressure: mdiThermometerLines,
|
||||
// battery: mdiBattery, => not included by design since `sensorIcon()` will dynamically determine the icon
|
||||
carbon_dioxide: mdiMoleculeCo2,
|
||||
carbon_monoxide: mdiMoleculeCo,
|
||||
@ -138,6 +141,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
gas: mdiMeterGas,
|
||||
humidity: mdiWaterPercent,
|
||||
illuminance: mdiBrightness5,
|
||||
irradiance: mdiSunWireless,
|
||||
moisture: mdiWaterPercent,
|
||||
monetary: mdiCash,
|
||||
nitrogen_dioxide: mdiMolecule,
|
||||
|
@ -13,6 +13,8 @@ export const alarmControlPanelColor = (state?: string): string | undefined => {
|
||||
return "alarm-arming";
|
||||
case "triggered":
|
||||
return "alarm-triggered";
|
||||
case "disarmed":
|
||||
return "alarm-disarmed";
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
export const lockColor = (state?: string): string | undefined => {
|
||||
switch (state) {
|
||||
case "unlocked":
|
||||
return "lock-unlocked";
|
||||
case "locked":
|
||||
return "lock-locked";
|
||||
case "jammed":
|
||||
|
@ -2,6 +2,8 @@ export const personColor = (state: string): string | undefined => {
|
||||
switch (state) {
|
||||
case "home":
|
||||
return "person-home";
|
||||
case "not_home":
|
||||
return "person-not-home";
|
||||
default:
|
||||
return "person-zone";
|
||||
}
|
||||
|
@ -3,12 +3,12 @@ import { batteryStateColor } from "./battery_color";
|
||||
|
||||
export const sensorColor = (
|
||||
stateObj: HassEntity,
|
||||
compareState: string
|
||||
state: string
|
||||
): string | undefined => {
|
||||
const deviceClass = stateObj?.attributes.device_class;
|
||||
|
||||
if (deviceClass === "battery") {
|
||||
return batteryStateColor(compareState);
|
||||
return batteryStateColor(state);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
@ -16,15 +16,15 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
|
||||
// Custom cases
|
||||
switch (domain) {
|
||||
case "alarm_control_panel":
|
||||
return compareState !== "disarmed";
|
||||
case "cover":
|
||||
return !["closed", "closing"].includes(compareState);
|
||||
case "device_tracker":
|
||||
case "person":
|
||||
return compareState !== "not_home";
|
||||
case "alarm_control_panel":
|
||||
return compareState !== "disarmed";
|
||||
case "lock":
|
||||
return compareState !== "unlocked";
|
||||
return compareState !== "locked";
|
||||
case "media_player":
|
||||
return compareState !== "standby";
|
||||
case "vacuum":
|
||||
|
@ -1,5 +1,6 @@
|
||||
/** Return an color representing a state. */
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
import { UpdateEntity, updateIsInstalling } from "../../data/update";
|
||||
import { alarmControlPanelColor } from "./color/alarm_control_panel_color";
|
||||
import { binarySensorColor } from "./color/binary_sensor_color";
|
||||
@ -10,7 +11,7 @@ import { sensorColor } from "./color/sensor_color";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { stateActive } from "./state_active";
|
||||
|
||||
const STATIC_COLORED_DOMAIN = new Set([
|
||||
const STATIC_ACTIVE_COLORED_DOMAIN = new Set([
|
||||
"alert",
|
||||
"automation",
|
||||
"calendar",
|
||||
@ -30,25 +31,33 @@ const STATIC_COLORED_DOMAIN = new Set([
|
||||
"vacuum",
|
||||
]);
|
||||
|
||||
export const stateColorCss = (stateObj?: HassEntity, state?: string) => {
|
||||
if (!stateObj || !stateActive(stateObj, state)) {
|
||||
return `var(--rgb-disabled-color)`;
|
||||
export const stateColorCss = (stateObj: HassEntity, state?: string) => {
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
if (compareState === UNAVAILABLE) {
|
||||
return `var(--rgb-state-unavailable-color)`;
|
||||
}
|
||||
|
||||
const color = stateColor(stateObj, state);
|
||||
const domainColor = stateColor(stateObj, state);
|
||||
|
||||
if (color) {
|
||||
return `var(--rgb-state-${color}-color)`;
|
||||
if (domainColor) {
|
||||
return `var(--rgb-state-${domainColor}-color)`;
|
||||
}
|
||||
|
||||
return `var(--rgb-state-default-color)`;
|
||||
if (!stateActive(stateObj, state)) {
|
||||
return `var(--rgb-state-inactive-color)`;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const stateColor = (stateObj: HassEntity, state?: string) => {
|
||||
const compareState = state !== undefined ? state : stateObj?.state;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
|
||||
if (STATIC_COLORED_DOMAIN.has(domain)) {
|
||||
if (
|
||||
STATIC_ACTIVE_COLORED_DOMAIN.has(domain) &&
|
||||
stateActive(stateObj, state)
|
||||
) {
|
||||
return domain.replace("_", "-");
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,6 @@ export const iconColorCSS = css`
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-state-icon[data-state="unavailable"] {
|
||||
color: var(--state-unavailable-color);
|
||||
color: rgb(var(--rgb-state-unavailable-color));
|
||||
}
|
||||
`;
|
||||
|
@ -37,8 +37,11 @@ export class TextBarElement extends BarElement {
|
||||
}
|
||||
const textColor =
|
||||
options.textColor ||
|
||||
(options.backgroundColor &&
|
||||
(luminosity(hex2rgb(options.backgroundColor)) > 0.5 ? "#000" : "#fff"));
|
||||
(options?.backgroundColor === "transparent"
|
||||
? "transparent"
|
||||
: luminosity(hex2rgb(options.backgroundColor)) > 0.5
|
||||
? "#000"
|
||||
: "#fff");
|
||||
|
||||
// ctx.font = "12px arial";
|
||||
ctx.fillStyle = textColor;
|
||||
|
@ -5,6 +5,7 @@ import { labBrighten } from "../../../common/color/lab";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
const DOMAIN_STATE_SHADES: Record<string, Record<string, number>> = {
|
||||
media_player: {
|
||||
@ -49,21 +50,23 @@ function computeTimelineStateColor(
|
||||
computedStyles: CSSStyleDeclaration,
|
||||
stateObj?: HassEntity
|
||||
): string | undefined {
|
||||
if (!stateObj || !stateActive(stateObj, state)) {
|
||||
const rgb = cssToRgb("--rgb-disabled-color", computedStyles);
|
||||
if (!stateObj || state === UNAVAILABLE) {
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
const color = stateColor(stateObj, state);
|
||||
|
||||
if (!color && !stateActive(stateObj, state)) {
|
||||
const rgb = cssToRgb("--rgb-state-inactive-color", computedStyles);
|
||||
if (!rgb) return undefined;
|
||||
return rgb2hex(rgb);
|
||||
}
|
||||
const color = stateColor(stateObj, state);
|
||||
|
||||
if (!color) return undefined;
|
||||
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
|
||||
const rgb = cssToRgb(`--rgb-state-${color}-color`, computedStyles);
|
||||
|
||||
if (!rgb) return undefined;
|
||||
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
const shade = DOMAIN_STATE_SHADES[domain]?.[state] as number | number;
|
||||
if (!shade) {
|
||||
return rgb2hex(rgb);
|
||||
|
@ -14,7 +14,7 @@ import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { stateActive } from "../../common/entity/state_active";
|
||||
import { stateColor } from "../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../common/entity/state_color";
|
||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||
import { cameraUrlWithWidthHeight } from "../../data/camera";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@ -107,12 +107,13 @@ export class StateBadge extends LitElement {
|
||||
}
|
||||
hostStyle.backgroundImage = `url(${imageUrl})`;
|
||||
this._showIcon = false;
|
||||
} else if (stateActive(stateObj) && this._stateColor) {
|
||||
const iconColor = stateColor(stateObj);
|
||||
} else if (this._stateColor && stateActive(stateObj)) {
|
||||
const color = stateColorCss(stateObj);
|
||||
if (color) {
|
||||
iconStyle.color = `rgb(${color})`;
|
||||
}
|
||||
if (stateObj.attributes.rgb_color) {
|
||||
iconStyle.color = `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||
} else if (iconColor) {
|
||||
iconStyle.color = `rgb(var(--rgb-state-${iconColor}-color))`;
|
||||
}
|
||||
if (stateObj.attributes.brightness) {
|
||||
const brightness = stateObj.attributes.brightness;
|
||||
|
@ -31,7 +31,7 @@ export interface HaFormBaseSchema {
|
||||
|
||||
export interface HaFormGridSchema extends HaFormBaseSchema {
|
||||
type: "grid";
|
||||
name: "";
|
||||
name: string;
|
||||
column_min_width?: string;
|
||||
schema: readonly HaFormSchema[];
|
||||
}
|
||||
|
@ -69,7 +69,6 @@ const defaultFullCalendarConfig: CalendarOptions = {
|
||||
initialView: "dayGridMonth",
|
||||
dayMaxEventRows: true,
|
||||
height: "parent",
|
||||
eventDisplay: "list-item",
|
||||
locales: allLocales,
|
||||
views: {
|
||||
listWeek: {
|
||||
@ -97,6 +96,8 @@ export class HAFullCalendar extends LitElement {
|
||||
|
||||
@property() public initialView: FullCalendarView = "dayGridMonth";
|
||||
|
||||
@property() public eventDisplay = "auto";
|
||||
|
||||
@property({ attribute: false }) public error?: string = undefined;
|
||||
|
||||
private calendar?: Calendar;
|
||||
@ -233,6 +234,10 @@ export class HAFullCalendar extends LitElement {
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
if (changedProps.has("eventDisplay")) {
|
||||
this.calendar!.setOption("eventDisplay", this.eventDisplay);
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant;
|
||||
|
||||
if (oldHass && oldHass.language !== this.hass.language) {
|
||||
@ -246,6 +251,7 @@ export class HAFullCalendar extends LitElement {
|
||||
locale: this.hass.language,
|
||||
firstDay: firstWeekdayIndex(this.hass.locale),
|
||||
initialView: this.initialView,
|
||||
eventDisplay: this.eventDisplay,
|
||||
eventTimeFormat: {
|
||||
hour: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
|
||||
minute: useAmPm(this.hass.locale) ? "numeric" : "2-digit",
|
||||
@ -320,6 +326,7 @@ export class HAFullCalendar extends LitElement {
|
||||
this._activeView = "dayGridDay";
|
||||
this.calendar!.changeView("dayGridDay");
|
||||
this.calendar!.gotoDate(info.dateStr);
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _handleNext(): void {
|
||||
@ -537,6 +544,7 @@ export class HAFullCalendar extends LitElement {
|
||||
a.fc-daygrid-day-number {
|
||||
float: none !important;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fc .fc-daygrid-day-number {
|
||||
@ -547,12 +555,8 @@ export class HAFullCalendar extends LitElement {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
td.fc-day-today .fc-daygrid-day-top {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
td.fc-day-today .fc-daygrid-day-number {
|
||||
height: 24px;
|
||||
height: 26px;
|
||||
color: var(--text-primary-color) !important;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
@ -561,7 +565,6 @@ export class HAFullCalendar extends LitElement {
|
||||
white-space: nowrap;
|
||||
width: max-content;
|
||||
min-width: 24px;
|
||||
line-height: 140%;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-events {
|
||||
@ -571,6 +574,7 @@ export class HAFullCalendar extends LitElement {
|
||||
.fc-event {
|
||||
border-radius: 4px;
|
||||
line-height: 1.7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fc-daygrid-block-event .fc-event-main {
|
||||
|
@ -3,6 +3,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { canShowPage } from "../../../common/config/can_show_page";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-navigation-list";
|
||||
import "../../../components/ha-tip";
|
||||
@ -84,7 +85,7 @@ class HaConfigSystemNavigation extends LitElement {
|
||||
"percent_used",
|
||||
`${Math.round(
|
||||
(this._storageInfo.used / this._storageInfo.total) * 100
|
||||
)}%`,
|
||||
)}${blankBeforePercent(this.hass.locale)}%`,
|
||||
"free_space",
|
||||
`${this._storageInfo.free} GB`
|
||||
)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import {
|
||||
mdiCog,
|
||||
mdiDelete,
|
||||
@ -7,18 +8,19 @@ import {
|
||||
mdiPencil,
|
||||
mdiPlusCircle,
|
||||
} from "@mdi/js";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { SENSOR_ENTITIES } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-alert";
|
||||
@ -59,6 +61,7 @@ import {
|
||||
showConfirmationDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@ -74,8 +77,6 @@ import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { SENSOR_ENTITIES } from "../../../common/const";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
@ -639,7 +640,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
batteryState
|
||||
? html`
|
||||
<div class="battery">
|
||||
${batteryIsBinary ? "" : batteryState.state + " %"}
|
||||
${batteryIsBinary
|
||||
? ""
|
||||
: batteryState.state +
|
||||
blankBeforePercent(this.hass.locale) +
|
||||
"%"}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${batteryState}
|
||||
|
@ -7,6 +7,7 @@ import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import {
|
||||
@ -339,7 +340,9 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
battery && computeStateDomain(battery) === "binary_sensor";
|
||||
return battery && (batteryIsBinary || !isNaN(battery.state as any))
|
||||
? html`
|
||||
${batteryIsBinary ? "" : battery.state + " %"}
|
||||
${batteryIsBinary
|
||||
? ""
|
||||
: battery.state + blankBeforePercent(this.hass.locale) + "%"}
|
||||
<ha-battery-icon
|
||||
.hass=${this.hass!}
|
||||
.batteryStateObj=${battery}
|
||||
|
@ -9,6 +9,7 @@ import { ifDefined } from "lit/directives/if-defined";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { numberFormatToLocale } from "../../../common/number/format_number";
|
||||
import { round } from "../../../common/number/round";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/chart/ha-chart-base";
|
||||
import "../../../components/ha-alert";
|
||||
@ -169,7 +170,8 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
|
||||
max: 100,
|
||||
min: 0,
|
||||
stepSize: 1,
|
||||
callback: (value) => value + "%",
|
||||
callback: (value) =>
|
||||
value + blankBeforePercent(this.hass.locale) + "%",
|
||||
},
|
||||
},
|
||||
x: {
|
||||
@ -386,7 +388,8 @@ class HaConfigHardware extends SubscribeMixin(LitElement) {
|
||||
)}
|
||||
</div>
|
||||
<div class="value">
|
||||
${this._systemStatusData.cpu_percent || "-"}%
|
||||
${this._systemStatusData.cpu_percent ||
|
||||
"-"}${blankBeforePercent(this.hass.locale)}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
|
@ -10,6 +10,8 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "./mqtt-subscribe-card";
|
||||
|
||||
const qosLevel = ["0", "1", "2"];
|
||||
|
||||
@customElement("mqtt-config-panel")
|
||||
class HaPanelDevMqtt extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -20,6 +22,8 @@ class HaPanelDevMqtt extends LitElement {
|
||||
|
||||
@state() private payload = "";
|
||||
|
||||
@state() private qos = "0";
|
||||
|
||||
private inited = false;
|
||||
|
||||
protected firstUpdated() {
|
||||
@ -29,6 +33,9 @@ class HaPanelDevMqtt extends LitElement {
|
||||
if (localStorage && localStorage["panel-dev-mqtt-payload"]) {
|
||||
this.payload = localStorage["panel-dev-mqtt-payload"];
|
||||
}
|
||||
if (localStorage && localStorage["panel-dev-mqtt-qos"]) {
|
||||
this.qos = localStorage["panel-dev-mqtt-qos"];
|
||||
}
|
||||
this.inited = true;
|
||||
}
|
||||
|
||||
@ -54,6 +61,15 @@ class HaPanelDevMqtt extends LitElement {
|
||||
.value=${this.topic}
|
||||
@change=${this._handleTopic}
|
||||
></ha-textfield>
|
||||
<ha-select
|
||||
.label=${this.hass.localize("ui.panel.config.mqtt.qos")}
|
||||
.value=${this.qos}
|
||||
@selected=${this._handleQos}
|
||||
>${qosLevel.map(
|
||||
(qos) =>
|
||||
html`<mwc-list-item .value=${qos}>${qos}</mwc-list-item>`
|
||||
)}
|
||||
</ha-select>
|
||||
|
||||
<p>${this.hass.localize("ui.panel.config.mqtt.payload")}</p>
|
||||
<ha-code-editor
|
||||
@ -95,6 +111,14 @@ class HaPanelDevMqtt extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleQos(ev: CustomEvent) {
|
||||
const newValue = (ev.target! as any).value;
|
||||
if (newValue >= 0 && newValue !== this.qos && localStorage && this.inited) {
|
||||
this.qos = newValue;
|
||||
localStorage["panel-dev-mqtt-qos"] = this.qos;
|
||||
}
|
||||
}
|
||||
|
||||
private _publish(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
@ -102,6 +126,7 @@ class HaPanelDevMqtt extends LitElement {
|
||||
this.hass.callService("mqtt", "publish", {
|
||||
topic: this.topic,
|
||||
payload_template: this.payload,
|
||||
qos: parseInt(this.qos),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ class HuiEnergyCarbonGaugeCard
|
||||
min="0"
|
||||
max="100"
|
||||
.value=${value}
|
||||
.locale=${this.hass!.locale}
|
||||
.locale=${this.hass.locale}
|
||||
label="%"
|
||||
style=${styleMap({
|
||||
"--gauge-color": this._computeSeverity(value),
|
||||
|
@ -108,7 +108,7 @@ class HuiEnergySolarGaugeCard
|
||||
min="0"
|
||||
max="100"
|
||||
.value=${value}
|
||||
.locale=${this.hass!.locale}
|
||||
.locale=${this.hass.locale}
|
||||
label="%"
|
||||
style=${styleMap({
|
||||
"--gauge-color": this._computeSeverity(value),
|
||||
|
@ -269,12 +269,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
--alarm-color-disarmed: var(--label-badge-green);
|
||||
--alarm-color-pending: var(--label-badge-yellow);
|
||||
--alarm-color-triggered: var(--label-badge-red);
|
||||
--alarm-color-armed: var(--label-badge-red);
|
||||
--alarm-color-autoarm: rgba(0, 153, 255, 0.1);
|
||||
--alarm-state-color: var(--alarm-color-armed);
|
||||
--alarm-state-color: rgb(var(--rgb-state-alarm-armed-color));
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
@ -292,25 +287,25 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
.unavailable {
|
||||
--alarm-state-color: var(--state-unavailable-color);
|
||||
--alarm-state-color: rgb(var(--rgb-state-unavailable-color));
|
||||
}
|
||||
|
||||
.disarmed {
|
||||
--alarm-state-color: var(--alarm-color-disarmed);
|
||||
--alarm-state-color: rgb(var(--rgb-state-alarm-disarmed-color));
|
||||
}
|
||||
|
||||
.triggered {
|
||||
--alarm-state-color: var(--alarm-color-triggered);
|
||||
--alarm-state-color: rgb(var(--rgb-state-alarm-trigger-color));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
.arming {
|
||||
--alarm-state-color: var(--alarm-color-pending);
|
||||
--alarm-state-color: rgb(var(--rgb-state-alarm-arming-color));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
.pending {
|
||||
--alarm-state-color: var(--alarm-color-pending);
|
||||
--alarm-state-color: rgb(var(--rgb-state-alarm-pending-color));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import { iconColorCSS } from "../../../common/style/icon_color_css";
|
||||
import "../../../components/ha-card";
|
||||
@ -79,6 +79,15 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@state() private _shouldRenderRipple = false;
|
||||
|
||||
private getStateColor(stateObj: HassEntity, config: ButtonCardConfig) {
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
return (
|
||||
config &&
|
||||
(config.state_color ||
|
||||
(domain === "light" && config.state_color !== false))
|
||||
);
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return (
|
||||
(this._config?.show_icon ? 4 : 0) + (this._config?.show_name ? 1 : 0)
|
||||
@ -146,13 +155,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
const name = this._config.show_name
|
||||
? this._config.name || (stateObj ? computeStateName(stateObj) : "")
|
||||
: "";
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
|
||||
const active =
|
||||
(this._config.state_color ||
|
||||
(domain === "light" && this._config.state_color !== false)) &&
|
||||
stateObj &&
|
||||
stateActive(stateObj);
|
||||
const active = stateObj && stateActive(stateObj);
|
||||
const colored = active && this.getStateColor(stateObj, this._config);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
@ -187,9 +192,8 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
.icon=${this._config.icon}
|
||||
.state=${stateObj}
|
||||
style=${styleMap({
|
||||
color: stateObj && active ? this._computeColor(stateObj) : "",
|
||||
filter:
|
||||
stateObj && active ? this._computeBrightness(stateObj) : "",
|
||||
color: colored ? this._computeColor(stateObj) : "",
|
||||
filter: colored ? this._computeBrightness(stateObj) : "",
|
||||
height: this._config.icon_height
|
||||
? this._config.icon_height
|
||||
: "",
|
||||
@ -305,22 +309,24 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeBrightness(stateObj: HassEntity | LightEntity): string {
|
||||
if (!stateObj.attributes.brightness) {
|
||||
if (!stateObj.attributes.brightness && stateActive(stateObj)) {
|
||||
const brightness = stateObj.attributes.brightness;
|
||||
return `brightness(${(brightness + 245) / 5}%)`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private _computeColor(stateObj: HassEntity | LightEntity): string {
|
||||
if (stateObj.attributes.rgb_color) {
|
||||
private _computeColor(
|
||||
stateObj: HassEntity | LightEntity
|
||||
): string | undefined {
|
||||
if (stateObj.attributes.rgb_color && stateActive(stateObj)) {
|
||||
return `rgb(${stateObj.attributes.rgb_color.join(",")})`;
|
||||
}
|
||||
const iconColor = stateColor(stateObj);
|
||||
const iconColor = stateColorCss(stateObj);
|
||||
if (iconColor) {
|
||||
return `rgb(var(--rgb-state-${iconColor}-color))`;
|
||||
return `rgb(${iconColor})`;
|
||||
}
|
||||
return "";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
|
@ -66,6 +66,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@state() private _calendars: Calendar[] = [];
|
||||
|
||||
@state() private _eventDisplay = "list-item";
|
||||
|
||||
@state() private _narrow = false;
|
||||
|
||||
@state() private _veryNarrow = false;
|
||||
@ -134,6 +136,7 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
.hass=${this.hass}
|
||||
.views=${views}
|
||||
.initialView=${this._config.initial_view!}
|
||||
.eventDisplay=${this._eventDisplay}
|
||||
.error=${this._error}
|
||||
@view-changed=${this._handleViewChanged}
|
||||
></ha-full-calendar>
|
||||
@ -163,6 +166,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _handleViewChanged(ev: HASSDomEvent<CalendarViewChanged>): void {
|
||||
this._eventDisplay =
|
||||
ev.detail.view === "dayGridMonth" ? "list-item" : "auto";
|
||||
this._startDate = ev.detail.start;
|
||||
this._endDate = ev.detail.end;
|
||||
this._fetchCalendarEvents();
|
||||
|
@ -16,7 +16,7 @@ import { computeStateDisplay } from "../../../common/entity/compute_state_displa
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColor } from "../../../common/entity/state_color";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import {
|
||||
formatNumber,
|
||||
@ -76,6 +76,15 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private _footerElement?: HuiErrorCard | LovelaceHeaderFooter;
|
||||
|
||||
private getStateColor(stateObj: HassEntity, config: EntityCardConfig) {
|
||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||
return (
|
||||
config &&
|
||||
(config.state_color ||
|
||||
(domain === "light" && config.state_color !== false))
|
||||
);
|
||||
}
|
||||
|
||||
public setConfig(config: EntityCardConfig): void {
|
||||
if (!config.entity) {
|
||||
throw new Error("Entity must be specified");
|
||||
@ -124,10 +133,8 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const name = this._config.name || computeStateName(stateObj);
|
||||
|
||||
const active =
|
||||
(this._config.state_color ||
|
||||
(domain === "light" && this._config.state_color !== false)) &&
|
||||
stateActive(stateObj);
|
||||
const active = stateObj && stateActive(stateObj);
|
||||
const colored = active && this.getStateColor(stateObj, this._config);
|
||||
|
||||
return html`
|
||||
<ha-card @click=${this._handleClick} tabindex="0">
|
||||
@ -141,7 +148,7 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
data-domain=${ifDefined(domain)}
|
||||
data-state=${stateObj.state}
|
||||
style=${styleMap({
|
||||
color: active ? this._computeColor(stateObj) : "",
|
||||
color: colored ? this._computeColor(stateObj) : undefined,
|
||||
height: this._config.icon_height
|
||||
? this._config.icon_height
|
||||
: "",
|
||||
@ -186,22 +193,14 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeColor(stateObj: HassEntity | LightEntity): string {
|
||||
const domain = computeStateDomain(stateObj);
|
||||
if (
|
||||
!(
|
||||
this._config?.state_color ||
|
||||
(domain === "light" && this._config?.state_color !== false)
|
||||
) ||
|
||||
!stateActive(stateObj)
|
||||
) {
|
||||
return "";
|
||||
}
|
||||
const iconColor = stateColor(stateObj);
|
||||
private _computeColor(
|
||||
stateObj: HassEntity | LightEntity
|
||||
): string | undefined {
|
||||
const iconColor = stateColorCss(stateObj);
|
||||
if (iconColor) {
|
||||
return `rgb(var(--rgb-state-${iconColor}-color))`;
|
||||
return `rgb(${iconColor})`;
|
||||
}
|
||||
return "";
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { memoize } from "@fullcalendar/common";
|
||||
import { mdiHelp } from "@mdi/js";
|
||||
import { mdiExclamationThick, mdiHelp } from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@ -9,7 +9,6 @@ import { hsv2rgb, rgb2hsv } from "../../../common/color/convert-color";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import { stateIconPath } from "../../../common/entity/state_icon_path";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
@ -129,8 +128,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeStateColor = memoize((entity: HassEntity, color?: string) => {
|
||||
if (UNAVAILABLE_STATES.includes(entity.state)) {
|
||||
return undefined;
|
||||
// Use custom color
|
||||
if (color) {
|
||||
return computeRgbColor(color);
|
||||
}
|
||||
|
||||
// Use default color for person/device_tracker because color is on the badge
|
||||
@ -141,16 +141,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
return "var(--rgb-state-default-color)";
|
||||
}
|
||||
|
||||
if (!stateActive(entity)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (color) {
|
||||
return computeRgbColor(color);
|
||||
}
|
||||
|
||||
let stateColor = stateColorCss(entity);
|
||||
|
||||
// Use light color if the light support rgb
|
||||
if (
|
||||
computeDomain(entity.entity_id) === "light" &&
|
||||
entity.attributes.rgb_color
|
||||
@ -166,10 +157,11 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
hsvColor[1] = 0.4;
|
||||
}
|
||||
}
|
||||
stateColor = hsv2rgb(hsvColor).join(",");
|
||||
return hsv2rgb(hsvColor).join(",");
|
||||
}
|
||||
|
||||
return stateColor;
|
||||
// Fallback to state color
|
||||
return stateColorCss(entity) ?? "var(--rgb-state-default-color)";
|
||||
});
|
||||
|
||||
private _computeStateDisplay(stateObj: HassEntity): TemplateResult | string {
|
||||
@ -233,10 +225,21 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<ha-card class="disabled">
|
||||
<ha-card
|
||||
style=${styleMap({
|
||||
"--tile-color": `var(--rgb-disabled-color)`,
|
||||
})}
|
||||
>
|
||||
<div class="tile">
|
||||
<div class="icon-container">
|
||||
<ha-tile-icon .iconPath=${mdiHelp}></ha-tile-icon>
|
||||
<ha-tile-icon class="icon" .iconPath=${mdiHelp}></ha-tile-icon>
|
||||
<ha-tile-badge
|
||||
class="badge"
|
||||
.iconPath=${mdiExclamationThick}
|
||||
style=${styleMap({
|
||||
"--tile-badge-background-color": `rgb(var(--rgb-red-color))`,
|
||||
})}
|
||||
></ha-tile-badge>
|
||||
</div>
|
||||
<ha-tile-info
|
||||
.primary=${entityId}
|
||||
@ -360,16 +363,13 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
--tile-color: var(--rgb-disabled-color);
|
||||
--tile-color: var(--rgb-state-default-color);
|
||||
--tile-tap-padding: 6px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
ha-card {
|
||||
height: 100%;
|
||||
}
|
||||
ha-card.disabled {
|
||||
background: rgba(var(--rgb-disabled-color), 0.1);
|
||||
}
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -22,11 +22,11 @@ function personBadgeIcon(entity: HassEntity) {
|
||||
function personBadgeColor(entity: HassEntity) {
|
||||
switch (entity.state) {
|
||||
case "home":
|
||||
return "var(--rgb-badge-person-home-color)";
|
||||
return "var(--rgb-state-person-home-color)";
|
||||
case "not_home":
|
||||
return "var(--rgb-badge-person-not-home-color)";
|
||||
return "var(--rgb-state-person-not-home-color)";
|
||||
default:
|
||||
return "var(--rgb-badge-person-zone-color)";
|
||||
return "var(--rgb-state-person-zone-color)";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -491,9 +491,15 @@ export const generateDefaultViewConfig = (
|
||||
|
||||
const areaCards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const [areaId, areaEntities] of Object.entries(
|
||||
const sortedAreas = Object.entries(
|
||||
splittedByAreaDevice.areasWithEntities
|
||||
)) {
|
||||
).sort((a, b) => {
|
||||
const areaA = areaEntries[a[0]];
|
||||
const areaB = areaEntries[b[0]];
|
||||
return stringCompare(areaA.name, areaB.name);
|
||||
});
|
||||
|
||||
for (const [areaId, areaEntities] of sortedAreas) {
|
||||
const area = areaEntries[areaId];
|
||||
areaCards.push(
|
||||
...computeCards(
|
||||
@ -506,13 +512,20 @@ export const generateDefaultViewConfig = (
|
||||
);
|
||||
}
|
||||
|
||||
areaCards.sort((a, b) => stringCompare(a.title || "", b.title || ""));
|
||||
|
||||
const deviceCards: LovelaceCardConfig[] = [];
|
||||
|
||||
for (const [deviceId, deviceEntities] of Object.entries(
|
||||
const sortedDevices = Object.entries(
|
||||
splittedByAreaDevice.devicesWithEntities
|
||||
)) {
|
||||
).sort((a, b) => {
|
||||
const deviceA = deviceEntries[a[0]];
|
||||
const deviceB = deviceEntries[b[0]];
|
||||
return stringCompare(
|
||||
deviceA.name_by_user || deviceA.name || "",
|
||||
deviceB.name_by_user || deviceB.name || ""
|
||||
);
|
||||
});
|
||||
|
||||
for (const [deviceId, deviceEntities] of sortedDevices) {
|
||||
const device = deviceEntries[deviceId];
|
||||
deviceCards.push(
|
||||
...computeCards(
|
||||
@ -534,8 +547,6 @@ export const generateDefaultViewConfig = (
|
||||
);
|
||||
}
|
||||
|
||||
deviceCards.sort((a, b) => stringCompare(a.title || "", b.title || ""));
|
||||
|
||||
let energyCard: LovelaceCardConfig | undefined;
|
||||
|
||||
if (energyPrefs) {
|
||||
|
@ -93,7 +93,7 @@ export class HuiGaugeCardEditor
|
||||
...(showSeverity
|
||||
? ([
|
||||
{
|
||||
name: "",
|
||||
name: "severity",
|
||||
type: "grid",
|
||||
schema: [
|
||||
{
|
||||
|
@ -86,15 +86,17 @@ class HuiCoverOpenCloseTileFeature
|
||||
`
|
||||
: null}
|
||||
${supportsFeature(this.stateObj, CoverEntityFeature.STOP)
|
||||
? html`<ha-tile-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||
)}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStop(this.stateObj)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||
</ha-tile-button> `
|
||||
? html`
|
||||
<ha-tile-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||
)}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStop(this.stateObj)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||
</ha-tile-button>
|
||||
`
|
||||
: null}
|
||||
${supportsFeature(this.stateObj, CoverEntityFeature.CLOSE)
|
||||
? html`
|
||||
|
@ -80,15 +80,17 @@ class HuiCoverTiltTileFeature
|
||||
`
|
||||
: null}
|
||||
${supportsFeature(this.stateObj, CoverEntityFeature.STOP_TILT)
|
||||
? html`<ha-tile-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||
)}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStopTilt(this.stateObj)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||
</ha-tile-button> `
|
||||
? html`
|
||||
<ha-tile-button
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.cover.stop_cover"
|
||||
)}
|
||||
@click=${this._onStopTap}
|
||||
.disabled=${!canStopTilt(this.stateObj)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiStop}></ha-svg-icon>
|
||||
</ha-tile-button>
|
||||
`
|
||||
: null}
|
||||
${supportsFeature(this.stateObj, CoverEntityFeature.CLOSE_TILT)
|
||||
? html`
|
||||
|
@ -40,7 +40,7 @@ export const VACUUM_COMMANDS_FEATURES: Record<
|
||||
VacuumCommand,
|
||||
VacuumEntityFeature[]
|
||||
> = {
|
||||
start_pause: [VacuumEntityFeature.PAUSE],
|
||||
start_pause: [VacuumEntityFeature.PAUSE, VacuumEntityFeature.START],
|
||||
stop: [VacuumEntityFeature.STOP],
|
||||
clean_spot: [VacuumEntityFeature.CLEAN_SPOT],
|
||||
locate: [VacuumEntityFeature.LOCATE],
|
||||
|
@ -13,7 +13,7 @@ class HaPickDashboardRow extends LitElement {
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@state() private _dashboards: LovelaceDashboard[] = [];
|
||||
@state() private _dashboards?: LovelaceDashboard[];
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
@ -29,30 +29,37 @@ class HaPickDashboardRow extends LitElement {
|
||||
<span slot="description">
|
||||
${this.hass.localize("ui.panel.profile.dashboard.description")}
|
||||
</span>
|
||||
<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.dropdown_label"
|
||||
)}
|
||||
.disabled=${!this._dashboards.length}
|
||||
.value=${this.hass.defaultPanel}
|
||||
@selected=${this._dashboardChanged}
|
||||
>
|
||||
<mwc-list-item value="lovelace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.default_dashboard_label"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
${this._dashboards.map((dashboard) => {
|
||||
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<mwc-list-item .value=${dashboard.url_path}>
|
||||
${dashboard.title}
|
||||
${this._dashboards
|
||||
? html`<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.dropdown_label"
|
||||
)}
|
||||
.disabled=${!this._dashboards?.length}
|
||||
.value=${this.hass.defaultPanel}
|
||||
@selected=${this._dashboardChanged}
|
||||
>
|
||||
<mwc-list-item value="lovelace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.default_dashboard_label"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`;
|
||||
})}
|
||||
</ha-select>
|
||||
${this._dashboards.map((dashboard) => {
|
||||
if (!this.hass.user!.is_admin && dashboard.require_admin) {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<mwc-list-item .value=${dashboard.url_path}>
|
||||
${dashboard.title}
|
||||
</mwc-list-item>
|
||||
`;
|
||||
})}
|
||||
</ha-select>`
|
||||
: html`<ha-select
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.profile.dashboard.dropdown_label"
|
||||
)}
|
||||
disabled
|
||||
></ha-select>`}
|
||||
</ha-settings-row>
|
||||
`;
|
||||
}
|
||||
|
@ -194,6 +194,10 @@ export class HaPickThemeRow extends LitElement {
|
||||
|
||||
private _handleThemeSelection(ev) {
|
||||
const theme = ev.target.value;
|
||||
if (theme === this.hass.selectedTheme?.theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (theme === "Backend-selected") {
|
||||
if (this.hass.selectedTheme?.theme) {
|
||||
fireEvent(this, "settheme", {
|
||||
|
@ -134,24 +134,29 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-white-color: 255, 255, 255;
|
||||
|
||||
/* rgb state color */
|
||||
--rgb-state-default-color: 68, 115, 158;
|
||||
--rgb-state-alarm-armed-color: var(--rgb-red-color);
|
||||
--rgb-state-default-color: var(--rgb-dark-primary-color, 68, 115, 158);
|
||||
--rgb-state-inactive-color: var(--rgb-grey-color);
|
||||
--rgb-state-unavailable-color: var(--rgb-disabled-color);
|
||||
|
||||
/* rgb state domain colors */
|
||||
--rgb-state-alarm-armed-color: var(--rgb-green-color);
|
||||
--rgb-state-alarm-arming-color: var(--rgb-orange-color);
|
||||
--rgb-state-alarm-disarmed-color: var(--rgb-grey-color);
|
||||
--rgb-state-alarm-pending-color: var(--rgb-orange-color);
|
||||
--rgb-state-alarm-triggered-color: var(--rgb-red-color);
|
||||
--rgb-state-alert-color: var(--rgb-red-color);
|
||||
--rgb-state-automation-color: var(--rgb-amber-color);
|
||||
--rgb-state-binary-sensor-alerting-color: var(--rgb-red-color);
|
||||
--rgb-state-binary-sensor-color: var(--rgb-blue-color);
|
||||
--rgb-state-calendar-color: var(--rgb-blue-color);
|
||||
--rgb-state-camera-color: var(--rgb-blue-color);
|
||||
--rgb-state-binary-sensor-color: var(--rgb-amber-color);
|
||||
--rgb-state-calendar-color: var(--rgb-amber-color);
|
||||
--rgb-state-camera-color: var(--rgb-amber-color);
|
||||
--rgb-state-climate-auto-color: var(--rgb-green-color);
|
||||
--rgb-state-climate-cool-color: var(--rgb-blue-color);
|
||||
--rgb-state-climate-dry-color: var(--rgb-orange-color);
|
||||
--rgb-state-climate-fan-only-color: var(--rgb-cyan-color);
|
||||
--rgb-state-climate-heat-color: var(--rgb-deep-orange-color);
|
||||
--rgb-state-climate-heat-cool-color: var(--rgb-amber-color);
|
||||
--rgb-state-climate-idle-color: var(--rgb-disabled-color);
|
||||
--rgb-state-climate-idle-color: var(--rgb-off-color);
|
||||
--rgb-state-cover-color: var(--rgb-purple-color);
|
||||
--rgb-state-fan-color: var(--rgb-cyan-color);
|
||||
--rgb-state-group-color: var(--rgb-amber-color);
|
||||
@ -159,17 +164,19 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-state-input-boolean-color: var(--rgb-amber-color);
|
||||
--rgb-state-light-color: var(--rgb-amber-color);
|
||||
--rgb-state-lock-jammed-color: var(--rgb-red-color);
|
||||
--rgb-state-lock-locked-color: var(--rgb-red-color);
|
||||
--rgb-state-lock-locked-color: var(--rgb-green-color);
|
||||
--rgb-state-lock-pending-color: var(--rgb-orange-color);
|
||||
--rgb-state-media-player-color: var(--rgb-indigo-color);
|
||||
--rgb-state-lock-unlocked-color: var(--rgb-red-color);
|
||||
--rgb-state-media-player-color: var(--rgb-light-blue-color);
|
||||
--rgb-state-person-home-color: var(--rgb-green-color);
|
||||
--rgb-state-person-not-home-color: var(--rgb-grey-color);
|
||||
--rgb-state-person-zone-color: var(--rgb-blue-color);
|
||||
--rgb-state-remote-color: var(--rgb-blue-color);
|
||||
--rgb-state-remote-color: var(--rgb-amber-color);
|
||||
--rgb-state-script-color: var(--rgb-amber-color);
|
||||
--rgb-state-sensor-battery-high-color: var(--rgb-green-color);
|
||||
--rgb-state-sensor-battery-low-color: var(--rgb-red-color);
|
||||
--rgb-state-sensor-battery-medium-color: var(--rgb-orange-color);
|
||||
--rgb-state-sensor-battery-unknown-color: var(--rgb-disabled-color);
|
||||
--rgb-state-sensor-battery-unknown-color: var(--rgb-off-color);
|
||||
--rgb-state-siren-color: var(--rgb-red-color);
|
||||
--rgb-state-sun-day-color: var(--rgb-amber-color);
|
||||
--rgb-state-sun-night-color: var(--rgb-deep-purple-color);
|
||||
@ -179,11 +186,6 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--rgb-state-update-installing-color: var(--rgb-orange-color);
|
||||
--rgb-state-vacuum-color: var(--rgb-teal-color);
|
||||
|
||||
/* rgb state badge color */
|
||||
--rgb-badge-person-home-color: var(--rgb-state-person-home-color);
|
||||
--rgb-badge-person-zone-color: var(--rgb-state-person-zone-color);
|
||||
--rgb-badge-person-not-home-color: var(--rgb-red-color);
|
||||
|
||||
/* input components */
|
||||
--input-idle-line-color: rgba(0, 0, 0, 0.42);
|
||||
--input-hover-line-color: rgba(0, 0, 0, 0.87);
|
||||
|
@ -48,7 +48,7 @@ export const darkStyles = {
|
||||
"energy-grid-return-color": "#a280db",
|
||||
"map-filter":
|
||||
"invert(.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(.3)",
|
||||
"rgb-disabled-color": "111, 111, 111",
|
||||
"rgb-disabled-color": "70, 70, 70",
|
||||
};
|
||||
|
||||
export const derivedStyles = {
|
||||
|
@ -3233,7 +3233,8 @@
|
||||
"subscribe_to": "Topic to subscribe to",
|
||||
"start_listening": "Start listening",
|
||||
"stop_listening": "Stop listening",
|
||||
"message_received": "Message {id} received on {topic} at {time}:"
|
||||
"message_received": "Message {id} received on {topic} at {time}:",
|
||||
"qos": "QoS"
|
||||
},
|
||||
"zha": {
|
||||
"common": {
|
||||
@ -4349,6 +4350,7 @@
|
||||
"primary": "Primary",
|
||||
"accent": "Accent",
|
||||
"disabled": "Disabled",
|
||||
"inactive": "Inactive",
|
||||
"red": "Red",
|
||||
"pink": "Pink",
|
||||
"purple": "Purple",
|
||||
|
Loading…
x
Reference in New Issue
Block a user