Compare commits

...
Author SHA1 Message Date
Ludeeus 68bdb6d229 Add active property 2021-11-03 08:05:55 +00:00
Ludeeus 3d540709aa HaTargetPicker 2021-10-28 16:11:09 +00:00
Ludeeus 69a0bc214c Fix events 2021-10-28 14:33:58 +00:00
Ludeeus 9ca04290bf fix alignment 2021-10-28 14:20:29 +00:00
Ludeeus 5b55b408e6 HassioAddonInfo 2021-10-28 13:47:01 +00:00
Ludeeus d5c64b78d9 QuickBar 2021-10-28 13:11:17 +00:00
Ludeeus 60b09dd57d HuiAlarmPanelCard 2021-10-28 13:05:27 +00:00
Ludeeus abfec31cfe HaDeviceAutomationCard 2021-10-28 13:04:28 +00:00
Ludeeus 90e3112ae1 fix outline ripple 2021-10-27 07:59:39 +00:00
Ludeeus 380509cf57 Fix clickhandlers 2021-10-26 15:17:39 +00:00
Ludeeus 88139f64ab 1px outline 2021-10-26 15:05:09 +00:00
Ludeeus f90e04274b fix demo 2021-10-26 13:25:38 +00:00
Ludeeus 886d14bcf6 prevent jumping for outlined 2021-10-26 13:22:41 +00:00
Ludeeus 64c831e91f css var outlined 2021-10-26 13:11:05 +00:00
Ludeeus 8e53019cf2 Use outlined 2021-10-26 12:28:48 +00:00
Ludeeus daab076bc1 remove index 2021-10-26 12:07:56 +00:00
Ludeeus 37a970f195 add controll to demo 2021-10-26 12:04:48 +00:00
Ludeeus c5ffb4cb4e Add demo for label property 2021-10-26 11:54:58 +00:00
Ludeeus 8c28fa747d Showcase multiline set 2021-10-26 11:53:35 +00:00
Ludeeus 322d01196f target ha-svg-icon only 2021-10-26 11:50:48 +00:00
Ludeeus 9ec77c88b1 Move styles from the chip used in targets to ha-chip 2021-10-26 11:47:43 +00:00
Philip AllgaierandGitHub 29a1167782 Ensure consistent card look on device config page (#10386) 2021-10-26 11:12:27 +02:00
Joakim SørensenandGitHub d61a77f2d9 Add running device class to binary sensor (#10400) 2021-10-25 23:05:35 +02:00
Philip AllgaierandGitHub b9bde1960b Ensure explicit false values from customize form get stored (#10381) 2021-10-25 20:33:26 +02:00
Nathan OrickandGitHub a12c2eea5d Ensure Sortable is recreated when card editors are reopened (#10382) 2021-10-25 19:49:00 +02:00
MartinTandGitHub b5c717a559 Do not close edit dialog when more info is escaped (#10249) 2021-10-25 19:48:17 +02:00
Joakim SørensenandGitHub 3adbc4cfaf Use ha-chip instead of ha-label-badge for add-on capabilities (#10398) 2021-10-25 18:25:37 +02:00
Paulus SchoutsenandGitHub dd11fb1b99 Add automation editor to gallery (#10392) 2021-10-25 15:53:32 +00:00
Bram KragtenandGitHub bf0d102c86 Fix timezone issues with date formatting for ES5 (#10370) 2021-10-25 08:33:15 -07:00
dad2b92d2e Use ha-chip for alarm control panel card (#10393)
Co-authored-by: Bram Kragten <[email protected]>
2021-10-25 15:28:29 +00:00
Nathan OrickandGitHub d027ec0018 Add stopPropagation to move click handlers (#10379) 2021-10-25 17:08:30 +02:00
Philip AllgaierandGitHub 0c038398aa Fix various slugify() issues + add tests (#10383) 2021-10-25 16:26:38 +02:00
Raman GuptaandGitHub 5c3e0cc016 Add additional properties to zwave_js device info panel (#10132) 2021-10-25 16:13:59 +02:00
Zack BarettandGitHub 9bcd26ce57 Fix Full Calendar Background color (#10373) 2021-10-25 15:23:55 +02:00
Rogério RibeiroandGitHub 3e8a6c418c Update markdown card to allow word to be broken (#10387) 2021-10-25 12:43:38 +00:00
Paulus SchoutsenandGitHub 279f3e1183 Trim device name from entities on device page (#10285) 2021-10-25 12:56:33 +02:00
Paulus SchoutsenandGitHub f77339ad85 Make all automation type pickers use natural width to be able to show… (#10391) 2021-10-25 12:55:26 +02:00
Bram KragtenandGitHub da73b316ff Remove deprecated icons that where replaced (#10371) 2021-10-25 12:12:16 +02:00
Michael IrigoyenandGitHub 82a49d2cbf Update MDI to v6.4.95 (#10389) 2021-10-25 11:00:32 +02:00
49 changed files with 1340 additions and 665 deletions
+2 -3
View File
@@ -99,6 +99,7 @@ const findDifferentiator = (curString, prevString) => {
gulp.task("gen-icons-json", (done) => {
const meta = getMeta();
const metaAndRemoved = addRemovedMeta(meta);
const split = splitBySize(metaAndRemoved);
@@ -138,11 +139,9 @@ gulp.task("gen-icons-json", (done) => {
JSON.stringify({ version: package.version, parts })
);
const orderedMeta = orderMeta(meta);
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconList.json"),
JSON.stringify(orderedMeta.map((icon) => icon.name))
JSON.stringify(orderMeta(meta).map((icon) => icon.name))
);
done();
@@ -0,0 +1,91 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import "../../../src/panels/config/automation/action/ha-automation-action";
import { HaChooseAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-choose";
import { HaDelayAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-delay";
import { HaDeviceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
import { HaEventAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-event";
import { HaRepeatAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
import { HaSceneAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-scene";
import { HaServiceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-service";
import { HaWaitForTriggerAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
import { HaWaitAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
import { Action } from "../../../src/data/script";
import { HaConditionAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-condition";
const SCHEMAS: { name: string; actions: Action[] }[] = [
{ name: "Event", actions: [HaEventAction.defaultConfig] },
{ name: "Device", actions: [HaDeviceAction.defaultConfig] },
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
];
@customElement("demo-automation-editor-action")
class DemoHaAutomationEditorAction extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.actions);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-action
slot=${slot}
.hass=${this.hass}
.actions=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-action>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-action": DemoHaAutomationEditorAction;
}
}
@@ -0,0 +1,127 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import type { Condition } from "../../../src/data/automation";
import "../../../src/panels/config/automation/condition/ha-automation-condition";
import { HaDeviceCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
import { HaLogicalCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
import HaNumericStateCondition from "../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state";
import { HaStateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-state";
import { HaSunCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-sun";
import { HaTemplateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-template";
import { HaTimeCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-time";
import { HaTriggerCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
import { HaZoneCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
{
name: "State",
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
},
{
name: "Numeric State",
conditions: [
{ condition: "numeric_state", ...HaNumericStateCondition.defaultConfig },
],
},
{
name: "Sun",
conditions: [{ condition: "sun", ...HaSunCondition.defaultConfig }],
},
{
name: "Zone",
conditions: [{ condition: "zone", ...HaZoneCondition.defaultConfig }],
},
{
name: "Time",
conditions: [{ condition: "time", ...HaTimeCondition.defaultConfig }],
},
{
name: "Template",
conditions: [
{ condition: "template", ...HaTemplateCondition.defaultConfig },
],
},
{
name: "Device",
conditions: [{ condition: "device", ...HaDeviceCondition.defaultConfig }],
},
{
name: "And",
conditions: [{ condition: "and", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Or",
conditions: [{ condition: "or", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Not",
conditions: [{ condition: "not", ...HaLogicalCondition.defaultConfig }],
},
{
name: "Trigger",
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
},
];
@customElement("demo-automation-editor-condition")
class DemoHaAutomationEditorCondition extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.conditions);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-condition
slot=${slot}
.hass=${this.hass}
.conditions=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-condition>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-condition": DemoHaAutomationEditorCondition;
}
}
@@ -0,0 +1,159 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import type { HomeAssistant } from "../../../src/types";
import "../components/demo-black-white-row";
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
import type { Trigger } from "../../../src/data/automation";
import { HaGeolocationTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
import { HaEventTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-event";
import { HaHassTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
import { HaNumericStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
import { HaSunTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-sun";
import { HaTagTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-tag";
import { HaTemplateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-template";
import { HaTimeTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time";
import { HaTimePatternTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern";
import { HaWebhookTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-webhook";
import { HaZoneTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-zone";
import { HaDeviceTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-device";
import { HaStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
import { HaMQTTTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
import "../../../src/panels/config/automation/trigger/ha-automation-trigger";
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
{
name: "State",
triggers: [{ platform: "state", ...HaStateTrigger.defaultConfig }],
},
{
name: "MQTT",
triggers: [{ platform: "mqtt", ...HaMQTTTrigger.defaultConfig }],
},
{
name: "GeoLocation",
triggers: [
{ platform: "geo_location", ...HaGeolocationTrigger.defaultConfig },
],
},
{
name: "Home Assistant",
triggers: [{ platform: "homeassistant", ...HaHassTrigger.defaultConfig }],
},
{
name: "Numeric State",
triggers: [
{ platform: "numeric_state", ...HaNumericStateTrigger.defaultConfig },
],
},
{
name: "Sun",
triggers: [{ platform: "sun", ...HaSunTrigger.defaultConfig }],
},
{
name: "Time Pattern",
triggers: [
{ platform: "time_pattern", ...HaTimePatternTrigger.defaultConfig },
],
},
{
name: "Webhook",
triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }],
},
{
name: "Zone",
triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }],
},
{
name: "Tag",
triggers: [{ platform: "tag", ...HaTagTrigger.defaultConfig }],
},
{
name: "Time",
triggers: [{ platform: "time", ...HaTimeTrigger.defaultConfig }],
},
{
name: "Template",
triggers: [{ platform: "template", ...HaTemplateTrigger.defaultConfig }],
},
{
name: "Event",
triggers: [{ platform: "event", ...HaEventTrigger.defaultConfig }],
},
{
name: "Device Trigger",
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
},
];
@customElement("demo-automation-editor-trigger")
class DemoHaAutomationEditorTrigger extends LitElement {
@state() private hass!: HomeAssistant;
private data: any = SCHEMAS.map((info) => info.triggers);
constructor() {
super();
const hass = provideHass(this);
hass.updateTranslations(null, "en");
hass.updateTranslations("config", "en");
mockEntityRegistry(hass);
mockDeviceRegistry(hass);
mockAreaRegistry(hass);
mockHassioSupervisor(hass);
}
protected render(): TemplateResult {
const valueChanged = (ev) => {
const sampleIdx = ev.target.sampleIdx;
this.data[sampleIdx] = ev.detail.value;
this.requestUpdate();
};
return html`
${SCHEMAS.map(
(info, sampleIdx) => html`
<demo-black-white-row
.title=${info.name}
.value=${this.data[sampleIdx]}
>
${["light", "dark"].map(
(slot) =>
html`
<ha-automation-trigger
slot=${slot}
.hass=${this.hass}
.triggers=${this.data[sampleIdx]}
.sampleIdx=${sampleIdx}
@value-changed=${valueChanged}
></ha-automation-trigger>
`
)}
</demo-black-white-row>
`
)}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-automation-editor-trigger": DemoHaAutomationEditorTrigger;
}
}
+144 -22
View File
@@ -1,55 +1,177 @@
import { mdiHomeAssistant } from "@mdi/js";
import "../../../src/components/ha-switch";
import "../../../src/components/ha-formfield";
import { mdiClose, mdiHomeAssistant } from "@mdi/js";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import { customElement, state } from "lit/decorators";
import "../../../src/components/ha-card";
import "../../../src/components/ha-chip";
import "../../../src/components/ha-chip-set";
import type { HaChipSetItem } from "../../../src/components/ha-chip-set";
import "../../../src/components/ha-svg-icon";
const chips: {
icon?: string;
content?: string;
}[] = [
const chips: HaChipSetItem[] = [
{
leadingIcon: mdiHomeAssistant,
},
{
label: "Demo chip",
},
{
leadingIcon: mdiHomeAssistant,
label: "Demo chip",
},
{
leadingIcon: mdiHomeAssistant,
label: "Demo chip",
active: true,
},
{},
{
icon: mdiHomeAssistant,
trailingIcon: mdiClose,
label: "Demo chip",
},
{
content: "Content",
label: "Automation",
},
{
icon: mdiHomeAssistant,
content: "Content",
label: "Blueprint",
},
{
label: "Script",
},
{
label: "Scene",
},
{
label: "Person",
},
];
@customElement("demo-ha-chip")
export class DemoHaChip extends LitElement {
@state() standaloneIsDisabled = false;
@state() standaloneIsOutlined = false;
@state() standaloneIsActive = false;
protected render(): TemplateResult {
return html`
<ha-card header="ha-chip demo">
<div class="filters">
<ha-formfield label="Disable standalone chips">
<ha-switch @change=${this._toggleDisable}></ha-switch>
</ha-formfield>
<ha-formfield label="Outline standalone chips">
<ha-switch @change=${this._toggleOutline}></ha-switch>
</ha-formfield>
<ha-formfield label="Activate standalone chips">
<ha-switch @change=${this._toggleActive}></ha-switch>
</ha-formfield>
</div>
<ha-card header="Standalone ha-chip demo">
<div class="card-content">
${chips.map(
(chip) => html`
<ha-chip .hasIcon=${chip.icon !== undefined}>
${chip.icon
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
</ha-svg-icon>`
: ""}
${chip.content}
</ha-chip>
`
)}
<div class="standalone">
<span>Simple:</span>
<ha-chip
.active=${this.standaloneIsActive}
.outlined=${this.standaloneIsOutlined}
?disabled=${this.standaloneIsDisabled}
>Demo chip</ha-chip
>
</div>
<div class="standalone">
<span>Label property:</span>
<ha-chip
.active=${this.standaloneIsActive}
.outlined=${this.standaloneIsOutlined}
?disabled=${this.standaloneIsDisabled}
label="Demo chip"
></ha-chip>
</div>
<div class="standalone">
<span>With leadingIcon:</span>
<ha-chip
.active=${this.standaloneIsActive}
.leadingIcon=${mdiHomeAssistant}
.outlined=${this.standaloneIsOutlined}
?disabled=${this.standaloneIsDisabled}
>Demo chip</ha-chip
>
</div>
<div class="standalone">
<span>With trailingIcon property:</span>
<ha-chip
.active=${this.standaloneIsActive}
.trailingIcon=${mdiHomeAssistant}
.outlined=${this.standaloneIsOutlined}
?disabled=${this.standaloneIsDisabled}
>Demo chip</ha-chip
>
</div>
<div class="standalone">
<span>With trailing-icon slot:</span>
<ha-chip
.active=${this.standaloneIsActive}
.outlined=${this.standaloneIsOutlined}
?disabled=${this.standaloneIsDisabled}
>
Demo chip
<ha-svg-icon
slot="trailing-icon"
class="trailing"
.path=${mdiHomeAssistant}
></ha-svg-icon>
</ha-chip>
</div>
</div>
</ha-card>
<ha-card header="ha-chip-set demo">
<div class="card-content">
<ha-chip-set .items=${chips}> </ha-chip-set>
<ha-chip-set
.items=${chips.map((chip) => ({ ...chip, outlined: true }))}
>
</ha-chip-set>
</div>
</ha-card>
`;
}
private _toggleActive() {
this.standaloneIsActive = !this.standaloneIsActive;
}
private _toggleDisable() {
this.standaloneIsDisabled = !this.standaloneIsDisabled;
}
private _toggleOutline() {
this.standaloneIsOutlined = !this.standaloneIsOutlined;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.standalone {
margin: 4px 0;
display: flex;
justify-content: space-between;
}
.filters {
margin: 16px;
}
ha-formfield {
margin: 12px 0;
display: block;
}
`;
}
}
+18
View File
@@ -181,6 +181,24 @@ class HaGallery extends PolymerElement {
this.$.notifications.showDialog({ message: "Alert action clicked" })
);
this.addEventListener("chip-clicked", (ev) =>
this.$.notifications.showDialog({
message: `Chip ${ev.detail.index} clicked `,
})
);
this.addEventListener("chip-clicked", () =>
this.$.notifications.showDialog({
message: "Chip clicked",
})
);
this.addEventListener("chip-clicked-trailing", () =>
this.$.notifications.showDialog({
message: "Chip trailing icon clicked",
})
);
this.addEventListener("hass-more-info", (ev) => {
if (ev.detail.entityId) {
this.$.notifications.showDialog({
+175 -182
View File
@@ -11,6 +11,12 @@ import {
mdiHomeAssistant,
mdiKey,
mdiNetwork,
mdiNumeric1,
mdiNumeric2,
mdiNumeric3,
mdiNumeric4,
mdiNumeric5,
mdiNumeric6,
mdiPound,
mdiShield,
} from "@mdi/js";
@@ -25,7 +31,7 @@ import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-chip-set";
import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon";
@@ -73,6 +79,15 @@ const STAGE_ICON = {
deprecated: mdiExclamationThick,
};
const RATING_ICON = {
1: mdiNumeric1,
2: mdiNumeric2,
3: mdiNumeric3,
4: mdiNumeric4,
5: mdiNumeric5,
6: mdiNumeric6,
};
@customElement("hassio-addon-info")
class HassioAddonInfo extends LitElement {
@property({ type: Boolean }) public narrow!: boolean;
@@ -110,6 +125,137 @@ class HassioAddonInfo extends LitElement {
)}`,
},
];
const capabilities = [
{
id: "rating",
icon: RATING_ICON[this.addon.rating],
class: [6, 5].includes(Number(this.addon.rating))
? "green"
: [3, 4].includes(Number(this.addon.rating))
? "yellow"
: "red",
label: this.supervisor.localize(
"addon.dashboard.capability.label.rating"
),
},
];
if (this.addon.stage !== "stable") {
capabilities.push({
id: "rating",
icon: STAGE_ICON[this.addon.stage],
class:
this.addon.stage === "experimental"
? "yellow"
: this.addon.stage === "deprecated"
? "red"
: "",
label: this.supervisor.localize(
`addon.dashboard.capability.stages.${this.addon.stage}`
),
});
}
if (this.addon.host_network) {
capabilities.push({
id: "host_network",
icon: mdiNetwork,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.host"
),
});
}
if (this.addon.full_access) {
capabilities.push({
id: "full_access",
icon: mdiChip,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
),
});
}
if (this.addon.homeassistant_api) {
capabilities.push({
id: "homeassistant_api",
icon: mdiHomeAssistant,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.core"
),
});
}
if (this._computeHassioApi) {
capabilities.push({
id: "hassio_api",
icon: mdiHomeAssistant,
class: "",
label:
this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role,
});
}
if (this.addon.docker_api) {
capabilities.push({
id: "docker_api",
icon: mdiDocker,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.docker"
),
});
}
if (this.addon.host_pid) {
capabilities.push({
id: "host_pid",
icon: mdiPound,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
),
});
}
if (this.addon.apparmor !== "default") {
capabilities.push({
id: "apparmor",
icon: mdiShield,
class: this._computeApparmorClassName,
label: this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
),
});
}
if (this.addon.auth_api) {
capabilities.push({
id: "auth_api",
icon: mdiKey,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.auth"
),
});
}
if (this.addon.ingress) {
capabilities.push({
id: "ingress",
icon: mdiCursorDefaultClickOutline,
class: "",
label: this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
),
});
}
return html`
${this.addon.update_available
? html`
@@ -246,6 +392,21 @@ class HassioAddonInfo extends LitElement {
>`}
</div>
<div class="capabilities">
<ha-chip-set>
${capabilities.map(
(capability) => html`<ha-chip
@click=${this._showMoreInfo}
.leadingIcon=${capability.icon}
.label=${capability.label}
.id=${capability.id}
class=${capability.class}
outlined
></ha-chip>`
)}
</ha-chip-set>
</div>
<div class="description light-color">
${this.addon.description}.<br />
${this.supervisor.localize(
@@ -266,172 +427,6 @@ class HassioAddonInfo extends LitElement {
/>
`
: ""}
<div class="security">
${this.addon.stage !== "stable"
? html` <ha-label-badge
class=${classMap({
yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated",
})}
@click=${this._showMoreInfo}
id="stage"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.stage"
)}
description=""
>
<ha-svg-icon
.path=${STAGE_ICON[this.addon.stage]}
></ha-svg-icon>
</ha-label-badge>`
: ""}
<ha-label-badge
class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)),
yellow: [3, 4].includes(Number(this.addon.rating)),
red: [1, 2].includes(Number(this.addon.rating)),
})}
@click=${this._showMoreInfo}
id="rating"
label="rating"
description=""
>
${this.addon.rating}
</ha-label-badge>
${this.addon.host_network
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_network"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host"
)}
description=""
>
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.full_access
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="full_access"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hardware"
)}
description=""
>
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.homeassistant_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="homeassistant_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hass"
)}
description=""
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this._computeHassioApi
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="hassio_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.hassio"
)}
.description=${this.supervisor.localize(
`addon.dashboard.capability.role.${this.addon.hassio_role}`
) || this.addon.hassio_role}
>
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.docker_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="docker_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.docker"
)}
description=""
>
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.host_pid
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="host_pid"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.host_pid"
)}
description=""
>
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.apparmor
? html`
<ha-label-badge
@click=${this._showMoreInfo}
class=${this._computeApparmorClassName}
id="apparmor"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.apparmor"
)}
description=""
>
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.auth_api
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="auth_api"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.auth"
)}
description=""
>
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
`
: ""}
${this.addon.ingress
? html`
<ha-label-badge
@click=${this._showMoreInfo}
id="ingress"
.label=${this.supervisor.localize(
"addon.dashboard.capability.label.ingress"
)}
description=""
>
<ha-svg-icon
.path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
`
: ""}
</div>
${this.addon.version
? html`
<div
@@ -1175,34 +1170,29 @@ class HassioAddonInfo extends LitElement {
.description a {
color: var(--primary-color);
}
ha-chip {
text-transform: capitalize;
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
--ha-chip-background-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
--ha-chip-background-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
.capabilities {
margin: -16px 0 8px;
}
.card-actions {
justify-content: space-between;
display: flex;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
@@ -1242,6 +1232,9 @@ class HassioAddonInfo extends LitElement {
}
@media (max-width: 720px) {
ha-chip {
line-height: 36px;
}
.addon-options {
max-width: 100%;
}
+2 -2
View File
@@ -67,8 +67,8 @@
"@material/mwc-tab-bar": "0.25.2",
"@material/mwc-textfield": "0.25.2",
"@material/top-app-bar": "14.0.0-canary.353ca7e9f.0",
"@mdi/js": "6.3.95",
"@mdi/svg": "6.3.95",
"@mdi/js": "6.4.95",
"@mdi/svg": "6.4.95",
"@polymer/app-layout": "^3.1.0",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
+32 -28
View File
@@ -1,43 +1,45 @@
import {
mdiBattery,
mdiBatteryOutline,
mdiBatteryCharging,
mdiThermometer,
mdiSnowflake,
mdiServerNetworkOff,
mdiServerNetwork,
mdiDoorClosed,
mdiDoorOpen,
mdiGarage,
mdiGarageOpen,
mdiPowerPlugOff,
mdiPowerPlug,
mdiCheckCircle,
mdiAlertCircle,
mdiSmoke,
mdiFire,
mdiBattery,
mdiBatteryCharging,
mdiBatteryOutline,
mdiBrightness5,
mdiBrightness7,
mdiCheckboxMarkedCircle,
mdiCheckCircle,
mdiCropPortrait,
mdiDoorClosed,
mdiDoorOpen,
mdiFire,
mdiGarage,
mdiGarageOpen,
mdiHome,
mdiHomeOutline,
mdiLock,
mdiLockOpen,
mdiWaterOff,
mdiWater,
mdiWalk,
mdiRun,
mdiHomeOutline,
mdiHome,
mdiSquare,
mdiSquareOutline,
mdiMusicNoteOff,
mdiMusicNote,
mdiMusicNoteOff,
mdiPackage,
mdiPackageUp,
mdiCropPortrait,
mdiPlay,
mdiPowerPlug,
mdiPowerPlugOff,
mdiRadioboxBlank,
mdiRun,
mdiServerNetwork,
mdiServerNetworkOff,
mdiSmoke,
mdiSnowflake,
mdiSquare,
mdiSquareOutline,
mdiStop,
mdiThermometer,
mdiVibrate,
mdiWalk,
mdiWater,
mdiWaterOff,
mdiWindowClosed,
mdiWindowOpen,
mdiRadioboxBlank,
mdiCheckboxMarkedCircle,
} from "@mdi/js";
import { HassEntity } from "home-assistant-js-websocket";
@@ -85,6 +87,8 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
case "presence":
return is_off ? mdiHomeOutline : mdiHome;
case "running":
return is_off ? mdiStop : mdiPlay;
case "sound":
return is_off ? mdiMusicNoteOff : mdiMusicNote;
case "update":
@@ -0,0 +1,24 @@
/**
* Strips a device name from an entity name.
* @param entityName the entity name
* @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
* @returns
*/
export const stripPrefixFromEntityName = (
entityName: string,
lowerCasedPrefixWithSpaceSuffix: string
) => {
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
return undefined;
}
const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
// If first word already has an upper case letter (e.g. from brand name)
// leave as-is, otherwise capitalize the first word.
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
? newName
: newName[0].toUpperCase() + newName.slice(1);
};
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
+4 -4
View File
@@ -12,8 +12,8 @@ export const slugify = (value: string, delimiter = "_") => {
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
.replace(/[^\w-]+/g, "") // Remove all non-word characters
.replace(/-/, delimiter) // Replace - with delimiter
.replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single delimiter
.replace(new RegExp(`/^${delimiter}+/`), "") // Trim delimiter from start of text
.replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
.replace(/-/g, delimiter) // Replace - with delimiter
.replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter
.replace(new RegExp(`^${delimiter}+`), "") // Trim delimiter from start of text
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
};
+24 -26
View File
@@ -9,49 +9,47 @@ import {
unsafeCSS,
} from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
import "./ha-chip";
declare global {
// for fire event
interface HASSDomEvents {
"chip-clicked": { index: string };
}
export interface HaChipSetItem {
label?: string;
leadingIcon?: string;
trailingIcon?: string;
outlined?: boolean;
active?: boolean;
}
@customElement("ha-chip-set")
export class HaChipSet extends LitElement {
@property() public items = [];
@property({ attribute: false }) public items?: HaChipSetItem[];
protected render(): TemplateResult {
if (this.items.length === 0) {
return html``;
}
return html`
<div class="mdc-chip-set">
${this.items.map(
(item, idx) =>
html`
<ha-chip .index=${idx} @click=${this._handleClick}>
${item}
</ha-chip>
`
)}
${this.items
? this.items.map(
(item, idx) =>
html`
<ha-chip
.index=${idx}
.active=${item.active || false}
.label=${item.label}
.leadingIcon=${item.leadingIcon}
.trailingIcon=${item.trailingIcon}
?outlined=${item.outlined}
>
</ha-chip>
`
)
: html`<slot></slot>`}
</div>
`;
}
private _handleClick(ev): void {
fireEvent(this, "chip-clicked", {
index: ev.currentTarget.index,
});
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
ha-chip {
.mdc-chip-set > ha-chip, slot::slotted(ha-chip) {
margin: 4px;
}
`;
+137 -19
View File
@@ -1,4 +1,5 @@
// @ts-ignore
import "./ha-circular-progress";
import "./ha-svg-icon"; // @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import {
css,
@@ -9,56 +10,173 @@ import {
unsafeCSS,
} from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event";
declare global {
// for fire event
interface HASSDomEvents {
"chip-clicked": { index: string };
"chip-clicked": { index: number | undefined };
"chip-clicked-trailing": { index: number | undefined };
}
}
@customElement("ha-chip")
export class HaChip extends LitElement {
@property() public index = 0;
@property({ type: Number }) public index?: number;
@property({ type: Boolean }) public hasIcon = false;
@property({ type: Boolean }) public outlined = false;
@property({ type: Boolean }) public active = false;
@property() public label?: string;
@property() public leadingIcon?: string;
@property() public trailingIcon?: string;
protected render(): TemplateResult {
return html`
<div class="mdc-chip" .index=${this.index}>
${this.hasIcon
? html`<div class="mdc-chip__icon mdc-chip__icon--leading">
<slot name="icon"></slot>
</div>`
: null}
<div
class="mdc-chip ${this.outlined ? "outlined" : ""}"
@click=${this._handleClick}
>
${this.leadingIcon
? html`<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
${this.active
? html`<ha-circular-progress alt="active" size="tiny" active>
</ha-circular-progress>`
: html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${this.leadingIcon}
>
</ha-svg-icon>`}
</span>
</span>`
: ""}
<div class="mdc-chip__ripple"></div>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"><slot></slot></span>
<span role="row" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">
${this.label || html`<slot></slot>`}
</span>
</span>
</span>
<span role="gridcell">
<span role="button" tabindex="-1" class="mdc-chip__primary-action">
${this.trailingIcon
? html`<ha-svg-icon
@click=${this._handleTrailingClick}
class="mdc-chip__icon mdc-chip__icon--trailing"
.path=${this.trailingIcon}
>
</ha-svg-icon>`
: html`<slot name="trailing-icon"></slot>`}
</span>
</span>
</div>
`;
}
private _handleClick(): void {
fireEvent(this, "chip-clicked", { index: this.index });
}
private _handleTrailingClick(): void {
fireEvent(this, "chip-clicked-trailing", {
index: this.index,
});
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
background-color: var(
--ha-chip-background-color,
rgba(var(--rgb-primary-text-color), 0.15)
);
color: var(--ha-chip-text-color, var(--primary-text-color));
background-color: var(--ha-chip-background-color, var(--primary-color));
color: var(--ha-chip-text-color, var(--text-primary-color));
}
.mdc-chip:hover {
color: var(--ha-chip-text-color, var(--primary-text-color));
color: var(--ha-chip-text-color, var(--text-primary-color));
}
.mdc-chip.outlined {
margin: 0 -1px !important;
border: 1px solid var(--ha-chip-background-color, var(--primary-color));
background-color: var(
--ha-chip-outlined-background-color,
var(--card-background-color)
);
color: var(--ha-chip-outlined-text-color, var(--primary-text-color));
}
.mdc-chip:not(.outlined) .mdc-chip__icon.mdc-chip__icon--leading {
margin-left: -12px !important;
margin-right: -2px;
color: var(--ha-chip-icon-color, var(--text-primary-color));
}
ha-circular-progress {
--mdc-theme-primary: var(
--ha-chip-icon-color,
var(--text-primary-color)
);
padding: 8px;
margin-left: -12px !important;
margin-right: -2px;
}
.mdc-chip.outlined ha-circular-progress {
border-radius: 50%;
margin-right: 4px;
background-color: var(--ha-chip-background-color, var(--primary-color));
--mdc-theme-primary: var(
--ha-chip-icon-color,
var(--text-primary-color)
);
}
.mdc-chip.outlined ha-svg-icon,
slot[name="trailing-icon"]::slotted(ha-svg-icon) {
border-radius: 50%;
background-color: var(--ha-chip-background-color, var(--primary-color));
color: var(--ha-chip-icon-color, var(--text-primary-color));
}
.mdc-chip.outlined .mdc-chip__icon.mdc-chip__icon--leading {
margin-left: -13px !important;
color: var(--ha-chip-icon-color, var(--text-primary-color));
}
.mdc-chip__icon.mdc-chip__icon--trailing,
slot[name="trailing-icon"]::slotted(ha-svg-icon) {
width: 18px;
height: 18px;
font-size: 18px;
padding: 2px;
color: var(--ha-chip-icon-color, var(--text-primary-color));
margin-right: -8px;
display: inline-flex;
align-items: center;
--mdc-icon-size: 12px;
}
slot[name="trailing-icon"]::slotted(ha-svg-icon) {
margin-left: 4px;
}
.mdc-chip__icon--leading {
display: flex;
align-items: center;
justify-content: center;
color: inherit;
border-radius: 50%;
padding: 6px;
--mdc-icon-size: 20px;
color: var(--ha-chip-icon-color, var(--ha-chip-text-color));
}
:host([disabled]) .mdc-chip {
opacity: var(--light-disabled-opacity);
pointer-events: none;
}
`;
}
+1
View File
@@ -52,6 +52,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
return html`
<mwc-select
fixedMenuPosition
naturalMenuWidth
.label=${this.label}
.value=${this.data}
.disabled=${this.disabled}
-20
View File
@@ -52,18 +52,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
newName: "cast-variant",
removeIn: "2021.12",
},
application: {
newName: "application-outline",
removeIn: "2021.12",
},
"application-cog": {
newName: "application-cog-outline",
removeIn: "2021.12",
},
"application-settings": {
newName: "application-settings-outline",
removeIn: "2021.12",
},
bandcamp: {
removeIn: "2021.12",
},
@@ -77,14 +65,6 @@ const mdiDeprecatedIcons: DeprecatedIcon = {
newName: "cross-bolnisi",
removeIn: "2021.12",
},
"boom-gate-up": {
newName: "boom-gate-arrow-up",
removeIn: "2021.12",
},
"boom-gate-up-outline": {
newName: "boom-gate-arrow-up-outline",
removeIn: "2021.12",
},
"boom-gate-down": {
newName: "boom-gate-arrow-down",
removeIn: "2021.12",
+103 -190
View File
@@ -1,5 +1,3 @@
// @ts-ignore
import chipStyles from "@material/chips/dist/mdc.chips.min.css";
import "@material/mwc-button/mwc-button";
import {
mdiClose,
@@ -14,13 +12,13 @@ import {
HassServiceTarget,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
import { css, CSSResultGroup, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { fireEvent } from "../common/dom/fire_event";
import { ensureArray } from "../common/ensure-array";
import { computeDomain } from "../common/entity/compute_domain";
import { computeStateName } from "../common/entity/compute_state_name";
import { stateIconPath } from "../common/entity/state_icon_path";
import {
AreaRegistryEntry,
subscribeAreaRegistry,
@@ -41,6 +39,7 @@ import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
import "./entity/ha-entity-picker";
import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
import "./ha-area-picker";
import "./ha-chip-set";
import "./ha-icon-button";
import "./ha-svg-icon";
@@ -114,7 +113,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
if (!this._areas || !this._devices || !this._entities) {
return html``;
}
return html`<div class="mdc-chip-set items">
return html`<ha-chip-set>
${this.value?.area_id
? ensureArray(this.value.area_id).map((area_id) => {
const area = this._areas![area_id];
@@ -150,70 +149,39 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
);
})
: ""}
</div>
</ha-chip-set>
${this._renderPicker()}
<div class="mdc-chip-set">
<div
class="mdc-chip area_id add"
<ha-chip-set>
<ha-chip
class="area_id add"
.type=${"area_id"}
@click=${this._showPicker}
.leadingIcon=${mdiPlus}
.label=${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_area_id"
)}</span
>
</span>
</span>
</div>
<div
class="mdc-chip device_id add"
</ha-chip>
<ha-chip
class="device_id add"
.type=${"device_id"}
@click=${this._showPicker}
.leadingIcon=${mdiPlus}
.label=${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_device_id"
)}</span
>
</span>
</span>
</div>
<div
class="mdc-chip entity_id add"
</ha-chip>
<ha-chip
class="entity_id add"
.type=${"entity_id"}
@click=${this._showPicker}
>
<div class="mdc-chip__ripple"></div>
<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${mdiPlus}
></ha-svg-icon>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text"
>${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}</span
>
</span>
</span>
</div>
</div>`;
.leadingIcon=${mdiPlus}
.label=${this.hass.localize(
"ui.components.target-picker.add_entity_id"
)}
></ha-chip>
</ha-chip-set>`;
}
private async _showPicker(ev) {
@@ -233,69 +201,59 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
iconPath?: string
) {
return html`
<div
class="mdc-chip ${classMap({
[type]: true,
})}"
<ha-chip
class=${type}
.id=${id}
.leadingIcon=${iconPath ||
entityState?.attributes.icon ||
stateIconPath(entityState)}
.label=${name}
outlined
>
${iconPath
? html`<ha-svg-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.path=${iconPath}
></ha-svg-icon>`
: ""}
${entityState
? html`<ha-state-icon
class="mdc-chip__icon mdc-chip__icon--leading"
.state=${entityState}
></ha-state-icon>`
: ""}
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">${name}</span>
<div slot="trailing-icon">
${type === "entity_id"
? ""
: html`
<span>
<ha-icon-button
class="expand-btn"
tabindex="-1"
role="button"
.label=${this.hass.localize(
"ui.components.target-picker.expand"
)}
.path=${mdiUnfoldMoreVertical}
hideTitle
.id=${id}
.type=${type}
@click=${this._handleExpand}
></ha-icon-button>
<paper-tooltip class="expand" animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}</paper-tooltip
>
</span>
`}
<span>
<ha-icon-button
tabindex="-1"
role="button"
.label=${this.hass.localize("ui.components.target-picker.expand")}
.path=${mdiClose}
hideTitle
.id=${id}
.type=${type}
@click=${this._handleRemove}
></ha-icon-button>
<paper-tooltip animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.remove_${type}`
)}</paper-tooltip
>
</span>
</span>
${type === "entity_id"
? ""
: html` <span role="gridcell">
<ha-icon-button
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${this.hass.localize(
"ui.components.target-picker.expand"
)}
.path=${mdiUnfoldMoreVertical}
hideTooltip
.id=${id}
.type=${type}
@click=${this._handleExpand}
></ha-icon-button>
<paper-tooltip class="expand" animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.expand_${type}`
)}</paper-tooltip
>
</span>`}
<span role="gridcell">
<ha-icon-button
class="mdc-chip__icon mdc-chip__icon--trailing"
tabindex="-1"
role="button"
.label=${this.hass.localize("ui.components.target-picker.expand")}
.path=${mdiClose}
hideTooltip
.id=${id}
.type=${type}
@click=${this._handleRemove}
></ha-icon-button>
<paper-tooltip animation-delay="0"
>${this.hass.localize(
`ui.components.target-picker.remove_${type}`
)}</paper-tooltip
>
</span>
</div>
</div>
</ha-chip>
`;
}
@@ -528,84 +486,39 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(chipStyles)}
.mdc-chip {
color: var(--primary-text-color);
}
.items {
z-index: 2;
}
.mdc-chip-set {
padding: 4px 0;
}
.mdc-chip.add {
color: rgba(0, 0, 0, 0.87);
}
.mdc-chip:not(.add) {
cursor: default;
}
.mdc-chip ha-icon-button {
--mdc-icon-button-size: 24px;
display: flex;
align-items: center;
outline: none;
}
.mdc-chip ha-icon-button ha-svg-icon {
border-radius: 50%;
background: var(--secondary-text-color);
}
.mdc-chip__icon.mdc-chip__icon--trailing {
width: 16px;
height: 16px;
--mdc-icon-size: 14px;
color: var(--secondary-text-color);
}
.mdc-chip__icon--leading {
display: flex;
align-items: center;
justify-content: center;
--mdc-icon-size: 20px;
border-radius: 50%;
padding: 6px;
margin-left: -14px !important;
}
.expand-btn {
margin-right: 0;
}
.mdc-chip.area_id:not(.add) {
border: 2px solid #fed6a4;
background: var(--card-background-color);
ha-chip {
--ha-chip-icon-color: var(--secondary-text-color);
--ha-chip-text-color: var(--primary-text-color);
}
.mdc-chip.area_id:not(.add) .mdc-chip__icon--leading,
.mdc-chip.area_id.add {
background: #fed6a4;
}
.mdc-chip.device_id:not(.add) {
border: 2px 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: 2px 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:hover {
ha-chip:hover {
z-index: 5;
}
ha-icon-button {
--mdc-icon-size: 18px;
--mdc-icon-button-size: 24px;
color: var(--secondary-text-color);
outline: none;
}
ha-chip > div {
margin-right: -8px;
display: inline-flex;
align-items: center;
}
ha-chip.area_id {
--ha-chip-background-color: #fed6a4;
}
ha-chip.device_id {
--ha-chip-background-color: #a8e1fb;
}
ha-chip.entity_id {
--ha-chip-background-color: #d2e7b9;
}
paper-tooltip.expand {
min-width: 200px;
}
:host([disabled]) .mdc-chip {
opacity: var(--light-disabled-opacity);
pointer-events: none;
}
`;
}
}
+4 -4
View File
@@ -194,10 +194,10 @@ export interface NumericStateCondition extends BaseCondition {
export interface SunCondition extends BaseCondition {
condition: "sun";
after_offset: number;
before_offset: number;
after: "sunrise" | "sunset";
before: "sunrise" | "sunset";
after_offset?: number;
before_offset?: number;
after?: "sunrise" | "sunset";
before?: "sunrise" | "sunset";
}
export interface ZoneCondition extends BaseCondition {
+3
View File
@@ -84,6 +84,9 @@ export interface ZWaveJSNodeStatus {
ready: boolean;
status: number;
is_secure: boolean | string;
is_routing: boolean | null;
zwave_plus_version: number | null;
highest_security_class: SecurityClass | null;
}
export interface ZwaveJSNodeMetadata {
+11 -15
View File
@@ -269,23 +269,15 @@ export class QuickBar extends LitElement {
class="command-item"
hasMeta
>
<span>
<span class="command-entry">
<ha-chip
.label=${item.categoryText}
hasIcon
.leadingIcon=${item.iconPath}
class="command-category ${item.categoryKey}"
>
${item.iconPath
? html`<ha-svg-icon
.path=${item.iconPath}
slot="icon"
></ha-svg-icon>`
: ""}
${item.categoryText}</ha-chip
>
</ha-chip>
${item.primaryText}
</span>
<span class="command-text">${item.primaryText}</span>
</mwc-list-item>
`;
}
@@ -607,6 +599,10 @@ export class QuickBar extends LitElement {
}
}
ha-chip {
margin-right: 8px;
}
ha-icon.entity,
ha-svg-icon.entity {
margin-left: 20px;
@@ -623,7 +619,6 @@ export class QuickBar extends LitElement {
}
.command-category {
--ha-chip-icon-color: #585858;
--ha-chip-text-color: #212121;
}
@@ -639,8 +634,9 @@ export class QuickBar extends LitElement {
--ha-chip-background-color: var(--warning-color);
}
span.command-text {
margin-left: 8px;
span.command-entry {
display: flex;
align-items: center;
}
mwc-list-item {
+4
View File
@@ -371,6 +371,10 @@ export class HAFullCalendar extends LitElement {
);
--fc-theme-standard-border-color: var(--divider-color);
--fc-border-color: var(--divider-color);
--fc-page-bg-color: var(
--ha-card-background,
var(--card-background-color, white)
);
}
a {
@@ -230,6 +230,7 @@ export default class HaAutomationActionRow extends LitElement {
"ui.panel.config.automation.editor.actions.type_select"
)}
.value=${getType(this.action)}
naturalMenuWidth
@selected=${this._typeChanged}
>
${this._processedTypes(this.hass.localize).map(
@@ -90,6 +90,7 @@ export default class HaAutomationConditionEditor extends LitElement {
"ui.panel.config.automation.editor.conditions.type_select"
)}
.value=${this.condition.condition}
naturalMenuWidth
@selected=${this._typeChanged}
>
${this._processedTypes(this.hass.localize).map(
@@ -1,10 +1,11 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import { fireEvent } from "../../../../../common/dom/fire_event";
import { LogicalCondition } from "../../../../../data/automation";
import { Condition, LogicalCondition } from "../../../../../data/automation";
import { HomeAssistant } from "../../../../../types";
import "../ha-automation-condition";
import { ConditionElement } from "../ha-automation-condition-row";
import { HaStateCondition } from "./ha-automation-condition-state";
@customElement("ha-automation-condition-logical")
export class HaLogicalCondition extends LitElement implements ConditionElement {
@@ -13,7 +14,14 @@ export class HaLogicalCondition extends LitElement implements ConditionElement {
@property() public condition!: LogicalCondition;
public static get defaultConfig() {
return { conditions: [{ condition: "state" }] };
return {
conditions: [
{
condition: "state",
...HaStateCondition.defaultConfig,
},
] as Condition[],
};
}
protected render() {
@@ -179,6 +179,7 @@ export default class HaAutomationTriggerRow extends LitElement {
"ui.panel.config.automation.editor.triggers.type_select"
)}
.value=${this.trigger.platform}
naturalMenuWidth
@selected=${this._typeChanged}
>
${this._processedTypes(this.hass.localize).map(
@@ -10,7 +10,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
const includeDomains = ["zone"];
@customElement("ha-automation-trigger-geo_location")
export default class HaGeolocationTrigger extends LitElement {
export class HaGeolocationTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public trigger!: GeoLocationTrigger;
@@ -19,7 +19,7 @@ export default class HaGeolocationTrigger extends LitElement {
return {
source: "",
zone: "",
event: "enter",
event: "enter" as GeoLocationTrigger["event"],
};
}
@@ -8,14 +8,14 @@ import "../../../../../components/ha-formfield";
import "../../../../../components/ha-radio";
@customElement("ha-automation-trigger-homeassistant")
export default class HaHassTrigger extends LitElement {
export class HaHassTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public trigger!: HassTrigger;
public static get defaultConfig() {
return {
event: "start",
event: "start" as HassTrigger["event"],
};
}
@@ -12,7 +12,7 @@ import { handleChangeEvent } from "../ha-automation-trigger-row";
import "../../../../../components/ha-duration-input";
@customElement("ha-automation-trigger-numeric_state")
export default class HaNumericStateTrigger extends LitElement {
export class HaNumericStateTrigger extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public trigger!: NumericStateTrigger;
@@ -20,7 +20,8 @@ export class HaSunTrigger extends LitElement implements TriggerElement {
public static get defaultConfig() {
return {
event: "sunrise",
event: "sunrise" as SunTrigger["event"],
offset: 0,
};
}
@@ -27,7 +27,7 @@ export class HaZoneTrigger extends LitElement {
return {
entity_id: "",
zone: "",
event: "enter",
event: "enter" as ZoneTrigger["event"],
};
}
@@ -25,7 +25,7 @@ class HaConfigCustomize extends LitElement {
protected render(): TemplateResult {
return html`
<hass-tabs-subpage
.hass=${this.hass}
.hass=${this.hass}
.narrow=${this.narrow}
.route=${this.route}
back-path="/config"
@@ -180,10 +180,16 @@ export class HaFormCustomize extends LocalizeMixin(PolymerElement) {
this.newAttributes
);
attrs.forEach((attr) => {
if (attr.closed || attr.secondary || !attr.attribute || !attr.value)
if (
attr.closed ||
attr.secondary ||
!attr.attribute ||
attr.value === null ||
attr.value === undefined
)
return;
const value = attr.type === "json" ? JSON.parse(attr.value) : attr.value;
if (!value) return;
if (value === null || value === undefined) return;
data[attr.attribute] = value;
});
@@ -57,9 +57,9 @@ export abstract class HaDeviceAutomationCard<
<div class="content">
<ha-chip-set
@chip-clicked=${this._handleAutomationClicked}
.items=${this.automations.map((automation) =>
this._localizeDeviceAutomation(this.hass, automation)
)}
.items=${this.automations.map((automation) => ({
label: this._localizeDeviceAutomation(this.hass, automation),
}))}
>
</ha-chip-set>
</div>
@@ -15,18 +15,23 @@ import { domainIcon } from "../../../../common/entity/domain_icon";
import "../../../../components/entity/state-badge";
import "../../../../components/ha-card";
import "../../../../components/ha-icon";
import { HomeAssistant } from "../../../../types";
import { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
import type { LovelaceRowConfig } from "../../../lovelace/entity-rows/types";
import type { HomeAssistant } from "../../../../types";
import type { HuiErrorCard } from "../../../lovelace/cards/hui-error-card";
import { createRowElement } from "../../../lovelace/create-element/create-row-element";
import { addEntitiesToLovelaceView } from "../../../lovelace/editor/add-entities-to-view";
import { LovelaceRow } from "../../../lovelace/entity-rows/types";
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
import { EntityRegistryStateEntry } from "../ha-config-device-page";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { stripPrefixFromEntityName } from "../../../../common/entity/strip_prefix_from_entity_name";
@customElement("ha-device-entities-card")
export class HaDeviceEntitiesCard extends LitElement {
@property() public header!: string;
@property() public deviceName!: string;
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public entities!: EntityRegistryStateEntry[];
@@ -119,9 +124,21 @@ export class HaDeviceEntitiesCard extends LitElement {
}
private _renderEntity(entry: EntityRegistryStateEntry): TemplateResult {
const element = createRowElement({ entity: entry.entity_id });
const config: LovelaceRowConfig = {
entity: entry.entity_id,
};
const element = createRowElement(config);
if (this.hass) {
element.hass = this.hass;
const state = this.hass.states[entry.entity_id];
const name = stripPrefixFromEntityName(
computeStateName(state),
`${this.deviceName} `.toLowerCase()
);
if (name) {
config.name = name;
}
}
// @ts-ignore
element.entry = entry;
@@ -131,7 +148,11 @@ export class HaDeviceEntitiesCard extends LitElement {
private _renderEntry(entry: EntityRegistryStateEntry): TemplateResult {
return html`
<paper-icon-item .entry=${entry} @click=${this._openEditEntry}>
<paper-icon-item
class="disabled-entry"
.entry=${entry}
@click=${this._openEditEntry}
>
<ha-svg-icon
slot="item-icon"
.path=${domainIcon(computeDomain(entry.entity_id))}
@@ -188,6 +209,9 @@ export class HaDeviceEntitiesCard extends LitElement {
.disabled-entry {
color: var(--secondary-text-color);
}
#entities {
margin-top: -24px; /* match the spacing between card title and content of the device info card above it */
}
#entities > * {
margin: 8px 16px 8px 8px;
}
@@ -196,8 +220,9 @@ export class HaDeviceEntitiesCard extends LitElement {
}
paper-icon-item {
min-height: 40px;
padding: 0 8px;
padding: 0 16px;
cursor: pointer;
--paper-item-icon-width: 48px;
}
.name {
font-size: 14px;
@@ -18,6 +18,7 @@ import {
nodeStatus,
ZWaveJSNodeStatus,
ZWaveJSNodeIdentifiers,
SecurityClass,
} from "../../../../../../data/zwave_js";
import { haStyle } from "../../../../../../resources/styles";
import { HomeAssistant } from "../../../../../../types";
@@ -117,13 +118,33 @@ export class HaDeviceInfoZWaveJS extends LitElement {
: this.hass.localize("ui.common.no")}
</div>
<div>
${this.hass.localize("ui.panel.config.zwave_js.device_info.is_secure")}:
${this._node.is_secure === true
? this.hass.localize("ui.common.yes")
${this.hass.localize(
"ui.panel.config.zwave_js.device_info.highest_security"
)}:
${this._node.highest_security_class !== null
? this.hass.localize(
`ui.panel.config.zwave_js.security_classes.${
SecurityClass[this._node.highest_security_class]
}.title`
)
: this._node.is_secure === false
? this.hass.localize("ui.common.no")
? this.hass.localize(
"ui.panel.config.zwave_js.security_classes.none.title"
)
: this.hass.localize("ui.panel.config.zwave_js.device_info.unknown")}
</div>
<div>
${this.hass.localize(
"ui.panel.config.zwave_js.device_info.zwave_plus"
)}:
${this._node.zwave_plus_version
? this.hass.localize(
"ui.panel.config.zwave_js.device_info.zwave_plus_version",
"version",
this._node.zwave_plus_version
)
: this.hass.localize("ui.common.no")}
</div>
`;
}
@@ -179,6 +179,7 @@ export class HaConfigDevicePage extends LitElement {
`;
}
const deviceName = computeDeviceName(device, this.hass);
const integrations = this._integrations(device, this.entries);
const entities = this._entities(this.deviceId, this.entities);
const entitiesByCategory = this._entitiesByCategory(entities);
@@ -204,9 +205,7 @@ export class HaConfigDevicePage extends LitElement {
${
this.narrow
? html`
<span slot="header">
${computeDeviceName(device, this.hass)}
</span>
<span slot="header">${deviceName}</span>
<ha-icon-button
slot="toolbar-icon"
.path=${mdiPencil}
@@ -230,7 +229,7 @@ export class HaConfigDevicePage extends LitElement {
: html`
<div class="header-name">
<div>
<h1>${computeDeviceName(device, this.hass)}</h1>
<h1>${deviceName}</h1>
${area
? html`
<a href="/config/areas/area/${area.area_id}"
@@ -353,6 +352,7 @@ export class HaConfigDevicePage extends LitElement {
.header=${this.hass.localize(
`ui.panel.config.devices.entities.${category}`
)}
.deviceName=${deviceName}
.entities=${entitiesByCategory[category]}
.showDisabled=${device.disabled_by !== null}
>
@@ -420,7 +420,7 @@ export class HaConfigDevicePage extends LitElement {
: "";
})
: html`
<paper-item class="no-link">
<div class="card-content">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
@@ -428,7 +428,7 @@ export class HaConfigDevicePage extends LitElement {
"ui.panel.config.devices.automation.automations"
)
)}
</paper-item>
</div>
`}
</ha-card>
`
@@ -502,7 +502,7 @@ export class HaConfigDevicePage extends LitElement {
: "";
})
: html`
<paper-item class="no-link">
<div class="card-content">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
@@ -510,7 +510,7 @@ export class HaConfigDevicePage extends LitElement {
"ui.panel.config.devices.scene.scenes"
)
)}
</paper-item>
</div>
`
}
</ha-card>
@@ -558,7 +558,7 @@ export class HaConfigDevicePage extends LitElement {
: "";
})
: html`
<paper-item class="no-link">
<div class="card-content">
${this.hass.localize(
"ui.panel.config.devices.add_prompt",
"name",
@@ -566,7 +566,7 @@ export class HaConfigDevicePage extends LitElement {
"ui.panel.config.devices.script.scripts"
)
)}
</paper-item>
</div>
`}
</ha-card>
`
@@ -953,19 +953,11 @@ export class HaConfigDevicePage extends LitElement {
font-size: var(--paper-font-body1_-_font-size);
}
paper-item.no-link {
cursor: default;
}
a {
text-decoration: none;
color: var(--primary-color);
}
ha-card {
padding-bottom: 8px;
}
ha-card a {
color: var(--primary-text-color);
}
@@ -202,12 +202,12 @@ class DialogZWaveJSAddNode extends LitElement {
(securityClass) => html`<ha-formfield
.label=${html`<b
>${this.hass.localize(
`ui.panel.config.zwave_js.add_node.security_classes.${SecurityClass[securityClass]}.title`
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.title`
)}</b
>
<div class="secondary">
${this.hass.localize(
`ui.panel.config.zwave_js.add_node.security_classes.${SecurityClass[securityClass]}.description`
`ui.panel.config.zwave_js.security_classes.${SecurityClass[securityClass]}.description`
)}
</div>`}
>
@@ -8,17 +8,18 @@ import {
PropertyValues,
TemplateResult,
} from "lit";
import { customElement, property, state, query } from "lit/decorators";
import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event";
import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon";
import "../../../components/ha-card";
import "../../../components/ha-label-badge";
import "../../../components/ha-chip";
import {
callAlarmAction,
FORMAT_NUMBER,
} from "../../../data/alarm_control_panel";
import { UNAVAILABLE } from "../../../data/entity";
import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entities";
import { createEntityNotFoundWarning } from "../components/hui-warning";
@@ -144,19 +145,22 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
`;
}
const stateLabel = this._stateDisplay(stateObj.state);
return html`
<ha-card
.header=${this._config.name ||
stateObj.attributes.friendly_name ||
this._stateDisplay(stateObj.state)}
>
<ha-label-badge
class=${classMap({ [stateObj.state]: true })}
.label=${this._stateIconLabel(stateObj.state)}
@click=${this._handleMoreInfo}
>
<ha-svg-icon .path=${alarmPanelIcon(stateObj.state)}></ha-svg-icon>
</ha-label-badge>
<ha-card>
<h1 class="card-header">
${this._config.name ||
stateObj.attributes.friendly_name ||
stateLabel}
<ha-chip
.leadingIcon=${alarmPanelIcon(stateObj.state)}
.label=${stateLabel}
class=${classMap({ [stateObj.state]: true })}
@click=${this._handleMoreInfo}
>
</ha-chip>
</h1>
<div id="armActions" class="actions">
${(stateObj.state === "disarmed"
? this._config.states!
@@ -215,23 +219,16 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
`;
}
private _stateIconLabel(entityState: string): string {
const stateLabel = entityState.split("_").pop();
return stateLabel === "disarmed" ||
stateLabel === "triggered" ||
!stateLabel
? ""
: this._stateDisplay(entityState);
}
private _actionDisplay(entityState: string): string {
return this.hass!.localize(`ui.card.alarm_control_panel.${entityState}`);
}
private _stateDisplay(entityState: string): string {
return this.hass!.localize(
`component.alarm_control_panel.state._.${entityState}`
);
return entityState === UNAVAILABLE
? this.hass!.localize("state.default.unavailable")
: this.hass!.localize(
`component.alarm_control_panel.state._.${entityState}`
) || entityState;
}
private _handlePadClick(e: MouseEvent): void {
@@ -273,15 +270,20 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
--alarm-state-color: var(--alarm-color-armed);
}
ha-label-badge {
--ha-label-badge-color: var(--alarm-state-color);
--label-badge-text-color: var(--alarm-state-color);
--label-badge-background-color: var(--card-background-color);
color: var(--alarm-state-color);
position: absolute;
right: 12px;
top: 8px;
cursor: pointer;
ha-chip {
--ha-chip-background-color: var(--alarm-state-color);
--ha-chip-text-color: var(--text-primary-color);
line-height: initial;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.unavailable {
--alarm-state-color: var(--state-unavailable-color);
}
.disarmed {
@@ -171,6 +171,7 @@ export class HuiMarkdownCard extends LitElement implements LovelaceCard {
}
ha-markdown {
padding: 0 16px 16px;
word-wrap: break-word;
}
ha-markdown.no-header {
padding-top: 16px;
@@ -3,6 +3,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { splitByGroups } from "../../../common/entity/split_by_groups";
import { stripPrefixFromEntityName } from "../../../common/entity/strip_prefix_from_entity_name";
import { stringCompare } from "../../../common/string/compare";
import { LocalizeFunc } from "../../../common/translations/localize";
import type { AreaRegistryEntry } from "../../../data/area_registry";
@@ -92,7 +93,7 @@ export const computeCards = (
const entities: Array<string | LovelaceRowConfig> = [];
const titlePrefix = entityCardOptions.title
? `${entityCardOptions.title} `
? `${entityCardOptions.title} `.toLowerCase()
: undefined;
for (const [entityId, stateObj] of states) {
@@ -153,16 +154,18 @@ export const computeCards = (
) {
// Do nothing.
} else {
let name: string;
let name: string | undefined;
const entityConf =
titlePrefix &&
stateObj &&
// eslint-disable-next-line no-cond-assign
(name = computeStateName(stateObj)) !== titlePrefix &&
name.startsWith(titlePrefix)
(name = stripPrefixFromEntityName(
computeStateName(stateObj),
titlePrefix
))
? {
entity: entityId,
name: adjustName(name.substr(titlePrefix.length)),
name,
}
: entityId;
@@ -181,15 +184,6 @@ export const computeCards = (
return cards;
};
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
const adjustName = (name: string): string =>
// If first word already has an upper case letter (e.g. from brand name)
// leave as-is, otherwise capitalize the first word.
hasUpperCase(name.substr(0, name.indexOf(" ")))
? name
: name[0].toUpperCase() + name.slice(1);
const computeDefaultViewStates = (
entities: HassEntities,
entityEntries: EntityRegistryEntry[]
@@ -10,10 +10,6 @@ import {
import { customElement, property, state } from "lit/decorators";
import { guard } from "lit/directives/guard";
import type { SortableEvent } from "sortablejs";
import Sortable, {
AutoScroll,
OnSpill,
} from "sortablejs/modular/sortable.core.esm";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
@@ -22,6 +18,8 @@ import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types";
import { EntityConfig } from "../entity-rows/types";
let Sortable;
@customElement("hui-entity-editor")
export class HuiEntityEditor extends LitElement {
@property({ attribute: false }) protected hass?: HomeAssistant;
@@ -34,7 +32,7 @@ export class HuiEntityEditor extends LitElement {
@state() private _renderEmptySortable = false;
private _sortable?: Sortable;
private _sortable?;
public connectedCallback() {
super.connectedCallback();
@@ -86,11 +84,6 @@ export class HuiEntityEditor extends LitElement {
`;
}
protected firstUpdated(): void {
Sortable.mount(OnSpill);
Sortable.mount(new AutoScroll());
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
@@ -128,7 +121,17 @@ export class HuiEntityEditor extends LitElement {
this._renderEmptySortable = false;
}
private _createSortable() {
private async _createSortable() {
if (!Sortable) {
const sortableImport = await import(
"sortablejs/modular/sortable.core.esm"
);
Sortable = sortableImport.Sortable;
Sortable.mount(sortableImport.OnSpill);
Sortable.mount(sortableImport.AutoScroll());
}
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
animation: 150,
fallbackClass: "sortable-fallback",
@@ -75,6 +75,8 @@ export class HuiDialogEditCard
@state() private _dirty = false;
@state() private _isEscapeEnabled = true;
public async showDialog(params: EditCardDialogParams): Promise<void> {
this._params = params;
this._GUImode = true;
@@ -93,6 +95,9 @@ export class HuiDialogEditCard
}
public closeDialog(): boolean {
this._isEscapeEnabled = true;
window.removeEventListener("dialog-closed", this._enableEscapeKeyClose);
window.removeEventListener("hass-more-info", this._disableEscapeKeyClose);
if (this._dirty) {
this._confirmCancel();
return false;
@@ -125,6 +130,16 @@ export class HuiDialogEditCard
}
}
private _enableEscapeKeyClose = (ev: any) => {
if (ev.detail.dialog === "ha-more-info-dialog") {
this._isEscapeEnabled = true;
}
};
private _disableEscapeKeyClose = () => {
this._isEscapeEnabled = false;
};
protected render(): TemplateResult {
if (!this._params) {
return html``;
@@ -157,6 +172,7 @@ export class HuiDialogEditCard
<ha-dialog
open
scrimClickAction
.escapeKeyAction=${this._isEscapeEnabled ? undefined : ""}
@keydown=${this._ignoreKeydown}
@closed=${this._cancel}
@opened=${this._opened}
@@ -280,6 +296,8 @@ export class HuiDialogEditCard
}
private _opened() {
window.addEventListener("dialog-closed", this._enableEscapeKeyClose);
window.addEventListener("hass-more-info", this._disableEscapeKeyClose);
this._cardEditorEl?.focusYamlEditor();
}
@@ -10,10 +10,6 @@ import {
import { customElement, property, state } from "lit/decorators";
import { guard } from "lit/directives/guard";
import type { SortableEvent } from "sortablejs";
import Sortable, {
AutoScroll,
OnSpill,
} from "sortablejs/modular/sortable.core.esm";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/entity/ha-entity-picker";
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
@@ -23,6 +19,8 @@ import { sortableStyles } from "../../../resources/ha-sortable-style";
import { HomeAssistant } from "../../../types";
import { EntityConfig, LovelaceRowConfig } from "../entity-rows/types";
let Sortable;
declare global {
interface HASSDomEvents {
"entities-changed": {
@@ -43,7 +41,7 @@ export class HuiEntitiesCardRowEditor extends LitElement {
@state() private _renderEmptySortable = false;
private _sortable?: Sortable;
private _sortable?;
public connectedCallback() {
super.connectedCallback();
@@ -134,11 +132,6 @@ export class HuiEntitiesCardRowEditor extends LitElement {
`;
}
protected firstUpdated(): void {
Sortable.mount(OnSpill);
Sortable.mount(new AutoScroll());
}
protected updated(changedProps: PropertyValues): void {
super.updated(changedProps);
@@ -176,7 +169,17 @@ export class HuiEntitiesCardRowEditor extends LitElement {
this._renderEmptySortable = false;
}
private _createSortable() {
private async _createSortable() {
if (!Sortable) {
const sortableImport = await import(
"sortablejs/modular/sortable.core.esm"
);
Sortable = sortableImport.Sortable;
Sortable.mount(sortableImport.OnSpill);
Sortable.mount(sortableImport.AutoScroll());
}
this._sortable = new Sortable(this.shadowRoot!.querySelector(".entities"), {
animation: 150,
fallbackClass: "sortable-fallback",
+4 -2
View File
@@ -713,7 +713,8 @@ class HUIRoot extends LitElement {
});
}
private _moveViewLeft() {
private _moveViewLeft(ev) {
ev.stopPropagation();
if (this._curView === 0) {
return;
}
@@ -724,7 +725,8 @@ class HUIRoot extends LitElement {
lovelace.saveConfig(swapView(lovelace.config, oldIndex, newIndex));
}
private _moveViewRight() {
private _moveViewRight(ev) {
ev.stopPropagation();
if ((this._curView! as number) + 1 === this.lovelace!.config.views.length) {
return;
}
+1
View File
@@ -13,6 +13,7 @@ import "@formatjs/intl-relativetimeformat/polyfill";
import "@formatjs/intl-relativetimeformat/locale-data/en";
import "@formatjs/intl-datetimeformat/polyfill";
import "@formatjs/intl-datetimeformat/locale-data/en";
import "@formatjs/intl-datetimeformat/add-all-tz";
// To use comlink under ES5
import "proxy-polyfill";
+10 -1
View File
@@ -179,7 +179,16 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
});
subscribeEntities(conn, (states) => this._updateHass({ states }));
subscribeConfig(conn, (config) => this._updateHass({ config }));
subscribeConfig(conn, (config) => {
if (
this.hass?.config?.time_zone !== config.time_zone &&
"__setDefaultTimeZone" in Intl.DateTimeFormat
) {
// @ts-ignore
Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone);
}
this._updateHass({ config });
});
subscribeServices(conn, (services) => this._updateHass({ services }));
subscribePanels(conn, (panels) => this._updateHass({ panels }));
subscribeFrontendUserData(conn, "core", (userData) =>
+30 -22
View File
@@ -2831,8 +2831,10 @@
"reinterview_device": "Re-interview Device",
"heal_node": "Heal Device",
"remove_failed": "Remove Failed Device",
"is_secure": "Secure",
"unknown": "Unknown"
"highest_security": "Highest Security",
"unknown": "Unknown",
"zwave_plus": "Z-Wave Plus",
"zwave_plus_version": "Version {version}"
},
"node_config": {
"header": "Z-Wave Device Configuration",
@@ -2868,24 +2870,27 @@
"inclusion_finished": "The device has been added.",
"view_device": "View Device",
"interview_started": "The device is being interviewed. This may take some time.",
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
"security_classes": {
"S2_Unauthenticated": {
"title": "S2 Unauthenticated",
"description": "Like S2 Authenticated, but without verification that the correct device is included"
},
"S2_Authenticated": {
"title": "S2 Authenticated",
"description": "Example: Lighting, Sensors and Security Systems"
},
"S2_AccessControl": {
"title": "S2 Access Control",
"description": "Example: Door Locks and Garage Doors"
},
"S0_Legacy": {
"title": "S0 Legacy",
"description": "Example: Legacy Door Locks without S2 support"
}
"interview_failed": "The device interview failed. Additional information may be available in the logs."
},
"security_classes": {
"None": {
"title": "None"
},
"S2_Unauthenticated": {
"title": "S2 Unauthenticated",
"description": "Like S2 Authenticated, but without verification that the correct device is included"
},
"S2_Authenticated": {
"title": "S2 Authenticated",
"description": "Example: Lighting, Sensors and Security Systems"
},
"S2_AccessControl": {
"title": "S2 Access Control",
"description": "Example: Door Locks and Garage Doors"
},
"S0_Legacy": {
"title": "S0 Legacy",
"description": "Example: Legacy Door Locks without S2 support"
}
},
"remove_node": {
@@ -4079,11 +4084,10 @@
"description": "This add-on is using Ingress to embed its interface securely into Home Assistant."
},
"label": {
"stage": "stage",
"core": "Core",
"rating": "rating",
"hardware": "hardware",
"host": "host",
"hass": "hass",
"hassio": "hassio",
"docker": "docker",
"host_pid": "host pid",
@@ -4091,6 +4095,10 @@
"auth": "auth",
"ingress": "ingress"
},
"stages": {
"experimental": "Experimental",
"deprecated": "Deprecated"
},
"role": {
"manager": "manager",
"default": "default",
+1
View File
@@ -31,6 +31,7 @@ const hassAttributeUtil = {
"power",
"presence",
"problem",
"running",
"safety",
"smoke",
"sound",
+27
View File
@@ -0,0 +1,27 @@
import { assert } from "chai";
import { slugify } from "../../../src/common/string/slugify";
describe("slugify", () => {
// With default delimiter
assert.strictEqual(slugify("abc"), "abc");
assert.strictEqual(slugify("ABC"), "abc");
assert.strictEqual(slugify("abc DEF"), "abc_def");
assert.strictEqual(slugify("abc-DEF"), "abc_def");
assert.strictEqual(slugify("abc_DEF"), "abc_def");
assert.strictEqual(slugify("abc å DEF"), "abc_a_def");
assert.strictEqual(slugify("abc:DEF"), "abc_def");
assert.strictEqual(slugify("abc&DEF"), "abc_and_def");
assert.strictEqual(slugify("abc^^DEF"), "abcdef");
assert.strictEqual(slugify("abc DEF"), "abc_def");
assert.strictEqual(slugify("_abc DEF"), "abc_def");
assert.strictEqual(slugify("abc DEF_"), "abc_def");
assert.strictEqual(slugify("abc-DEF ghi"), "abc_def_ghi");
assert.strictEqual(slugify("abc-DEF-ghi"), "abc_def_ghi");
assert.strictEqual(slugify("abc - DEF - ghi"), "abc_def_ghi");
assert.strictEqual(slugify("abc---DEF---ghi"), "abc_def_ghi");
assert.strictEqual(slugify("___abc___DEF___ghi___"), "abc_def_ghi");
// With custom delimiter
assert.strictEqual(slugify("abc def", "-"), "abc-def");
assert.strictEqual(slugify("abc-def", "-"), "abc-def");
});
+10 -10
View File
@@ -2898,17 +2898,17 @@ __metadata:
languageName: node
linkType: hard
"@mdi/js@npm:6.3.95":
version: 6.3.95
resolution: "@mdi/js@npm:6.3.95"
checksum: e0ecdf2a2b1b46ead42266cc9dcc29c08495c8d3af53314bf28e0c2b637d528e9e1d16e523c1ab678b78c8930a7d29ee91a43f35f15a4ae5aa00244bc836da54
"@mdi/js@npm:6.4.95":
version: 6.4.95
resolution: "@mdi/js@npm:6.4.95"
checksum: 76055821b0b611090f1fadff3cb93b0d2c05f7c52778a0e8966abe1c85d3aae8312c497e91882bb8cb02bf9235edda901da944099553afb2dbacafb11ed40b18
languageName: node
linkType: hard
"@mdi/svg@npm:6.3.95":
version: 6.3.95
resolution: "@mdi/svg@npm:6.3.95"
checksum: ed8d8042fbac21ca2011c799ba57767502cce820c15cd99d5dbacd89275d0350bd9c89557c7301e42986c85e80975c6a72d96f92c4efdf667ddb3d730904225b
"@mdi/svg@npm:6.4.95":
version: 6.4.95
resolution: "@mdi/svg@npm:6.4.95"
checksum: 118945d58484dfa686b3127fabbbbf8e20c20ce1989d158cd270e3da53e22fd30d3c285e3f5d0472f602a9842f2adb8321d853f85ff397ccf8bfefa846bd729e
languageName: node
linkType: hard
@@ -9026,8 +9026,8 @@ fsevents@^1.2.7:
"@material/mwc-tab-bar": 0.25.2
"@material/mwc-textfield": 0.25.2
"@material/top-app-bar": 14.0.0-canary.353ca7e9f.0
"@mdi/js": 6.3.95
"@mdi/svg": 6.3.95
"@mdi/js": 6.4.95
"@mdi/svg": 6.4.95
"@open-wc/dev-server-hmr": ^0.0.2
"@polymer/app-layout": ^3.1.0
"@polymer/iron-flex-layout": ^3.0.1