mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-10 13:39:43 +00:00
Compare commits
1 Commits
dev
...
target-sel
Author | SHA1 | Date | |
---|---|---|---|
![]() |
11f65ef0f7 |
@@ -149,7 +149,7 @@ class HassioAddonConfig extends LitElement {
|
||||
)
|
||||
);
|
||||
|
||||
private _filteredSchema = memoizeOne(
|
||||
private _filteredShchema = memoizeOne(
|
||||
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
|
||||
schema.filter((entry) => entry.name in options || entry.required)
|
||||
);
|
||||
@@ -161,7 +161,7 @@ class HassioAddonConfig extends LitElement {
|
||||
showForm &&
|
||||
JSON.stringify(this.addon.schema) !==
|
||||
JSON.stringify(
|
||||
this._filteredSchema(this.addon.options, this.addon.schema!)
|
||||
this._filteredShchema(this.addon.options, this.addon.schema!)
|
||||
);
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
@@ -207,7 +207,7 @@ class HassioAddonConfig extends LitElement {
|
||||
.schema=${this._convertSchema(
|
||||
this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredSchema(
|
||||
: this._filteredShchema(
|
||||
this.addon.options,
|
||||
this.addon.schema!
|
||||
)
|
||||
|
@@ -1,11 +1,3 @@
|
||||
export default {
|
||||
trailingComma: "es5",
|
||||
overrides: [
|
||||
{
|
||||
files: "*.globals.ts",
|
||||
options: {
|
||||
printWidth: 9999, // Effectively disables line wrapping for these files
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@@ -38,7 +38,6 @@ export class HaHeaderBar extends LitElement {
|
||||
.mdc-top-app-bar {
|
||||
position: static;
|
||||
color: var(--mdc-theme-on-primary, #fff);
|
||||
padding: var(--header-bar-padding);
|
||||
}
|
||||
.mdc-top-app-bar__section.mdc-top-app-bar__section--align-start {
|
||||
flex: 1;
|
||||
|
@@ -666,7 +666,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
tooltip.style.display = "block";
|
||||
tooltip.style.position = "fixed";
|
||||
tooltip.style.top = `${top}px`;
|
||||
tooltip.style.left = `calc(${item.offsetLeft + item.clientWidth + 8}px + var(--safe-area-inset-left, 0px))`;
|
||||
tooltip.style.left = `${item.offsetLeft + item.clientWidth + 8}px`;
|
||||
}
|
||||
|
||||
private _hideTooltip() {
|
||||
@@ -705,10 +705,9 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
background-color: var(--sidebar-background-color);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: calc(14px + var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
.menu {
|
||||
height: calc(var(--header-height) + var(--safe-area-inset-top, 0px));
|
||||
height: var(--header-height);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
padding: 0 4px;
|
||||
@@ -726,16 +725,12 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
);
|
||||
font-size: var(--ha-font-size-xl);
|
||||
align-items: center;
|
||||
padding-left: calc(4px + var(--safe-area-inset-left, 0px));
|
||||
padding-inline-start: calc(4px + var(--safe-area-inset-left, 0px));
|
||||
padding-left: calc(4px + var(--safe-area-inset-left));
|
||||
padding-inline-start: calc(4px + var(--safe-area-inset-left));
|
||||
padding-inline-end: initial;
|
||||
padding-top: var(--safe-area-inset-top, 0px);
|
||||
}
|
||||
:host([expanded]) .menu {
|
||||
width: calc(256px + var(--safe-area-inset-left, 0px));
|
||||
}
|
||||
:host([narrow][expanded]) .menu {
|
||||
width: 100%;
|
||||
width: calc(256px + var(--safe-area-inset-left));
|
||||
}
|
||||
.menu ha-icon-button {
|
||||
color: var(--sidebar-icon-color);
|
||||
@@ -761,23 +756,22 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
ha-fade-in,
|
||||
ha-md-list {
|
||||
height: calc(
|
||||
100% - var(--header-height) - var(--safe-area-inset-top, 0px) -
|
||||
132px - var(--safe-area-inset-bottom, 0px)
|
||||
100% - var(--header-height) - 132px - var(--safe-area-inset-bottom)
|
||||
);
|
||||
}
|
||||
|
||||
ha-fade-in {
|
||||
padding: 4px 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding: 4px 0;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
background: none;
|
||||
margin-left: var(--safe-area-inset-left, 0px);
|
||||
margin-left: var(--safe-area-inset-left);
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
@@ -797,9 +791,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
:host([expanded]) ha-md-list-item {
|
||||
width: 248px;
|
||||
}
|
||||
:host([narrow][expanded]) ha-md-list-item {
|
||||
width: calc(240px - var(--safe-area-inset-left, 0px));
|
||||
width: calc(248px - var(--safe-area-inset-left));
|
||||
}
|
||||
|
||||
ha-md-list-item.selected {
|
||||
|
@@ -1,39 +1,16 @@
|
||||
// @ts-ignore
|
||||
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
|
||||
import "@material/mwc-menu/mwc-menu-surface";
|
||||
import {
|
||||
mdiClose,
|
||||
mdiDevices,
|
||||
mdiHome,
|
||||
mdiLabel,
|
||||
mdiPlus,
|
||||
mdiTextureBox,
|
||||
mdiUnfoldMoreVertical,
|
||||
} from "@mdi/js";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import type { ComboBoxLightOpenedChangedEvent } from "@vaadin/combo-box/vaadin-combo-box-light";
|
||||
import type {
|
||||
HassEntity,
|
||||
HassServiceTarget,
|
||||
UnsubscribeFunc,
|
||||
} from "home-assistant-js-websocket";
|
||||
import type { HassServiceTarget } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing, unsafeCSS } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeCssColor } from "../common/color/compute-color";
|
||||
import { hex2rgb } from "../common/color/convert-color";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { computeDeviceNameDisplay } from "../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { isValidEntityId } from "../common/entity/valid_entity_id";
|
||||
import type { AreaRegistryEntry } from "../data/area_registry";
|
||||
import type { DeviceRegistryEntry } from "../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../data/entity_registry";
|
||||
import type { LabelRegistryEntry } from "../data/label_registry";
|
||||
import { subscribeLabelRegistry } from "../data/label_registry";
|
||||
import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./device/ha-device-picker";
|
||||
@@ -41,12 +18,13 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||
import "./entity/ha-entity-picker";
|
||||
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
||||
import "./ha-area-floor-picker";
|
||||
import { floorDefaultIconPath } from "./ha-floor-icon";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-input-helper-text";
|
||||
import "./ha-label-picker";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-tooltip";
|
||||
import "./target-picker/ha-target-picker-item-group";
|
||||
import type { TargetType } from "./target-picker/ha-target-picker-item-row";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@@ -58,6 +36,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public compact = false;
|
||||
|
||||
@property({ attribute: false, type: Array }) public createDomains?: string[];
|
||||
|
||||
/**
|
||||
@@ -96,18 +76,8 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@query(".add-container", true) private _addContainer?: HTMLDivElement;
|
||||
|
||||
@state() private _labels?: LabelRegistryEntry[];
|
||||
|
||||
private _opened = false;
|
||||
|
||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||
return [
|
||||
subscribeLabelRegistry(this.hass.connection, (labels) => {
|
||||
this._labels = labels;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (this.addOnTop) {
|
||||
return html` ${this._renderChips()} ${this._renderItems()} `;
|
||||
@@ -116,87 +86,82 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _renderItems() {
|
||||
if (
|
||||
!this.value?.floor_id &&
|
||||
!this.value?.area_id &&
|
||||
!this.value?.device_id &&
|
||||
!this.value?.entity_id &&
|
||||
!this.value?.label_id
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="mdc-chip-set items">
|
||||
<div class="item-groups">
|
||||
${this.value?.floor_id
|
||||
? ensureArray(this.value.floor_id).map((floor_id) => {
|
||||
const floor = this.hass.floors[floor_id];
|
||||
return this._renderChip(
|
||||
"floor_id",
|
||||
floor_id,
|
||||
floor?.name || floor_id,
|
||||
undefined,
|
||||
floor?.icon,
|
||||
floor ? floorDefaultIconPath(floor) : mdiHome
|
||||
);
|
||||
})
|
||||
: ""}
|
||||
? html`
|
||||
<ha-target-picker-item-group
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@remove-target-group=${this._handleRemoveGroup}
|
||||
type="floor"
|
||||
.hass=${this.hass}
|
||||
.items=${ensureArray(this.value?.floor_id)}
|
||||
.collapsed=${this.compact}
|
||||
>
|
||||
</ha-target-picker-item-group>
|
||||
`
|
||||
: nothing}
|
||||
${this.value?.area_id
|
||||
? ensureArray(this.value.area_id).map((area_id) => {
|
||||
const area = this.hass.areas![area_id];
|
||||
return this._renderChip(
|
||||
"area_id",
|
||||
area_id,
|
||||
area?.name || area_id,
|
||||
undefined,
|
||||
area?.icon,
|
||||
mdiTextureBox
|
||||
);
|
||||
})
|
||||
? html`
|
||||
<ha-target-picker-item-group
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@remove-target-group=${this._handleRemoveGroup}
|
||||
type="area"
|
||||
.hass=${this.hass}
|
||||
.items=${ensureArray(this.value?.area_id)}
|
||||
.collapsed=${this.compact}
|
||||
>
|
||||
</ha-target-picker-item-group>
|
||||
`
|
||||
: nothing}
|
||||
${this.value?.device_id
|
||||
? ensureArray(this.value.device_id).map((device_id) => {
|
||||
const device = this.hass.devices![device_id];
|
||||
return this._renderChip(
|
||||
"device_id",
|
||||
device_id,
|
||||
device
|
||||
? computeDeviceNameDisplay(device, this.hass)
|
||||
: device_id,
|
||||
undefined,
|
||||
undefined,
|
||||
mdiDevices
|
||||
);
|
||||
})
|
||||
? html`
|
||||
<ha-target-picker-item-group
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@remove-target-group=${this._handleRemoveGroup}
|
||||
type="device"
|
||||
.hass=${this.hass}
|
||||
.items=${ensureArray(this.value?.device_id)}
|
||||
.collapsed=${this.compact}
|
||||
>
|
||||
</ha-target-picker-item-group>
|
||||
`
|
||||
: nothing}
|
||||
${this.value?.entity_id
|
||||
? ensureArray(this.value.entity_id).map((entity_id) => {
|
||||
const entity = this.hass.states[entity_id];
|
||||
return this._renderChip(
|
||||
"entity_id",
|
||||
entity_id,
|
||||
entity ? computeStateName(entity) : entity_id,
|
||||
entity
|
||||
);
|
||||
})
|
||||
? html`
|
||||
<ha-target-picker-item-group
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@remove-target-group=${this._handleRemoveGroup}
|
||||
type="entity"
|
||||
.hass=${this.hass}
|
||||
.items=${ensureArray(this.value?.entity_id)}
|
||||
.collapsed=${this.compact}
|
||||
>
|
||||
</ha-target-picker-item-group>
|
||||
`
|
||||
: nothing}
|
||||
${this.value?.label_id
|
||||
? ensureArray(this.value.label_id).map((label_id) => {
|
||||
const label = this._labels?.find(
|
||||
(lbl) => lbl.label_id === label_id
|
||||
);
|
||||
let color = label?.color
|
||||
? computeCssColor(label.color)
|
||||
: undefined;
|
||||
if (color?.startsWith("var(")) {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
color = computedStyles.getPropertyValue(
|
||||
color.substring(4, color.length - 1)
|
||||
);
|
||||
}
|
||||
if (color?.startsWith("#")) {
|
||||
color = hex2rgb(color).join(",");
|
||||
}
|
||||
return this._renderChip(
|
||||
"label_id",
|
||||
label_id,
|
||||
label ? label.name : label_id,
|
||||
undefined,
|
||||
label?.icon,
|
||||
mdiLabel,
|
||||
color
|
||||
);
|
||||
})
|
||||
? html`
|
||||
<ha-target-picker-item-group
|
||||
@remove-target-item=${this._handleRemove}
|
||||
@remove-target-group=${this._handleRemoveGroup}
|
||||
type="label"
|
||||
.hass=${this.hass}
|
||||
.items=${ensureArray(this.value?.label_id)}
|
||||
.collapsed=${this.compact}
|
||||
>
|
||||
</ha-target-picker-item-group>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
@@ -299,89 +264,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
this._addMode = ev.currentTarget.type;
|
||||
}
|
||||
|
||||
private _renderChip(
|
||||
type: "floor_id" | "area_id" | "device_id" | "entity_id" | "label_id",
|
||||
id: string,
|
||||
name: string,
|
||||
entityState?: HassEntity,
|
||||
icon?: string | null,
|
||||
fallbackIconPath?: string,
|
||||
color?: string
|
||||
) {
|
||||
return html`
|
||||
<div
|
||||
class="mdc-chip ${classMap({
|
||||
[type]: true,
|
||||
})}"
|
||||
style=${color
|
||||
? `--color: rgb(${color}); --background-color: rgba(${color}, .5)`
|
||||
: ""}
|
||||
>
|
||||
${icon
|
||||
? html`<ha-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.icon=${icon}
|
||||
></ha-icon>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.path=${fallbackIconPath}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${entityState
|
||||
? html`<ha-state-icon
|
||||
class="mdc-chip__icon mdc-chip__icon--leading"
|
||||
.hass=${this.hass}
|
||||
.stateObj=${entityState}
|
||||
></ha-state-icon>`
|
||||
: ""}
|
||||
<span role="gridcell">
|
||||
<span role="button" tabindex="0" class="mdc-chip__primary-action">
|
||||
<span class="mdc-chip__text">${name}</span>
|
||||
</span>
|
||||
</span>
|
||||
${type === "entity_id"
|
||||
? ""
|
||||
: html`<span role="gridcell">
|
||||
<ha-tooltip
|
||||
.content=${this.hass.localize(
|
||||
`ui.components.target-picker.expand_${type}`
|
||||
)}
|
||||
>
|
||||
<ha-icon-button
|
||||
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.expand"
|
||||
)}
|
||||
.path=${mdiUnfoldMoreVertical}
|
||||
hide-title
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleExpand}
|
||||
></ha-icon-button>
|
||||
</ha-tooltip>
|
||||
</span>`}
|
||||
<span role="gridcell">
|
||||
<ha-tooltip
|
||||
.content=${this.hass.localize(
|
||||
`ui.components.target-picker.remove_${type}`
|
||||
)}
|
||||
>
|
||||
<ha-icon-button
|
||||
class="mdc-chip__icon mdc-chip__icon--trailing"
|
||||
.label=${this.hass.localize("ui.components.target-picker.remove")}
|
||||
.path=${mdiClose}
|
||||
hide-title
|
||||
.id=${id}
|
||||
.type=${type}
|
||||
@click=${this._handleRemove}
|
||||
></ha-icon-button>
|
||||
</ha-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderPicker() {
|
||||
if (!this._addMode) {
|
||||
return nothing;
|
||||
@@ -524,130 +406,44 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleExpand(ev) {
|
||||
const target = ev.currentTarget as any;
|
||||
const newAreas: string[] = [];
|
||||
const newDevices: string[] = [];
|
||||
const newEntities: string[] = [];
|
||||
|
||||
if (target.type === "floor_id") {
|
||||
Object.values(this.hass.areas).forEach((area) => {
|
||||
if (
|
||||
area.floor_id === target.id &&
|
||||
!this.value!.area_id?.includes(area.area_id) &&
|
||||
this._areaMeetsFilter(area)
|
||||
) {
|
||||
newAreas.push(area.area_id);
|
||||
}
|
||||
});
|
||||
} else if (target.type === "area_id") {
|
||||
Object.values(this.hass.devices).forEach((device) => {
|
||||
if (
|
||||
device.area_id === target.id &&
|
||||
!this.value!.device_id?.includes(device.id) &&
|
||||
this._deviceMeetsFilter(device)
|
||||
) {
|
||||
newDevices.push(device.id);
|
||||
}
|
||||
});
|
||||
Object.values(this.hass.entities).forEach((entity) => {
|
||||
if (
|
||||
entity.area_id === target.id &&
|
||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||
this._entityRegMeetsFilter(entity)
|
||||
) {
|
||||
newEntities.push(entity.entity_id);
|
||||
}
|
||||
});
|
||||
} else if (target.type === "device_id") {
|
||||
Object.values(this.hass.entities).forEach((entity) => {
|
||||
if (
|
||||
entity.device_id === target.id &&
|
||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||
this._entityRegMeetsFilter(entity)
|
||||
) {
|
||||
newEntities.push(entity.entity_id);
|
||||
}
|
||||
});
|
||||
} else if (target.type === "label_id") {
|
||||
Object.values(this.hass.areas).forEach((area) => {
|
||||
if (
|
||||
area.labels.includes(target.id) &&
|
||||
!this.value!.area_id?.includes(area.area_id) &&
|
||||
this._areaMeetsFilter(area)
|
||||
) {
|
||||
newAreas.push(area.area_id);
|
||||
}
|
||||
});
|
||||
Object.values(this.hass.devices).forEach((device) => {
|
||||
if (
|
||||
device.labels.includes(target.id) &&
|
||||
!this.value!.device_id?.includes(device.id) &&
|
||||
this._deviceMeetsFilter(device)
|
||||
) {
|
||||
newDevices.push(device.id);
|
||||
}
|
||||
});
|
||||
Object.values(this.hass.entities).forEach((entity) => {
|
||||
if (
|
||||
entity.labels.includes(target.id) &&
|
||||
!this.value!.entity_id?.includes(entity.entity_id) &&
|
||||
this._entityRegMeetsFilter(entity)
|
||||
) {
|
||||
newEntities.push(entity.entity_id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let value = this.value;
|
||||
if (newEntities.length) {
|
||||
value = this._addItems(value, "entity_id", newEntities);
|
||||
}
|
||||
if (newDevices.length) {
|
||||
value = this._addItems(value, "device_id", newDevices);
|
||||
}
|
||||
if (newAreas.length) {
|
||||
value = this._addItems(value, "area_id", newAreas);
|
||||
}
|
||||
value = this._removeItem(value, target.type, target.id);
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
private _handleRemove(ev) {
|
||||
const target = ev.currentTarget as any;
|
||||
const { type, id } = ev.detail;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this._removeItem(this.value, target.type, target.id),
|
||||
value: this._removeItem(this.value, type, id),
|
||||
});
|
||||
}
|
||||
|
||||
private _addItems(
|
||||
value: this["value"],
|
||||
type: string,
|
||||
ids: string[]
|
||||
): this["value"] {
|
||||
return {
|
||||
...value,
|
||||
[type]: value![type] ? ensureArray(value![type])!.concat(ids) : ids,
|
||||
};
|
||||
private _handleRemoveGroup(ev) {
|
||||
const type = ev.detail;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this._removeGroup(type),
|
||||
});
|
||||
}
|
||||
|
||||
private _removeGroup(type: TargetType): this["value"] {
|
||||
const newVal = { ...this.value };
|
||||
delete newVal[`${type}_id`];
|
||||
return newVal;
|
||||
}
|
||||
|
||||
private _removeItem(
|
||||
value: this["value"],
|
||||
type: string,
|
||||
type: TargetType,
|
||||
id: string
|
||||
): this["value"] {
|
||||
const newVal = ensureArray(value![type])!.filter(
|
||||
const typeId = `${type}_id`;
|
||||
|
||||
const newVal = ensureArray(value![typeId])!.filter(
|
||||
(val) => String(val) !== id
|
||||
);
|
||||
if (newVal.length) {
|
||||
return {
|
||||
...value,
|
||||
[type]: newVal,
|
||||
[typeId]: newVal,
|
||||
};
|
||||
}
|
||||
const val = { ...value }!;
|
||||
delete val[type];
|
||||
delete val[typeId];
|
||||
if (Object.keys(val).length) {
|
||||
return val;
|
||||
}
|
||||
@@ -679,80 +475,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
private _areaMeetsFilter(area: AreaRegistryEntry): boolean {
|
||||
const areaDevices = Object.values(this.hass.devices).filter(
|
||||
(device) => device.area_id === area.area_id
|
||||
);
|
||||
|
||||
if (areaDevices.some((device) => this._deviceMeetsFilter(device))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const areaEntities = Object.values(this.hass.entities).filter(
|
||||
(entity) => entity.area_id === area.area_id
|
||||
);
|
||||
|
||||
if (areaEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _deviceMeetsFilter(device: DeviceRegistryEntry): boolean {
|
||||
const devEntities = Object.values(this.hass.entities).filter(
|
||||
(entity) => entity.device_id === device.id
|
||||
);
|
||||
|
||||
if (!devEntities.some((entity) => this._entityRegMeetsFilter(entity))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.deviceFilter) {
|
||||
if (!this.deviceFilter(device)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _entityRegMeetsFilter(entity: EntityRegistryDisplayEntry): boolean {
|
||||
if (entity.hidden || entity.entity_category) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.includeDomains &&
|
||||
!this.includeDomains.includes(computeDomain(entity.entity_id))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (this.includeDeviceClasses) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
!stateObj.attributes.device_class ||
|
||||
!this.includeDeviceClasses!.includes(stateObj.attributes.device_class)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.entityFilter) {
|
||||
const stateObj = this.hass.states[entity.entity_id];
|
||||
if (!stateObj) {
|
||||
return false;
|
||||
}
|
||||
if (!this.entityFilter!(stateObj)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
${unsafeCSS(chipStyles)}
|
||||
@@ -811,41 +533,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
margin-inline-end: 0;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
.mdc-chip.area_id:not(.add),
|
||||
.mdc-chip.floor_id:not(.add) {
|
||||
border: 1px solid #fed6a4;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.area_id.add,
|
||||
.mdc-chip.floor_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.floor_id.add {
|
||||
background: #fed6a4;
|
||||
}
|
||||
.mdc-chip.device_id:not(.add) {
|
||||
border: 1px solid #a8e1fb;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.device_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.device_id.add {
|
||||
background: #a8e1fb;
|
||||
}
|
||||
.mdc-chip.entity_id:not(.add) {
|
||||
border: 1px solid #d2e7b9;
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.entity_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.entity_id.add {
|
||||
background: #d2e7b9;
|
||||
}
|
||||
.mdc-chip.label_id:not(.add) {
|
||||
border: 1px solid var(--color, #e0e0e0);
|
||||
background: var(--card-background-color);
|
||||
}
|
||||
.mdc-chip.label_id:not(.add) .mdc-chip__icon--leading,
|
||||
.mdc-chip.label_id.add {
|
||||
background: var(--background-color, #e0e0e0);
|
||||
}
|
||||
.mdc-chip:hover {
|
||||
z-index: 5;
|
||||
}
|
||||
@@ -865,6 +552,12 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
ha-tooltip {
|
||||
--ha-tooltip-arrow-size: 0;
|
||||
}
|
||||
|
||||
.item-groups {
|
||||
overflow: hidden;
|
||||
border: 2px solid var(--divider-color);
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
@@ -873,4 +566,12 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-target-picker": HaTargetPicker;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"remove-target-item": {
|
||||
type: string;
|
||||
id: string;
|
||||
};
|
||||
"remove-target-group": string;
|
||||
}
|
||||
}
|
||||
|
101
src/components/target-picker/ha-target-picker-item-group.ts
Normal file
101
src/components/target-picker/ha-target-picker-item-group.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { mdiBroom, mdiMinus, mdiPlus } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-md-list";
|
||||
import "./ha-target-picker-item-row";
|
||||
|
||||
@customElement("ha-target-picker-item-group")
|
||||
export class HaTargetPickerItemGroup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public type!: "entity" | "device" | "area" | "label" | "floor";
|
||||
|
||||
@property({ attribute: false }) public items!: string[];
|
||||
|
||||
@property({ type: Boolean }) public collapsed = false;
|
||||
|
||||
protected render() {
|
||||
return html` <div class="heading">
|
||||
${this.hass.localize(
|
||||
`ui.components.target-picker.selected.${this.type}`,
|
||||
{
|
||||
count: this.items.length,
|
||||
}
|
||||
)}
|
||||
<div class="icons">
|
||||
<ha-icon-button
|
||||
title=${this.hass.localize(
|
||||
`ui.components.target-picker.remove_${this.type}s`
|
||||
)}
|
||||
.path=${mdiBroom}
|
||||
@click=${this._removeGroup}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
title=${this.hass.localize(
|
||||
this.collapsed
|
||||
? "ui.components.target-picker.expand"
|
||||
: "ui.components.target-picker.collapse"
|
||||
)}
|
||||
.path=${this.collapsed ? mdiPlus : mdiMinus}
|
||||
@click=${this._toggleCollapsed}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
${this.collapsed
|
||||
? nothing
|
||||
: html`
|
||||
<ha-md-list>
|
||||
${this.items.map(
|
||||
(item) =>
|
||||
html`
|
||||
<ha-target-picker-item-row
|
||||
.hass=${this.hass}
|
||||
.type=${this.type}
|
||||
.item=${item}
|
||||
></ha-target-picker-item-row>
|
||||
</ha-md-list-item>`
|
||||
)}
|
||||
</ha-md-list>
|
||||
`}`;
|
||||
}
|
||||
|
||||
private _toggleCollapsed() {
|
||||
this.collapsed = !this.collapsed;
|
||||
}
|
||||
|
||||
private _removeGroup() {
|
||||
fireEvent(this, "remove-target-group", this.type);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.heading {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
padding: 4px 8px;
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
color: var(--secondary-text-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.icons {
|
||||
display: flex;
|
||||
}
|
||||
.icons ha-icon-button {
|
||||
--mdc-icon-size: 16px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-target-picker-item-group": HaTargetPickerItemGroup;
|
||||
}
|
||||
}
|
359
src/components/target-picker/ha-target-picker-item-row.ts
Normal file
359
src/components/target-picker/ha-target-picker-item-row.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiClose,
|
||||
mdiDevices,
|
||||
mdiHome,
|
||||
mdiTextureBox,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../common/entity/compute_area_name";
|
||||
import {
|
||||
computeDeviceName,
|
||||
computeDeviceNameDisplay,
|
||||
} from "../../common/entity/compute_device_name";
|
||||
import { computeEntityName } from "../../common/entity/compute_entity_name";
|
||||
import { getEntityContext } from "../../common/entity/context/get_entity_context";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { getConfigEntry } from "../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../data/device_registry";
|
||||
import type { EntityRegistryDisplayEntry } from "../../data/entity_registry";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { brandsUrl } from "../../util/brands-url";
|
||||
import { floorDefaultIconPath } from "../ha-floor-icon";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-md-list";
|
||||
import "../ha-md-list-item";
|
||||
import "../ha-state-icon";
|
||||
|
||||
export type TargetType = "entity" | "device" | "area" | "label" | "floor";
|
||||
|
||||
@customElement("ha-target-picker-item-row")
|
||||
export class HaTargetPickerItemRow extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public type!: TargetType;
|
||||
|
||||
@property() public item!: string;
|
||||
|
||||
@state() private _expanded = false;
|
||||
|
||||
@state() private _iconImg?: string;
|
||||
|
||||
@state() private _domainName?: string;
|
||||
|
||||
protected render() {
|
||||
const { name, context, iconPath, fallbackIconPath, stateObject } =
|
||||
this._itemData(this.type, this.item);
|
||||
|
||||
const showDevices = ["floor", "area", "label"].includes(this.type);
|
||||
const showEntities = this.type !== "entity";
|
||||
|
||||
let devices: DeviceRegistryEntry[] = [];
|
||||
|
||||
if (showDevices) {
|
||||
devices = this._getDevices(this.hass.devices, this.type, this.item);
|
||||
}
|
||||
|
||||
let entities: EntityRegistryDisplayEntry[] = [];
|
||||
if (showEntities) {
|
||||
entities = this._getEntities(
|
||||
devices,
|
||||
this.hass.entities,
|
||||
this.type,
|
||||
this.item
|
||||
);
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-md-list-item>
|
||||
${this.type !== "entity"
|
||||
? html`<ha-icon-button
|
||||
class="expand-button ${classMap({
|
||||
expanded: entities.length && this._expanded,
|
||||
})}"
|
||||
.path=${mdiChevronDown}
|
||||
slot="start"
|
||||
@click=${this._toggleExpand}
|
||||
.disabled=${entities.length === 0}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
${iconPath
|
||||
? html`<ha-icon slot="start" .icon=${iconPath}></ha-icon>`
|
||||
: this._iconImg
|
||||
? html`<img
|
||||
slot="start"
|
||||
alt=${this._domainName || ""}
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
src=${this._iconImg}
|
||||
/>`
|
||||
: fallbackIconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${fallbackIconPath}
|
||||
></ha-svg-icon>`
|
||||
: stateObject
|
||||
? html`
|
||||
<ha-state-icon
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObject}
|
||||
slot="start"
|
||||
>
|
||||
</ha-state-icon>
|
||||
`
|
||||
: nothing}
|
||||
<div slot="headline">${name}</div>
|
||||
${context
|
||||
? html`<span slot="supporting-text">${context}</span>`
|
||||
: nothing}
|
||||
${showEntities || showDevices || this._domainName
|
||||
? html`
|
||||
<div slot="end" class="summary">
|
||||
${showEntities
|
||||
? html`<span class="main"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.entities_count",
|
||||
{
|
||||
count: entities.length,
|
||||
}
|
||||
)}</span
|
||||
>`
|
||||
: nothing}
|
||||
${showDevices
|
||||
? html`<span class="secondary"
|
||||
>${this.hass.localize(
|
||||
"ui.components.target-picker.devices_count",
|
||||
{
|
||||
count: devices.length,
|
||||
}
|
||||
)}</span
|
||||
>`
|
||||
: nothing}
|
||||
${this._domainName && !showDevices
|
||||
? html`<span class="secondary domain"
|
||||
>${this._domainName}</span
|
||||
>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-icon-button
|
||||
.path=${mdiClose}
|
||||
slot="end"
|
||||
@click=${this._removeItem}
|
||||
></ha-icon-button>
|
||||
</ha-md-list-item>
|
||||
${this._expanded
|
||||
? html`
|
||||
${entities.map(
|
||||
(entity) =>
|
||||
html`<ha-md-list-item class="indent">
|
||||
<div slot="headline">
|
||||
${computeEntityName(
|
||||
this.hass.states[entity.entity_id],
|
||||
this.hass
|
||||
)}
|
||||
</div>
|
||||
<span slot="supporting-text">${entity.entity_id}</span>
|
||||
</ha-md-list-item>`
|
||||
)}
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
private _itemData = memoizeOne((type: TargetType, item: string) => {
|
||||
if (type === "floor") {
|
||||
const floor = this.hass.floors?.[item];
|
||||
return {
|
||||
name: floor?.name || item,
|
||||
iconPath: floor?.icon,
|
||||
fallbackIconPath: floor ? floorDefaultIconPath(floor) : mdiHome,
|
||||
};
|
||||
}
|
||||
if (type === "area") {
|
||||
const area = this.hass.areas?.[item];
|
||||
return {
|
||||
name: area?.name || item,
|
||||
context: area.floor_id && this.hass.floors?.[area.floor_id]?.name,
|
||||
iconPath: area?.icon,
|
||||
fallbackIconPath: mdiTextureBox,
|
||||
};
|
||||
}
|
||||
if (type === "device") {
|
||||
const device = this.hass.devices?.[item];
|
||||
|
||||
if (device.primary_config_entry) {
|
||||
this._getDomainIcon(device.primary_config_entry);
|
||||
}
|
||||
|
||||
return {
|
||||
name: device ? computeDeviceNameDisplay(device, this.hass) : item,
|
||||
context: device?.area_id && this.hass.areas?.[device.area_id]?.name,
|
||||
fallbackIconPath: mdiDevices,
|
||||
};
|
||||
}
|
||||
if (type === "entity") {
|
||||
const stateObject = this.hass.states[item];
|
||||
const entityName = computeEntityName(stateObject, this.hass);
|
||||
const { area, device } = getEntityContext(stateObject, this.hass);
|
||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
const context = [areaName, entityName ? deviceName : undefined]
|
||||
.filter(Boolean)
|
||||
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
|
||||
return {
|
||||
name: entityName || deviceName || item,
|
||||
context,
|
||||
stateObject,
|
||||
};
|
||||
}
|
||||
return { name: item };
|
||||
});
|
||||
|
||||
private _removeItem() {
|
||||
fireEvent(this, "remove-target-item", {
|
||||
type: this.type,
|
||||
id: this.item,
|
||||
});
|
||||
}
|
||||
|
||||
private _getDevices = memoizeOne(
|
||||
(
|
||||
devices: HomeAssistant["devices"],
|
||||
type: TargetType,
|
||||
item: string
|
||||
): DeviceRegistryEntry[] =>
|
||||
Object.values(devices).filter((device) => {
|
||||
if (!device.area_id) {
|
||||
return false;
|
||||
}
|
||||
if (type === "area") {
|
||||
return device.area_id === item;
|
||||
}
|
||||
if (type === "label") {
|
||||
return device.labels?.includes(item);
|
||||
}
|
||||
|
||||
return this.hass.areas[device.area_id]?.floor_id === item;
|
||||
})
|
||||
);
|
||||
|
||||
private _getEntities = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
entities: HomeAssistant["entities"],
|
||||
type: TargetType,
|
||||
item: string
|
||||
): EntityRegistryDisplayEntry[] => {
|
||||
const deviceIds = devices.map((device) => device.id);
|
||||
|
||||
return Object.values(entities).filter((entity) => {
|
||||
if (entity.hidden || entity.entity_category) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deviceIds.includes(entity.device_id || "")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === "area") {
|
||||
return entity.area_id === item;
|
||||
}
|
||||
if (entity.area_id && type === "floor") {
|
||||
return this.hass.areas[entity.area_id]?.floor_id === item;
|
||||
}
|
||||
if (type === "device") {
|
||||
return entity.device_id === item;
|
||||
}
|
||||
if (type === "label") {
|
||||
return entity.labels?.includes(item);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private async _getDomainIcon(configEntryId: string) {
|
||||
try {
|
||||
const data = await getConfigEntry(this.hass, configEntryId);
|
||||
const domain = data.config_entry.domain;
|
||||
this._iconImg = brandsUrl({
|
||||
domain: domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
});
|
||||
this._domainName = domain
|
||||
? domainToName(this.hass.localize, domain)
|
||||
: undefined;
|
||||
} catch {
|
||||
// failed to load config entry -> ignore
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleExpand() {
|
||||
this._expanded = !this._expanded;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-md-list-item {
|
||||
--md-list-item-top-space: 0;
|
||||
--md-list-item-bottom-space: 0;
|
||||
--md-list-item-trailing-space: 8px;
|
||||
}
|
||||
state-badge {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.expand-button {
|
||||
margin: 0 -12px;
|
||||
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.expand-button.expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 32px;
|
||||
}
|
||||
.summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
.summary .main {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
.summary .secondary {
|
||||
font-size: var(--ha-font-size-s);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.summary .secondary.domain {
|
||||
font-family: var(--ha-font-family-code);
|
||||
}
|
||||
|
||||
ha-md-list-item.indent {
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
ha-md-list-item.indent:last-of-type {
|
||||
border-bottom: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-target-picker-item-row": HaTargetPickerItemRow;
|
||||
}
|
||||
}
|
@@ -1,14 +1,8 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { Selector } from "./selector";
|
||||
|
||||
export const enum AITaskEntityFeature {
|
||||
GENERATE_DATA = 1,
|
||||
SUPPORT_ATTACHMENTS = 2,
|
||||
GENERATE_IMAGE = 4,
|
||||
}
|
||||
export interface AITaskPreferences {
|
||||
gen_data_entity_id: string | null;
|
||||
gen_image_entity_id: string | null;
|
||||
}
|
||||
|
||||
export interface GenDataTask {
|
||||
|
@@ -246,7 +246,7 @@ export const clearStatistics = (hass: HomeAssistant, statistic_ids: string[]) =>
|
||||
});
|
||||
|
||||
export const calculateStatisticSumGrowth = (
|
||||
values?: StatisticValue[]
|
||||
values: StatisticValue[]
|
||||
): number | null => {
|
||||
let growth: number | null = null;
|
||||
|
||||
|
@@ -49,7 +49,7 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
);
|
||||
this.style.setProperty(
|
||||
"--mdc-drawer-width",
|
||||
`min(100vw, calc(${narrow ? window.innerWidth + "px" : "500px"} + var(--safe-area-inset-left, 0px)))`
|
||||
narrow ? window.innerWidth + "px" : "500px"
|
||||
);
|
||||
this._open = true;
|
||||
}
|
||||
@@ -152,40 +152,24 @@ export class HuiNotificationDrawer extends LitElement {
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--primary-background-color);
|
||||
--header-bar-padding: var(--safe-area-inset-top, 0px) 0 0
|
||||
var(--safe-area-inset-left, 0px);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-header-bar {
|
||||
--header-bar-padding: var(--safe-area-inset-top, 0px)
|
||||
var(--safe-area-inset-right, 0px) 0 var(--safe-area-inset-left, 0px);
|
||||
}
|
||||
}
|
||||
|
||||
.notifications {
|
||||
overflow-y: auto;
|
||||
padding-top: 16px;
|
||||
padding-left: var(--safe-area-inset-left, 0px);
|
||||
padding-inline-start: var(--safe-area-inset-left, 0px);
|
||||
padding-bottom: var(--safe-area-inset-bottom, 0px);
|
||||
height: calc(
|
||||
100% - 1px - var(--header-height) - var(--safe-area-inset-top, 0px)
|
||||
);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
padding-inline-start: var(--safe-area-inset-left);
|
||||
padding-inline-end: var(--safe-area-inset-right);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
height: calc(100% - 1px - var(--header-height));
|
||||
box-sizing: border-box;
|
||||
background-color: var(--primary-background-color);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.notifications {
|
||||
padding-right: var(--safe-area-inset-right, 0px);
|
||||
padding-inline-end: var(--safe-area-inset-right, 0px);
|
||||
}
|
||||
}
|
||||
|
||||
.notification {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
@@ -144,13 +144,13 @@ export class HomeAssistantMain extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
--mdc-drawer-width: calc(56px + var(--safe-area-inset-left, 0px));
|
||||
--mdc-drawer-width: 56px;
|
||||
--mdc-top-app-bar-width: calc(100% - var(--mdc-drawer-width));
|
||||
--safe-area-content-inset-left: 0px;
|
||||
--safe-area-content-inset-right: var(--safe-area-inset-right);
|
||||
}
|
||||
:host([expanded]) {
|
||||
--mdc-drawer-width: calc(256px + var(--safe-area-inset-left, 0px));
|
||||
--mdc-drawer-width: calc(256px + var(--safe-area-inset-left));
|
||||
}
|
||||
:host([modal]) {
|
||||
--mdc-drawer-width: unset;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import type { LitElement } from "lit";
|
||||
import type { Constructor } from "../types";
|
||||
import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
|
||||
|
||||
declare global {
|
||||
type SupportedShortcuts = Record<string, () => void>;
|
||||
@@ -18,14 +17,6 @@ export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
|
||||
!event.altKey &&
|
||||
event.key in supportedShortcuts
|
||||
) {
|
||||
// Only capture the event if the user is not focused on an input
|
||||
if (!canOverrideAlphanumericInput(event.composedPath())) {
|
||||
return;
|
||||
}
|
||||
// Don't capture the event if the user is selecting text
|
||||
if (window.getSelection()?.toString()) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
supportedShortcuts[event.key]();
|
||||
return;
|
||||
|
@@ -50,9 +50,7 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
disabled:
|
||||
!this.indent &&
|
||||
(this.disabled ||
|
||||
(this.action.enabled === false && !this.yamlMode)),
|
||||
this.disabled || (this.action.enabled === false && !this.yamlMode),
|
||||
yaml: yamlMode,
|
||||
indent: this.indent,
|
||||
card: !this.inSidebar,
|
||||
|
@@ -53,9 +53,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
disabled:
|
||||
!this.indent &&
|
||||
(this.disabled ||
|
||||
(this.condition.enabled === false && !this.yamlMode)),
|
||||
this.disabled ||
|
||||
(this.condition.enabled === false && !this.yamlMode),
|
||||
yaml: yamlMode,
|
||||
indent: this.indent,
|
||||
card: !this.inSidebar,
|
||||
|
@@ -44,6 +44,7 @@ export const rowStyles = css`
|
||||
|
||||
export const editorStyles = css`
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
@@ -141,6 +141,7 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
haStyle,
|
||||
css`
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
@@ -125,7 +125,7 @@ export class HaWebhookTrigger extends LitElement {
|
||||
.path=${mdiContentCopy}
|
||||
></ha-icon-button>
|
||||
</ha-textfield>
|
||||
<ha-button-menu multi @closed=${stopPropagation} fixed>
|
||||
<ha-button-menu multi @closed=${stopPropagation}>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize(
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { mdiHelpCircle, mdiStarFourPoints } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import type { HaProgressButton } from "../../../components/buttons/ha-progress-button";
|
||||
@@ -9,7 +8,6 @@ import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-settings-row";
|
||||
import {
|
||||
AITaskEntityFeature,
|
||||
fetchAITaskPreferences,
|
||||
saveAITaskPreferences,
|
||||
type AITaskPreferences,
|
||||
@@ -17,15 +15,6 @@ import {
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
|
||||
const filterGenData = (entity: HassEntity) =>
|
||||
computeDomain(entity.entity_id) === "ai_task" &&
|
||||
supportsFeature(entity, AITaskEntityFeature.GENERATE_DATA);
|
||||
const filterGenImage = (entity: HassEntity) =>
|
||||
computeDomain(entity.entity_id) === "ai_task" &&
|
||||
supportsFeature(entity, AITaskEntityFeature.GENERATE_IMAGE);
|
||||
|
||||
@customElement("ai-task-pref")
|
||||
export class AITaskPref extends LitElement {
|
||||
@@ -37,8 +26,6 @@ export class AITaskPref extends LitElement {
|
||||
|
||||
private _gen_data_entity_id?: string | null;
|
||||
|
||||
private _gen_image_entity_id?: string | null;
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (!this.hass || !isComponentLoaded(this.hass, "ai_task")) {
|
||||
@@ -103,27 +90,7 @@ export class AITaskPref extends LitElement {
|
||||
isComponentLoaded(this.hass, "ai_task")}
|
||||
.value=${this._gen_data_entity_id ||
|
||||
this._prefs?.gen_data_entity_id}
|
||||
.entityFilter=${filterGenData}
|
||||
@value-changed=${this._handlePrefChange}
|
||||
></ha-entity-picker>
|
||||
</ha-settings-row>
|
||||
<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">
|
||||
${this.hass!.localize("ui.panel.config.ai_task.gen_image_header")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.ai_task.gen_image_description"
|
||||
)}
|
||||
</span>
|
||||
<ha-entity-picker
|
||||
data-name="gen_image_entity_id"
|
||||
.hass=${this.hass}
|
||||
.disabled=${this._prefs === undefined &&
|
||||
isComponentLoaded(this.hass, "ai_task")}
|
||||
.value=${this._gen_image_entity_id ||
|
||||
this._prefs?.gen_image_entity_id}
|
||||
.entityFilter=${filterGenImage}
|
||||
.includeDomains=${["ai_task"]}
|
||||
@value-changed=${this._handlePrefChange}
|
||||
></ha-entity-picker>
|
||||
</ha-settings-row>
|
||||
@@ -154,7 +121,6 @@ export class AITaskPref extends LitElement {
|
||||
const oldPrefs = this._prefs;
|
||||
const update: Partial<AITaskPreferences> = {
|
||||
gen_data_entity_id: this._gen_data_entity_id,
|
||||
gen_image_entity_id: this._gen_image_entity_id,
|
||||
};
|
||||
this._prefs = { ...this._prefs!, ...update };
|
||||
try {
|
||||
|
@@ -226,7 +226,6 @@ export class EnergyBatterySettings extends LitElement {
|
||||
.label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1024,7 +1024,6 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
c: () => this._copySelectedRow(),
|
||||
x: () => this._cutSelectedRow(),
|
||||
Delete: () => this._deleteSelectedRow(),
|
||||
Backspace: () => this._deleteSelectedRow(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -182,6 +182,7 @@ class HaPanelHistory extends LitElement {
|
||||
.disabled=${this._isLoading}
|
||||
add-on-top
|
||||
@value-changed=${this._targetsChanged}
|
||||
compact
|
||||
></ha-target-picker>
|
||||
</div>
|
||||
${this._isLoading
|
||||
|
@@ -104,6 +104,7 @@ export class HaPanelLogbook extends LitElement {
|
||||
.value=${this._targetPickerValue}
|
||||
add-on-top
|
||||
@value-changed=${this._targetsChanged}
|
||||
compact
|
||||
></ha-target-picker>
|
||||
</div>
|
||||
|
||||
|
@@ -7,26 +7,6 @@ import type { HomeAssistant } from "../../../../types";
|
||||
import { INTERVAL } from "../hui-clock-card";
|
||||
import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone";
|
||||
|
||||
function romanize12HourClock(num: number) {
|
||||
const numerals = [
|
||||
"", // 0 (not used)
|
||||
"I", // 1
|
||||
"II", // 2
|
||||
"III", // 3
|
||||
"IV", // 4
|
||||
"V", // 5
|
||||
"VI", // 6
|
||||
"VII", // 7
|
||||
"VIII", // 8
|
||||
"IX", // 9
|
||||
"X", // 10
|
||||
"XI", // 11
|
||||
"XII", // 12
|
||||
];
|
||||
if (num < 1 || num > 12) return "";
|
||||
return numerals[num];
|
||||
}
|
||||
|
||||
@customElement("hui-clock-card-analog")
|
||||
export class HuiClockCardAnalog extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@@ -121,36 +101,6 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
? `size-${this.config.clock_size}`
|
||||
: "";
|
||||
|
||||
const isNumbers = this.config?.face_style?.startsWith("numbers");
|
||||
const isRoman = this.config?.face_style?.startsWith("roman");
|
||||
const isUpright = this.config?.face_style?.endsWith("upright");
|
||||
|
||||
const indicator = (number?: number) => html`
|
||||
<div
|
||||
class=${classMap({
|
||||
line: true,
|
||||
numbers: isNumbers,
|
||||
roman: isRoman,
|
||||
})}
|
||||
>
|
||||
${number && this.config?.face_style !== "markers"
|
||||
? html`<div
|
||||
class=${classMap({
|
||||
number: true,
|
||||
[this.config?.clock_size ?? ""]: true,
|
||||
upright: isUpright,
|
||||
})}
|
||||
>
|
||||
${isRoman
|
||||
? romanize12HourClock(number)
|
||||
: isNumbers
|
||||
? number
|
||||
: nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="analog-clock ${sizeClass}"
|
||||
@@ -166,14 +116,14 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
${this.config.ticks === "quarter"
|
||||
? Array.from({ length: 4 }, (_, i) => i).map(
|
||||
(i) =>
|
||||
// 4 ticks (12, 3, 6, 9) at 0°, 90°, 180°, 270°
|
||||
// 4 ticks
|
||||
html`
|
||||
<div
|
||||
aria-hidden="true"
|
||||
aria-hidden
|
||||
class="tick hour"
|
||||
style=${`--tick-rotation: ${i * 90}deg;`}
|
||||
>
|
||||
${indicator([12, 3, 6, 9][i])}
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
@@ -181,30 +131,28 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
this.config.ticks === "hour"
|
||||
? Array.from({ length: 12 }, (_, i) => i).map(
|
||||
(i) =>
|
||||
// 12 ticks (1-12)
|
||||
// 12 ticks
|
||||
html`
|
||||
<div
|
||||
aria-hidden="true"
|
||||
aria-hidden
|
||||
class="tick hour"
|
||||
style=${`--tick-rotation: ${i * 30}deg;`}
|
||||
>
|
||||
${indicator(((i + 11) % 12) + 1)}
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
: this.config.ticks === "minute"
|
||||
? Array.from({ length: 60 }, (_, i) => i).map(
|
||||
(i) =>
|
||||
// 60 ticks (1-60)
|
||||
// 60 ticks
|
||||
html`
|
||||
<div
|
||||
aria-hidden="true"
|
||||
aria-hidden
|
||||
class="tick ${i % 5 === 0 ? "hour" : "minute"}"
|
||||
style=${`--tick-rotation: ${i * 6}deg;`}
|
||||
>
|
||||
${i % 5 === 0
|
||||
? indicator(((i / 5 + 11) % 12) + 1)
|
||||
: indicator()}
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
@@ -297,42 +245,6 @@ export class HuiClockCardAnalog extends LitElement {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.tick.hour .line.numbers,
|
||||
.tick.hour .line.roman {
|
||||
height: calc(var(--clock-size) * 0.03);
|
||||
}
|
||||
|
||||
.tick.minute .line.numbers,
|
||||
.tick.minute .line.roman {
|
||||
height: calc(var(--clock-size) * 0.015);
|
||||
}
|
||||
|
||||
.tick .number {
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 35%);
|
||||
color: var(--primary-text-color);
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
|
||||
.tick .number.upright {
|
||||
transform: translate(-50%, 35%) rotate(calc(var(--tick-rotation) * -1));
|
||||
}
|
||||
|
||||
.tick .number.small {
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
|
||||
.tick .number.medium {
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
|
||||
.tick .number.large {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
|
||||
.center-dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
@@ -225,7 +225,7 @@ class HuiEnergySankeyCard
|
||||
if (consumption.total.battery_to_grid > 0) {
|
||||
links.push({
|
||||
source: "battery",
|
||||
target: "grid_return",
|
||||
target: "grid",
|
||||
value: consumption.total.battery_to_grid,
|
||||
});
|
||||
}
|
||||
|
@@ -221,36 +221,25 @@ export class HuiEnergySourcesTableCard
|
||||
|
||||
let totalGrid = 0;
|
||||
let totalGridCost = 0;
|
||||
let totalSolar = 0;
|
||||
let totalBattery = 0;
|
||||
let totalGas = 0;
|
||||
let totalGasCost = 0;
|
||||
let totalWater = 0;
|
||||
let totalWaterCost = 0;
|
||||
|
||||
let hasGridCost = false;
|
||||
let hasGasCost = false;
|
||||
let hasWaterCost = false;
|
||||
|
||||
let totalGridCompare = 0;
|
||||
let totalGridCostCompare = 0;
|
||||
let totalSolarCompare = 0;
|
||||
let totalBatteryCompare = 0;
|
||||
|
||||
const totals = {
|
||||
gas: 0,
|
||||
water: 0,
|
||||
solar: 0,
|
||||
};
|
||||
const totalsCompare = {
|
||||
gas: 0,
|
||||
water: 0,
|
||||
solar: 0,
|
||||
};
|
||||
const totalCosts = {
|
||||
gas: 0,
|
||||
water: 0,
|
||||
};
|
||||
const totalCostsCompare = {
|
||||
gas: 0,
|
||||
water: 0,
|
||||
};
|
||||
const hasCosts = {
|
||||
gas: false,
|
||||
water: false,
|
||||
};
|
||||
let totalGasCompare = 0;
|
||||
let totalGasCostCompare = 0;
|
||||
let totalWaterCompare = 0;
|
||||
let totalWaterCostCompare = 0;
|
||||
|
||||
const types = energySourcesByType(this._data.prefs);
|
||||
|
||||
@@ -277,111 +266,12 @@ export class HuiEnergySourcesTableCard
|
||||
)
|
||||
);
|
||||
|
||||
const units = {
|
||||
solar: "kWh",
|
||||
gas: this._data.gasUnit,
|
||||
water: this._data.waterUnit,
|
||||
};
|
||||
const gasUnit = this._data.gasUnit;
|
||||
|
||||
const waterUnit = this._data.waterUnit;
|
||||
|
||||
const compare = this._data.statsCompare !== undefined;
|
||||
|
||||
const _extractStatData = (
|
||||
statId: string,
|
||||
costStatId: string | null
|
||||
): {
|
||||
hasData: boolean;
|
||||
energy: number;
|
||||
energyCompare: number;
|
||||
cost: number;
|
||||
costCompare: number;
|
||||
} => {
|
||||
const energy = calculateStatisticSumGrowth(this._data!.stats[statId]);
|
||||
|
||||
const compareEnergy =
|
||||
compare &&
|
||||
calculateStatisticSumGrowth(this._data!.statsCompare[statId]);
|
||||
|
||||
const cost =
|
||||
(costStatId &&
|
||||
calculateStatisticSumGrowth(this._data!.stats[costStatId])) ||
|
||||
0;
|
||||
|
||||
const costCompare =
|
||||
(compare &&
|
||||
costStatId &&
|
||||
calculateStatisticSumGrowth(this._data!.statsCompare[costStatId])) ||
|
||||
0;
|
||||
|
||||
if (energy === null && (!compare || compareEnergy === null)) {
|
||||
return {
|
||||
hasData: false,
|
||||
energy: energy || 0,
|
||||
energyCompare: compareEnergy || 0,
|
||||
cost,
|
||||
costCompare,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
hasData: true,
|
||||
energy: energy || 0,
|
||||
energyCompare: compareEnergy || 0,
|
||||
cost,
|
||||
costCompare,
|
||||
};
|
||||
};
|
||||
|
||||
const _renderSimpleCategory = (type: "solar" | "gas" | "water") =>
|
||||
html` ${types[type]?.map((source, idx) => {
|
||||
const cost_stat =
|
||||
type in hasCosts &&
|
||||
(source.stat_cost ||
|
||||
this._data!.info.cost_sensors[source.stat_energy_from]);
|
||||
|
||||
const { hasData, energy, energyCompare, cost, costCompare } =
|
||||
_extractStatData(source.stat_energy_from, cost_stat || null);
|
||||
|
||||
if (!hasData && !cost && !costCompare) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
totals[type] += energy;
|
||||
totalsCompare[type] += energyCompare;
|
||||
if (cost_stat) {
|
||||
hasCosts[type] = true;
|
||||
totalCosts[type] += cost;
|
||||
totalCostsCompare[type] += costCompare;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
type,
|
||||
source.stat_energy_from,
|
||||
idx,
|
||||
energy,
|
||||
energyCompare,
|
||||
units[type],
|
||||
cost_stat ? cost : null,
|
||||
cost_stat ? costCompare : null,
|
||||
showCosts,
|
||||
compare
|
||||
);
|
||||
})}
|
||||
${types[type]
|
||||
? this._renderTotalRow(
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.cards.energy.energy_sources_table.${type}_total`
|
||||
),
|
||||
totals[type],
|
||||
totalsCompare[type],
|
||||
units[type],
|
||||
hasCosts[type] ? totalCosts[type] : null,
|
||||
hasCosts[type] ? totalCostsCompare[type] : null,
|
||||
showCosts,
|
||||
compare
|
||||
)
|
||||
: ""}`;
|
||||
|
||||
return html` <ha-card>
|
||||
${this._config.title
|
||||
? html`<h1 class="card-header">${this._config.title}</h1>`
|
||||
@@ -446,24 +336,72 @@ export class HuiEnergySourcesTableCard
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mdc-data-table__content">
|
||||
${_renderSimpleCategory("solar")}
|
||||
${types.solar?.map((source, idx) => {
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.stats[source.stat_energy_from]
|
||||
) || 0;
|
||||
totalSolar += energy;
|
||||
|
||||
const compareEnergy =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalSolarCompare += compareEnergy;
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"solar",
|
||||
source.stat_energy_from,
|
||||
idx,
|
||||
energy,
|
||||
compareEnergy,
|
||||
"kWh",
|
||||
null,
|
||||
null,
|
||||
showCosts,
|
||||
compare
|
||||
);
|
||||
})}
|
||||
${types.solar
|
||||
? this._renderTotalRow(
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.solar_total"
|
||||
),
|
||||
totalSolar,
|
||||
totalSolarCompare,
|
||||
"kWh",
|
||||
null,
|
||||
null,
|
||||
showCosts,
|
||||
compare
|
||||
)
|
||||
: ""}
|
||||
${types.battery?.map((source, idx) => {
|
||||
const {
|
||||
hasData: hasFromData,
|
||||
energy: energyFrom,
|
||||
energyCompare: energyFromCompare,
|
||||
} = _extractStatData(source.stat_energy_from, null);
|
||||
const {
|
||||
hasData: hasToData,
|
||||
energy: energyTo,
|
||||
energyCompare: energyToCompare,
|
||||
} = _extractStatData(source.stat_energy_to, null);
|
||||
|
||||
if (!hasFromData && !hasToData) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const energyFrom =
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.stats[source.stat_energy_from]
|
||||
) || 0;
|
||||
const energyTo =
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.stats[source.stat_energy_to]
|
||||
) || 0;
|
||||
totalBattery += energyFrom - energyTo;
|
||||
|
||||
const energyFromCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
const energyToCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_to]
|
||||
)) ||
|
||||
0;
|
||||
totalBatteryCompare += energyFromCompare - energyToCompare;
|
||||
|
||||
return html` ${this._renderRow(
|
||||
@@ -509,39 +447,50 @@ export class HuiEnergySourcesTableCard
|
||||
${types.grid?.map(
|
||||
(source) =>
|
||||
html`${source.flow_from.map((flow, idx) => {
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.stats[flow.stat_energy_from]
|
||||
) || 0;
|
||||
totalGrid += energy;
|
||||
|
||||
const compareEnergy =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[flow.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalGridCompare += compareEnergy;
|
||||
|
||||
const cost_stat =
|
||||
flow.stat_cost ||
|
||||
this._data!.info.cost_sensors[flow.stat_energy_from];
|
||||
const {
|
||||
hasData,
|
||||
energy,
|
||||
energyCompare,
|
||||
cost,
|
||||
costCompare,
|
||||
} = _extractStatData(
|
||||
flow.stat_energy_from,
|
||||
cost_stat || null
|
||||
);
|
||||
|
||||
if (!hasData && !cost && !costCompare) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
totalGrid += energy;
|
||||
totalGridCompare += energyCompare;
|
||||
|
||||
if (cost_stat) {
|
||||
const cost = cost_stat
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.stats[cost_stat]
|
||||
) || 0
|
||||
: null;
|
||||
if (cost !== null) {
|
||||
hasGridCost = true;
|
||||
totalGridCost += cost;
|
||||
}
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalGridCostCompare += costCompare;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"grid_consumption",
|
||||
flow.stat_energy_from,
|
||||
idx,
|
||||
energy,
|
||||
energyCompare,
|
||||
compareEnergy,
|
||||
"kWh",
|
||||
cost,
|
||||
costCompare,
|
||||
@@ -550,41 +499,52 @@ export class HuiEnergySourcesTableCard
|
||||
);
|
||||
})}
|
||||
${source.flow_to.map((flow, idx) => {
|
||||
const energy =
|
||||
(calculateStatisticSumGrowth(
|
||||
this._data!.stats[flow.stat_energy_to]
|
||||
) || 0) * -1;
|
||||
totalGrid += energy;
|
||||
const cost_stat =
|
||||
flow.stat_compensation ||
|
||||
this._data!.info.cost_sensors[flow.stat_energy_to];
|
||||
const {
|
||||
hasData,
|
||||
energy,
|
||||
energyCompare,
|
||||
cost,
|
||||
costCompare,
|
||||
} = _extractStatData(
|
||||
flow.stat_energy_to,
|
||||
cost_stat || null
|
||||
);
|
||||
|
||||
if (!hasData && !cost && !costCompare) {
|
||||
return nothing;
|
||||
}
|
||||
totalGrid -= energy;
|
||||
totalGridCompare -= energyCompare;
|
||||
|
||||
if (cost_stat !== null) {
|
||||
const cost = cost_stat
|
||||
? (calculateStatisticSumGrowth(
|
||||
this._data!.stats[cost_stat]
|
||||
) || 0) * -1
|
||||
: null;
|
||||
if (cost !== null) {
|
||||
hasGridCost = true;
|
||||
totalGridCost -= cost;
|
||||
totalGridCostCompare -= costCompare;
|
||||
totalGridCost += cost;
|
||||
}
|
||||
|
||||
const energyCompare =
|
||||
((compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[flow.stat_energy_to]
|
||||
)) ||
|
||||
0) * -1;
|
||||
totalGridCompare += energyCompare;
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? (calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0) * -1
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalGridCostCompare += costCompare;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"grid_return",
|
||||
flow.stat_energy_to,
|
||||
idx,
|
||||
-energy,
|
||||
-energyCompare,
|
||||
energy,
|
||||
energyCompare,
|
||||
"kWh",
|
||||
-cost,
|
||||
-costCompare,
|
||||
cost,
|
||||
costCompare,
|
||||
showCosts,
|
||||
compare
|
||||
);
|
||||
@@ -606,9 +566,138 @@ export class HuiEnergySourcesTableCard
|
||||
compare
|
||||
)
|
||||
: ""}
|
||||
${_renderSimpleCategory("gas")} ${_renderSimpleCategory("water")}
|
||||
${[hasCosts.gas, hasCosts.water, hasGridCost].filter(Boolean)
|
||||
.length > 1
|
||||
${types.gas?.map((source, idx) => {
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.stats[source.stat_energy_from]
|
||||
) || 0;
|
||||
totalGas += energy;
|
||||
|
||||
const energyCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalGasCompare += energyCompare;
|
||||
|
||||
const cost_stat =
|
||||
source.stat_cost ||
|
||||
this._data!.info.cost_sensors[source.stat_energy_from];
|
||||
const cost = cost_stat
|
||||
? calculateStatisticSumGrowth(this._data!.stats[cost_stat]) ||
|
||||
0
|
||||
: null;
|
||||
if (cost !== null) {
|
||||
hasGasCost = true;
|
||||
totalGasCost += cost;
|
||||
}
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalGasCostCompare += costCompare;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"gas",
|
||||
source.stat_energy_from,
|
||||
idx,
|
||||
energy,
|
||||
energyCompare,
|
||||
gasUnit,
|
||||
cost,
|
||||
costCompare,
|
||||
showCosts,
|
||||
compare
|
||||
);
|
||||
})}
|
||||
${types.gas
|
||||
? this._renderTotalRow(
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.gas_total"
|
||||
),
|
||||
totalGas,
|
||||
totalGasCompare,
|
||||
gasUnit,
|
||||
hasGasCost ? totalGasCost : null,
|
||||
hasGasCost ? totalGasCostCompare : null,
|
||||
showCosts,
|
||||
compare
|
||||
)
|
||||
: ""}
|
||||
${types.water?.map((source, idx) => {
|
||||
const energy =
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.stats[source.stat_energy_from]
|
||||
) || 0;
|
||||
totalWater += energy;
|
||||
|
||||
const energyCompare =
|
||||
(compare &&
|
||||
calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[source.stat_energy_from]
|
||||
)) ||
|
||||
0;
|
||||
totalWaterCompare += energyCompare;
|
||||
|
||||
const cost_stat =
|
||||
source.stat_cost ||
|
||||
this._data!.info.cost_sensors[source.stat_energy_from];
|
||||
const cost = cost_stat
|
||||
? calculateStatisticSumGrowth(this._data!.stats[cost_stat]) ||
|
||||
0
|
||||
: null;
|
||||
if (cost !== null) {
|
||||
hasWaterCost = true;
|
||||
totalWaterCost += cost;
|
||||
}
|
||||
|
||||
const costCompare =
|
||||
compare && cost_stat
|
||||
? calculateStatisticSumGrowth(
|
||||
this._data!.statsCompare[cost_stat]
|
||||
) || 0
|
||||
: null;
|
||||
if (costCompare !== null) {
|
||||
totalWaterCostCompare += costCompare;
|
||||
}
|
||||
|
||||
return this._renderRow(
|
||||
computedStyles,
|
||||
"water",
|
||||
source.stat_energy_from,
|
||||
idx,
|
||||
energy,
|
||||
energyCompare,
|
||||
waterUnit,
|
||||
cost,
|
||||
costCompare,
|
||||
showCosts,
|
||||
compare
|
||||
);
|
||||
})}
|
||||
${types.water
|
||||
? this._renderTotalRow(
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.water_total"
|
||||
),
|
||||
totalWater,
|
||||
totalWaterCompare,
|
||||
waterUnit,
|
||||
hasWaterCost ? totalWaterCost : null,
|
||||
hasWaterCost ? totalWaterCostCompare : null,
|
||||
showCosts,
|
||||
compare
|
||||
)
|
||||
: ""}
|
||||
${[hasGasCost, hasWaterCost, hasGridCost].filter(Boolean).length >
|
||||
1
|
||||
? this._renderTotalRow(
|
||||
this.hass.localize(
|
||||
"ui.panel.lovelace.cards.energy.energy_sources_table.total_costs"
|
||||
@@ -616,10 +705,10 @@ export class HuiEnergySourcesTableCard
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
totalCosts.gas + totalGridCost + totalCosts.water,
|
||||
totalCostsCompare.gas +
|
||||
totalGasCost + totalGridCost + totalWaterCost,
|
||||
totalGasCostCompare +
|
||||
totalGridCostCompare +
|
||||
totalCostsCompare.water,
|
||||
totalWaterCostCompare,
|
||||
showCosts,
|
||||
compare
|
||||
)
|
||||
|
@@ -381,7 +381,6 @@ export interface ClockCardConfig extends LovelaceCardConfig {
|
||||
// Analog clock options
|
||||
border?: boolean;
|
||||
ticks?: "none" | "quarter" | "hour" | "minute";
|
||||
face_style?: "markers" | "numbers_upright" | "roman";
|
||||
}
|
||||
|
||||
export interface MediaControlCardConfig extends LovelaceCardConfig {
|
||||
|
@@ -52,16 +52,6 @@ const cardConfigStruct = assign(
|
||||
literal("hour")
|
||||
)
|
||||
),
|
||||
face_style: optional(
|
||||
defaulted(
|
||||
union([
|
||||
literal("markers"),
|
||||
literal("numbers_upright"),
|
||||
literal("roman"),
|
||||
]),
|
||||
literal("markers")
|
||||
)
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -75,11 +65,7 @@ export class HuiClockCardEditor
|
||||
@state() private _config?: ClockCardConfig;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
clockStyle: ClockCardConfig["clock_style"],
|
||||
ticks: ClockCardConfig["ticks"]
|
||||
) =>
|
||||
(localize: LocalizeFunc, clockStyle: "digital" | "analog") =>
|
||||
[
|
||||
{ name: "title", selector: { text: {} } },
|
||||
{
|
||||
@@ -170,37 +156,6 @@ export class HuiClockCardEditor
|
||||
},
|
||||
},
|
||||
},
|
||||
...(ticks !== "none"
|
||||
? ([
|
||||
{
|
||||
name: "face_style",
|
||||
description: {
|
||||
suffix: localize(
|
||||
`ui.panel.lovelace.editor.card.clock.face_style.description`
|
||||
),
|
||||
},
|
||||
default: "markers",
|
||||
selector: {
|
||||
select: {
|
||||
mode: "dropdown",
|
||||
options: [
|
||||
"markers",
|
||||
"numbers_upright",
|
||||
"roman",
|
||||
].map((value) => ({
|
||||
value,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.card.clock.face_style.${value}.label`
|
||||
),
|
||||
description: localize(
|
||||
`ui.panel.lovelace.editor.card.clock.face_style.${value}.description`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
] as const satisfies readonly HaFormSchema[])
|
||||
: []),
|
||||
{
|
||||
@@ -236,7 +191,6 @@ export class HuiClockCardEditor
|
||||
// Analog clock options
|
||||
border: false,
|
||||
ticks: "hour",
|
||||
face_style: "markers",
|
||||
...config,
|
||||
}));
|
||||
|
||||
@@ -256,8 +210,8 @@ export class HuiClockCardEditor
|
||||
.data=${this._data(this._config)}
|
||||
.schema=${this._schema(
|
||||
this.hass.localize,
|
||||
this._data(this._config).clock_style,
|
||||
this._data(this._config).ticks
|
||||
(this._data(this._config).clock_style as "digital" | "analog") ??
|
||||
"digital"
|
||||
)}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.computeHelper=${this._computeHelperCallback}
|
||||
@@ -277,17 +231,9 @@ export class HuiClockCardEditor
|
||||
if (ev.detail.value.clock_style === "analog") {
|
||||
ev.detail.value.border = ev.detail.value.border ?? false;
|
||||
ev.detail.value.ticks = ev.detail.value.ticks ?? "hour";
|
||||
ev.detail.value.face_style = ev.detail.value.face_style ?? "markers";
|
||||
} else {
|
||||
delete ev.detail.value.border;
|
||||
delete ev.detail.value.ticks;
|
||||
delete ev.detail.value.face_style;
|
||||
}
|
||||
|
||||
if (ev.detail.value.ticks !== "none") {
|
||||
ev.detail.value.face_style = ev.detail.value.face_style ?? "markers";
|
||||
} else {
|
||||
delete ev.detail.value.face_style;
|
||||
}
|
||||
|
||||
fireEvent(this, "config-changed", { config: ev.detail.value });
|
||||
@@ -333,10 +279,6 @@ export class HuiClockCardEditor
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.ticks.label`
|
||||
);
|
||||
case "face_style":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.face_style.label`
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
@@ -354,10 +296,6 @@ export class HuiClockCardEditor
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.ticks.description`
|
||||
);
|
||||
case "face_style":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.card.clock.face_style.description`
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
@@ -117,7 +117,10 @@ export const colorStyles = css`
|
||||
/* state color */
|
||||
--state-active-color: var(--amber-color);
|
||||
--state-inactive-color: var(--grey-color);
|
||||
--state-unavailable-color: var(--state-icon-unavailable-color, var(--disabled-text-color));
|
||||
--state-unavailable-color: var(
|
||||
--state-icon-unavailable-color,
|
||||
var(--disabled-text-color)
|
||||
);
|
||||
|
||||
/* state domain colors */
|
||||
--state-alarm_control_panel-armed_away-color: var(--green-color);
|
||||
@@ -195,9 +198,15 @@ export const colorStyles = css`
|
||||
--sidebar-selected-icon-color: var(--primary-color);
|
||||
--sidebar-icon-color: rgba(var(--rgb-primary-text-color), 0.6);
|
||||
--switch-checked-color: var(--primary-color);
|
||||
--switch-checked-button-color: var(--switch-checked-color, var(--primary-background-color));
|
||||
--switch-checked-button-color: var(
|
||||
--switch-checked-color,
|
||||
var(--primary-background-color)
|
||||
);
|
||||
--switch-checked-track-color: var(--switch-checked-color, #000000);
|
||||
--switch-unchecked-button-color: var(--switch-unchecked-color, var(--primary-background-color));
|
||||
--switch-unchecked-button-color: var(
|
||||
--switch-unchecked-color,
|
||||
var(--primary-background-color)
|
||||
);
|
||||
--switch-unchecked-track-color: var(--switch-unchecked-color, #000000);
|
||||
--slider-color: var(--primary-color);
|
||||
--slider-secondary-color: var(--light-primary-color);
|
||||
@@ -243,9 +252,15 @@ export const colorStyles = css`
|
||||
--mdc-text-field-idle-line-color: var(--input-idle-line-color);
|
||||
--mdc-text-field-hover-line-color: var(--input-hover-line-color);
|
||||
--mdc-text-field-disabled-line-color: var(--input-disabled-line-color);
|
||||
--mdc-text-field-outlined-idle-border-color: var(--input-outlined-idle-border-color);
|
||||
--mdc-text-field-outlined-hover-border-color: var(--input-outlined-hover-border-color);
|
||||
--mdc-text-field-outlined-disabled-border-color: var(--input-outlined-disabled-border-color);
|
||||
--mdc-text-field-outlined-idle-border-color: var(
|
||||
--input-outlined-idle-border-color
|
||||
);
|
||||
--mdc-text-field-outlined-hover-border-color: var(
|
||||
--input-outlined-hover-border-color
|
||||
);
|
||||
--mdc-text-field-outlined-disabled-border-color: var(
|
||||
--input-outlined-disabled-border-color
|
||||
);
|
||||
--mdc-text-field-fill-color: var(--input-fill-color);
|
||||
--mdc-text-field-disabled-fill-color: var(--input-disabled-fill-color);
|
||||
--mdc-text-field-ink-color: var(--input-ink-color);
|
||||
@@ -254,9 +269,15 @@ export const colorStyles = css`
|
||||
|
||||
--mdc-select-idle-line-color: var(--input-idle-line-color);
|
||||
--mdc-select-hover-line-color: var(--input-hover-line-color);
|
||||
--mdc-select-outlined-idle-border-color: var(--input-outlined-idle-border-color);
|
||||
--mdc-select-outlined-hover-border-color: var(--input-outlined-hover-border-color);
|
||||
--mdc-select-outlined-disabled-border-color: var(--input-outlined-disabled-border-color);
|
||||
--mdc-select-outlined-idle-border-color: var(
|
||||
--input-outlined-idle-border-color
|
||||
);
|
||||
--mdc-select-outlined-hover-border-color: var(
|
||||
--input-outlined-hover-border-color
|
||||
);
|
||||
--mdc-select-outlined-disabled-border-color: var(
|
||||
--input-outlined-disabled-border-color
|
||||
);
|
||||
--mdc-select-fill-color: var(--input-fill-color);
|
||||
--mdc-select-disabled-fill-color: var(--input-disabled-fill-color);
|
||||
--mdc-select-ink-color: var(--input-ink-color);
|
||||
@@ -264,8 +285,14 @@ export const colorStyles = css`
|
||||
--mdc-select-disabled-ink-color: var(--input-disabled-ink-color);
|
||||
--mdc-select-dropdown-icon-color: var(--input-dropdown-icon-color);
|
||||
--mdc-select-disabled-dropdown-icon-color: var(--input-disabled-ink-color);
|
||||
--ha-assist-chip-filled-container-color: rgba(var(--rgb-primary-text-color), 0.15);
|
||||
--ha-assist-chip-active-container-color: rgba(var(--rgb-primary-color), 0.15);
|
||||
--ha-assist-chip-filled-container-color: rgba(
|
||||
var(--rgb-primary-text-color),
|
||||
0.15
|
||||
);
|
||||
--ha-assist-chip-active-container-color: rgba(
|
||||
var(--rgb-primary-color),
|
||||
0.15
|
||||
);
|
||||
--chip-background-color: rgba(var(--rgb-primary-text-color), 0.15);
|
||||
|
||||
/* Vaadin */
|
||||
@@ -327,7 +354,8 @@ export const darkColorStyles = css`
|
||||
--codemirror-qualifier: #decb6b;
|
||||
--codemirror-type: #decb6b;
|
||||
--energy-grid-return-color: #a280db;
|
||||
--map-filter: invert(0.9) hue-rotate(170deg) brightness(1.5) contrast(1.2) saturate(0.3);
|
||||
--map-filter: invert(0.9) hue-rotate(170deg) brightness(1.5) contrast(1.2)
|
||||
saturate(0.3);
|
||||
--disabled-color: #464646;
|
||||
|
||||
--ha-button-primary-light-color: #4082a040;
|
||||
@@ -339,5 +367,9 @@ export const darkColorStyles = css`
|
||||
}
|
||||
`;
|
||||
|
||||
export const DefaultPrimaryColor = extractVar(colorStyles, "primary-color", coreColorVariables);
|
||||
export const DefaultPrimaryColor = extractVar(
|
||||
colorStyles,
|
||||
"primary-color",
|
||||
coreColorVariables
|
||||
);
|
||||
export const DefaultAccentColor = extractVar(colorStyles, "accent-color");
|
||||
|
@@ -28,10 +28,22 @@ export const mainStyles = css`
|
||||
--margin-title-rtl: 0 24px 0 0;
|
||||
|
||||
/* safe-area-insets */
|
||||
--safe-area-inset-top: var(--app-safe-area-inset-top, env(safe-area-inset-top, 0));
|
||||
--safe-area-inset-bottom: var(--app-safe-area-inset-bottom, env(safe-area-inset-bottom, 0));
|
||||
--safe-area-inset-left: var(--app-safe-area-inset-left, env(safe-area-inset-left, 0));
|
||||
--safe-area-inset-right: var(--app-safe-area-inset-right, env(safe-area-inset-right, 0));
|
||||
--safe-area-inset-top: var(
|
||||
--app-safe-area-inset-top,
|
||||
env(safe-area-inset-top, 0)
|
||||
);
|
||||
--safe-area-inset-bottom: var(
|
||||
--app-safe-area-inset-bottom,
|
||||
env(safe-area-inset-bottom, 0)
|
||||
);
|
||||
--safe-area-inset-left: var(
|
||||
--app-safe-area-inset-left,
|
||||
env(safe-area-inset-left, 0)
|
||||
);
|
||||
--safe-area-inset-right: var(
|
||||
--app-safe-area-inset-right,
|
||||
env(safe-area-inset-right, 0)
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
|
@@ -1,5 +1,8 @@
|
||||
import { css } from "lit";
|
||||
import { extractDerivedVars, extractVar } from "../../common/style/derived-css-vars";
|
||||
import {
|
||||
extractDerivedVars,
|
||||
extractVar,
|
||||
} from "../../common/style/derived-css-vars";
|
||||
|
||||
export const typographyStyles = css`
|
||||
html {
|
||||
@@ -14,10 +17,16 @@ export const typographyStyles = css`
|
||||
--ha-font-size-m: calc(14px * var(--ha-font-size-scale)); /* 1rem */
|
||||
--ha-font-size-l: calc(16px * var(--ha-font-size-scale)); /* 1.142857rem */
|
||||
--ha-font-size-xl: calc(20px * var(--ha-font-size-scale)); /* 1.428571rem */
|
||||
--ha-font-size-2xl: calc(24px * var(--ha-font-size-scale)); /* 1.714286rem */
|
||||
--ha-font-size-2xl: calc(
|
||||
24px * var(--ha-font-size-scale)
|
||||
); /* 1.714286rem */
|
||||
--ha-font-size-3xl: calc(28px * var(--ha-font-size-scale)); /* 2rem */
|
||||
--ha-font-size-4xl: calc(32px * var(--ha-font-size-scale)); /* 2.285714rem */
|
||||
--ha-font-size-5xl: calc(40px * var(--ha-font-size-scale)); /* 2.857143rem */
|
||||
--ha-font-size-4xl: calc(
|
||||
32px * var(--ha-font-size-scale)
|
||||
); /* 2.285714rem */
|
||||
--ha-font-size-5xl: calc(
|
||||
40px * var(--ha-font-size-scale)
|
||||
); /* 2.857143rem */
|
||||
|
||||
--ha-font-weight-light: 300;
|
||||
--ha-font-weight-normal: 400;
|
||||
@@ -51,8 +60,17 @@ export const typographyStyles = css`
|
||||
|
||||
export const typographyDerivedVariables = extractDerivedVars(typographyStyles);
|
||||
|
||||
export const haFontFamilyBody = extractVar(typographyStyles, "ha-font-family-body");
|
||||
export const haFontFamilyBody = extractVar(
|
||||
typographyStyles,
|
||||
"ha-font-family-body"
|
||||
);
|
||||
|
||||
export const haFontSmoothing = extractVar(typographyStyles, "ha-font-smoothing");
|
||||
export const haFontSmoothing = extractVar(
|
||||
typographyStyles,
|
||||
"ha-font-smoothing"
|
||||
);
|
||||
|
||||
export const haMozOsxFontSmoothing = extractVar(typographyStyles, "ha-moz-osx-font-smoothing");
|
||||
export const haMozOsxFontSmoothing = extractVar(
|
||||
typographyStyles,
|
||||
"ha-moz-osx-font-smoothing"
|
||||
);
|
||||
|
@@ -6,7 +6,8 @@ export const waMainStyles = css`
|
||||
--wa-focus-ring-style: solid;
|
||||
--wa-focus-ring-width: 2px;
|
||||
--wa-focus-ring-offset: 2px;
|
||||
--wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width) var(--wa-focus-ring-color);
|
||||
--wa-focus-ring: var(--wa-focus-ring-style) var(--wa-focus-ring-width)
|
||||
var(--wa-focus-ring-color);
|
||||
|
||||
--wa-space-l: 24px;
|
||||
--wa-shadow-l: 0 8px 8px -4px rgba(0, 0, 0, 0.2);
|
||||
|
@@ -661,20 +661,35 @@
|
||||
},
|
||||
"target-picker": {
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"expand_floor_id": "Split this floor into separate areas.",
|
||||
"expand_area_id": "Split this area into separate devices and entities.",
|
||||
"expand_device_id": "Split this device into separate entities.",
|
||||
"expand_label_id": "Split this label into separate areas, devices and entities.",
|
||||
"remove": "Remove",
|
||||
"remove_floor_id": "Remove floor",
|
||||
"remove_floors": "Remove floors",
|
||||
"remove_area_id": "Remove area",
|
||||
"remove_areas": "Remove areas",
|
||||
"remove_device_id": "Remove device",
|
||||
"remove_devices": "Remove devices",
|
||||
"remove_entity_id": "Remove entity",
|
||||
"remove_entitys": "Remove entities",
|
||||
"remove_label_id": "Remove label",
|
||||
"remove_labels": "Remove labels",
|
||||
"add_area_id": "Choose area",
|
||||
"add_device_id": "Choose device",
|
||||
"add_entity_id": "Choose entity",
|
||||
"add_label_id": "Choose label"
|
||||
"add_label_id": "Choose label",
|
||||
"devices_count": "{count} {count, plural,\n one {device}\n other {devices}\n}",
|
||||
"entities_count": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
"selected": {
|
||||
"entity": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
"device": "{count} {count, plural,\n one {device}\n other {devices}\n}",
|
||||
"area": "{count} {count, plural,\n one {area}\n other {areas}\n}",
|
||||
"label": "{count} {count, plural,\n one {label}\n other {labels}\n}",
|
||||
"floor": "{count} {count, plural,\n one {floor}\n other {floors}\n}"
|
||||
}
|
||||
},
|
||||
"subpage-data-table": {
|
||||
"filters": "Filters",
|
||||
@@ -2312,9 +2327,7 @@
|
||||
"header": "AI suggestions",
|
||||
"description": "Home Assistant can use generative AI to help you with tasks. Look for the button with the {button} icon throughout Home Assistant to get suggestions. Select an AI task entity to use this feature.",
|
||||
"gen_data_header": "Data generation tasks",
|
||||
"gen_data_description": "Suggest automation names.",
|
||||
"gen_image_header": "Image generation tasks",
|
||||
"gen_image_description": "Generate images."
|
||||
"gen_data_description": "Suggest automation names."
|
||||
},
|
||||
"category": {
|
||||
"caption": "Categories",
|
||||
@@ -7818,22 +7831,6 @@
|
||||
"label": "Minute",
|
||||
"description": "60 ticks (Every minute)"
|
||||
}
|
||||
},
|
||||
"face_style": {
|
||||
"label": "Clock face style",
|
||||
"description": "Which kind of indices to use for the clock face",
|
||||
"markers": {
|
||||
"label": "Markers",
|
||||
"description": "Show simple markers around the clock face (default)"
|
||||
},
|
||||
"numbers_upright": {
|
||||
"label": "Numbers",
|
||||
"description": "Show numbers around the clock face"
|
||||
},
|
||||
"roman": {
|
||||
"label": "Roman numerals",
|
||||
"description": "Show Roman numerals (I-XII) around the clock face"
|
||||
}
|
||||
}
|
||||
},
|
||||
"media-control": {
|
||||
|
24
yarn.lock
24
yarn.lock
@@ -8505,7 +8505,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fdir@npm:^6.5.0":
|
||||
"fdir@npm:^6.4.4, fdir@npm:^6.4.6":
|
||||
version: 6.5.0
|
||||
resolution: "fdir@npm:6.5.0"
|
||||
peerDependencies:
|
||||
@@ -14208,13 +14208,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.15":
|
||||
version: 0.2.15
|
||||
resolution: "tinyglobby@npm:0.2.15"
|
||||
"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.14":
|
||||
version: 0.2.14
|
||||
resolution: "tinyglobby@npm:0.2.14"
|
||||
dependencies:
|
||||
fdir: "npm:^6.5.0"
|
||||
picomatch: "npm:^4.0.3"
|
||||
checksum: 10/d72bd826a8b0fa5fa3929e7fe5ba48fceb2ae495df3a231b6c5408cd7d8c00b58ab5a9c2a76ba56a62ee9b5e083626f1f33599734bed1ffc4b792406408f0ca2
|
||||
fdir: "npm:^6.4.4"
|
||||
picomatch: "npm:^4.0.2"
|
||||
checksum: 10/3d306d319718b7cc9d79fb3f29d8655237aa6a1f280860a217f93417039d0614891aee6fc47c5db315f4fcc6ac8d55eb8e23e2de73b2c51a431b42456d9e5764
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14934,16 +14934,16 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0":
|
||||
version: 7.1.5
|
||||
resolution: "vite@npm:7.1.5"
|
||||
version: 7.1.2
|
||||
resolution: "vite@npm:7.1.2"
|
||||
dependencies:
|
||||
esbuild: "npm:^0.25.0"
|
||||
fdir: "npm:^6.5.0"
|
||||
fdir: "npm:^6.4.6"
|
||||
fsevents: "npm:~2.3.3"
|
||||
picomatch: "npm:^4.0.3"
|
||||
postcss: "npm:^8.5.6"
|
||||
rollup: "npm:^4.43.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
tinyglobby: "npm:^0.2.14"
|
||||
peerDependencies:
|
||||
"@types/node": ^20.19.0 || >=22.12.0
|
||||
jiti: ">=1.21.0"
|
||||
@@ -14984,7 +14984,7 @@ __metadata:
|
||||
optional: true
|
||||
bin:
|
||||
vite: bin/vite.js
|
||||
checksum: 10/59edeef7e98757a668b2ad8a1731a5657fa83e22a165a36b7359225ea98a9be39b2f486710c0cf5085edb85daee7c8b6b6b0bd85d0ef32a1aa84aef71aabd0f0
|
||||
checksum: 10/942188896db181bd5f95c576303eb617cd08580eb129d0223bc87a8ed9b8dc0d60d8487c077fd9a01087c55600af132c5fc677e51b4b48a4a54a376056969edb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Reference in New Issue
Block a user