mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-04 23:41:46 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6f8f0495c |
@@ -261,8 +261,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
|
||||
@state() private _required = false;
|
||||
|
||||
@state() private _helper = false;
|
||||
|
||||
@state() private _label = true;
|
||||
|
||||
private data = SCHEMAS.map(() => ({}));
|
||||
@@ -420,13 +418,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-formfield label="Helper text">
|
||||
<ha-switch
|
||||
.name=${"helper"}
|
||||
.checked=${this._helper}
|
||||
@change=${this._handleOptionChange}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
</div>
|
||||
${SCHEMAS.map((info, idx) => {
|
||||
const data = this.data[idx];
|
||||
@@ -455,7 +446,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
.disabled=${this._disabled}
|
||||
.required=${this._required}
|
||||
@value-changed=${valueChanged}
|
||||
.helper=${this._helper ? "Helper text" : undefined}
|
||||
></ha-selector>
|
||||
</ha-settings-row>
|
||||
`
|
||||
@@ -476,8 +466,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
||||
width: 60;
|
||||
}
|
||||
.options {
|
||||
max-width: 800px;
|
||||
margin: 16px auto;
|
||||
padding: 16px 48px;
|
||||
}
|
||||
.options ha-formfield {
|
||||
margin-right: 16px;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
UPDATE_SUPPORT_INSTALL,
|
||||
UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
UPDATE_SUPPORT_AUTO_UPDATE,
|
||||
} from "../../../../src/data/update";
|
||||
import "../../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
@@ -18,7 +19,7 @@ import { LONG_TEXT } from "../../data/text";
|
||||
|
||||
const base_attributes = {
|
||||
title: "Awesome",
|
||||
installed_version: "1.2.2",
|
||||
current_version: "1.2.2",
|
||||
latest_version: "1.2.3",
|
||||
release_url: "https://home-assistant.io",
|
||||
supported_features: UPDATE_SUPPORT_INSTALL,
|
||||
@@ -50,7 +51,7 @@ const ENTITIES = [
|
||||
}),
|
||||
getEntity("update", "update5", "off", {
|
||||
...base_attributes,
|
||||
installed_version: "1.2.3",
|
||||
current_version: "1.2.3",
|
||||
friendly_name: "No update",
|
||||
}),
|
||||
getEntity("update", "update6", "off", {
|
||||
@@ -102,8 +103,8 @@ const ENTITIES = [
|
||||
}),
|
||||
getEntity("update", "update14", "off", {
|
||||
...base_attributes,
|
||||
installed_version: null,
|
||||
friendly_name: "Update without installed_version",
|
||||
current_version: null,
|
||||
friendly_name: "Update without current_version",
|
||||
}),
|
||||
getEntity("update", "update15", "off", {
|
||||
...base_attributes,
|
||||
@@ -128,16 +129,11 @@ const ENTITIES = [
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_RELEASE_NOTES,
|
||||
}),
|
||||
getEntity("update", "update19", "on", {
|
||||
getEntity("update", "update18", "on", {
|
||||
...base_attributes,
|
||||
friendly_name: "Update with auto update",
|
||||
auto_update: true,
|
||||
}),
|
||||
getEntity("update", "update20", "on", {
|
||||
...base_attributes,
|
||||
in_progress: true,
|
||||
title: undefined,
|
||||
friendly_name: "Installing without title",
|
||||
supported_features:
|
||||
base_attributes.supported_features + UPDATE_SUPPORT_AUTO_UPDATE,
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -39,14 +39,7 @@ import type { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
const SUPPORTED_UI_TYPES = [
|
||||
"string",
|
||||
"select",
|
||||
"boolean",
|
||||
"integer",
|
||||
"float",
|
||||
"schema",
|
||||
];
|
||||
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
|
||||
|
||||
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
new Type("!secret", {
|
||||
@@ -55,8 +48,6 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
}),
|
||||
]);
|
||||
|
||||
const MASKED_FIELDS = ["password", "secret", "token"];
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
@@ -84,66 +75,19 @@ class HassioAddonConfig extends LitElement {
|
||||
public computeLabel = (entry: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||
?.name ||
|
||||
this.addon.translations.en?.configuration?.[entry.name]?.name ||
|
||||
this.addon.translations.en?.configuration?.[entry.name].name ||
|
||||
entry.name;
|
||||
|
||||
public computeHelper = (entry: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||
?.description ||
|
||||
this.addon.translations.en?.configuration?.[entry.name]?.description ||
|
||||
"";
|
||||
|
||||
private _convertSchema = memoizeOne(
|
||||
// Convert supervisor schema to selectors
|
||||
(schema: Record<string, any>): HaFormSchema[] =>
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { select: { options: entry.options } },
|
||||
}
|
||||
: entry.type === "string"
|
||||
? entry.multiple
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
select: { options: [], multiple: true, custom_value: true },
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: {
|
||||
text: {
|
||||
type:
|
||||
entry.format || MASKED_FIELDS.includes(entry.name)
|
||||
? "password"
|
||||
: "text",
|
||||
},
|
||||
},
|
||||
}
|
||||
: entry.type === "boolean"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { boolean: {} },
|
||||
}
|
||||
: entry.type === "schema"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { object: {} },
|
||||
}
|
||||
: entry.type === "float" || entry.type === "integer"
|
||||
? {
|
||||
name: entry.name,
|
||||
required: entry.required,
|
||||
selector: { number: { mode: "box" } },
|
||||
}
|
||||
: entry
|
||||
)
|
||||
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
|
||||
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
|
||||
schema.map((entry) =>
|
||||
entry.type === "select"
|
||||
? {
|
||||
...entry,
|
||||
options: entry.options.map((option) => [option, option]),
|
||||
}
|
||||
: entry
|
||||
)
|
||||
);
|
||||
|
||||
private _filteredShchema = memoizeOne(
|
||||
@@ -196,8 +140,7 @@ class HassioAddonConfig extends LitElement {
|
||||
.data=${this._options!}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this.computeLabel}
|
||||
.computeHelper=${this.computeHelper}
|
||||
.schema=${this._convertSchema(
|
||||
.schema=${this._schema(
|
||||
this._showOptional
|
||||
? this.addon.schema!
|
||||
: this._filteredShchema(
|
||||
@@ -254,9 +197,8 @@ class HassioAddonConfig extends LitElement {
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._canShowSchema = !this.addon.schema!.find(
|
||||
(entry) =>
|
||||
// @ts-ignore
|
||||
!SUPPORTED_UI_TYPES.includes(entry.type)
|
||||
// @ts-ignore
|
||||
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||
);
|
||||
this._yamlMode = !this._canShowSchema;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -7,13 +8,10 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-alert";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -26,6 +24,16 @@ import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
interface NetworkItem {
|
||||
description: string;
|
||||
container: string;
|
||||
host: number | null;
|
||||
}
|
||||
|
||||
interface NetworkItemInput extends PaperInputElement {
|
||||
container: string;
|
||||
}
|
||||
|
||||
@customElement("hassio-addon-network")
|
||||
class HassioAddonNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -34,13 +42,9 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@state() private _showOptional = false;
|
||||
|
||||
@state() private _configHasChanged = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _config?: Record<string, any>;
|
||||
@state() private _config?: NetworkItem[];
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -52,10 +56,6 @@ class HassioAddonNetwork extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const hasHiddenOptions = Object.keys(this._config).find(
|
||||
(entry) => this._config![entry] === null
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
.header=${this.supervisor.localize(
|
||||
@@ -63,49 +63,52 @@ class HassioAddonNetwork extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<p>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.introduction"
|
||||
)}
|
||||
</p>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<ha-form
|
||||
.data=${this._config}
|
||||
@value-changed=${this._configChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
.computeHelper=${this._computeHelper}
|
||||
.schema=${this._createSchema(
|
||||
this._config,
|
||||
this._showOptional,
|
||||
this.hass.userData?.showAdvanced || false
|
||||
)}
|
||||
></ha-form>
|
||||
</div>
|
||||
${hasHiddenOptions
|
||||
? html`<ha-formfield
|
||||
class="show-optional"
|
||||
.label=${this.supervisor.localize(
|
||||
"addon.configuration.network.show_disabled"
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.container"
|
||||
)}
|
||||
</th>
|
||||
<th>
|
||||
${this.supervisor.localize(
|
||||
"addon.configuration.network.host"
|
||||
)}
|
||||
</th>
|
||||
<th>${this.supervisor.localize("common.description")}</th>
|
||||
</tr>
|
||||
${this._config!.map(
|
||||
(item) => html`
|
||||
<tr>
|
||||
<td>${item.container}</td>
|
||||
<td>
|
||||
<paper-input
|
||||
@value-changed=${this._configChanged}
|
||||
placeholder=${this.supervisor.localize(
|
||||
"addon.configuration.network.disabled"
|
||||
)}
|
||||
.value=${item.host ? String(item.host) : ""}
|
||||
.container=${item.container}
|
||||
no-label-float
|
||||
></paper-input>
|
||||
</td>
|
||||
<td>${this._computeDescription(item)}</td>
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
@change=${this._toggleOptional}
|
||||
.checked=${this._showOptional}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>`
|
||||
: ""}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
${this.supervisor.localize("common.reset_defaults")}
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged}
|
||||
>
|
||||
<ha-progress-button @click=${this._saveTapped}>
|
||||
${this.supervisor.localize("common.save")}
|
||||
</ha-progress-button>
|
||||
</div>
|
||||
@@ -120,60 +123,50 @@ class HassioAddonNetwork extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _createSchema = memoizeOne(
|
||||
(
|
||||
config: Record<string, number>,
|
||||
showOptional: boolean,
|
||||
advanced: boolean
|
||||
): HaFormSchema[] =>
|
||||
(showOptional
|
||||
? Object.keys(config)
|
||||
: Object.keys(config).filter((entry) => config[entry] !== null)
|
||||
).map((entry) => ({
|
||||
name: entry,
|
||||
selector: {
|
||||
number: {
|
||||
mode: "box",
|
||||
min: 0,
|
||||
max: 65535,
|
||||
unit_of_measurement: advanced ? entry : undefined,
|
||||
},
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
private _computeLabel = (_: HaFormSchema): string => "";
|
||||
|
||||
private _computeHelper = (item: HaFormSchema): string =>
|
||||
this.addon.translations[this.hass.language]?.network?.[item.name] ||
|
||||
this.addon.translations.en?.network?.[item.name] ||
|
||||
this.addon.network_description?.[item.name] ||
|
||||
item.name;
|
||||
private _computeDescription = (item: NetworkItem): string =>
|
||||
this.addon.translations[this.hass.language]?.network?.[item.container]
|
||||
?.description ||
|
||||
this.addon.translations.en?.network?.[item.container]?.description ||
|
||||
item.description;
|
||||
|
||||
private _setNetworkConfig(): void {
|
||||
this._config = this.addon.network || {};
|
||||
const network = this.addon.network || {};
|
||||
const description = this.addon.network_description || {};
|
||||
const items: NetworkItem[] = Object.keys(network).map((key) => ({
|
||||
container: key,
|
||||
host: network[key],
|
||||
description: description[key],
|
||||
}));
|
||||
this._config = items.sort((a, b) => (a.container > b.container ? 1 : -1));
|
||||
}
|
||||
|
||||
private async _configChanged(ev: CustomEvent): Promise<void> {
|
||||
this._configHasChanged = true;
|
||||
this._config! = ev.detail.value;
|
||||
private async _configChanged(ev: Event): Promise<void> {
|
||||
const target = ev.target as NetworkItemInput;
|
||||
this._config!.forEach((item) => {
|
||||
if (
|
||||
item.container === target.container &&
|
||||
item.host !== parseInt(String(target.value), 10)
|
||||
) {
|
||||
item.host = target.value ? parseInt(String(target.value), 10) : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
network: null,
|
||||
};
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
@@ -184,21 +177,19 @@ class HassioAddonNetwork extends LitElement {
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
button.actionError();
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleOptional() {
|
||||
this._showOptional = !this._showOptional;
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
this._error = undefined;
|
||||
const networkconfiguration = {};
|
||||
Object.entries(this._config!).forEach(([key, value]) => {
|
||||
networkconfiguration[key] = value ?? null;
|
||||
this._config!.forEach((item) => {
|
||||
networkconfiguration[item.container] = parseInt(String(item.host), 10);
|
||||
});
|
||||
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
@@ -207,13 +198,11 @@ class HassioAddonNetwork extends LitElement {
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "option",
|
||||
};
|
||||
button.actionSuccess();
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||
@@ -224,8 +213,8 @@ class HassioAddonNetwork extends LitElement {
|
||||
"error",
|
||||
extractApiErrorMessage(err)
|
||||
);
|
||||
button.actionError();
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -243,9 +232,6 @@ class HassioAddonNetwork extends LitElement {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.show-optional {
|
||||
padding: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -32,6 +32,13 @@ interface AddonCheckboxItem extends CheckboxItem {
|
||||
|
||||
const _computeFolders = (folders): CheckboxItem[] => {
|
||||
const list: CheckboxItem[] = [];
|
||||
if (folders.includes("homeassistant")) {
|
||||
list.push({
|
||||
slug: "homeassistant",
|
||||
name: "Home Assistant configuration",
|
||||
checked: false,
|
||||
});
|
||||
}
|
||||
if (folders.includes("ssl")) {
|
||||
list.push({ slug: "ssl", name: "SSL", checked: false });
|
||||
}
|
||||
@@ -93,7 +100,7 @@ export class SupervisorBackupContent extends LitElement {
|
||||
this.folders = _computeFolders(
|
||||
this.backup
|
||||
? this.backup.folders
|
||||
: ["ssl", "share", "media", "addons/local"]
|
||||
: ["homeassistant", "ssl", "share", "media", "addons/local"]
|
||||
);
|
||||
this.addons = _computeAddons(
|
||||
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
|
||||
@@ -180,7 +187,7 @@ export class SupervisorBackupContent extends LitElement {
|
||||
>
|
||||
<ha-checkbox
|
||||
.checked=${this.homeAssistant}
|
||||
@change=${this.toggleHomeAssistant}
|
||||
@click=${this.toggleHomeAssistant}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</ha-formfield>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = home-assistant-frontend
|
||||
version = 20220405.0
|
||||
version = 20220330.0
|
||||
author = The Home Assistant Authors
|
||||
author_email = hello@home-assistant.io
|
||||
license = Apache-2.0
|
||||
|
||||
@@ -29,11 +29,8 @@ import {
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRadioboxBlank,
|
||||
mdiSmoke,
|
||||
mdiSnowflake,
|
||||
mdiSmokeDetector,
|
||||
mdiSmokeDetectorAlert,
|
||||
mdiSmokeDetectorVariant,
|
||||
mdiSmokeDetectorVariantAlert,
|
||||
mdiSquare,
|
||||
mdiSquareOutline,
|
||||
mdiStop,
|
||||
@@ -55,8 +52,6 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
return is_off ? mdiBattery : mdiBatteryOutline;
|
||||
case "battery_charging":
|
||||
return is_off ? mdiBattery : mdiBatteryCharging;
|
||||
case "carbon_monoxide":
|
||||
return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
|
||||
case "cold":
|
||||
return is_off ? mdiThermometer : mdiSnowflake;
|
||||
case "connectivity":
|
||||
@@ -73,7 +68,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
case "tamper":
|
||||
return is_off ? mdiCheckCircle : mdiAlertCircle;
|
||||
case "smoke":
|
||||
return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
|
||||
return is_off ? mdiCheckCircle : mdiSmoke;
|
||||
case "heat":
|
||||
return is_off ? mdiThermometer : mdiFire;
|
||||
case "light":
|
||||
|
||||
@@ -8,25 +8,26 @@ import {
|
||||
mdiCalendar,
|
||||
mdiCast,
|
||||
mdiCastConnected,
|
||||
mdiCheckCircleOutline,
|
||||
mdiClock,
|
||||
mdiCloseCircleOutline,
|
||||
mdiGestureTapButton,
|
||||
mdiLanConnect,
|
||||
mdiLanDisconnect,
|
||||
mdiLightSwitch,
|
||||
mdiLock,
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
mdiLockOpen,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
mdiPackageUp,
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiRestart,
|
||||
mdiToggleSwitchVariant,
|
||||
mdiToggleSwitchVariantOff,
|
||||
mdiToggleSwitch,
|
||||
mdiToggleSwitchOff,
|
||||
mdiCheckCircleOutline,
|
||||
mdiCloseCircleOutline,
|
||||
mdiWeatherNight,
|
||||
mdiPackage,
|
||||
mdiPackageDown,
|
||||
} from "@mdi/js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||
@@ -108,11 +109,9 @@ export const domainIcon = (
|
||||
case "outlet":
|
||||
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
|
||||
case "switch":
|
||||
return compareState === "on"
|
||||
? mdiToggleSwitchVariant
|
||||
: mdiToggleSwitchVariantOff;
|
||||
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||
default:
|
||||
return mdiToggleSwitchVariant;
|
||||
return mdiLightSwitch;
|
||||
}
|
||||
|
||||
case "sensor": {
|
||||
|
||||
@@ -347,8 +347,8 @@ class StatisticsChart extends LitElement {
|
||||
statTypes.forEach((type) => {
|
||||
let val: number | null;
|
||||
if (type === "sum") {
|
||||
if (initVal === null) {
|
||||
initVal = val = stat.state || 0;
|
||||
if (!initVal) {
|
||||
initVal = val = stat.state;
|
||||
prevSum = stat.sum;
|
||||
} else {
|
||||
val = initVal + ((stat.sum || 0) - prevSum!);
|
||||
|
||||
@@ -52,8 +52,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public devices?: DeviceRegistryEntry[];
|
||||
|
||||
@property() public areas?: AreaRegistryEntry[];
|
||||
@@ -271,7 +269,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
||||
? this.hass.localize("ui.components.device-picker.device")
|
||||
: this.label}
|
||||
.value=${this._value}
|
||||
.helper=${this.helper}
|
||||
.renderer=${rowRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-device-picker";
|
||||
import type { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker";
|
||||
|
||||
@customElement("ha-devices-picker")
|
||||
class HaDevicesPicker extends LitElement {
|
||||
@@ -12,10 +11,6 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
@property() public value?: string[];
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
/**
|
||||
@@ -42,8 +37,6 @@ class HaDevicesPicker extends LitElement {
|
||||
|
||||
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
|
||||
|
||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
@@ -58,13 +51,11 @@ class HaDevicesPicker extends LitElement {
|
||||
allow-custom-entity
|
||||
.curValue=${entityId}
|
||||
.hass=${this.hass}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedDeviceLabel}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._deviceChanged}
|
||||
></ha-device-picker>
|
||||
</div>
|
||||
@@ -72,16 +63,12 @@ class HaDevicesPicker extends LitElement {
|
||||
)}
|
||||
<div>
|
||||
<ha-device-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this.deviceFilter}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
.label=${this.pickDeviceLabel}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !currentDevices.length}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._addDevice}
|
||||
></ha-device-picker>
|
||||
</div>
|
||||
|
||||
@@ -14,12 +14,8 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
|
||||
@property({ type: Array }) public value?: string[];
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public required?: boolean;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {string}
|
||||
@@ -98,7 +94,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.entityFilter=${this._entityFilter}
|
||||
.value=${entityId}
|
||||
.label=${this.pickedEntityLabel}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._entityChanged}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
@@ -106,7 +101,6 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
)}
|
||||
<div>
|
||||
<ha-entity-picker
|
||||
allow-custom-entity
|
||||
.hass=${this.hass}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
@@ -116,9 +110,7 @@ class HaEntitiesPickerLight extends LitElement {
|
||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||
.entityFilter=${this._entityFilter}
|
||||
.label=${this.pickEntityLabel}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !currentEntities.length}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._addEntity}
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
||||
@@ -28,8 +28,6 @@ class HaEntityAttributePicker extends LitElement {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) private _opened = false;
|
||||
|
||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||
@@ -66,7 +64,6 @@ class HaEntityAttributePicker extends LitElement {
|
||||
)}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
item-value-path="value"
|
||||
item-label-path="label"
|
||||
|
||||
@@ -48,8 +48,6 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
/**
|
||||
* Show entities from specific domains.
|
||||
* @type {Array}
|
||||
@@ -306,7 +304,6 @@ export class HaEntityPicker extends LitElement {
|
||||
.label=${this.label === undefined
|
||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||
: this.label}
|
||||
.helper=${this.helper}
|
||||
.allowCustomValue=${this.allowCustomEntity}
|
||||
.filteredItems=${this._states}
|
||||
.renderer=${rowRenderer}
|
||||
|
||||
@@ -29,8 +29,6 @@ class HaAddonPicker extends LitElement {
|
||||
|
||||
@property() public value = "";
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _addons?: HassioAddonInfo[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -64,7 +62,6 @@ class HaAddonPicker extends LitElement {
|
||||
.value=${this._value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.renderer=${rowRenderer}
|
||||
.items=${this._addons}
|
||||
item-value-path="slug"
|
||||
|
||||
@@ -49,8 +49,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
@@ -314,7 +312,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
||||
return html`
|
||||
<ha-combo-box
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
item-value-path="area_id"
|
||||
item-id-path="area_id"
|
||||
item-label-path="name"
|
||||
|
||||
@@ -15,8 +15,6 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public value?: string[];
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-add" })
|
||||
@@ -92,7 +90,6 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
||||
.noAdd=${this.noAdd}
|
||||
.hass=${this.hass}
|
||||
.label=${this.pickAreaLabel}
|
||||
.helper=${this.helper}
|
||||
.includeDomains=${this.includeDomains}
|
||||
.excludeDomains=${this.excludeDomains}
|
||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||
@@ -100,7 +97,7 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
||||
.entityFilter=${this.entityFilter}
|
||||
.disabled=${this.disabled}
|
||||
.placeholder=${this.placeholder}
|
||||
.required=${this.required && !currentAreas.length}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._addArea}
|
||||
></ha-area-picker>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import "./ha-select";
|
||||
import "./ha-textfield";
|
||||
import "./ha-input-helper-text";
|
||||
|
||||
export interface TimeChangedEvent {
|
||||
days?: number;
|
||||
@@ -131,7 +130,7 @@ export class HaBaseTimeInput extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.label
|
||||
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
||||
? html`<label>${this.label}${this.required ? "*" : ""}</label>`
|
||||
: ""}
|
||||
<div class="time-input-wrap">
|
||||
${this.enableDay
|
||||
@@ -254,9 +253,7 @@ export class HaBaseTimeInput extends LitElement {
|
||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||
</ha-select>`}
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
${this.helper ? html`<div class="helper">${this.helper}</div>` : ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -353,6 +350,13 @@ export class HaBaseTimeInput extends LitElement {
|
||||
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.helper {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
font-size: 0.75rem;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,19 +117,6 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
--mdc-shape-small: 4px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
||||
:host([dir="rtl"]) ha-icon-button:first-child,
|
||||
:host([dir="rtl"]) mwc-button:first-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
--mdc-shape-small: 0 4px 4px 0;
|
||||
--mdc-button-outline-width: 1px;
|
||||
}
|
||||
:host([dir="rtl"]) ha-icon-button:last-child,
|
||||
:host([dir="rtl"]) mwc-button:last-child {
|
||||
--mdc-shape-small: 4px 0 0 4px;
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ export class HaComboBox extends LitElement {
|
||||
|
||||
@property() public validationMessage?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
|
||||
@property({ type: Boolean }) public invalid?: boolean;
|
||||
@@ -149,8 +147,6 @@ export class HaComboBox extends LitElement {
|
||||
.suffix=${html`<div style="width: 28px;"></div>`}
|
||||
.icon=${this.icon}
|
||||
.invalid=${this.invalid}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
>
|
||||
<slot name="icon" slot="leadingIcon"></slot>
|
||||
</ha-textfield>
|
||||
|
||||
@@ -39,15 +39,11 @@ export class HaDateInput extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
render() {
|
||||
return html`<ha-textfield
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
iconTrailing
|
||||
helperPersistent
|
||||
@click=${this._openDialog}
|
||||
.value=${this.value
|
||||
? formatDateNumeric(new Date(this.value), this.locale)
|
||||
|
||||
@@ -77,7 +77,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="root" part="root">
|
||||
<div class="root">
|
||||
${this.error && this.error.base
|
||||
? html`
|
||||
<ha-alert alert-type="error">
|
||||
@@ -173,6 +173,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
// .root has overflow: auto to avoid margin collapse
|
||||
return css`
|
||||
.root {
|
||||
margin-bottom: -24px;
|
||||
|
||||
@@ -31,8 +31,6 @@ export class HaIconPicker extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property() public fallbackPath?: string;
|
||||
@@ -59,7 +57,6 @@ export class HaIconPicker extends LitElement {
|
||||
allow-custom-value
|
||||
.filteredItems=${iconItems}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.placeholder=${this.placeholder}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
|
||||
@customElement("ha-input-helper-text")
|
||||
class InputHelperText extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`<slot></slot>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
font-size: 0.75rem;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input-helper-text": InputHelperText;
|
||||
}
|
||||
}
|
||||
@@ -46,14 +46,11 @@ class HaLabeledSlider extends PolymerElement {
|
||||
value="{{value}}"
|
||||
></ha-slider>
|
||||
</div>
|
||||
<template is="dom-if" if="[[helper]]">
|
||||
<ha-input-helper-text>[[helper]]</ha-input-helper-text>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
_getTitle() {
|
||||
return `${this.caption}${this.caption && this.required ? " *" : ""}`;
|
||||
return `${this.caption}${this.required ? "*" : ""}`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
@@ -65,7 +62,6 @@ class HaLabeledSlider extends PolymerElement {
|
||||
max: Number,
|
||||
pin: Boolean,
|
||||
step: Number,
|
||||
helper: String,
|
||||
|
||||
extra: {
|
||||
type: Boolean,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
@@ -42,15 +43,18 @@ class HaMenuButton extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const hasNotifications =
|
||||
this._hasNotifications &&
|
||||
(this.narrow || this.hass.dockedSidebar === "always_hidden");
|
||||
(this.narrow || this.hass.dockedSidebar === "always_hidden") &&
|
||||
(this._hasNotifications ||
|
||||
Object.keys(this.hass.states).some(
|
||||
(entityId) => computeDomain(entityId) === "configurator"
|
||||
));
|
||||
return html`
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
.path=${mdiMenu}
|
||||
@click=${this._toggleMenu}
|
||||
></ha-icon-button>
|
||||
${hasNotifications ? html`<div class="dot"></div>` : ""}
|
||||
${hasNotifications ? html` <div class="dot"></div> ` : ""}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,6 @@ export class HaAddonSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -25,7 +23,6 @@ export class HaAddonSelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-entity
|
||||
|
||||
@@ -18,8 +18,6 @@ export class HaAreaSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() public _configEntries?: ConfigEntry[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -49,7 +47,6 @@ export class HaAreaSelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
no-add
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
@@ -69,7 +66,6 @@ export class HaAreaSelector extends LitElement {
|
||||
<ha-areas-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.pickAreaLabel=${this.label}
|
||||
no-add
|
||||
.deviceFilter=${this._filterDevices}
|
||||
|
||||
@@ -16,8 +16,6 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -34,7 +32,6 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
||||
this.context?.filter_entity}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
allow-custom-value
|
||||
|
||||
@@ -4,7 +4,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-formfield";
|
||||
import "../ha-switch";
|
||||
import "../ha-input-helper-text";
|
||||
|
||||
@customElement("ha-selector-boolean")
|
||||
export class HaBooleanSelector extends LitElement {
|
||||
@@ -14,23 +13,16 @@ export class HaBooleanSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
<ha-switch
|
||||
.checked=${this.value}
|
||||
@change=${this._handleChange}
|
||||
.disabled=${this.disabled}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
`;
|
||||
return html`<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||
<ha-switch
|
||||
.checked=${this.value}
|
||||
@change=${this._handleChange}
|
||||
.disabled=${this.disabled}
|
||||
></ha-switch>
|
||||
</ha-formfield>`;
|
||||
}
|
||||
|
||||
private _handleChange(ev) {
|
||||
@@ -43,10 +35,12 @@ export class HaBooleanSelector extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-formfield {
|
||||
display: flex;
|
||||
:host {
|
||||
height: 56px;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
ha-formfield {
|
||||
width: 100%;
|
||||
--mdc-typography-body2-font-size: 1em;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -16,8 +16,6 @@ export class HaColorRGBSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -26,11 +24,9 @@ export class HaColorRGBSelector extends LitElement {
|
||||
return html`
|
||||
<ha-textfield
|
||||
type="color"
|
||||
helperPersistent
|
||||
.value=${this.value ? rgb2hex(this.value as any) : ""}
|
||||
.label=${this.label || ""}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
.disalbled=${this.disabled}
|
||||
@change=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
|
||||
@@ -15,8 +15,6 @@ export class HaColorTempSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -26,12 +24,11 @@ export class HaColorTempSelector extends LitElement {
|
||||
<ha-labeled-slider
|
||||
pin
|
||||
icon="hass:thermometer"
|
||||
.caption=${this.label || ""}
|
||||
.caption=${this.label}
|
||||
.min=${this.selector.color_temp.min_mireds ?? 153}
|
||||
.max=${this.selector.color_temp.max_mireds ?? 500}
|
||||
.value=${this.value}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
@change=${this._valueChanged}
|
||||
></ha-labeled-slider>
|
||||
|
||||
@@ -14,8 +14,6 @@ export class HaDateSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -28,7 +26,6 @@ export class HaDateSelector extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.value=${this.value}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
>
|
||||
</ha-date-input>
|
||||
`;
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { HomeAssistant } from "../../types";
|
||||
import "../ha-date-input";
|
||||
import type { HaDateInput } from "../ha-date-input";
|
||||
import "../ha-time-input";
|
||||
import "../ha-input-helper-text";
|
||||
import type { HaTimeInput } from "../ha-time-input";
|
||||
|
||||
@customElement("ha-selector-datetime")
|
||||
@@ -19,8 +18,6 @@ export class HaDateTimeSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -33,28 +30,23 @@ export class HaDateTimeSelector extends LitElement {
|
||||
const values = this.value?.split(" ");
|
||||
|
||||
return html`
|
||||
<div class="input">
|
||||
<ha-date-input
|
||||
.label=${this.label}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.value=${values?.[0]}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
enable-second
|
||||
.value=${values?.[1] || "0:00:00"}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>
|
||||
</div>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
<ha-date-input
|
||||
.label=${this.label}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.value=${values?.[0]}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-date-input>
|
||||
<ha-time-input
|
||||
enable-second
|
||||
.value=${values?.[1] || "0:00:00"}
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -66,7 +58,7 @@ export class HaDateTimeSelector extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.input {
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -17,8 +17,6 @@ export class HaDeviceSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() public _configEntries?: ConfigEntry[];
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -45,7 +43,6 @@ export class HaDeviceSelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||
? [this.selector.device.entity.device_class]
|
||||
@@ -65,15 +62,12 @@ export class HaDeviceSelector extends LitElement {
|
||||
<ha-devices-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||
? [this.selector.device.entity.device_class]
|
||||
: undefined}
|
||||
.includeDomains=${this.selector.device.entity?.domain
|
||||
? [this.selector.device.entity.domain]
|
||||
: undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-devices-picker>
|
||||
`;
|
||||
|
||||
@@ -23,8 +23,6 @@ export class HaEntitySelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -35,7 +33,6 @@ export class HaEntitySelector extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.includeEntities=${this.selector.entity.include_entities}
|
||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||
.entityFilter=${this._filterEntities}
|
||||
@@ -50,11 +47,9 @@ export class HaEntitySelector extends LitElement {
|
||||
<ha-entities-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.includeEntities=${this.selector.entity.include_entities}
|
||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
></ha-entities-picker>
|
||||
`;
|
||||
|
||||
@@ -15,8 +15,6 @@ export class HaIconSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = true;
|
||||
@@ -28,7 +26,6 @@ export class HaIconSelector extends LitElement {
|
||||
.value=${this.value}
|
||||
.required=${this.required}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.fallbackPath=${this.selector.icon.fallbackPath}
|
||||
.placeholder=${this.selector.icon.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
|
||||
@@ -20,8 +20,6 @@ export class HaLocationSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
@@ -29,7 +27,6 @@ export class HaLocationSelector extends LitElement {
|
||||
<ha-locations-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
.helper=${this.helper}
|
||||
.locations=${this._location(this.selector, this.value)}
|
||||
@location-updated=${this._locationChanged}
|
||||
@radius-updated=${this._radiusChanged}
|
||||
|
||||
@@ -33,8 +33,6 @@ export class HaMediaSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public required = true;
|
||||
@@ -88,7 +86,6 @@ export class HaMediaSelector extends LitElement {
|
||||
.label=${this.label ||
|
||||
this.hass.localize("ui.components.selectors.media.pick_media_player")}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
include-domains='["media_player"]'
|
||||
allow-custom-entity
|
||||
|
||||
@@ -6,7 +6,6 @@ import { NumberSelector } from "../../data/selector";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-slider";
|
||||
import "../ha-textfield";
|
||||
import "../ha-input-helper-text";
|
||||
|
||||
@customElement("ha-selector-number")
|
||||
export class HaNumberSelector extends LitElement {
|
||||
@@ -27,13 +26,8 @@ export class HaNumberSelector extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render() {
|
||||
const isBox = this.selector.number.mode === "box";
|
||||
|
||||
return html`
|
||||
${this.label ? html`${this.label}${this.required ? " *" : ""}` : ""}
|
||||
<div class="input">
|
||||
${!isBox
|
||||
? html`<ha-slider
|
||||
return html`${this.selector.number.mode !== "box"
|
||||
? html`${this.label}${this.required ? "*" : ""}<ha-slider
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this._value}
|
||||
@@ -45,33 +39,28 @@ export class HaNumberSelector extends LitElement {
|
||||
@change=${this._handleSliderChange}
|
||||
>
|
||||
</ha-slider>`
|
||||
: ""}
|
||||
<ha-textfield
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
.label=${this.selector.number.mode !== "box" ? undefined : this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this.value ?? ""}
|
||||
.step=${this.selector.number.step ?? 1}
|
||||
helperPersistent
|
||||
.helper=${isBox ? this.helper : undefined}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.suffix=${this.selector.number.unit_of_measurement}
|
||||
type="number"
|
||||
autoValidate
|
||||
?no-spinner=${this.selector.number.mode !== "box"}
|
||||
@input=${this._handleInputChange}
|
||||
>
|
||||
</ha-textfield>
|
||||
</div>
|
||||
${!isBox && this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
`;
|
||||
<ha-textfield
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
.label=${this.selector.number.mode !== "box" ? undefined : this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
class=${classMap({ single: this.selector.number.mode === "box" })}
|
||||
.min=${this.selector.number.min}
|
||||
.max=${this.selector.number.max}
|
||||
.value=${this.value ?? ""}
|
||||
.step=${this.selector.number.step ?? 1}
|
||||
helperPersistent
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.suffix=${this.selector.number.unit_of_measurement}
|
||||
type="number"
|
||||
autoValidate
|
||||
?no-spinner=${this.selector.number.mode !== "box"}
|
||||
@input=${this._handleInputChange}
|
||||
>
|
||||
</ha-textfield>`;
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
@@ -103,7 +92,7 @@ export class HaNumberSelector extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.input {
|
||||
:host {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@@ -3,7 +3,6 @@ import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-yaml-editor";
|
||||
import "../ha-input-helper-text";
|
||||
|
||||
@customElement("ha-selector-object")
|
||||
export class HaObjectSelector extends LitElement {
|
||||
@@ -13,8 +12,6 @@ export class HaObjectSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property() public placeholder?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
@@ -23,17 +20,13 @@ export class HaObjectSelector extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.readonly=${this.disabled}
|
||||
.label=${this.label}
|
||||
.required=${this.required}
|
||||
.placeholder=${this.placeholder}
|
||||
.defaultValue=${this.value}
|
||||
@value-changed=${this._handleChange}
|
||||
></ha-yaml-editor>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""} `;
|
||||
.hass=${this.hass}
|
||||
.readonly=${this.disabled}
|
||||
.required=${this.required}
|
||||
.placeholder=${this.placeholder}
|
||||
.defaultValue=${this.value}
|
||||
@value-changed=${this._handleChange}
|
||||
></ha-yaml-editor>`;
|
||||
}
|
||||
|
||||
private _handleChange(ev) {
|
||||
|
||||
@@ -58,7 +58,6 @@ export class HaSelectSelector extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${this._renderHelper()}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -77,7 +76,6 @@ export class HaSelectSelector extends LitElement {
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${this._renderHelper()}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -109,9 +107,8 @@ export class HaSelectSelector extends LitElement {
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required && !value.length}
|
||||
.required=${this.required}
|
||||
.value=${this._filter}
|
||||
.items=${options.filter((item) => !this.value?.includes(item.value))}
|
||||
@filter-changed=${this._filterChanged}
|
||||
@@ -134,7 +131,6 @@ export class HaSelectSelector extends LitElement {
|
||||
item-label-path="label"
|
||||
.hass=${this.hass}
|
||||
.label=${this.label}
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.items=${options}
|
||||
@@ -165,12 +161,6 @@ export class HaSelectSelector extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderHelper() {
|
||||
return this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: "";
|
||||
}
|
||||
|
||||
private get _mode(): "list" | "dropdown" {
|
||||
return (
|
||||
this.selector.select.mode ||
|
||||
|
||||
@@ -26,8 +26,6 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@state() private _entityPlaformLookup?: Record<string, string>;
|
||||
|
||||
@state() private _configEntries?: ConfigEntry[];
|
||||
@@ -66,7 +64,6 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
||||
return html`<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityRegFilter=${this._filterRegEntities}
|
||||
.entityFilter=${this._filterEntities}
|
||||
|
||||
@@ -14,8 +14,6 @@ export class HaTimeSelector extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
@@ -27,7 +25,6 @@ export class HaTimeSelector extends LitElement {
|
||||
.locale=${this.hass.locale}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
.label=${this.label}
|
||||
enable-second
|
||||
></ha-time-input>
|
||||
|
||||
@@ -36,9 +36,10 @@ import memoizeOne from "memoize-one";
|
||||
import { LocalStorage } from "../common/decorators/local-storage";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import { throttle } from "../common/util/throttle";
|
||||
import { ActionHandlerDetail } from "../data/lovelace";
|
||||
import {
|
||||
PersistentNotification,
|
||||
@@ -293,7 +294,11 @@ class HaSidebar extends LitElement {
|
||||
toggleAttribute(this, "rtl", computeRTL(this.hass));
|
||||
}
|
||||
|
||||
this._calculateCounts();
|
||||
this._updatesCount = Object.values(this.hass.states).filter(
|
||||
(entity) =>
|
||||
computeStateDomain(entity) === "update" &&
|
||||
updateCanInstall(entity as UpdateEntity)
|
||||
).length;
|
||||
|
||||
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
||||
return;
|
||||
@@ -307,21 +312,6 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _calculateCounts = throttle(() => {
|
||||
let updateCount = 0;
|
||||
|
||||
for (const entityId of Object.keys(this.hass.states)) {
|
||||
if (
|
||||
entityId.startsWith("update.") &&
|
||||
updateCanInstall(this.hass.states[entityId] as UpdateEntity)
|
||||
) {
|
||||
updateCount++;
|
||||
}
|
||||
}
|
||||
|
||||
this._updatesCount = updateCount;
|
||||
}, 5000);
|
||||
|
||||
private _renderHeader() {
|
||||
return html`<div
|
||||
class="menu"
|
||||
@@ -529,9 +519,14 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
|
||||
private _renderNotifications() {
|
||||
const notificationCount = this._notifications
|
||||
let notificationCount = this._notifications
|
||||
? this._notifications.length
|
||||
: 0;
|
||||
for (const entityId in this.hass.states) {
|
||||
if (computeDomain(entityId) === "configurator") {
|
||||
notificationCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return html`<div
|
||||
class="notifications-container"
|
||||
@@ -1039,8 +1034,6 @@ class HaSidebar extends LitElement {
|
||||
|
||||
.notification-badge,
|
||||
.configuration-badge {
|
||||
left: calc(var(--app-drawer-width) - 42px);
|
||||
position: absolute;
|
||||
min-width: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -43,7 +43,6 @@ import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
||||
import "./ha-area-picker";
|
||||
import "./ha-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-input-helper-text";
|
||||
|
||||
@customElement("ha-target-picker")
|
||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
@@ -53,8 +52,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
/**
|
||||
* Show only targets with entities from specific domains.
|
||||
* @type {Array}
|
||||
@@ -216,11 +213,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""} `;
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private async _showPicker(ev) {
|
||||
|
||||
@@ -14,8 +14,6 @@ export class HaTimeInput extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
@@ -48,7 +46,6 @@ export class HaTimeInput extends LitElement {
|
||||
@value-changed=${this._timeChanged}
|
||||
.enableSecond=${this.enableSecond}
|
||||
.required=${this.required}
|
||||
.helper=${this.helper}
|
||||
></ha-base-time-input>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -61,9 +61,7 @@ export class HaYamlEditor extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this.label
|
||||
? html`<p>${this.label}${this.required ? " *" : ""}</p>`
|
||||
: ""}
|
||||
${this.label ? html`<p>${this.label}${this.required ? "*" : ""}</p>` : ""}
|
||||
<ha-code-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._yaml}
|
||||
|
||||
@@ -21,7 +21,6 @@ import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-map";
|
||||
import type { HaMap } from "./ha-map";
|
||||
import "../ha-input-helper-text";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -51,8 +50,6 @@ export class HaLocationsEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public locations?: MarkerLocation[];
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ type: Boolean }) public autoFit = false;
|
||||
|
||||
@property({ type: Number }) public zoom = 16;
|
||||
@@ -105,18 +102,13 @@ export class HaLocationsEditor extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-map
|
||||
.hass=${this.hass}
|
||||
.layers=${this._getLayers(this._circles, this._locationMarkers)}
|
||||
.zoom=${this.zoom}
|
||||
.autoFit=${this.autoFit}
|
||||
.darkMode=${this.darkMode}
|
||||
></ha-map>
|
||||
${this.helper
|
||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
||||
: ""}
|
||||
`;
|
||||
return html`<ha-map
|
||||
.hass=${this.hass}
|
||||
.layers=${this._getLayers(this._circles, this._locationMarkers)}
|
||||
.zoom=${this.zoom}
|
||||
.autoFit=${this.autoFit}
|
||||
.darkMode=${this.darkMode}
|
||||
></ha-map>`;
|
||||
}
|
||||
|
||||
private _getLayers = memoizeOne(
|
||||
@@ -295,10 +287,13 @@ export class HaLocationsEditor extends LitElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-map {
|
||||
:host {
|
||||
display: block;
|
||||
height: 300px;
|
||||
}
|
||||
ha-map {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@ import { BlueprintInput } from "./blueprint";
|
||||
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import { Action, MODES } from "./script";
|
||||
|
||||
export const AUTOMATION_DEFAULT_MODE: ManualAutomationConfig["mode"] = "single";
|
||||
|
||||
export interface AutomationEntity extends HassEntityBase {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
id?: string;
|
||||
@@ -69,7 +67,7 @@ export interface BaseTrigger {
|
||||
|
||||
export interface StateTrigger extends BaseTrigger {
|
||||
platform: "state";
|
||||
entity_id: string | string[];
|
||||
entity_id: string;
|
||||
attribute?: string;
|
||||
from?: string | number;
|
||||
to?: string | string[] | number;
|
||||
|
||||
@@ -21,8 +21,7 @@ export type AddonState = "started" | "stopped" | null;
|
||||
export type AddonRepository = "core" | "local" | string;
|
||||
|
||||
interface AddonTranslations {
|
||||
network?: Record<string, string>;
|
||||
configuration?: Record<string, { name?: string; description?: string }>;
|
||||
[key: string]: Record<string, Record<string, Record<string, string>>>;
|
||||
}
|
||||
|
||||
export interface HassioAddonInfo {
|
||||
@@ -92,7 +91,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
slug: string;
|
||||
startup: AddonStartup;
|
||||
stdin: boolean;
|
||||
translations: Record<string, AddonTranslations>;
|
||||
translations: AddonTranslations;
|
||||
watchdog: null | boolean;
|
||||
webui: null | string;
|
||||
}
|
||||
|
||||
@@ -1,278 +0,0 @@
|
||||
import {
|
||||
mdiEarth,
|
||||
mdiNavigationVariantOutline,
|
||||
mdiReload,
|
||||
mdiServerNetwork,
|
||||
} from "@mdi/js";
|
||||
import { canShowPage } from "../common/config/can_show_page";
|
||||
import { componentsWithService } from "../common/config/components_with_service";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateDisplay } from "../common/entity/compute_state_display";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { domainIcon } from "../common/entity/domain_icon";
|
||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
||||
import { ScorableTextItem } from "../common/string/filter/sequence-matching";
|
||||
import { PageNavigation } from "../layouts/hass-tabs-subpage";
|
||||
import { configSections } from "../panels/config/ha-panel-config";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { AreaRegistryEntry } from "./area_registry";
|
||||
import { computeDeviceName, DeviceRegistryEntry } from "./device_registry";
|
||||
import { EntityRegistryEntry } from "./entity_registry";
|
||||
import { domainToName } from "./integration";
|
||||
import { getPanelNameTranslationKey } from "./panel";
|
||||
|
||||
export interface QuickBarItem extends ScorableTextItem {
|
||||
primaryText: string;
|
||||
primaryTextAlt?: string;
|
||||
secondaryText?: string;
|
||||
metaText?: string;
|
||||
categoryKey:
|
||||
| "reload"
|
||||
| "navigation"
|
||||
| "server_control"
|
||||
| "entity"
|
||||
| "suggestion";
|
||||
actionData: string | string[];
|
||||
iconPath?: string;
|
||||
icon?: string;
|
||||
path?: string;
|
||||
isSuggestion?: boolean;
|
||||
}
|
||||
|
||||
export type NavigationInfo = PageNavigation &
|
||||
Pick<QuickBarItem, "primaryText" | "secondaryText">;
|
||||
|
||||
export type BaseNavigationCommand = Pick<QuickBarItem, "primaryText" | "path">;
|
||||
|
||||
export const generateEntityItems = (
|
||||
hass: HomeAssistant,
|
||||
entities: { [entityId: string]: EntityRegistryEntry },
|
||||
devices: { [deviceId: string]: DeviceRegistryEntry },
|
||||
areas: { [areaId: string]: AreaRegistryEntry }
|
||||
): QuickBarItem[] =>
|
||||
Object.keys(hass.states)
|
||||
.map((entityId) => {
|
||||
const entityState = hass.states[entityId];
|
||||
const entity = entities[entityId];
|
||||
const deviceName = entity?.device_id
|
||||
? computeDeviceName(devices[entity.device_id], hass)
|
||||
: undefined;
|
||||
const entityItem = {
|
||||
primaryText: computeStateName(entityState),
|
||||
primaryTextAlt: computeStateDisplay(
|
||||
hass.localize,
|
||||
entityState,
|
||||
hass.locale
|
||||
),
|
||||
secondaryText:
|
||||
(deviceName ? `${deviceName} | ` : "") +
|
||||
(hass.userData?.showAdvanced ? entityId : ""),
|
||||
metaText: entity?.area_id ? areas[entity.area_id].name : undefined,
|
||||
icon: entityState.attributes.icon,
|
||||
iconPath: entityState.attributes.icon
|
||||
? undefined
|
||||
: domainIcon(computeDomain(entityId), entityState),
|
||||
actionData: entityId,
|
||||
categoryKey: "entity" as const,
|
||||
};
|
||||
|
||||
return {
|
||||
...entityItem,
|
||||
strings: [entityItem.primaryText, entityItem.secondaryText],
|
||||
};
|
||||
})
|
||||
.sort((a, b) => caseInsensitiveStringCompare(a.primaryText, b.primaryText));
|
||||
|
||||
export const generateCommandItems = (
|
||||
hass: HomeAssistant
|
||||
): Array<QuickBarItem[]> => [
|
||||
generateNavigationCommands(hass),
|
||||
generateReloadCommands(hass),
|
||||
generateServerControlCommands(hass),
|
||||
];
|
||||
|
||||
export const generateReloadCommands = (hass: HomeAssistant): QuickBarItem[] => {
|
||||
// Get all domains that have a direct "reload" service
|
||||
const reloadableDomains = componentsWithService(hass, "reload");
|
||||
|
||||
const commands = reloadableDomains.map((domain) => ({
|
||||
primaryText:
|
||||
hass.localize(`ui.dialogs.quick-bar.commands.reload.${domain}`) ||
|
||||
hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.reload",
|
||||
"domain",
|
||||
domainToName(hass.localize, domain)
|
||||
),
|
||||
actionData: [domain, "reload"],
|
||||
secondaryText: "Reload changes made to the domain file",
|
||||
}));
|
||||
|
||||
// Add "frontend.reload_themes"
|
||||
commands.push({
|
||||
primaryText: hass.localize("ui.dialogs.quick-bar.commands.reload.themes"),
|
||||
actionData: ["frontend", "reload_themes"],
|
||||
secondaryText: "Reload changes made to themes.yaml",
|
||||
});
|
||||
|
||||
// Add "homeassistant.reload_core_config"
|
||||
commands.push({
|
||||
primaryText: hass.localize("ui.dialogs.quick-bar.commands.reload.core"),
|
||||
actionData: ["homeassistant", "reload_core_config"],
|
||||
secondaryText: "Reload changes made to configuration.yaml",
|
||||
});
|
||||
|
||||
return commands.map((command) => ({
|
||||
...command,
|
||||
categoryKey: "reload",
|
||||
iconPath: mdiReload,
|
||||
strings: [
|
||||
`${hass.localize("ui.dialogs.quick-bar.commands.types.reload")} ${
|
||||
command.primaryText
|
||||
}`,
|
||||
],
|
||||
}));
|
||||
};
|
||||
|
||||
export const generateServerControlCommands = (
|
||||
hass: HomeAssistant
|
||||
): QuickBarItem[] => {
|
||||
const serverActions = ["restart", "stop"];
|
||||
|
||||
return serverActions.map((action) => {
|
||||
const categoryKey: QuickBarItem["categoryKey"] = "server_control";
|
||||
|
||||
const item = {
|
||||
primaryText: hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.server_control.perform_action",
|
||||
"action",
|
||||
hass.localize(`ui.dialogs.quick-bar.commands.server_control.${action}`)
|
||||
),
|
||||
categoryKey,
|
||||
actionData: action,
|
||||
};
|
||||
|
||||
return {
|
||||
...item,
|
||||
strings: [
|
||||
`${hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
)} ${item.primaryText}`,
|
||||
],
|
||||
secondaryText: "Control your server",
|
||||
iconPath: mdiServerNetwork,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const generateNavigationCommands = (
|
||||
hass: HomeAssistant
|
||||
): QuickBarItem[] => {
|
||||
const panelItems = generateNavigationPanelCommands(hass);
|
||||
const sectionItems = generateNavigationConfigSectionCommands(hass);
|
||||
|
||||
return finalizeNavigationCommands([...panelItems, ...sectionItems], hass);
|
||||
};
|
||||
|
||||
export const generateNavigationPanelCommands = (
|
||||
hass: HomeAssistant
|
||||
): BaseNavigationCommand[] =>
|
||||
Object.keys(hass.panels)
|
||||
.filter((panelKey) => panelKey !== "_my_redirect")
|
||||
.map((panelKey) => {
|
||||
const panel = hass.panels[panelKey];
|
||||
const translationKey = getPanelNameTranslationKey(panel);
|
||||
|
||||
const primaryText =
|
||||
hass.localize(translationKey) || panel.title || panel.url_path;
|
||||
|
||||
return {
|
||||
primaryText,
|
||||
path: `/${panel.url_path}`,
|
||||
icon: panel.icon,
|
||||
secondaryText: "Panel",
|
||||
};
|
||||
});
|
||||
|
||||
export const generateNavigationConfigSectionCommands = (
|
||||
hass: HomeAssistant
|
||||
): BaseNavigationCommand[] => {
|
||||
const items: NavigationInfo[] = [];
|
||||
|
||||
for (const sectionKey of Object.keys(configSections)) {
|
||||
for (const page of configSections[sectionKey]) {
|
||||
if (!canShowPage(hass, page)) {
|
||||
continue;
|
||||
}
|
||||
if (!page.component) {
|
||||
continue;
|
||||
}
|
||||
const info = getNavigationInfoFromConfig(page, hass);
|
||||
|
||||
if (!info) {
|
||||
continue;
|
||||
}
|
||||
// Add to list, but only if we do not already have an entry for the same path and component
|
||||
if (
|
||||
items.some(
|
||||
(e) => e.path === info.path && e.component === info.component
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
items.push({
|
||||
iconPath: mdiNavigationVariantOutline,
|
||||
...info,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
};
|
||||
|
||||
export const getNavigationInfoFromConfig = (
|
||||
page: PageNavigation,
|
||||
hass: HomeAssistant
|
||||
): NavigationInfo | undefined => {
|
||||
if (!page.component) {
|
||||
return undefined;
|
||||
}
|
||||
const caption = hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.navigation.${page.component}`
|
||||
);
|
||||
|
||||
if (page.translationKey && caption) {
|
||||
return {
|
||||
...page,
|
||||
primaryText: caption,
|
||||
secondaryText: "Configuration Page",
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const finalizeNavigationCommands = (
|
||||
items: BaseNavigationCommand[],
|
||||
hass: HomeAssistant
|
||||
): QuickBarItem[] =>
|
||||
items.map((item) => {
|
||||
const categoryKey: QuickBarItem["categoryKey"] = "navigation";
|
||||
|
||||
const navItem = {
|
||||
secondaryText: "Navigation",
|
||||
iconPath: mdiEarth,
|
||||
...item,
|
||||
actionData: item.path!,
|
||||
};
|
||||
|
||||
return {
|
||||
categoryKey,
|
||||
...navItem,
|
||||
strings: [
|
||||
`${hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
)} ${navItem.primaryText}`,
|
||||
],
|
||||
};
|
||||
});
|
||||
+2
-2
@@ -11,10 +11,10 @@ export const UPDATE_SUPPORT_SPECIFIC_VERSION = 2;
|
||||
export const UPDATE_SUPPORT_PROGRESS = 4;
|
||||
export const UPDATE_SUPPORT_BACKUP = 8;
|
||||
export const UPDATE_SUPPORT_RELEASE_NOTES = 16;
|
||||
export const UPDATE_SUPPORT_AUTO_UPDATE = 32;
|
||||
|
||||
interface UpdateEntityAttributes extends HassEntityAttributeBase {
|
||||
auto_update: boolean | null;
|
||||
installed_version: string | null;
|
||||
current_version: string | null;
|
||||
in_progress: boolean | number;
|
||||
latest_version: string | null;
|
||||
release_summary: string | null;
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import { fetchIntegrationManifest } from "../../data/integration";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
@@ -44,10 +43,10 @@ import "./step-flow-create-entry";
|
||||
import "./step-flow-external";
|
||||
import "./step-flow-form";
|
||||
import "./step-flow-loading";
|
||||
import "./step-flow-menu";
|
||||
import "./step-flow-pick-flow";
|
||||
import "./step-flow-pick-handler";
|
||||
import "./step-flow-progress";
|
||||
import "./step-flow-menu";
|
||||
|
||||
let instance = 0;
|
||||
|
||||
@@ -238,32 +237,22 @@ class DataEntryFlowDialog extends LitElement {
|
||||
""
|
||||
: html`
|
||||
<div class="dialog-actions">
|
||||
${([
|
||||
"form",
|
||||
"menu",
|
||||
"external",
|
||||
"progress",
|
||||
"data_entry_flow_progressed",
|
||||
].includes(this._step?.type as any) &&
|
||||
this._params.manifest?.is_built_in) ||
|
||||
this._params.manifest?.documentation
|
||||
${["form", "menu", "external"].includes(
|
||||
this._step?.type as any
|
||||
)
|
||||
? html`
|
||||
<a
|
||||
href=${this._params.manifest.is_built_in
|
||||
? documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._params.manifest.domain}`
|
||||
)
|
||||
: this._params?.manifest?.documentation}
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._step!.handler}`
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<ha-icon-button
|
||||
><ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.help")}
|
||||
.path=${mdiHelpCircle}
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
>
|
||||
</ha-icon-button
|
||||
></ha-icon-button
|
||||
></a>
|
||||
`
|
||||
: ""}
|
||||
@@ -438,17 +427,6 @@ class DataEntryFlowDialog extends LitElement {
|
||||
this._handler = undefined;
|
||||
}
|
||||
this._processStep(step);
|
||||
if (this._params!.manifest === undefined) {
|
||||
try {
|
||||
this._params!.manifest = await fetchIntegrationManifest(
|
||||
this.hass,
|
||||
this._params?.domain || step.handler
|
||||
);
|
||||
} catch (_) {
|
||||
// No manifest
|
||||
this._params!.manifest = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._step = null;
|
||||
this._flowsInProgress = flowsInProgress;
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
DataEntryFlowStepMenu,
|
||||
DataEntryFlowStepProgress,
|
||||
} from "../../data/data_entry_flow";
|
||||
import { IntegrationManifest } from "../../data/integration";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
export interface FlowHandlers {
|
||||
@@ -123,8 +122,6 @@ export interface DataEntryFlowDialogParams {
|
||||
startFlowHandler?: string;
|
||||
searchQuery?: string;
|
||||
continueFlowId?: string;
|
||||
manifest?: IntegrationManifest | null;
|
||||
domain?: string;
|
||||
dialogClosedCallback?: (params: {
|
||||
flowFinished: boolean;
|
||||
entryId?: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { html } from "lit";
|
||||
import { ConfigEntry } from "../../data/config_entries";
|
||||
import { domainToName, IntegrationManifest } from "../../data/integration";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import {
|
||||
createOptionsFlow,
|
||||
deleteOptionsFlow,
|
||||
@@ -16,15 +16,12 @@ export const loadOptionsFlowDialog = loadDataEntryFlowDialog;
|
||||
|
||||
export const showOptionsFlowDialog = (
|
||||
element: HTMLElement,
|
||||
configEntry: ConfigEntry,
|
||||
manifest?: IntegrationManifest | null
|
||||
configEntry: ConfigEntry
|
||||
): void =>
|
||||
showFlowDialog(
|
||||
element,
|
||||
{
|
||||
startFlowHandler: configEntry.entry_id,
|
||||
domain: configEntry.domain,
|
||||
manifest,
|
||||
},
|
||||
{
|
||||
loadDevicesAndAreas: false,
|
||||
|
||||
@@ -28,7 +28,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
const localize = this.hass.localize;
|
||||
|
||||
return html`
|
||||
<h2>${localize("ui.panel.config.integrations.config_flow.success")}!</h2>
|
||||
<h2>Success!</h2>
|
||||
<div class="content">
|
||||
${this.flowConfig.renderCreateEntryDescription(this.hass, this.step)}
|
||||
${this.step.result?.state === "not_loaded"
|
||||
@@ -41,11 +41,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
${this.devices.length === 0
|
||||
? ""
|
||||
: html`
|
||||
<p>
|
||||
${localize(
|
||||
"ui.panel.config.integrations.config_flow.found_following_devices"
|
||||
)}:
|
||||
</p>
|
||||
<p>We found the following devices:</p>
|
||||
<div class="devices">
|
||||
${this.devices.map(
|
||||
(device) =>
|
||||
@@ -53,12 +49,7 @@ class StepFlowCreateEntry extends LitElement {
|
||||
<div class="device">
|
||||
<div>
|
||||
<b>${computeDeviceName(device, this.hass)}</b><br />
|
||||
${!device.model && !device.manufacturer
|
||||
? html` `
|
||||
: html`${device.model}
|
||||
${device.manufacturer
|
||||
? html`(${device.manufacturer})`
|
||||
: ""}`}
|
||||
${device.model} (${device.manufacturer})
|
||||
</div>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -190,10 +190,6 @@ class StepFlowForm extends LitElement {
|
||||
margin-top: 24px;
|
||||
display: block;
|
||||
}
|
||||
h2 {
|
||||
word-break: break-word;
|
||||
padding-right: 72px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ class MoreInfoFan extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
.has-direction .container-direction,
|
||||
.has-oscillating .container-oscillating {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
ha-select {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
UpdateEntity,
|
||||
updateIsInstalling,
|
||||
updateReleaseNotes,
|
||||
UPDATE_SUPPORT_AUTO_UPDATE,
|
||||
UPDATE_SUPPORT_BACKUP,
|
||||
UPDATE_SUPPORT_INSTALL,
|
||||
UPDATE_SUPPORT_PROGRESS,
|
||||
@@ -56,18 +57,21 @@ class MoreInfoUpdate extends LitElement {
|
||||
></mwc-linear-progress>`
|
||||
: html`<mwc-linear-progress indeterminate></mwc-linear-progress>`
|
||||
: ""}
|
||||
<h3>${this.stateObj.attributes.title}</h3>
|
||||
${this.stateObj.attributes.title
|
||||
? html`<h3>${this.stateObj.attributes.title}</h3>`
|
||||
: ""}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
|
||||
<div class="row">
|
||||
<div class="key">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.update.installed_version"
|
||||
"ui.dialogs.more_info_control.update.current_version"
|
||||
)}
|
||||
</div>
|
||||
<div class="value">
|
||||
${this.stateObj.attributes.installed_version ??
|
||||
${this.stateObj.attributes.current_version ??
|
||||
this.hass.localize("state.default.unavailable")}
|
||||
</div>
|
||||
</div>
|
||||
@@ -127,20 +131,16 @@ class MoreInfoUpdate extends LitElement {
|
||||
: ""}
|
||||
<hr />
|
||||
<div class="actions">
|
||||
${this.stateObj.attributes.auto_update
|
||||
${supportsFeature(this.stateObj, UPDATE_SUPPORT_AUTO_UPDATE)
|
||||
? ""
|
||||
: html`
|
||||
<mwc-button
|
||||
@click=${this._handleSkip}
|
||||
.disabled=${skippedVersion ||
|
||||
this.stateObj.state === "off" ||
|
||||
updateIsInstalling(this.stateObj)}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.update.skip"
|
||||
)}
|
||||
</mwc-button>
|
||||
`}
|
||||
: html`<mwc-button
|
||||
@click=${this._handleSkip}
|
||||
.disabled=${skippedVersion ||
|
||||
this.stateObj.state === "off" ||
|
||||
updateIsInstalling(this.stateObj)}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.more_info_control.update.skip")}
|
||||
</mwc-button>`}
|
||||
${supportsFeature(this.stateObj, UPDATE_SUPPORT_INSTALL)
|
||||
? html`
|
||||
<mwc-button
|
||||
@@ -240,10 +240,6 @@ class MoreInfoUpdate extends LitElement {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
mwc-linear-progress {
|
||||
margin-bottom: -10px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,114 +2,118 @@ import "@lit-labs/virtualizer";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { ListItem } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import {
|
||||
mdiClose,
|
||||
mdiConsoleLine,
|
||||
mdiEarth,
|
||||
mdiMagnify,
|
||||
mdiReload,
|
||||
mdiServerNetwork,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||
import { canShowPage } from "../../common/config/can_show_page";
|
||||
import { componentsWithService } from "../../common/config/components_with_service";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { domainIcon } from "../../common/entity/domain_icon";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { fuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
fuzzyFilterSort,
|
||||
ScorableTextItem,
|
||||
} from "../../common/string/filter/sequence-matching";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-chip";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-header-bar";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-textfield";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { getPanelNameTranslationKey } from "../../data/panel";
|
||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
import { configSections } from "../../panels/config/ha-panel-config";
|
||||
import { haStyleDialog, haStyleScrollbar } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../data/entity_registry";
|
||||
import {
|
||||
generateCommandItems,
|
||||
generateEntityItems,
|
||||
QuickBarItem,
|
||||
} from "../../data/quick-bar";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
haStyleScrollbar,
|
||||
} from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
ConfirmationDialogParams,
|
||||
showConfirmationDialog,
|
||||
} from "../generic/show-dialog-box";
|
||||
import { QuickBarParams } from "./show-dialog-quick-bar";
|
||||
|
||||
interface QuickBarItem extends ScorableTextItem {
|
||||
primaryText: string;
|
||||
iconPath?: string;
|
||||
action(data?: any): void;
|
||||
}
|
||||
|
||||
interface CommandItem extends QuickBarItem {
|
||||
categoryKey: "reload" | "navigation" | "server_control";
|
||||
categoryText: string;
|
||||
}
|
||||
|
||||
interface EntityItem extends QuickBarItem {
|
||||
altText: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
const isCommandItem = (item: QuickBarItem): item is CommandItem =>
|
||||
(item as CommandItem).categoryKey !== undefined;
|
||||
|
||||
interface QuickBarNavigationItem extends CommandItem {
|
||||
path: string;
|
||||
}
|
||||
|
||||
type NavigationInfo = PageNavigation & Pick<QuickBarItem, "primaryText">;
|
||||
|
||||
type BaseNavigationCommand = Pick<
|
||||
QuickBarNavigationItem,
|
||||
"primaryText" | "path"
|
||||
>;
|
||||
@customElement("ha-quick-bar")
|
||||
export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
export class QuickBar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _items?: Array<QuickBarItem[]>;
|
||||
@state() private _commandItems?: CommandItem[];
|
||||
|
||||
private _filteredItems?: QuickBarItem[];
|
||||
@state() private _entityItems?: EntityItem[];
|
||||
|
||||
@state() private _filter = "";
|
||||
|
||||
@state() private _search = "";
|
||||
|
||||
@state() private _opened = false;
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _done = false;
|
||||
@state() private _commandMode = false;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _narrow = false;
|
||||
|
||||
@state() private _hint?: string;
|
||||
|
||||
@state() private _entities?: EntityRegistryEntry[];
|
||||
|
||||
@state() private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@state() private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@query("ha-textfield", false) private _filterInputField?: HTMLElement;
|
||||
|
||||
// @ts-ignore
|
||||
@LocalStorage("suggestions", true, {
|
||||
attribute: false,
|
||||
})
|
||||
private _suggestions: QuickBarItem[] = [];
|
||||
|
||||
private _focusSet = false;
|
||||
|
||||
private _focusListElement?: ListItem | null;
|
||||
|
||||
private _filterItems = memoizeOne(
|
||||
(items: QuickBarItem[], filter: string): QuickBarItem[] =>
|
||||
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
|
||||
);
|
||||
|
||||
public async showDialog(params: QuickBarParams) {
|
||||
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
|
||||
this._hint = params.hint;
|
||||
this._narrow = matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
subscribeAreaRegistry(this.hass.connection!, (areas) => {
|
||||
this._areas = areas;
|
||||
}),
|
||||
subscribeDeviceRegistry(this.hass.connection!, (devices) => {
|
||||
this._devices = devices;
|
||||
}),
|
||||
subscribeEntityRegistry(this.hass.connection!, (entities) => {
|
||||
this._entities = entities;
|
||||
}),
|
||||
];
|
||||
this._initializeItemsIfNeeded();
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
this._opened = false;
|
||||
this._focusSet = false;
|
||||
this._filter = "";
|
||||
@@ -117,46 +121,28 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues): void {
|
||||
super.willUpdate(changedProperties);
|
||||
if (
|
||||
(changedProperties.has("_entity") ||
|
||||
changedProperties.has("_areas") ||
|
||||
changedProperties.has("_devices")) &&
|
||||
this._areas &&
|
||||
this._devices &&
|
||||
this._entities
|
||||
) {
|
||||
this._initializeItems();
|
||||
this._opened = true;
|
||||
}
|
||||
}
|
||||
private _getItems = memoizeOne(
|
||||
(commandMode: boolean, commandItems, entityItems, filter: string) => {
|
||||
const items = commandMode ? commandItems : entityItems;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._opened) {
|
||||
if (items && filter && filter !== " ") {
|
||||
return this._filterItems(items, filter);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
);
|
||||
|
||||
protected render() {
|
||||
if (!this._open) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
let sectionCount = 0;
|
||||
|
||||
if (this._items && this._filter && this._filter !== "") {
|
||||
const newFilteredItems: QuickBarItem[] = [];
|
||||
this._items.forEach((arr) => {
|
||||
const items = this._filterItems(arr, this._filter).slice(0, 3);
|
||||
|
||||
if (items.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
sectionCount++;
|
||||
newFilteredItems.push(...items);
|
||||
});
|
||||
|
||||
this._filteredItems = newFilteredItems;
|
||||
} else {
|
||||
sectionCount++;
|
||||
this._filteredItems = this._suggestions;
|
||||
}
|
||||
const items: QuickBarItem[] | undefined = this._getItems(
|
||||
this._commandMode,
|
||||
this._commandItems,
|
||||
this._entityItems,
|
||||
this._filter
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@@ -175,18 +161,28 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.dialogs.quick-bar.filter_placeholder"
|
||||
)}
|
||||
.value=${this._search}
|
||||
.icon=${true}
|
||||
.value=${this._commandMode ? `>${this._search}` : this._search}
|
||||
icon
|
||||
.iconTrailing=${this._search !== undefined || this._narrow}
|
||||
@input=${this._handleSearchChange}
|
||||
@keydown=${this._handleInputKeyDown}
|
||||
@focus=${this._setFocusFirstListItem}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="leadingIcon"
|
||||
class="prefix"
|
||||
.path=${mdiMagnify}
|
||||
></ha-svg-icon>
|
||||
${this._commandMode
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="leadingIcon"
|
||||
class="prefix"
|
||||
.path=${mdiConsoleLine}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
slot="leadingIcon"
|
||||
class="prefix"
|
||||
.path=${mdiMagnify}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
${this._search || this._narrow
|
||||
? html`
|
||||
<div slot="trailingIcon">
|
||||
@@ -209,11 +205,12 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
: ""}
|
||||
</ha-textfield>
|
||||
</div>
|
||||
${!this._filteredItems
|
||||
? html`
|
||||
<ha-circular-progress size="small" active></ha-circular-progress>
|
||||
`
|
||||
: this._filteredItems.length === 0 && this._filter !== ""
|
||||
${!items
|
||||
? html`<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
></ha-circular-progress>`
|
||||
: items.length === 0
|
||||
? html`
|
||||
<div class="nothing-found">
|
||||
${this.hass.localize("ui.dialogs.quick-bar.nothing_found")}
|
||||
@@ -221,26 +218,26 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
`
|
||||
: html`
|
||||
<mwc-list>
|
||||
<lit-virtualizer
|
||||
scroller
|
||||
@keydown=${this._handleListItemKeyDown}
|
||||
@rangechange=${this._handleRangeChanged}
|
||||
@click=${this._handleItemClick}
|
||||
class="ha-scrollbar"
|
||||
style=${styleMap({
|
||||
height: this._narrow
|
||||
? "calc(100vh - 56px)"
|
||||
: `${Math.min(
|
||||
this._filteredItems.length * 72 +
|
||||
sectionCount * 37 +
|
||||
18,
|
||||
this._done ? 600 : 0
|
||||
)}px`,
|
||||
})}
|
||||
.items=${this._filteredItems}
|
||||
.renderItem=${this._renderItem}
|
||||
>
|
||||
</lit-virtualizer>
|
||||
${this._opened
|
||||
? html`<lit-virtualizer
|
||||
scroller
|
||||
@keydown=${this._handleListItemKeyDown}
|
||||
@rangechange=${this._handleRangeChanged}
|
||||
@click=${this._handleItemClick}
|
||||
class="ha-scrollbar"
|
||||
style=${styleMap({
|
||||
height: this._narrow
|
||||
? "calc(100vh - 56px)"
|
||||
: `${Math.min(
|
||||
items.length * (this._commandMode ? 56 : 72) + 26,
|
||||
500
|
||||
)}px`,
|
||||
})}
|
||||
.items=${items}
|
||||
.renderItem=${this._renderItem}
|
||||
>
|
||||
</lit-virtualizer>`
|
||||
: ""}
|
||||
</mwc-list>
|
||||
`}
|
||||
${this._hint ? html`<div class="hint">${this._hint}</div>` : ""}
|
||||
@@ -248,111 +245,102 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
private _initializeItemsIfNeeded() {
|
||||
if (this._commandMode) {
|
||||
this._commandItems = this._commandItems || this._generateCommandItems();
|
||||
} else {
|
||||
this._entityItems = this._entityItems || this._generateEntityItems();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleOpened() {
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
private async _handleRangeChanged(e) {
|
||||
if (this._focusSet) {
|
||||
return;
|
||||
}
|
||||
if (e.firstVisible > -1) {
|
||||
this._focusSet = true;
|
||||
await this.updateComplete;
|
||||
this._setFocusFirstListItem();
|
||||
}
|
||||
}
|
||||
|
||||
private _renderItem = (item: QuickBarItem, index: number): TemplateResult => {
|
||||
if (!item) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const previous = this._filteredItems![index - 1];
|
||||
|
||||
return html`
|
||||
<div class="entry-container" style="z-index: 5">
|
||||
${index === 0 || item?.categoryKey !== previous?.categoryKey
|
||||
? html`
|
||||
<div class="entry-title">
|
||||
${item.isSuggestion
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.types.suggestions"
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${item.categoryKey}`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<mwc-list-item
|
||||
.twoline=${Boolean(item.secondaryText)}
|
||||
.hasMeta=${Boolean(item.metaText)}
|
||||
.item=${item}
|
||||
index=${ifDefined(index)}
|
||||
graphic="icon"
|
||||
class=${item.secondaryText ? "single-line" : ""}
|
||||
>
|
||||
${item.iconPath
|
||||
? html`<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>`}
|
||||
<span
|
||||
>${item.primaryText}
|
||||
<span class="secondary">${item.primaryTextAlt}</span></span
|
||||
>
|
||||
${item.secondaryText
|
||||
? html`
|
||||
<span slot="secondary" class="item-text secondary"
|
||||
>${item.secondaryText}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
${item.metaText
|
||||
? html`<ha-chip slot="meta">${item.metaText}</ha-chip>`
|
||||
: ""}
|
||||
</mwc-list-item>
|
||||
</div>
|
||||
`;
|
||||
return isCommandItem(item)
|
||||
? this._renderCommandItem(item, index)
|
||||
: this._renderEntityItem(item as EntityItem, index);
|
||||
};
|
||||
|
||||
private _initializeItems() {
|
||||
const deviceLookup: { [deviceId: string]: DeviceRegistryEntry } = {};
|
||||
for (const device of this._devices!) {
|
||||
deviceLookup[device.id] = device;
|
||||
}
|
||||
private _renderEntityItem(item: EntityItem, index?: number) {
|
||||
return html`
|
||||
<mwc-list-item
|
||||
.twoline=${Boolean(item.altText)}
|
||||
.item=${item}
|
||||
index=${ifDefined(index)}
|
||||
graphic="icon"
|
||||
>
|
||||
${item.iconPath
|
||||
? html`<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
class="entity"
|
||||
slot="graphic"
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon
|
||||
.icon=${item.icon}
|
||||
class="entity"
|
||||
slot="graphic"
|
||||
></ha-icon>`}
|
||||
<span>${item.primaryText}</span>
|
||||
${item.altText
|
||||
? html`
|
||||
<span slot="secondary" class="item-text secondary"
|
||||
>${item.altText}</span
|
||||
>
|
||||
`
|
||||
: null}
|
||||
</mwc-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
const entityLookup: { [entityId: string]: EntityRegistryEntry } = {};
|
||||
for (const entity of this._entities!) {
|
||||
entityLookup[entity.entity_id] = entity;
|
||||
}
|
||||
private _renderCommandItem(item: CommandItem, index?: number) {
|
||||
return html`
|
||||
<mwc-list-item
|
||||
.item=${item}
|
||||
index=${ifDefined(index)}
|
||||
class="command-item"
|
||||
hasMeta
|
||||
>
|
||||
<span>
|
||||
<ha-chip
|
||||
.label=${item.categoryText}
|
||||
hasIcon
|
||||
class="command-category ${item.categoryKey}"
|
||||
>
|
||||
${item.iconPath
|
||||
? html`<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="icon"
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
${item.categoryText}</ha-chip
|
||||
>
|
||||
</span>
|
||||
|
||||
const areaLookup: { [areaId: string]: AreaRegistryEntry } = {};
|
||||
for (const area of this._areas!) {
|
||||
areaLookup[area.area_id] = area;
|
||||
}
|
||||
|
||||
this._items = this._items || [
|
||||
generateEntityItems(this.hass, entityLookup, deviceLookup, areaLookup),
|
||||
...generateCommandItems(this.hass),
|
||||
];
|
||||
<span class="command-text">${item.primaryText}</span>
|
||||
</mwc-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private async processItemAndCloseDialog(item: QuickBarItem, index: number) {
|
||||
if (!this._suggestions.includes(item)) {
|
||||
this._suggestions.unshift({ ...item, isSuggestion: true });
|
||||
this._suggestions = this._suggestions.slice(0, 3);
|
||||
}
|
||||
this._addSpinnerToCommandItem(index);
|
||||
|
||||
this._addSpinnerToItem(index);
|
||||
|
||||
switch (item.categoryKey) {
|
||||
case "entity":
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: item.actionData as string,
|
||||
});
|
||||
break;
|
||||
case "reload":
|
||||
this.hass.callService(item.actionData[0], item.actionData[1]);
|
||||
break;
|
||||
case "navigation":
|
||||
navigate(item.actionData as string);
|
||||
break;
|
||||
case "server_control":
|
||||
showConfirmationDialog(this, {
|
||||
confirmText: this.hass.localize("ui.dialogs.generic.ok"),
|
||||
confirm: () =>
|
||||
this.hass.callService("homeassistant", item.actionData as string),
|
||||
});
|
||||
break;
|
||||
}
|
||||
await item.action();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
@@ -371,28 +359,56 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _getItemAtIndex(index: number): ListItem | null {
|
||||
return this.renderRoot.querySelector(`mwc-list-item[index="${index}"]`);
|
||||
}
|
||||
|
||||
private _addSpinnerToCommandItem(index: number): void {
|
||||
const spinner = document.createElement("ha-circular-progress");
|
||||
spinner.size = "small";
|
||||
spinner.slot = "meta";
|
||||
spinner.active = true;
|
||||
this._getItemAtIndex(index)?.appendChild(spinner);
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent): void {
|
||||
const newFilter = (ev.currentTarget as any).value;
|
||||
const oldCommandMode = this._commandMode;
|
||||
const oldSearch = this._search;
|
||||
let newCommandMode: boolean;
|
||||
let newSearch: string;
|
||||
|
||||
if (newFilter.startsWith(">")) {
|
||||
newCommandMode = true;
|
||||
newSearch = newFilter.substring(1);
|
||||
} else {
|
||||
newCommandMode = false;
|
||||
newSearch = newFilter;
|
||||
}
|
||||
|
||||
if (oldCommandMode === newCommandMode && oldSearch === newSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._commandMode = newCommandMode;
|
||||
this._search = newSearch;
|
||||
|
||||
if (this._hint) {
|
||||
this._hint = undefined;
|
||||
}
|
||||
|
||||
if (this._focusSet && this._focusListElement) {
|
||||
if (oldCommandMode !== this._commandMode) {
|
||||
this._focusSet = false;
|
||||
// @ts-ignore
|
||||
this._focusListElement.rippleHandlers.endFocus();
|
||||
this._initializeItemsIfNeeded();
|
||||
this._filter = this._search;
|
||||
} else {
|
||||
if (this._focusSet && this._focusListElement) {
|
||||
this._focusSet = false;
|
||||
// @ts-ignore
|
||||
this._focusListElement.rippleHandlers.endFocus();
|
||||
}
|
||||
this._debouncedSetFilter(this._search);
|
||||
}
|
||||
this._debouncedSetFilter(this._search);
|
||||
}
|
||||
|
||||
private _clearSearch() {
|
||||
@@ -438,62 +454,258 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _handleItemClick(ev) {
|
||||
const target =
|
||||
ev.target.nodeName === "MWC-LIST-ITEM"
|
||||
? ev.target
|
||||
: ev.target.parentElement === "MWC-LIST-ITEM"
|
||||
? ev.target.parentElement
|
||||
: ev.target.parentElement.parentElement;
|
||||
|
||||
// Need to resolve when clicking on the chip
|
||||
if (!target.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listItem = ev.target.closest("mwc-list-item");
|
||||
this.processItemAndCloseDialog(
|
||||
target.item,
|
||||
Number((target as HTMLElement).getAttribute("index"))
|
||||
listItem.item,
|
||||
Number(listItem.getAttribute("index"))
|
||||
);
|
||||
}
|
||||
|
||||
private _handleOpened() {
|
||||
this.updateComplete.then(() => {
|
||||
this._done = true;
|
||||
private _generateEntityItems(): EntityItem[] {
|
||||
return Object.keys(this.hass.states)
|
||||
.map((entityId) => {
|
||||
const entityState = this.hass.states[entityId];
|
||||
const entityItem = {
|
||||
primaryText: computeStateName(entityState),
|
||||
altText: entityId,
|
||||
icon: entityState.attributes.icon,
|
||||
iconPath: entityState.attributes.icon
|
||||
? undefined
|
||||
: domainIcon(computeDomain(entityId), entityState),
|
||||
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
||||
};
|
||||
|
||||
return {
|
||||
...entityItem,
|
||||
strings: [entityItem.primaryText, entityItem.altText],
|
||||
};
|
||||
})
|
||||
.sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a.primaryText, b.primaryText)
|
||||
);
|
||||
}
|
||||
|
||||
private _generateCommandItems(): CommandItem[] {
|
||||
return [
|
||||
...this._generateReloadCommands(),
|
||||
...this._generateServerControlCommands(),
|
||||
...this._generateNavigationCommands(),
|
||||
].sort((a, b) =>
|
||||
caseInsensitiveStringCompare(a.strings.join(" "), b.strings.join(" "))
|
||||
);
|
||||
}
|
||||
|
||||
private _generateReloadCommands(): CommandItem[] {
|
||||
// Get all domains that have a direct "reload" service
|
||||
const reloadableDomains = componentsWithService(this.hass, "reload");
|
||||
|
||||
const commands = reloadableDomains.map((domain) => ({
|
||||
primaryText:
|
||||
this.hass.localize(`ui.dialogs.quick-bar.commands.reload.${domain}`) ||
|
||||
this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.reload",
|
||||
"domain",
|
||||
domainToName(this.hass.localize, domain)
|
||||
),
|
||||
action: () => this.hass.callService(domain, "reload"),
|
||||
iconPath: mdiReload,
|
||||
categoryText: this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.reload`
|
||||
),
|
||||
}));
|
||||
|
||||
// Add "frontend.reload_themes"
|
||||
commands.push({
|
||||
primaryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.themes"
|
||||
),
|
||||
action: () => this.hass.callService("frontend", "reload_themes"),
|
||||
iconPath: mdiReload,
|
||||
categoryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.types.reload"
|
||||
),
|
||||
});
|
||||
|
||||
// Add "homeassistant.reload_core_config"
|
||||
commands.push({
|
||||
primaryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.reload.core"
|
||||
),
|
||||
action: () =>
|
||||
this.hass.callService("homeassistant", "reload_core_config"),
|
||||
iconPath: mdiReload,
|
||||
categoryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.types.reload"
|
||||
),
|
||||
});
|
||||
|
||||
return commands.map((command) => ({
|
||||
...command,
|
||||
categoryKey: "reload",
|
||||
strings: [`${command.categoryText} ${command.primaryText}`],
|
||||
}));
|
||||
}
|
||||
|
||||
private _generateServerControlCommands(): CommandItem[] {
|
||||
const serverActions = ["restart", "stop"];
|
||||
|
||||
return serverActions.map((action) => {
|
||||
const categoryKey: CommandItem["categoryKey"] = "server_control";
|
||||
|
||||
const item = {
|
||||
primaryText: this.hass.localize(
|
||||
"ui.dialogs.quick-bar.commands.server_control.perform_action",
|
||||
"action",
|
||||
this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.server_control.${action}`
|
||||
)
|
||||
),
|
||||
iconPath: mdiServerNetwork,
|
||||
categoryText: this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
),
|
||||
categoryKey,
|
||||
action: () => this.hass.callService("homeassistant", action),
|
||||
};
|
||||
|
||||
return this._generateConfirmationCommand(
|
||||
{
|
||||
...item,
|
||||
strings: [`${item.categoryText} ${item.primaryText}`],
|
||||
},
|
||||
this.hass.localize("ui.dialogs.generic.ok")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async _handleRangeChanged(e) {
|
||||
if (this._focusSet) {
|
||||
return;
|
||||
}
|
||||
if (e.firstVisible > -1) {
|
||||
this._focusSet = true;
|
||||
await this.updateComplete;
|
||||
this._setFocusFirstListItem();
|
||||
}
|
||||
private _generateNavigationCommands(): CommandItem[] {
|
||||
const panelItems = this._generateNavigationPanelCommands();
|
||||
const sectionItems = this._generateNavigationConfigSectionCommands();
|
||||
|
||||
return this._finalizeNavigationCommands([...panelItems, ...sectionItems]);
|
||||
}
|
||||
|
||||
private _getItemAtIndex(index: number): ListItem | null {
|
||||
return this.renderRoot.querySelector(`mwc-list-item[index="${index}"]`);
|
||||
private _generateNavigationPanelCommands(): BaseNavigationCommand[] {
|
||||
return Object.keys(this.hass.panels)
|
||||
.filter((panelKey) => panelKey !== "_my_redirect")
|
||||
.map((panelKey) => {
|
||||
const panel = this.hass.panels[panelKey];
|
||||
const translationKey = getPanelNameTranslationKey(panel);
|
||||
|
||||
const primaryText =
|
||||
this.hass.localize(translationKey) || panel.title || panel.url_path;
|
||||
|
||||
return {
|
||||
primaryText,
|
||||
path: `/${panel.url_path}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private _addSpinnerToItem(index: number): void {
|
||||
const spinner = document.createElement("ha-circular-progress");
|
||||
spinner.size = "small";
|
||||
spinner.slot = "meta";
|
||||
spinner.active = true;
|
||||
this._getItemAtIndex(index)?.appendChild(spinner);
|
||||
private _generateNavigationConfigSectionCommands(): BaseNavigationCommand[] {
|
||||
const items: NavigationInfo[] = [];
|
||||
|
||||
for (const sectionKey of Object.keys(configSections)) {
|
||||
for (const page of configSections[sectionKey]) {
|
||||
if (!canShowPage(this.hass, page)) {
|
||||
continue;
|
||||
}
|
||||
if (!page.component) {
|
||||
continue;
|
||||
}
|
||||
const info = this._getNavigationInfoFromConfig(page);
|
||||
|
||||
if (!info) {
|
||||
continue;
|
||||
}
|
||||
// Add to list, but only if we do not already have an entry for the same path and component
|
||||
if (
|
||||
items.some(
|
||||
(e) => e.path === info.path && e.component === info.component
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
items.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private _getNavigationInfoFromConfig(
|
||||
page: PageNavigation
|
||||
): NavigationInfo | undefined {
|
||||
if (!page.component) {
|
||||
return undefined;
|
||||
}
|
||||
const caption = this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.navigation.${page.component}`
|
||||
);
|
||||
|
||||
if (page.translationKey && caption) {
|
||||
return { ...page, primaryText: caption };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _generateConfirmationCommand(
|
||||
item: CommandItem,
|
||||
confirmText: ConfirmationDialogParams["confirmText"]
|
||||
): CommandItem {
|
||||
return {
|
||||
...item,
|
||||
action: () =>
|
||||
showConfirmationDialog(this, {
|
||||
confirmText,
|
||||
confirm: item.action,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private _finalizeNavigationCommands(
|
||||
items: BaseNavigationCommand[]
|
||||
): CommandItem[] {
|
||||
return items.map((item) => {
|
||||
const categoryKey: CommandItem["categoryKey"] = "navigation";
|
||||
|
||||
const navItem = {
|
||||
...item,
|
||||
iconPath: mdiEarth,
|
||||
categoryText: this.hass.localize(
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
),
|
||||
action: () => navigate(item.path),
|
||||
};
|
||||
|
||||
return {
|
||||
...navItem,
|
||||
strings: [`${navItem.categoryText} ${navItem.primaryText}`],
|
||||
categoryKey,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleIfAlreadyOpened() {
|
||||
return this._opened ? !this._commandMode : false;
|
||||
}
|
||||
|
||||
private _filterItems = memoizeOne(
|
||||
(items: QuickBarItem[], filter: string): QuickBarItem[] =>
|
||||
fuzzyFilterSort<QuickBarItem>(filter.trimLeft(), items)
|
||||
);
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
haStyleDialog,
|
||||
haStyle,
|
||||
css`
|
||||
.heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--mdc-theme-primary: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.heading ha-textfield {
|
||||
@@ -527,8 +739,8 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
mwc-list-item ha-icon,
|
||||
mwc-list-item ha-svg-icon {
|
||||
ha-icon.entity,
|
||||
ha-svg-icon.entity {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@@ -536,30 +748,38 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-textfield {
|
||||
--mdc-text-field-fill-color: transparent;
|
||||
--mdc-theme-primary: var(--divider-color);
|
||||
--mdc-text-field-idle-line-color: var(--divider-color);
|
||||
}
|
||||
|
||||
ha-textfield ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.entry-container {
|
||||
width: 100%;
|
||||
.command-category {
|
||||
--ha-chip-icon-color: #585858;
|
||||
--ha-chip-text-color: #212121;
|
||||
}
|
||||
|
||||
.entry-title {
|
||||
padding-left: 16px;
|
||||
padding-top: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
.command-category.reload {
|
||||
--ha-chip-background-color: #cddc39;
|
||||
}
|
||||
|
||||
.command-category.navigation {
|
||||
--ha-chip-background-color: var(--light-primary-color);
|
||||
}
|
||||
|
||||
.command-category.server_control {
|
||||
--ha-chip-background-color: var(--warning-color);
|
||||
}
|
||||
|
||||
span.command-text {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
mwc-list-item {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
mwc-list-item.command-item {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.hint {
|
||||
@@ -581,10 +801,6 @@ export class QuickBar extends SubscribeMixin(LitElement) {
|
||||
lit-virtualizer {
|
||||
contain: size layout !important;
|
||||
}
|
||||
|
||||
ha-chip {
|
||||
margin-left: -48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ import { useAmPm } from "../../common/datetime/use_am_pm";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-icon-button-prev";
|
||||
import "../../components/ha-icon-button-next";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import type {
|
||||
CalendarEvent,
|
||||
CalendarViewChanged,
|
||||
@@ -127,25 +124,26 @@ export class HAFullCalendar extends LitElement {
|
||||
"ui.components.calendar.today"
|
||||
)}</mwc-button
|
||||
>
|
||||
<ha-icon-button-prev
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.previous")}
|
||||
.path=${mdiChevronLeft}
|
||||
class="prev"
|
||||
@click=${this._handlePrev}
|
||||
>
|
||||
</ha-icon-button-prev>
|
||||
<ha-icon-button-next
|
||||
</ha-icon-button>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("ui.common.next")}
|
||||
.path=${mdiChevronRight}
|
||||
class="next"
|
||||
@click=${this._handleNext}
|
||||
>
|
||||
</ha-icon-button-next>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<h1>${this.calendar.view.title}</h1>
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
></ha-button-toggle-group>
|
||||
`
|
||||
: html`
|
||||
@@ -181,7 +179,6 @@ export class HAFullCalendar extends LitElement {
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
></ha-button-toggle-group>
|
||||
</div>
|
||||
`}
|
||||
|
||||
@@ -165,9 +165,6 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
right: 0;
|
||||
padding: 4px;
|
||||
}
|
||||
ha-form::part(root) {
|
||||
overflow: visible;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import "../../../components/ha-card";
|
||||
import "../../../components/ha-textarea";
|
||||
import "../../../components/ha-textfield";
|
||||
import {
|
||||
AUTOMATION_DEFAULT_MODE,
|
||||
Condition,
|
||||
ManualAutomationConfig,
|
||||
Trigger,
|
||||
@@ -100,7 +99,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.modes.label"
|
||||
)}
|
||||
.value=${this.config.mode || AUTOMATION_DEFAULT_MODE}
|
||||
.value=${this.config.mode}
|
||||
@selected=${this._modeChanged}
|
||||
fixedMenuPosition
|
||||
>
|
||||
|
||||
@@ -442,9 +442,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
z-index: 3;
|
||||
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
||||
}
|
||||
.rtl .card-menu {
|
||||
float: left;
|
||||
}
|
||||
.triggered {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
@@ -473,6 +470,9 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--text-accent-color, var(--text-primary-color));
|
||||
}
|
||||
.rtl .card-menu {
|
||||
float: left;
|
||||
}
|
||||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import {
|
||||
array,
|
||||
assert,
|
||||
assign,
|
||||
literal,
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
union,
|
||||
} from "superstruct";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../../../../common/ensure-array";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||
import { StateTrigger } from "../../../../../data/automation";
|
||||
@@ -26,7 +24,7 @@ const stateTriggerStruct = assign(
|
||||
baseTriggerStruct,
|
||||
object({
|
||||
platform: literal("state"),
|
||||
entity_id: optional(union([string(), array(string())])),
|
||||
entity_id: optional(string()),
|
||||
attribute: optional(string()),
|
||||
from: optional(string()),
|
||||
to: optional(string()),
|
||||
@@ -41,15 +39,11 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
@property() public trigger!: StateTrigger;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { entity_id: [] };
|
||||
return { entity_id: "" };
|
||||
}
|
||||
|
||||
private _schema = memoizeOne((entityId) => [
|
||||
{
|
||||
name: "entity_id",
|
||||
required: true,
|
||||
selector: { entity: { multiple: true } },
|
||||
},
|
||||
{ name: "entity_id", required: true, selector: { entity: {} } },
|
||||
{
|
||||
name: "attribute",
|
||||
selector: { attribute: { entity_id: entityId } },
|
||||
@@ -91,11 +85,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
|
||||
protected render() {
|
||||
const trgFor = createDurationData(this.trigger.for);
|
||||
|
||||
const data = {
|
||||
...this.trigger,
|
||||
entity_id: ensureArray(this.trigger.entity_id),
|
||||
for: trgFor,
|
||||
};
|
||||
const data = { ...this.trigger, ...{ for: trgFor } };
|
||||
const schema = this._schema(this.trigger.entity_id);
|
||||
|
||||
return html`
|
||||
|
||||
@@ -11,13 +11,10 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-area-picker";
|
||||
import "../../../components/ha-expansion-panel";
|
||||
@@ -99,7 +96,7 @@ const OVERRIDE_SENSOR_UNITS = {
|
||||
pressure: ["hPa", "Pa", "kPa", "bar", "cbar", "mbar", "mmHg", "inHg", "psi"],
|
||||
};
|
||||
|
||||
const SWITCH_AS_DOMAINS = ["cover", "fan", "light", "lock", "siren"];
|
||||
const SWITCH_AS_DOMAINS = ["light", "lock", "cover", "fan", "siren"];
|
||||
|
||||
@customElement("entity-registry-settings")
|
||||
export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
@@ -276,29 +273,22 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
@selected=${this._deviceClassChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${this._deviceClassesSorted(
|
||||
domain,
|
||||
this._deviceClassOptions[0],
|
||||
this.hass.localize
|
||||
).map(
|
||||
(entry) => html`
|
||||
<mwc-list-item .value=${entry.deviceClass}>
|
||||
${entry.label}
|
||||
${this._deviceClassOptions[0].map(
|
||||
(deviceClass: string) => html`
|
||||
<mwc-list-item .value=${deviceClass}>
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}`
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
${this._deviceClassOptions[0].length &&
|
||||
this._deviceClassOptions[1].length
|
||||
? html`<li divider role="separator"></li>`
|
||||
: ""}
|
||||
${this._deviceClassesSorted(
|
||||
domain,
|
||||
this._deviceClassOptions[1],
|
||||
this.hass.localize
|
||||
).map(
|
||||
(entry) => html`
|
||||
<mwc-list-item .value=${entry.deviceClass}>
|
||||
${entry.label}
|
||||
<li divider role="separator"></li>
|
||||
${this._deviceClassOptions[1].map(
|
||||
(deviceClass: string) => html`
|
||||
<mwc-list-item .value=${deviceClass}>
|
||||
${this.hass.localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${deviceClass}`
|
||||
)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
@@ -306,9 +296,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
`
|
||||
: ""}
|
||||
${this._deviceClass &&
|
||||
stateObj?.attributes.unit_of_measurement &&
|
||||
stateObj.attributes.unit_of_measurement &&
|
||||
OVERRIDE_SENSOR_UNITS[this._deviceClass]?.includes(
|
||||
stateObj?.attributes.unit_of_measurement
|
||||
stateObj.attributes.unit_of_measurement
|
||||
)
|
||||
? html`
|
||||
<ha-select
|
||||
@@ -342,14 +332,10 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
<mwc-list-item value="switch" selected>
|
||||
${domainToName(this.hass.localize, "switch")}</mwc-list-item
|
||||
>
|
||||
<li divider role="separator"></li>
|
||||
${this._switchAsDomainsSorted(
|
||||
SWITCH_AS_DOMAINS,
|
||||
this.hass.localize
|
||||
).map(
|
||||
(entry) => html`
|
||||
<mwc-list-item .value=${entry.domain}>
|
||||
${entry.label}
|
||||
${SWITCH_AS_DOMAINS.map(
|
||||
(as_domain) => html`
|
||||
<mwc-list-item .value=${as_domain}>
|
||||
${domainToName(this.hass.localize, as_domain)}
|
||||
</mwc-list-item>
|
||||
`
|
||||
)}
|
||||
@@ -730,31 +716,9 @@ export class EntityRegistrySettings extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _showOptionsFlow() {
|
||||
showOptionsFlowDialog(this, this._helperConfigEntry!, null);
|
||||
showOptionsFlowDialog(this, this._helperConfigEntry!);
|
||||
}
|
||||
|
||||
private _switchAsDomainsSorted = memoizeOne(
|
||||
(domains: string[], localize: LocalizeFunc) =>
|
||||
domains
|
||||
.map((entry) => ({
|
||||
domain: entry,
|
||||
label: domainToName(localize, entry),
|
||||
}))
|
||||
.sort((a, b) => stringCompare(a.label, b.label))
|
||||
);
|
||||
|
||||
private _deviceClassesSorted = memoizeOne(
|
||||
(domain: string, deviceClasses: string[], localize: LocalizeFunc) =>
|
||||
deviceClasses
|
||||
.map((entry) => ({
|
||||
deviceClass: entry,
|
||||
label: localize(
|
||||
`ui.dialogs.entity_registry.editor.device_classes.${domain}.${entry}`
|
||||
),
|
||||
}))
|
||||
.sort((a, b) => stringCompare(a.label, b.label))
|
||||
);
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -700,7 +700,6 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
this._handleFlowUpdated();
|
||||
},
|
||||
startFlowHandler: domain,
|
||||
manifest: this._manifests[domain],
|
||||
showAdvanced: this.hass.userData?.showAdvanced,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -482,11 +482,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
);
|
||||
|
||||
private _showOptions(ev) {
|
||||
showOptionsFlowDialog(
|
||||
this,
|
||||
ev.target.closest("ha-card").configEntry,
|
||||
this.manifest
|
||||
);
|
||||
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
||||
}
|
||||
|
||||
private _handleRename(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
|
||||
@@ -179,7 +179,7 @@ class ZWaveJSNodeConfig extends SubscribeMixin(LitElement) {
|
||||
</em>
|
||||
</p>
|
||||
</div>
|
||||
${this._nodeMetadata.comments?.length > 0
|
||||
${this._nodeMetadata.comments.length > 0
|
||||
? html`
|
||||
<div>
|
||||
${this._nodeMetadata.comments.map(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiChevronRight } from "@mdi/js";
|
||||
import formatISO9075 from "date-fns/formatISO9075";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -70,11 +69,12 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||
|
||||
public showDialog(params: DialogStatisticsAdjustSumParams): void {
|
||||
this._params = params;
|
||||
// moment is in format YYYY-MM-DD HH:mm:ss because of selector
|
||||
// Here we create a date with minutes set to %5
|
||||
const now = new Date();
|
||||
now.setMinutes(now.getMinutes() - (now.getMinutes() % 5), 0);
|
||||
this._moment = formatISO9075(now);
|
||||
this._moment = `${now.getFullYear()}-${
|
||||
now.getMonth() + 1
|
||||
}-${now.getDate()} ${now.getHours()}:${
|
||||
now.getMinutes() - (now.getMinutes() % 5)
|
||||
}:00`;
|
||||
this._fetchStats();
|
||||
}
|
||||
|
||||
@@ -167,9 +167,6 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||
time. This can mess up your beautiful graphs! Select a time below to
|
||||
find the bad moment and adjust the data.
|
||||
</div>
|
||||
<div class="text-content">
|
||||
<b>Statistic:</b> ${this._params!.statistic.statistic_id}
|
||||
</div>
|
||||
<ha-selector-datetime
|
||||
label="Pick a time"
|
||||
.hass=${this.hass}
|
||||
@@ -194,7 +191,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||
private _renderAdjustStat() {
|
||||
return html`
|
||||
<div class="text-content">
|
||||
<b>Statistic:</b> ${this._params!.statistic.statistic_id}
|
||||
${this._params!.statistic.name || this._params!.statistic.statistic_id}
|
||||
</div>
|
||||
|
||||
<div class="table-row">
|
||||
@@ -253,10 +250,7 @@ export class DialogStatisticsFixUnsupportedUnitMetadata extends LitElement {
|
||||
this._stats5min = undefined;
|
||||
this._statsHour = undefined;
|
||||
const statId = this._params!.statistic.statistic_id;
|
||||
|
||||
// moment is in format YYYY-MM-DD HH:mm:ss because of selector
|
||||
// Here we convert it to an ISO string.
|
||||
const moment = new Date(this._moment!.replace(" ", "T"));
|
||||
const moment = new Date(this._moment!);
|
||||
|
||||
// Search 3 hours before and 3 hours after chosen time
|
||||
const hourStatStart = new Date(moment.getTime());
|
||||
|
||||
@@ -253,10 +253,6 @@ class HaPanelHistory extends LitElement {
|
||||
padding: 8px 16px 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .filters {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
ha-date-range-picker {
|
||||
margin-right: 16px;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -113,7 +113,6 @@ export class HuiAreaCard
|
||||
.filter(
|
||||
(entry) =>
|
||||
!entry.entity_category &&
|
||||
!entry.hidden_by &&
|
||||
(entry.area_id
|
||||
? entry.area_id === areaId
|
||||
: entry.device_id && devicesInArea.has(entry.device_id))
|
||||
|
||||
@@ -200,7 +200,6 @@ class HuiPictureEntityCard extends LitElement implements LovelaceCard {
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
color: var(--ha-picture-card-text-color, white);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.both {
|
||||
|
||||
@@ -21,9 +21,6 @@ export const turnOnOffEntity = (
|
||||
case "input_button":
|
||||
service = "press";
|
||||
break;
|
||||
case "scene":
|
||||
service = "turn_on";
|
||||
break;
|
||||
default:
|
||||
service = turnOn ? "turn_on" : "turn_off";
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import type { LovelaceRowEditor } from "../../types";
|
||||
import { entitiesConfigStruct } from "../structs/entities-struct";
|
||||
|
||||
const SecondaryInfoValues: { [key: string]: { domains?: string[] } } = {
|
||||
none: {},
|
||||
"entity-id": {},
|
||||
"last-changed": {},
|
||||
"last-updated": {},
|
||||
|
||||
@@ -13,7 +13,6 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/ha-select";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import type { InputSelectEntity } from "../../../data/input_select";
|
||||
import { SelectEntity, setSelectOption } from "../../../data/select";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EntitiesCardEntityConfig } from "../cards/types";
|
||||
@@ -107,14 +106,9 @@ class HuiSelectEntityRow extends LitElement implements LovelaceRow {
|
||||
}
|
||||
|
||||
private _selectedChanged(ev): void {
|
||||
const stateObj = this.hass!.states[
|
||||
this._config!.entity
|
||||
] as InputSelectEntity;
|
||||
const stateObj = this.hass!.states[this._config!.entity];
|
||||
const option = ev.target.value;
|
||||
if (
|
||||
option === stateObj.state ||
|
||||
!stateObj.attributes.options.includes(option)
|
||||
) {
|
||||
if (option === stateObj.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
mdiFileMultiple,
|
||||
mdiFormatListBulletedTriangle,
|
||||
mdiHelp,
|
||||
mdiMagnify,
|
||||
mdiHelpCircle,
|
||||
mdiMicrophone,
|
||||
mdiPencil,
|
||||
@@ -73,7 +72,6 @@ import { showEditViewDialog } from "./editor/view-editor/show-edit-view-dialog";
|
||||
import type { Lovelace } from "./types";
|
||||
import "./views/hui-view";
|
||||
import type { HUIView } from "./views/hui-view";
|
||||
import { showQuickBar } from "../../dialogs/quick-bar/show-dialog-quick-bar";
|
||||
|
||||
class HUIRoot extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -266,10 +264,6 @@ class HUIRoot extends LitElement {
|
||||
</ha-tabs>
|
||||
`
|
||||
: html`<div main-title>${this.config.title}</div>`}
|
||||
<ha-icon-button
|
||||
.path=${mdiMagnify}
|
||||
@click=${this._showQuickBar}
|
||||
></ha-icon-button>
|
||||
${!this.narrow &&
|
||||
this._conversation(this.hass.config.components)
|
||||
? html`
|
||||
@@ -557,7 +551,7 @@ class HUIRoot extends LitElement {
|
||||
let newSelectView;
|
||||
let force = false;
|
||||
|
||||
const viewPath = decodeURI(this.route!.path.split("/")[1]);
|
||||
const viewPath = this.route!.path.split("/")[1];
|
||||
|
||||
if (changedProperties.has("route")) {
|
||||
const views = this.config.views;
|
||||
@@ -679,13 +673,6 @@ class HUIRoot extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _showQuickBar(): void {
|
||||
showQuickBar(this, {
|
||||
commandMode: false,
|
||||
hint: this.hass.localize("ui.dialogs.quick-bar.key_e_hint"),
|
||||
});
|
||||
}
|
||||
|
||||
private _handleRawEditor(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
|
||||
@@ -170,12 +170,11 @@ class PanelMediaBrowser extends LitElement {
|
||||
media_content_id: undefined,
|
||||
},
|
||||
...navigateIdsEncoded.map((navigateId) => {
|
||||
const decoded = decodeURIComponent(navigateId);
|
||||
// Don't use split because media_content_id could contain commas
|
||||
const delimiter = decoded.indexOf(",");
|
||||
const [media_content_type, media_content_id] =
|
||||
decodeURIComponent(navigateId).split(",");
|
||||
return {
|
||||
media_content_type: decoded.substring(0, delimiter),
|
||||
media_content_id: decoded.substring(delimiter + 1),
|
||||
media_content_type,
|
||||
media_content_id,
|
||||
};
|
||||
}),
|
||||
];
|
||||
|
||||
+176
-192
@@ -12,172 +12,158 @@ import "../../layouts/hass-error-screen";
|
||||
import { HomeAssistant, Route } from "../../types";
|
||||
import { documentationUrl } from "../../util/documentation-url";
|
||||
|
||||
const getRedirect = (
|
||||
path: string,
|
||||
hasSupervisor: boolean
|
||||
): Redirect | undefined =>
|
||||
((
|
||||
{
|
||||
developer_states: {
|
||||
redirect: "/developer-tools/state",
|
||||
},
|
||||
developer_services: {
|
||||
redirect: "/developer-tools/service",
|
||||
},
|
||||
developer_call_service: {
|
||||
redirect: "/developer-tools/service",
|
||||
params: {
|
||||
service: "string",
|
||||
},
|
||||
},
|
||||
developer_template: {
|
||||
redirect: "/developer-tools/template",
|
||||
},
|
||||
developer_events: {
|
||||
redirect: "/developer-tools/event",
|
||||
},
|
||||
developer_statistics: {
|
||||
redirect: "/developer-tools/statistics",
|
||||
},
|
||||
config: {
|
||||
redirect: "/config",
|
||||
},
|
||||
cloud: {
|
||||
component: "cloud",
|
||||
redirect: "/config/cloud",
|
||||
},
|
||||
integrations: {
|
||||
redirect: "/config/integrations",
|
||||
},
|
||||
config_flow_start: {
|
||||
redirect: "/config/integrations/add",
|
||||
params: {
|
||||
domain: "string",
|
||||
},
|
||||
},
|
||||
config_mqtt: {
|
||||
component: "mqtt",
|
||||
redirect: "/config/mqtt",
|
||||
},
|
||||
config_zha: {
|
||||
component: "zha",
|
||||
redirect: "/config/zha/dashboard",
|
||||
},
|
||||
config_zwave_js: {
|
||||
component: "zwave_js",
|
||||
redirect: "/config/zwave_js/dashboard",
|
||||
},
|
||||
config_energy: {
|
||||
component: "energy",
|
||||
redirect: "/config/energy/dashboard",
|
||||
},
|
||||
devices: {
|
||||
redirect: "/config/devices/dashboard",
|
||||
},
|
||||
entities: {
|
||||
redirect: "/config/entities",
|
||||
},
|
||||
energy: {
|
||||
component: "energy",
|
||||
redirect: "/energy",
|
||||
},
|
||||
areas: {
|
||||
redirect: "/config/areas/dashboard",
|
||||
},
|
||||
blueprints: {
|
||||
component: "blueprint",
|
||||
redirect: "/config/blueprint/dashboard",
|
||||
},
|
||||
blueprint_import: {
|
||||
component: "blueprint",
|
||||
redirect: "/config/blueprint/dashboard/import",
|
||||
params: {
|
||||
blueprint_url: "url",
|
||||
},
|
||||
},
|
||||
automations: {
|
||||
component: "automation",
|
||||
redirect: "/config/automation/dashboard",
|
||||
},
|
||||
scenes: {
|
||||
component: "scene",
|
||||
redirect: "/config/scene/dashboard",
|
||||
},
|
||||
scripts: {
|
||||
component: "script",
|
||||
redirect: "/config/script/dashboard",
|
||||
},
|
||||
helpers: {
|
||||
redirect: "/config/helpers",
|
||||
},
|
||||
tags: {
|
||||
component: "tag",
|
||||
redirect: "/config/tags",
|
||||
},
|
||||
lovelace_dashboards: {
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/dashboards",
|
||||
},
|
||||
lovelace_resources: {
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/resources",
|
||||
},
|
||||
people: {
|
||||
component: "person",
|
||||
redirect: "/config/person",
|
||||
},
|
||||
zones: {
|
||||
component: "zone",
|
||||
redirect: "/config/zone",
|
||||
},
|
||||
users: {
|
||||
redirect: "/config/users",
|
||||
},
|
||||
general: {
|
||||
redirect: "/config/core",
|
||||
},
|
||||
server_controls: {
|
||||
redirect: "/config/server_control",
|
||||
},
|
||||
logs: {
|
||||
redirect: "/config/logs",
|
||||
},
|
||||
info: {
|
||||
redirect: "/config/info",
|
||||
},
|
||||
customize: {
|
||||
// customize was removed in 2021.12, fallback to dashboard
|
||||
redirect: "/config/dashboard",
|
||||
},
|
||||
profile: {
|
||||
redirect: "/profile/dashboard",
|
||||
},
|
||||
logbook: {
|
||||
component: "logbook",
|
||||
redirect: "/logbook",
|
||||
},
|
||||
history: {
|
||||
component: "history",
|
||||
redirect: "/history",
|
||||
},
|
||||
media_browser: {
|
||||
component: "media_source",
|
||||
redirect: "/media-browser",
|
||||
},
|
||||
backup: {
|
||||
component: hasSupervisor ? "hassio" : "backup",
|
||||
redirect: hasSupervisor ? "/hassio/backups" : "/config/backup",
|
||||
},
|
||||
supervisor_snapshots: {
|
||||
component: hasSupervisor ? "hassio" : "backup",
|
||||
redirect: hasSupervisor ? "/hassio/backups" : "/config/backup",
|
||||
},
|
||||
supervisor_backups: {
|
||||
component: hasSupervisor ? "hassio" : "backup",
|
||||
redirect: hasSupervisor ? "/hassio/backups" : "/config/backup",
|
||||
},
|
||||
} as Redirects
|
||||
)[path]);
|
||||
const REDIRECTS: Redirects = {
|
||||
developer_states: {
|
||||
redirect: "/developer-tools/state",
|
||||
},
|
||||
developer_services: {
|
||||
redirect: "/developer-tools/service",
|
||||
},
|
||||
developer_call_service: {
|
||||
redirect: "/developer-tools/service",
|
||||
params: {
|
||||
service: "string",
|
||||
},
|
||||
},
|
||||
developer_template: {
|
||||
redirect: "/developer-tools/template",
|
||||
},
|
||||
developer_events: {
|
||||
redirect: "/developer-tools/event",
|
||||
},
|
||||
developer_statistics: {
|
||||
redirect: "/developer-tools/statistics",
|
||||
},
|
||||
config: {
|
||||
redirect: "/config",
|
||||
},
|
||||
cloud: {
|
||||
component: "cloud",
|
||||
redirect: "/config/cloud",
|
||||
},
|
||||
integrations: {
|
||||
redirect: "/config/integrations",
|
||||
},
|
||||
config_flow_start: {
|
||||
redirect: "/config/integrations/add",
|
||||
params: {
|
||||
domain: "string",
|
||||
},
|
||||
},
|
||||
config_mqtt: {
|
||||
component: "mqtt",
|
||||
redirect: "/config/mqtt",
|
||||
},
|
||||
config_zha: {
|
||||
component: "zha",
|
||||
redirect: "/config/zha/dashboard",
|
||||
},
|
||||
config_zwave_js: {
|
||||
component: "zwave_js",
|
||||
redirect: "/config/zwave_js/dashboard",
|
||||
},
|
||||
config_energy: {
|
||||
component: "energy",
|
||||
redirect: "/config/energy/dashboard",
|
||||
},
|
||||
devices: {
|
||||
redirect: "/config/devices/dashboard",
|
||||
},
|
||||
entities: {
|
||||
redirect: "/config/entities",
|
||||
},
|
||||
energy: {
|
||||
component: "energy",
|
||||
redirect: "/energy",
|
||||
},
|
||||
areas: {
|
||||
redirect: "/config/areas/dashboard",
|
||||
},
|
||||
blueprints: {
|
||||
component: "blueprint",
|
||||
redirect: "/config/blueprint/dashboard",
|
||||
},
|
||||
blueprint_import: {
|
||||
component: "blueprint",
|
||||
redirect: "/config/blueprint/dashboard/import",
|
||||
params: {
|
||||
blueprint_url: "url",
|
||||
},
|
||||
},
|
||||
automations: {
|
||||
component: "automation",
|
||||
redirect: "/config/automation/dashboard",
|
||||
},
|
||||
scenes: {
|
||||
component: "scene",
|
||||
redirect: "/config/scene/dashboard",
|
||||
},
|
||||
scripts: {
|
||||
component: "script",
|
||||
redirect: "/config/script/dashboard",
|
||||
},
|
||||
helpers: {
|
||||
redirect: "/config/helpers",
|
||||
},
|
||||
tags: {
|
||||
component: "tag",
|
||||
redirect: "/config/tags",
|
||||
},
|
||||
lovelace_dashboards: {
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/dashboards",
|
||||
},
|
||||
lovelace_resources: {
|
||||
component: "lovelace",
|
||||
redirect: "/config/lovelace/resources",
|
||||
},
|
||||
backup: {
|
||||
component: "backup",
|
||||
redirect: "/config/backup",
|
||||
},
|
||||
people: {
|
||||
component: "person",
|
||||
redirect: "/config/person",
|
||||
},
|
||||
zones: {
|
||||
component: "zone",
|
||||
redirect: "/config/zone",
|
||||
},
|
||||
users: {
|
||||
redirect: "/config/users",
|
||||
},
|
||||
general: {
|
||||
redirect: "/config/core",
|
||||
},
|
||||
server_controls: {
|
||||
redirect: "/config/server_control",
|
||||
},
|
||||
logs: {
|
||||
redirect: "/config/logs",
|
||||
},
|
||||
info: {
|
||||
redirect: "/config/info",
|
||||
},
|
||||
customize: {
|
||||
// customize was removed in 2021.12, fallback to dashboard
|
||||
redirect: "/config/dashboard",
|
||||
},
|
||||
profile: {
|
||||
redirect: "/profile/dashboard",
|
||||
},
|
||||
logbook: {
|
||||
component: "logbook",
|
||||
redirect: "/logbook",
|
||||
},
|
||||
history: {
|
||||
component: "history",
|
||||
redirect: "/history",
|
||||
},
|
||||
media_browser: {
|
||||
component: "media_source",
|
||||
redirect: "/media-browser",
|
||||
},
|
||||
};
|
||||
|
||||
export type ParamType = "url" | "string";
|
||||
|
||||
@@ -198,17 +184,19 @@ class HaPanelMy extends LitElement {
|
||||
|
||||
@state() public _error?: string;
|
||||
|
||||
private _redirect?: Redirect;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
const path = this.route.path.substring(1);
|
||||
const hasSupervisor = isComponentLoaded(this.hass, "hassio");
|
||||
|
||||
this._redirect = getRedirect(path, hasSupervisor);
|
||||
if (path === "backup" && isComponentLoaded(this.hass, "hassio")) {
|
||||
navigate("/hassio/backups", {
|
||||
replace: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.startsWith("supervisor") && this._redirect === undefined) {
|
||||
if (!hasSupervisor) {
|
||||
if (path.startsWith("supervisor")) {
|
||||
if (!isComponentLoaded(this.hass, "hassio")) {
|
||||
this._error = "no_supervisor";
|
||||
return;
|
||||
}
|
||||
@@ -218,14 +206,16 @@ class HaPanelMy extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._redirect) {
|
||||
const redirect = REDIRECTS[path];
|
||||
|
||||
if (!redirect) {
|
||||
this._error = "not_supported";
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
this._redirect.component &&
|
||||
!isComponentLoaded(this.hass, this._redirect.component)
|
||||
redirect.component &&
|
||||
!isComponentLoaded(this.hass, redirect.component)
|
||||
) {
|
||||
this._error = "no_component";
|
||||
return;
|
||||
@@ -233,7 +223,7 @@ class HaPanelMy extends LitElement {
|
||||
|
||||
let url: string;
|
||||
try {
|
||||
url = this._createRedirectUrl();
|
||||
url = this._createRedirectUrl(redirect);
|
||||
} catch (err: any) {
|
||||
this._error = "url_error";
|
||||
return;
|
||||
@@ -264,16 +254,10 @@ class HaPanelMy extends LitElement {
|
||||
this.hass.localize(
|
||||
"ui.panel.my.component_not_loaded",
|
||||
"integration",
|
||||
html`<a
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
href=${documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${this._redirect!.component!}`
|
||||
)}
|
||||
>
|
||||
${domainToName(this.hass.localize, this._redirect!.component!)}
|
||||
</a>`
|
||||
domainToName(
|
||||
this.hass.localize,
|
||||
REDIRECTS[this.route.path.substr(1)].component!
|
||||
)
|
||||
) || "This redirect is not supported.";
|
||||
break;
|
||||
case "no_supervisor":
|
||||
@@ -296,18 +280,18 @@ class HaPanelMy extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
|
||||
private _createRedirectUrl(): string {
|
||||
const params = this._createRedirectParams();
|
||||
return `${this._redirect!.redirect}${params}`;
|
||||
private _createRedirectUrl(redirect: Redirect): string {
|
||||
const params = this._createRedirectParams(redirect);
|
||||
return `${redirect.redirect}${params}`;
|
||||
}
|
||||
|
||||
private _createRedirectParams(): string {
|
||||
private _createRedirectParams(redirect: Redirect): string {
|
||||
const params = extractSearchParamsObject();
|
||||
if (!this._redirect!.params && !Object.keys(params).length) {
|
||||
if (!redirect.params && !Object.keys(params).length) {
|
||||
return "";
|
||||
}
|
||||
const resultParams = {};
|
||||
Object.entries(this._redirect!.params || {}).forEach(([key, type]) => {
|
||||
Object.entries(redirect.params || {}).forEach(([key, type]) => {
|
||||
if (!params[key] || !this._checkParamType(type, params[key])) {
|
||||
throw Error();
|
||||
}
|
||||
|
||||
@@ -312,7 +312,6 @@ export const haStyleDialog = css`
|
||||
--mdc-dialog-heading-ink-color: var(--primary-text-color);
|
||||
--mdc-dialog-content-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
--ha-dialog-border-radius: 16px;
|
||||
}
|
||||
|
||||
ha-dialog .form {
|
||||
|
||||
@@ -664,10 +664,8 @@
|
||||
},
|
||||
"types": {
|
||||
"reload": "Reload",
|
||||
"navigation": "Navigation",
|
||||
"server_control": "Server",
|
||||
"entity": "Entity",
|
||||
"suggestion": "Suggestions"
|
||||
"navigation": "Navigate",
|
||||
"server_control": "Server"
|
||||
},
|
||||
"navigation": {
|
||||
"logs": "[%key:ui::panel::config::logs::caption%]",
|
||||
@@ -691,7 +689,7 @@
|
||||
"server_control": "[%key:ui::panel::config::server_control::caption%]"
|
||||
}
|
||||
},
|
||||
"filter_placeholder": "Search for entities, pages or commands",
|
||||
"filter_placeholder": "Entity Filter",
|
||||
"title": "Quick Search",
|
||||
"key_c_hint": "Press 'c' on any page to open this search bar",
|
||||
"nothing_found": "Nothing found!"
|
||||
@@ -733,7 +731,7 @@
|
||||
"setting": "Setting"
|
||||
},
|
||||
"update": {
|
||||
"installed_version": "Installed version",
|
||||
"current_version": "Current version",
|
||||
"latest_version": "Latest version",
|
||||
"release_announcement": "Read release announcement",
|
||||
"skip": "Skip",
|
||||
@@ -1452,7 +1450,7 @@
|
||||
"internal_url_label": "Local Network",
|
||||
"external_url_label": "Internet",
|
||||
"external_use_ha_cloud": "Use Home Assistant Cloud",
|
||||
"external_get_ha_cloud": "Access from anywhere, add Google & Alexa easily",
|
||||
"external_get_ha_cloud": "Access from anywhere using Home Assistant Cloud",
|
||||
"ha_cloud_remote_not_enabled": "Your Home Assistant Cloud remote connection is currently not enabled.",
|
||||
"enable_remote": "[%key:ui::common::enable%]",
|
||||
"internal_url_automatic": "Automatic",
|
||||
@@ -2713,14 +2711,12 @@
|
||||
"open_configuration_url": "Visit device"
|
||||
},
|
||||
"config_flow": {
|
||||
"success": "Success",
|
||||
"aborted": "Aborted",
|
||||
"close": "Close",
|
||||
"dismiss": "Dismiss dialog",
|
||||
"finish": "Finish",
|
||||
"submit": "Submit",
|
||||
"next": "Next",
|
||||
"found_following_devices": "We found the following devices",
|
||||
"no_config_flow": "This integration does not support configuration via the UI. If you followed this link from the Home Assistant website, make sure you run the latest version of Home Assistant.",
|
||||
"not_all_required_fields": "Not all required fields are filled in.",
|
||||
"error_saving_area": "Error saving area: {error}",
|
||||
@@ -4175,8 +4171,7 @@
|
||||
"container": "Container",
|
||||
"disabled": "Disabled",
|
||||
"header": "Network",
|
||||
"show_disabled": "Show disabled ports",
|
||||
"introduction": "Change the ports on your host that are exposed by the add-on"
|
||||
"host": "Host"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
@@ -4207,7 +4202,7 @@
|
||||
},
|
||||
"rating": {
|
||||
"title": "Add-on Security Rating",
|
||||
"description": "Home Assistant provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 8. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 8 is the highest score (considered the most secure and lowest risk)."
|
||||
"description": "Home Assistant provides a security rating to each of the add-ons, which indicates the risks involved when using this add-on. The more access an add-on requires on your system, the lower the score, thus raising the possible security risks.\n\nA score is on a scale from 1 to 6. Where 1 is the lowest score (considered the most insecure and highest risk) and a score of 6 is the highest score (considered the most secure and lowest risk)."
|
||||
},
|
||||
"host_network": {
|
||||
"title": "Host Network",
|
||||
|
||||
Reference in New Issue
Block a user