Merge pull request #10426 from home-assistant/dev

This commit is contained in:
Bram Kragten 2021-10-27 21:16:47 +02:00 committed by GitHub
commit e3c0530941
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 123 additions and 55 deletions

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20211026.0",
version="20211027.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
author="The Home Assistant Authors",

View File

@ -1,4 +1,5 @@
import "@material/mwc-button";
import { genClientId } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
@ -8,18 +9,18 @@ import {
TemplateResult,
} from "lit";
import { property, state } from "lit/decorators";
import "../components/ha-alert";
import "../components/ha-checkbox";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
import "../components/ha-form/ha-form";
import "../components/ha-formfield";
import "../components/ha-markdown";
import "../components/ha-alert";
import { AuthProvider } from "../data/auth";
import {
DataEntryFlowStep,
DataEntryFlowStepForm,
} from "../data/data_entry_flow";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
import "./ha-password-manager-polyfill";
type State = "loading" | "error" | "step";
@ -205,7 +206,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
.computeError=${this._computeErrorCallback(step)}
@value-changed=${this._stepDataChanged}
></ha-form>
${this.clientId === window.location.origin && step.step_id !== "mfa"
${this.clientId === genClientId() && step.step_id !== "mfa"
? html`
<ha-formfield
class="store-token"

View File

@ -1,2 +1,3 @@
/** An empty image which can be set as src of an img element. */
export default "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
export const emptyImageBase64 =
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";

View File

@ -24,6 +24,7 @@ const BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED = new Set([
"plug",
"power",
"presence",
"running",
"update",
]);

View File

@ -102,7 +102,12 @@ export class HaStatisticPicker extends LitElement {
</style>
<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>
<paper-icon-item>
<state-badge slot="item-icon" .stateObj=${item.state}></state-badge>
${item.state
? html`<state-badge
slot="item-icon"
.stateObj=${item.state}
></state-badge>`
: ""}
<paper-item-body two-line="">
${item.name}
<span secondary
@ -153,7 +158,10 @@ export class HaStatisticPicker extends LitElement {
const entityState = this.hass.states[meta.statistic_id];
if (!entityState) {
if (!entitiesOnly) {
output.push({ id: meta.statistic_id, name: meta.statistic_id });
output.push({
id: meta.statistic_id,
name: meta.name || meta.statistic_id,
});
}
return;
}

View File

@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { Person } from "../../data/person";
import { computeInitials } from "./ha-user-badge";
import { computeUserInitials } from "../../data/user";
@customElement("ha-person-badge")
class PersonBadge extends LitElement {
@ -22,7 +22,7 @@ class PersonBadge extends LitElement {
class="picture"
></div>`;
}
const initials = computeInitials(this.person.name);
const initials = computeUserInitials(this.person.name);
return html`<div
class="initials ${classMap({ long: initials!.length > 2 })}"
>

View File

@ -10,25 +10,9 @@ import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { styleMap } from "lit/directives/style-map";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { User } from "../../data/user";
import { computeUserInitials, User } from "../../data/user";
import { CurrentUser, HomeAssistant } from "../../types";
export const computeInitials = (name: string) => {
if (!name) {
return "?";
}
return (
name
.trim()
// Split by space and take first 3 words
.split(" ")
.slice(0, 3)
// Of each word, take first letter
.map((s) => s.substr(0, 1))
.join("")
);
};
@customElement("ha-user-badge")
class UserBadge extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@ -75,7 +59,7 @@ class UserBadge extends LitElement {
class="picture"
></div>`;
}
const initials = computeInitials(this.user.name);
const initials = computeUserInitials(this.user.name);
return html`<div
class="initials ${classMap({ long: initials!.length > 2 })}"
>

View File

@ -74,6 +74,8 @@ export interface StatisticValue {
export interface StatisticsMetaData {
unit_of_measurement: string;
statistic_id: string;
source: string;
name?: string | null;
}
export type StatisticsValidationResult =

View File

@ -57,3 +57,19 @@ export const deleteUser = async (hass: HomeAssistant, userId: string) =>
type: "config/auth/delete",
user_id: userId,
});
export const computeUserInitials = (name: string) => {
if (!name) {
return "?";
}
return (
name
.trim()
// Split by space and take first 3 words
.split(" ")
.slice(0, 3)
// Of each word, take first letter
.map((s) => s.substr(0, 1))
.join("")
);
};

View File

@ -73,7 +73,6 @@ export class DialogEnergyBatterySettings
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_into_battery"
)}
entities-only
@value-changed=${this._statisticToChanged}
></ha-statistic-picker>
@ -85,7 +84,6 @@ export class DialogEnergyBatterySettings
.label=${this.hass.localize(
"ui.panel.config.energy.battery.dialog.energy_out_of_battery"
)}
entities-only
@value-changed=${this._statisticFromChanged}
></ha-statistic-picker>

View File

@ -74,7 +74,6 @@ export class DialogEnergyDeviceSettings
.label=${this.hass.localize(
"ui.panel.config.energy.device_consumption.dialog.device_consumption_energy"
)}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>

View File

@ -106,7 +106,6 @@ export class DialogEnergyGasSettings
? "kWh"
: "m³"
})`}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>

View File

@ -103,7 +103,6 @@ export class DialogEnergyGridFlowSettings
.label=${this.hass.localize(
`ui.panel.config.energy.grid.flow_dialog.${this._params.direction}.energy_stat`
)}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>

View File

@ -85,7 +85,6 @@ export class DialogEnergySolarSettings
.label=${this.hass.localize(
"ui.panel.config.energy.solar.dialog.solar_production_energy"
)}
entities-only
@value-changed=${this._statisticChanged}
></ha-statistic-picker>

View File

@ -50,21 +50,21 @@ class HaPanelDevStatistics extends LitElement {
private _columns = memoizeOne(
(localize): DataTableColumnContainer => ({
state: {
title: "Entity",
title: "Name",
sortable: true,
filterable: true,
grows: true,
template: (entityState, data: any) =>
html`${entityState
? computeStateName(entityState)
: data.statistic_id}`,
: data.name || data.statistic_id}`,
},
statistic_id: {
title: "Statistic id",
sortable: true,
filterable: true,
hidden: this.narrow,
width: "30%",
width: "20%",
},
unit_of_measurement: {
title: "Unit",
@ -72,6 +72,12 @@ class HaPanelDevStatistics extends LitElement {
filterable: true,
width: "10%",
},
source: {
title: "Source",
sortable: true,
filterable: true,
width: "10%",
},
issues: {
title: "Issue",
sortable: true,
@ -146,6 +152,7 @@ class HaPanelDevStatistics extends LitElement {
this._data.push({
statistic_id: statisticId,
unit_of_measurement: "",
source: "",
state: this.hass.states[statisticId],
issues: issues[statisticId],
});

View File

@ -15,7 +15,6 @@ import { formatTimeWithSeconds } from "../../common/datetime/format_time";
import { restoreScroll } from "../../common/decorators/restore-scroll";
import { fireEvent } from "../../common/dom/fire_event";
import { computeDomain } from "../../common/entity/compute_domain";
import { domainIcon } from "../../common/entity/domain_icon";
import { computeRTL, emitRTLDirection } from "../../common/util/compute_rtl";
import "../../components/entity/state-badge";
import "../../components/ha-circular-progress";
@ -151,12 +150,12 @@ class HaLogbook extends LitElement {
html`
<state-badge
.hass=${this.hass}
.overrideIcon=${item.icon ??
domainIcon(domain, stateObj, item.state)}
.overrideIcon=${item.icon}
.overrideImage=${DOMAINS_WITH_DYNAMIC_PICTURE.has(domain)
? ""
: stateObj?.attributes.entity_picture_local ||
stateObj?.attributes.entity_picture}
.stateObj=${stateObj}
></state-badge>
`
: ""}

View File

@ -7,14 +7,12 @@ import {
TemplateResult,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateName } from "../../../common/entity/compute_state_name";
import "../../../components/ha-card";
import { UNAVAILABLE_STATES } from "../../../data/entity";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
@ -135,9 +133,9 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
</div>
`;
} else if (this._config.show_name) {
footer = html`<div class="footer">${name}</div>`;
footer = html`<div class="footer single">${name}</div>`;
} else if (this._config.show_state) {
footer = html`<div class="footer state">${entityState}</div>`;
footer = html`<div class="footer single">${entityState}</div>`;
}
return html`
@ -163,9 +161,6 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
? "0"
: undefined
)}
class=${classMap({
clickable: !UNAVAILABLE_STATES.includes(stateObj.state),
})}
></hui-image>
${footer}
</ha-card>
@ -182,7 +177,7 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
box-sizing: border-box;
}
hui-image.clickable {
hui-image {
cursor: pointer;
}
@ -212,8 +207,8 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
justify-content: space-between;
}
.state {
text-align: right;
.single {
text-align: center;
}
`;
}

View File

@ -78,7 +78,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
}
const configEntities = config.entities
? processConfigEntities(config.entities)
? processConfigEntities(config.entities, false)
: [];
this._entities = [];

View File

@ -16,6 +16,7 @@ import type { EntityRegistryEntry } from "../../../data/entity_registry";
import { domainToName } from "../../../data/integration";
import { LovelaceCardConfig, LovelaceViewConfig } from "../../../data/lovelace";
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../../data/sensor";
import { computeUserInitials } from "../../../data/user";
import {
AlarmPanelCardConfig,
EntitiesCardConfig,
@ -31,6 +32,8 @@ const HIDE_DOMAIN = new Set([
"device_tracker",
"geo_location",
"persistent_notification",
"script",
"sun",
"zone",
]);
@ -230,6 +233,62 @@ export const generateViewConfig = (
let cards: LovelaceCardConfig[] = [];
if ("person" in ungroupedEntitites) {
const personCards: LovelaceCardConfig[] = [];
if (ungroupedEntitites.person.length === 1) {
cards.push({
type: "entities",
entities: ungroupedEntitites.person,
});
} else {
let backgroundColor: string | undefined;
let foregroundColor = "";
for (const personEntityId of ungroupedEntitites.person) {
const stateObj = entities[personEntityId];
let image = stateObj.attributes.entity_picture;
if (!image) {
if (backgroundColor === undefined) {
const computedStyle = getComputedStyle(document.body);
backgroundColor = encodeURIComponent(
computedStyle.getPropertyValue("--light-primary-color").trim()
);
foregroundColor = encodeURIComponent(
(
computedStyle.getPropertyValue("--text-light-primary-color") ||
computedStyle.getPropertyValue("--primary-text-color")
).trim()
);
}
const initials = computeUserInitials(
stateObj.attributes.friendly_name || ""
);
image = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' width='50' height='50' style='background-color:${backgroundColor}'%3E%3Cg%3E%3Ctext font-family='roboto' x='50%25' y='50%25' text-anchor='middle' stroke='${foregroundColor}' font-size='1.3em' dy='.3em'%3E${initials}%3C/text%3E%3C/g%3E%3C/svg%3E`;
}
personCards.push({
type: "picture-entity",
entity: personEntityId,
aspect_ratio: "1",
show_name: false,
image,
});
}
cards.push({
type: "grid",
square: true,
columns: 3,
cards: personCards,
});
}
delete ungroupedEntitites.person;
}
splitted.groups.forEach((groupEntity) => {
cards = cards.concat(
computeCards(

View File

@ -52,7 +52,7 @@ export function hasConfigOrEntitiesChanged(
const oldHass = changedProps.get("hass") as HomeAssistant;
const entities = processConfigEntities(element._config!.entities);
const entities = processConfigEntities(element._config!.entities, false);
return entities.some(
(entity) =>

View File

@ -5,7 +5,8 @@ import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
export const processConfigEntities = <
T extends EntityConfig | LovelaceRowConfig
>(
entities: Array<T | string>
entities: Array<T | string>,
checkEntityId = true
): T[] => {
if (!entities || !Array.isArray(entities)) {
throw new Error("Entities need to be an array");
@ -35,7 +36,7 @@ export const processConfigEntities = <
throw new Error(`Invalid entity specified at position ${index}.`);
}
if (!isValidEntityId((config as EntityConfig).entity!)) {
if (checkEntityId && !isValidEntityId((config as EntityConfig).entity!)) {
throw new Error(
`Invalid entity ID at position ${index}: ${
(config as EntityConfig).entity

View File

@ -192,7 +192,7 @@ export class HuiImage extends LitElement {
: undefined,
backgroundImage:
useRatio && this._loadedImageSrc
? `url(${this._loadedImageSrc})`
? `url("${this._loadedImageSrc}")`
: undefined,
filter:
this._loadState === LoadState.Loaded || this.cameraView === "live"

View File

@ -61,7 +61,7 @@ export class HuiStatisticsGraphCardEditor
assert(config, cardConfigStruct);
this._config = config;
this._configEntities = config.entities
? processConfigEntities(config.entities).map((cfg) => cfg.entity)
? processConfigEntities(config.entities, false).map((cfg) => cfg.entity)
: [];
}