mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-24 21:37:21 +00:00
Merge pull request #10426 from home-assistant/dev
This commit is contained in:
commit
e3c0530941
2
setup.py
2
setup.py
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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";
|
||||
|
@ -24,6 +24,7 @@ const BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED = new Set([
|
||||
"plug",
|
||||
"power",
|
||||
"presence",
|
||||
"running",
|
||||
"update",
|
||||
]);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 })}"
|
||||
>
|
||||
|
@ -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 })}"
|
||||
>
|
||||
|
@ -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 =
|
||||
|
@ -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("")
|
||||
);
|
||||
};
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -106,7 +106,6 @@ export class DialogEnergyGasSettings
|
||||
? "kWh"
|
||||
: "m³"
|
||||
})`}
|
||||
entities-only
|
||||
@value-changed=${this._statisticChanged}
|
||||
></ha-statistic-picker>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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],
|
||||
});
|
||||
|
@ -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>
|
||||
`
|
||||
: ""}
|
||||
|
@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
const configEntities = config.entities
|
||||
? processConfigEntities(config.entities)
|
||||
? processConfigEntities(config.entities, false)
|
||||
: [];
|
||||
|
||||
this._entities = [];
|
||||
|
@ -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(
|
||||
|
@ -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) =>
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
: [];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user