mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-27 18:19:28 +00:00
Compare commits
3 Commits
limit-quic
...
20220401.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8e962fdecb | ||
![]() |
1f65193a97 | ||
![]() |
24484d0e74 |
@@ -3,10 +3,10 @@ const webpack = require("webpack");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const TerserPlugin = require("terser-webpack-plugin");
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
||||||
const log = require("fancy-log");
|
|
||||||
const WebpackBar = require("webpackbar");
|
|
||||||
const paths = require("./paths.js");
|
const paths = require("./paths.js");
|
||||||
const bundle = require("./bundle.js");
|
const bundle = require("./bundle.js");
|
||||||
|
const log = require("fancy-log");
|
||||||
|
const WebpackBar = require("webpackbar");
|
||||||
|
|
||||||
class LogStartCompilePlugin {
|
class LogStartCompilePlugin {
|
||||||
ignoredFirst = false;
|
ignoredFirst = false;
|
||||||
@@ -138,8 +138,6 @@ const createWebpackConfig = ({
|
|||||||
"lit/directives/cache$": "lit/directives/cache.js",
|
"lit/directives/cache$": "lit/directives/cache.js",
|
||||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||||
"lit/polyfill-support$": "lit/polyfill-support.js",
|
"lit/polyfill-support$": "lit/polyfill-support.js",
|
||||||
"@lit-labs/virtualizer/layouts/grid":
|
|
||||||
"@lit-labs/virtualizer/layouts/grid.js",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
|
@@ -8,7 +8,7 @@ import { mockEntityRegistry } from "../../../../demo/src/stubs/entity_registry";
|
|||||||
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
import { mockDeviceRegistry } from "../../../../demo/src/stubs/device_registry";
|
||||||
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
import { mockAreaRegistry } from "../../../../demo/src/stubs/area_registry";
|
||||||
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
import { mockHassioSupervisor } from "../../../../demo/src/stubs/hassio_supervisor";
|
||||||
import type { ConditionWithShorthand } from "../../../../src/data/automation";
|
import type { Condition } from "../../../../src/data/automation";
|
||||||
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
import "../../../../src/panels/config/automation/condition/ha-automation-condition";
|
||||||
import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
|
import { HaDeviceCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
|
||||||
import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
|
import { HaLogicalCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
|
||||||
@@ -20,7 +20,7 @@ import { HaTimeCondition } from "../../../../src/panels/config/automation/condit
|
|||||||
import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
|
import { HaTriggerCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
|
||||||
import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
|
import { HaZoneCondition } from "../../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
|
||||||
|
|
||||||
const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
|
||||||
{
|
{
|
||||||
name: "State",
|
name: "State",
|
||||||
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
|
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
|
||||||
@@ -69,14 +69,6 @@ const SCHEMAS: { name: string; conditions: ConditionWithShorthand[] }[] = [
|
|||||||
name: "Trigger",
|
name: "Trigger",
|
||||||
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
|
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "Shorthand",
|
|
||||||
conditions: [
|
|
||||||
{ and: HaLogicalCondition.defaultConfig.conditions },
|
|
||||||
{ or: HaLogicalCondition.defaultConfig.conditions },
|
|
||||||
{ not: HaLogicalCondition.defaultConfig.conditions },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-automation-editor-condition")
|
@customElement("demo-automation-editor-condition")
|
||||||
|
@@ -159,19 +159,13 @@ export class DemoHaAlert extends LitElement {
|
|||||||
|
|
||||||
firstUpdated(changedProps) {
|
firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
applyThemesOnElement(
|
applyThemesOnElement(this.shadowRoot!.querySelector(".dark"), {
|
||||||
this.shadowRoot!.querySelector(".dark"),
|
|
||||||
{
|
|
||||||
default_theme: "default",
|
default_theme: "default",
|
||||||
default_dark_theme: "default",
|
default_dark_theme: "default",
|
||||||
themes: {},
|
themes: {},
|
||||||
darkMode: true,
|
darkMode: true,
|
||||||
theme: "default",
|
theme: "default",
|
||||||
},
|
});
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles() {
|
static get styles() {
|
||||||
|
@@ -170,7 +170,6 @@ const SCHEMAS: {
|
|||||||
select: { options: ["Option 1", "Option 2"], mode: "list" },
|
select: { options: ["Option 1", "Option 2"], mode: "list" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
template: { name: "Template", selector: { template: {} } },
|
|
||||||
select: {
|
select: {
|
||||||
name: "Select",
|
name: "Select",
|
||||||
selector: {
|
selector: {
|
||||||
@@ -262,8 +261,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
|
|
||||||
@state() private _required = false;
|
@state() private _required = false;
|
||||||
|
|
||||||
@state() private _helper = false;
|
|
||||||
|
|
||||||
@state() private _label = true;
|
@state() private _label = true;
|
||||||
|
|
||||||
private data = SCHEMAS.map(() => ({}));
|
private data = SCHEMAS.map(() => ({}));
|
||||||
@@ -421,13 +418,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
@change=${this._handleOptionChange}
|
@change=${this._handleOptionChange}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
<ha-formfield label="Helper text">
|
|
||||||
<ha-switch
|
|
||||||
.name=${"helper"}
|
|
||||||
.checked=${this._helper}
|
|
||||||
@change=${this._handleOptionChange}
|
|
||||||
></ha-switch>
|
|
||||||
</ha-formfield>
|
|
||||||
</div>
|
</div>
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
const data = this.data[idx];
|
const data = this.data[idx];
|
||||||
@@ -456,7 +446,6 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
.disabled=${this._disabled}
|
.disabled=${this._disabled}
|
||||||
.required=${this._required}
|
.required=${this._required}
|
||||||
@value-changed=${valueChanged}
|
@value-changed=${valueChanged}
|
||||||
.helper=${this._helper ? "Helper text" : undefined}
|
|
||||||
></ha-selector>
|
></ha-selector>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
`
|
`
|
||||||
@@ -477,8 +466,7 @@ class DemoHaSelector extends LitElement implements ProvideHassElement {
|
|||||||
width: 60;
|
width: 60;
|
||||||
}
|
}
|
||||||
.options {
|
.options {
|
||||||
max-width: 800px;
|
padding: 16px 48px;
|
||||||
margin: 16px auto;
|
|
||||||
}
|
}
|
||||||
.options ha-formfield {
|
.options ha-formfield {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
title: Tips
|
|
||||||
---
|
|
@@ -1,73 +0,0 @@
|
|||||||
import { html, css, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement } from "lit/decorators";
|
|
||||||
import "../../../../src/components/ha-tip";
|
|
||||||
import "../../../../src/components/ha-card";
|
|
||||||
import { applyThemesOnElement } from "../../../../src/common/dom/apply_themes_on_element";
|
|
||||||
|
|
||||||
const tips: (string | TemplateResult)[] = [
|
|
||||||
"Test tip",
|
|
||||||
"Bigger test tip, with some random text just to fill up as much space as possible without it looking like I'm really trying to to that",
|
|
||||||
html`<i>Tip</i> <b>with</b> <sub>HTML</sub>`,
|
|
||||||
];
|
|
||||||
|
|
||||||
@customElement("demo-components-ha-tip")
|
|
||||||
export class DemoHaTip extends LitElement {
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
return html` ${["light", "dark"].map(
|
|
||||||
(mode) => html`
|
|
||||||
<div class=${mode}>
|
|
||||||
<ha-card header="ha-tip ${mode} demo">
|
|
||||||
<div class="card-content">
|
|
||||||
${tips.map((tip) => html`<ha-tip>${tip}</ha-tip>`)}
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
applyThemesOnElement(
|
|
||||||
this.shadowRoot!.querySelector(".dark"),
|
|
||||||
{
|
|
||||||
default_theme: "default",
|
|
||||||
default_dark_theme: "default",
|
|
||||||
themes: {},
|
|
||||||
darkMode: true,
|
|
||||||
theme: "default",
|
|
||||||
},
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles() {
|
|
||||||
return css`
|
|
||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.dark,
|
|
||||||
.light {
|
|
||||||
display: block;
|
|
||||||
background-color: var(--primary-background-color);
|
|
||||||
padding: 0 50px;
|
|
||||||
}
|
|
||||||
ha-tip {
|
|
||||||
margin-bottom: 14px;
|
|
||||||
}
|
|
||||||
ha-card {
|
|
||||||
margin: 24px auto;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"demo-components-ha-tip": DemoHaTip;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -133,12 +133,6 @@ const ENTITIES = [
|
|||||||
friendly_name: "Update with auto update",
|
friendly_name: "Update with auto update",
|
||||||
auto_update: true,
|
auto_update: true,
|
||||||
}),
|
}),
|
||||||
getEntity("update", "update20", "on", {
|
|
||||||
...base_attributes,
|
|
||||||
in_progress: true,
|
|
||||||
title: undefined,
|
|
||||||
friendly_name: "Installing without title",
|
|
||||||
}),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-more-info-update")
|
@customElement("demo-more-info-update")
|
||||||
|
@@ -39,14 +39,7 @@ import type { HomeAssistant } from "../../../../src/types";
|
|||||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
import { hassioStyle } from "../../resources/hassio-style";
|
||||||
|
|
||||||
const SUPPORTED_UI_TYPES = [
|
const SUPPORTED_UI_TYPES = ["string", "select", "boolean", "integer", "float"];
|
||||||
"string",
|
|
||||||
"select",
|
|
||||||
"boolean",
|
|
||||||
"integer",
|
|
||||||
"float",
|
|
||||||
"schema",
|
|
||||||
];
|
|
||||||
|
|
||||||
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||||
new Type("!secret", {
|
new Type("!secret", {
|
||||||
@@ -55,8 +48,6 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const MASKED_FIELDS = ["password", "secret", "token"];
|
|
||||||
|
|
||||||
@customElement("hassio-addon-config")
|
@customElement("hassio-addon-config")
|
||||||
class HassioAddonConfig extends LitElement {
|
class HassioAddonConfig extends LitElement {
|
||||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||||
@@ -84,63 +75,16 @@ class HassioAddonConfig extends LitElement {
|
|||||||
public computeLabel = (entry: HaFormSchema): string =>
|
public computeLabel = (entry: HaFormSchema): string =>
|
||||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
||||||
?.name ||
|
?.name ||
|
||||||
this.addon.translations.en?.configuration?.[entry.name]?.name ||
|
this.addon.translations.en?.configuration?.[entry.name].name ||
|
||||||
entry.name;
|
entry.name;
|
||||||
|
|
||||||
public computeHelper = (entry: HaFormSchema): string =>
|
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
|
||||||
this.addon.translations[this.hass.language]?.configuration?.[entry.name]
|
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
|
||||||
?.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) =>
|
schema.map((entry) =>
|
||||||
entry.type === "select"
|
entry.type === "select"
|
||||||
? {
|
? {
|
||||||
name: entry.name,
|
...entry,
|
||||||
required: entry.required,
|
options: entry.options.map((option) => [option, option]),
|
||||||
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
|
: entry
|
||||||
)
|
)
|
||||||
@@ -196,8 +140,7 @@ class HassioAddonConfig extends LitElement {
|
|||||||
.data=${this._options!}
|
.data=${this._options!}
|
||||||
@value-changed=${this._configChanged}
|
@value-changed=${this._configChanged}
|
||||||
.computeLabel=${this.computeLabel}
|
.computeLabel=${this.computeLabel}
|
||||||
.computeHelper=${this.computeHelper}
|
.schema=${this._schema(
|
||||||
.schema=${this._convertSchema(
|
|
||||||
this._showOptional
|
this._showOptional
|
||||||
? this.addon.schema!
|
? this.addon.schema!
|
||||||
: this._filteredShchema(
|
: this._filteredShchema(
|
||||||
@@ -254,9 +197,8 @@ class HassioAddonConfig extends LitElement {
|
|||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._canShowSchema = !this.addon.schema!.find(
|
this._canShowSchema = !this.addon.schema!.find(
|
||||||
(entry) =>
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
!SUPPORTED_UI_TYPES.includes(entry.type)
|
(entry) => !SUPPORTED_UI_TYPES.includes(entry.type) || entry.multiple
|
||||||
);
|
);
|
||||||
this._yamlMode = !this._canShowSchema;
|
this._yamlMode = !this._canShowSchema;
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -7,13 +8,10 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
|
||||||
import {
|
import {
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
HassioAddonSetOptionParams,
|
HassioAddonSetOptionParams,
|
||||||
@@ -26,6 +24,16 @@ import { HomeAssistant } from "../../../../src/types";
|
|||||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
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")
|
@customElement("hassio-addon-network")
|
||||||
class HassioAddonNetwork extends LitElement {
|
class HassioAddonNetwork extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -34,13 +42,9 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||||
|
|
||||||
@state() private _showOptional = false;
|
|
||||||
|
|
||||||
@state() private _configHasChanged = false;
|
|
||||||
|
|
||||||
@state() private _error?: string;
|
@state() private _error?: string;
|
||||||
|
|
||||||
@state() private _config?: Record<string, any>;
|
@state() private _config?: NetworkItem[];
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@@ -52,10 +56,6 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasHiddenOptions = Object.keys(this._config).find(
|
|
||||||
(entry) => this._config![entry] === null
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
.header=${this.supervisor.localize(
|
.header=${this.supervisor.localize(
|
||||||
@@ -63,49 +63,52 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>
|
|
||||||
${this.supervisor.localize(
|
|
||||||
"addon.configuration.network.introduction"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
${this._error
|
${this._error
|
||||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<ha-form
|
<table>
|
||||||
.data=${this._config}
|
<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}
|
@value-changed=${this._configChanged}
|
||||||
.computeLabel=${this._computeLabel}
|
placeholder=${this.supervisor.localize(
|
||||||
.computeHelper=${this._computeHelper}
|
"addon.configuration.network.disabled"
|
||||||
.schema=${this._createSchema(
|
|
||||||
this._config,
|
|
||||||
this._showOptional,
|
|
||||||
this.hass.userData?.showAdvanced || false
|
|
||||||
)}
|
)}
|
||||||
></ha-form>
|
.value=${item.host ? String(item.host) : ""}
|
||||||
|
.container=${item.container}
|
||||||
|
no-label-float
|
||||||
|
></paper-input>
|
||||||
|
</td>
|
||||||
|
<td>${this._computeDescription(item)}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
${hasHiddenOptions
|
|
||||||
? html`<ha-formfield
|
|
||||||
class="show-optional"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.configuration.network.show_disabled"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-switch
|
|
||||||
@change=${this._toggleOptional}
|
|
||||||
.checked=${this._showOptional}
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
</ha-formfield>`
|
|
||||||
: ""}
|
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||||
${this.supervisor.localize("common.reset_defaults")}
|
${this.supervisor.localize("common.reset_defaults")}
|
||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
<ha-progress-button
|
<ha-progress-button @click=${this._saveTapped}>
|
||||||
@click=${this._saveTapped}
|
|
||||||
.disabled=${!this._configHasChanged}
|
|
||||||
>
|
|
||||||
${this.supervisor.localize("common.save")}
|
${this.supervisor.localize("common.save")}
|
||||||
</ha-progress-button>
|
</ha-progress-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -120,60 +123,50 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createSchema = memoizeOne(
|
private _computeDescription = (item: NetworkItem): string =>
|
||||||
(
|
this.addon.translations[this.hass.language]?.network?.[item.container]
|
||||||
config: Record<string, number>,
|
?.description ||
|
||||||
showOptional: boolean,
|
this.addon.translations.en?.network?.[item.container]?.description ||
|
||||||
advanced: boolean
|
item.description;
|
||||||
): 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 _setNetworkConfig(): void {
|
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> {
|
private async _configChanged(ev: Event): Promise<void> {
|
||||||
this._configHasChanged = true;
|
const target = ev.target as NetworkItemInput;
|
||||||
this._config! = ev.detail.value;
|
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> {
|
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||||
const button = ev.currentTarget as any;
|
const button = ev.currentTarget as any;
|
||||||
|
button.progress = true;
|
||||||
|
|
||||||
const data: HassioAddonSetOptionParams = {
|
const data: HassioAddonSetOptionParams = {
|
||||||
network: null,
|
network: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||||
this._configHasChanged = false;
|
|
||||||
const eventdata = {
|
const eventdata = {
|
||||||
success: true,
|
success: true,
|
||||||
response: undefined,
|
response: undefined,
|
||||||
path: "option",
|
path: "option",
|
||||||
};
|
};
|
||||||
button.actionSuccess();
|
|
||||||
fireEvent(this, "hass-api-called", eventdata);
|
fireEvent(this, "hass-api-called", eventdata);
|
||||||
if (this.addon?.state === "started") {
|
if (this.addon?.state === "started") {
|
||||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||||
@@ -184,21 +177,19 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
"error",
|
"error",
|
||||||
extractApiErrorMessage(err)
|
extractApiErrorMessage(err)
|
||||||
);
|
);
|
||||||
button.actionError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggleOptional() {
|
button.progress = false;
|
||||||
this._showOptional = !this._showOptional;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||||
const button = ev.currentTarget as any;
|
const button = ev.currentTarget as any;
|
||||||
|
button.progress = true;
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
const networkconfiguration = {};
|
const networkconfiguration = {};
|
||||||
Object.entries(this._config!).forEach(([key, value]) => {
|
this._config!.forEach((item) => {
|
||||||
networkconfiguration[key] = value ?? null;
|
networkconfiguration[item.container] = parseInt(String(item.host), 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
const data: HassioAddonSetOptionParams = {
|
const data: HassioAddonSetOptionParams = {
|
||||||
@@ -207,13 +198,11 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||||
this._configHasChanged = false;
|
|
||||||
const eventdata = {
|
const eventdata = {
|
||||||
success: true,
|
success: true,
|
||||||
response: undefined,
|
response: undefined,
|
||||||
path: "option",
|
path: "option",
|
||||||
};
|
};
|
||||||
button.actionSuccess();
|
|
||||||
fireEvent(this, "hass-api-called", eventdata);
|
fireEvent(this, "hass-api-called", eventdata);
|
||||||
if (this.addon?.state === "started") {
|
if (this.addon?.state === "started") {
|
||||||
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
|
||||||
@@ -224,8 +213,8 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
"error",
|
"error",
|
||||||
extractApiErrorMessage(err)
|
extractApiErrorMessage(err)
|
||||||
);
|
);
|
||||||
button.actionError();
|
|
||||||
}
|
}
|
||||||
|
button.progress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@@ -243,9 +232,6 @@ class HassioAddonNetwork extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.show-optional {
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@ import "@material/mwc-button";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-ansi-to-html";
|
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import {
|
import {
|
||||||
fetchHassioAddonLogs,
|
fetchHassioAddonLogs,
|
||||||
@@ -12,6 +11,7 @@ import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
|||||||
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
|
||||||
import { haStyle } from "../../../../src/resources/styles";
|
import { haStyle } from "../../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../../src/types";
|
import { HomeAssistant } from "../../../../src/types";
|
||||||
|
import "../../components/hassio-ansi-to-html";
|
||||||
import { hassioStyle } from "../../resources/hassio-style";
|
import { hassioStyle } from "../../resources/hassio-style";
|
||||||
|
|
||||||
@customElement("hassio-addon-logs")
|
@customElement("hassio-addon-logs")
|
||||||
@@ -40,9 +40,9 @@ class HassioAddonLogs extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
${this._content
|
${this._content
|
||||||
? html`<ha-ansi-to-html
|
? html`<hassio-ansi-to-html
|
||||||
.content=${this._content}
|
.content=${this._content}
|
||||||
></ha-ansi-to-html>`
|
></hassio-ansi-to-html>`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { ActionDetail } from "@material/mwc-list";
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
import { mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -166,15 +166,7 @@ export class HassioBackups extends LitElement {
|
|||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage-data-table
|
<hass-tabs-subpage-data-table
|
||||||
.tabs=${atLeastVersion(this.hass.config.version, 2022, 5)
|
.tabs=${supervisorTabs(this.hass)}
|
||||||
? [
|
|
||||||
{
|
|
||||||
translationKey: "panel.backups",
|
|
||||||
path: `/hassio/backups`,
|
|
||||||
iconPath: mdiBackupRestore,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: supervisorTabs(this.hass)}
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.localizeFunc=${this.supervisor.localize}
|
.localizeFunc=${this.supervisor.localize}
|
||||||
.searchLabel=${this.supervisor.localize("search")}
|
.searchLabel=${this.supervisor.localize("search")}
|
||||||
@@ -190,9 +182,7 @@ export class HassioBackups extends LitElement {
|
|||||||
selectable
|
selectable
|
||||||
hasFab
|
hasFab
|
||||||
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
.mainPage=${!atLeastVersion(this.hass.config.version, 2021, 12)}
|
||||||
back-path=${atLeastVersion(this.hass.config.version, 2022, 5)
|
back-path="/config"
|
||||||
? "/config/system"
|
|
||||||
: "/config"}
|
|
||||||
supervisor
|
supervisor
|
||||||
>
|
>
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
|
@@ -10,8 +10,8 @@ interface State {
|
|||||||
backgroundColor: null | string;
|
backgroundColor: null | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement("ha-ansi-to-html")
|
@customElement("hassio-ansi-to-html")
|
||||||
class HaAnsiToHtml extends LitElement {
|
class HassioAnsiToHtml extends LitElement {
|
||||||
@property() public content!: string;
|
@property() public content!: string;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
@@ -241,6 +241,6 @@ class HaAnsiToHtml extends LitElement {
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-ansi-to-html": HaAnsiToHtml;
|
"hassio-ansi-to-html": HassioAnsiToHtml;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -180,7 +180,7 @@ export class SupervisorBackupContent extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
.checked=${this.homeAssistant}
|
.checked=${this.homeAssistant}
|
||||||
@change=${this.toggleHomeAssistant}
|
@click=${this.toggleHomeAssistant}
|
||||||
>
|
>
|
||||||
</ha-checkbox>
|
</ha-checkbox>
|
||||||
</ha-formfield>
|
</ha-formfield>
|
||||||
|
@@ -10,7 +10,6 @@ import { HomeAssistant, Route } from "../../../src/types";
|
|||||||
import { supervisorTabs } from "../hassio-tabs";
|
import { supervisorTabs } from "../hassio-tabs";
|
||||||
import "./hassio-addons";
|
import "./hassio-addons";
|
||||||
import "./hassio-update";
|
import "./hassio-update";
|
||||||
import "../../../src/layouts/hass-subpage";
|
|
||||||
|
|
||||||
@customElement("hassio-dashboard")
|
@customElement("hassio-dashboard")
|
||||||
class HassioDashboard extends LitElement {
|
class HassioDashboard extends LitElement {
|
||||||
@@ -23,31 +22,6 @@ class HassioDashboard extends LitElement {
|
|||||||
@property({ attribute: false }) public route!: Route;
|
@property({ attribute: false }) public route!: Route;
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (atLeastVersion(this.hass.config.version, 2022, 5)) {
|
|
||||||
return html`<hass-subpage
|
|
||||||
.hass=${this.hass}
|
|
||||||
.narrow=${this.narrow}
|
|
||||||
.route=${this.route}
|
|
||||||
.header=${this.supervisor.localize("panel.addons")}
|
|
||||||
>
|
|
||||||
<hassio-addons
|
|
||||||
.hass=${this.hass}
|
|
||||||
.supervisor=${this.supervisor}
|
|
||||||
></hassio-addons>
|
|
||||||
<a href="/hassio/store">
|
|
||||||
<ha-fab
|
|
||||||
.label=${this.supervisor.localize("panel.store")}
|
|
||||||
extended
|
|
||||||
class="non-tabs"
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="icon"
|
|
||||||
.path=${mdiStorePlus}
|
|
||||||
></ha-svg-icon> </ha-fab
|
|
||||||
></a>
|
|
||||||
</hass-subpage>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -100,12 +74,6 @@ class HassioDashboard extends LitElement {
|
|||||||
.content {
|
.content {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
ha-fab.non-tabs {
|
|
||||||
position: fixed;
|
|
||||||
right: calc(16px + env(safe-area-inset-right));
|
|
||||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,8 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { atLeastVersion } from "../../src/common/config/version";
|
import { atLeastVersion } from "../../src/common/config/version";
|
||||||
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
|
||||||
import { fireEvent } from "../../src/common/dom/fire_event";
|
import { fireEvent } from "../../src/common/dom/fire_event";
|
||||||
import { mainWindow } from "../../src/common/dom/get_main_window";
|
|
||||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||||
|
import { mainWindow } from "../../src/common/dom/get_main_window";
|
||||||
import { navigate } from "../../src/common/navigate";
|
import { navigate } from "../../src/common/navigate";
|
||||||
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
|
||||||
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../src/data/supervisor/supervisor";
|
||||||
@@ -73,14 +73,6 @@ export class HassioMain extends SupervisorBaseElement {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Forward keydown events to the main window for quickbar access
|
|
||||||
document.body.addEventListener("keydown", (ev) => {
|
|
||||||
// @ts-ignore
|
|
||||||
fireEvent(mainWindow, "hass-quick-bar-trigger", ev, {
|
|
||||||
bubbles: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
makeDialogManager(this, this.shadowRoot!);
|
makeDialogManager(this, this.shadowRoot!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,7 +15,7 @@ import {
|
|||||||
} from "../../src/panels/my/ha-panel-my";
|
} from "../../src/panels/my/ha-panel-my";
|
||||||
import { HomeAssistant, Route } from "../../src/types";
|
import { HomeAssistant, Route } from "../../src/types";
|
||||||
|
|
||||||
export const REDIRECTS: Redirects = {
|
const REDIRECTS: Redirects = {
|
||||||
supervisor: {
|
supervisor: {
|
||||||
redirect: "/hassio/dashboard",
|
redirect: "/hassio/dashboard",
|
||||||
},
|
},
|
||||||
|
@@ -8,10 +8,7 @@ import { atLeastVersion } from "../../src/common/config/version";
|
|||||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||||
import { HomeAssistant } from "../../src/types";
|
import { HomeAssistant } from "../../src/types";
|
||||||
|
|
||||||
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] =>
|
export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] => [
|
||||||
atLeastVersion(hass.config.version, 2022, 5)
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
{
|
||||||
translationKey: atLeastVersion(hass.config.version, 2021, 12)
|
translationKey: atLeastVersion(hass.config.version, 2021, 12)
|
||||||
? "panel.addons"
|
? "panel.addons"
|
||||||
@@ -31,4 +28,4 @@ export const supervisorTabs = (hass: HomeAssistant): PageNavigation[] =>
|
|||||||
path: `/hassio/system`,
|
path: `/hassio/system`,
|
||||||
iconPath: mdiCogs,
|
iconPath: mdiCogs,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -23,10 +23,6 @@ import {
|
|||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
import {
|
|
||||||
UNHEALTHY_REASON_URL,
|
|
||||||
UNSUPPORTED_REASON_URL,
|
|
||||||
} from "../../../src/panels/config/system-health/ha-config-system-health";
|
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
import { bytesToString } from "../../../src/util/bytes-to-string";
|
import { bytesToString } from "../../../src/util/bytes-to-string";
|
||||||
@@ -34,6 +30,11 @@ import { documentationUrl } from "../../../src/util/documentation-url";
|
|||||||
import "../components/supervisor-metric";
|
import "../components/supervisor-metric";
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
|
const UNSUPPORTED_REASON_URL = {};
|
||||||
|
const UNHEALTHY_REASON_URL = {
|
||||||
|
privileged: "/more-info/unsupported/privileged",
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hassio-supervisor-info")
|
@customElement("hassio-supervisor-info")
|
||||||
class HassioSupervisorInfo extends LitElement {
|
class HassioSupervisorInfo extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import "../../../src/components/ha-ansi-to-html";
|
|
||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -12,6 +11,7 @@ import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
|||||||
import "../../../src/layouts/hass-loading-screen";
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
import "../components/hassio-ansi-to-html";
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
interface LogProvider {
|
interface LogProvider {
|
||||||
@@ -89,8 +89,8 @@ class HassioSupervisorLog extends LitElement {
|
|||||||
|
|
||||||
<div class="card-content" id="content">
|
<div class="card-content" id="content">
|
||||||
${this._content
|
${this._content
|
||||||
? html`<ha-ansi-to-html .content=${this._content}>
|
? html`<hassio-ansi-to-html .content=${this._content}>
|
||||||
</ha-ansi-to-html>`
|
</hassio-ansi-to-html>`
|
||||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
|
@@ -106,10 +106,9 @@
|
|||||||
"deep-clone-simple": "^1.1.1",
|
"deep-clone-simple": "^1.1.1",
|
||||||
"deep-freeze": "^0.0.1",
|
"deep-freeze": "^0.0.1",
|
||||||
"fuse.js": "^6.0.0",
|
"fuse.js": "^6.0.0",
|
||||||
"fuzzysort": "^1.2.1",
|
|
||||||
"google-timezones-json": "^1.0.2",
|
"google-timezones-json": "^1.0.2",
|
||||||
"hls.js": "^1.1.5",
|
"hls.js": "^1.1.5",
|
||||||
"home-assistant-js-websocket": "^7.0.3",
|
"home-assistant-js-websocket": "^7.0.1",
|
||||||
"idb-keyval": "^5.1.3",
|
"idb-keyval": "^5.1.3",
|
||||||
"intl-messageformat": "^9.9.1",
|
"intl-messageformat": "^9.9.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
@@ -15,7 +15,7 @@ if [ -z $(which hass) ]; then
|
|||||||
echo "Installing Home Asstant core from dev."
|
echo "Installing Home Asstant core from dev."
|
||||||
python3 -m pip install --upgrade \
|
python3 -m pip install --upgrade \
|
||||||
colorlog \
|
colorlog \
|
||||||
git+https://github.com/home-assistant/home-assistant.git@dev
|
git+git://github.com/home-assistant/home-assistant.git@dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "${WD}/config" ]; then
|
if [ ! -d "${WD}/config" ]; then
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
[metadata]
|
[metadata]
|
||||||
name = home-assistant-frontend
|
name = home-assistant-frontend
|
||||||
version = 20220427.0
|
version = 20220401.0
|
||||||
author = The Home Assistant Authors
|
author = The Home Assistant Authors
|
||||||
author_email = hello@home-assistant.io
|
author_email = hello@home-assistant.io
|
||||||
license = Apache-2.0
|
license = Apache-2.0
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
import secondsToDuration from "./seconds_to_duration";
|
|
||||||
|
|
||||||
const DAY_IN_SECONDS = 86400;
|
|
||||||
const HOUR_IN_SECONDS = 3600;
|
|
||||||
const MINUTE_IN_SECONDS = 60;
|
|
||||||
|
|
||||||
export const UNIT_TO_SECOND_CONVERT = {
|
|
||||||
s: 1,
|
|
||||||
min: MINUTE_IN_SECONDS,
|
|
||||||
h: HOUR_IN_SECONDS,
|
|
||||||
d: DAY_IN_SECONDS,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatDuration = (duration: string, units: string): string =>
|
|
||||||
secondsToDuration(parseFloat(duration) * UNIT_TO_SECOND_CONVERT[units]) ||
|
|
||||||
"0";
|
|
@@ -12,7 +12,7 @@ export const isNavigationClick = (e: MouseEvent) => {
|
|||||||
|
|
||||||
const anchor = e
|
const anchor = e
|
||||||
.composedPath()
|
.composedPath()
|
||||||
.find((n) => (n as HTMLElement).tagName === "A") as
|
.filter((n) => (n as HTMLElement).tagName === "A")[0] as
|
||||||
| HTMLAnchorElement
|
| HTMLAnchorElement
|
||||||
| undefined;
|
| undefined;
|
||||||
if (
|
if (
|
||||||
|
@@ -29,11 +29,8 @@ import {
|
|||||||
mdiPowerPlug,
|
mdiPowerPlug,
|
||||||
mdiPowerPlugOff,
|
mdiPowerPlugOff,
|
||||||
mdiRadioboxBlank,
|
mdiRadioboxBlank,
|
||||||
|
mdiSmoke,
|
||||||
mdiSnowflake,
|
mdiSnowflake,
|
||||||
mdiSmokeDetector,
|
|
||||||
mdiSmokeDetectorAlert,
|
|
||||||
mdiSmokeDetectorVariant,
|
|
||||||
mdiSmokeDetectorVariantAlert,
|
|
||||||
mdiSquare,
|
mdiSquare,
|
||||||
mdiSquareOutline,
|
mdiSquareOutline,
|
||||||
mdiStop,
|
mdiStop,
|
||||||
@@ -55,8 +52,6 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
|||||||
return is_off ? mdiBattery : mdiBatteryOutline;
|
return is_off ? mdiBattery : mdiBatteryOutline;
|
||||||
case "battery_charging":
|
case "battery_charging":
|
||||||
return is_off ? mdiBattery : mdiBatteryCharging;
|
return is_off ? mdiBattery : mdiBatteryCharging;
|
||||||
case "carbon_monoxide":
|
|
||||||
return is_off ? mdiSmokeDetector : mdiSmokeDetectorAlert;
|
|
||||||
case "cold":
|
case "cold":
|
||||||
return is_off ? mdiThermometer : mdiSnowflake;
|
return is_off ? mdiThermometer : mdiSnowflake;
|
||||||
case "connectivity":
|
case "connectivity":
|
||||||
@@ -73,7 +68,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
|||||||
case "tamper":
|
case "tamper":
|
||||||
return is_off ? mdiCheckCircle : mdiAlertCircle;
|
return is_off ? mdiCheckCircle : mdiAlertCircle;
|
||||||
case "smoke":
|
case "smoke":
|
||||||
return is_off ? mdiSmokeDetectorVariant : mdiSmokeDetectorVariantAlert;
|
return is_off ? mdiCheckCircle : mdiSmoke;
|
||||||
case "heat":
|
case "heat":
|
||||||
return is_off ? mdiThermometer : mdiFire;
|
return is_off ? mdiThermometer : mdiFire;
|
||||||
case "light":
|
case "light":
|
||||||
|
@@ -13,7 +13,6 @@ import { formatNumber, isNumericState } from "../number/format_number";
|
|||||||
import { LocalizeFunc } from "../translations/localize";
|
import { LocalizeFunc } from "../translations/localize";
|
||||||
import { computeStateDomain } from "./compute_state_domain";
|
import { computeStateDomain } from "./compute_state_domain";
|
||||||
import { supportsFeature } from "./supports-feature";
|
import { supportsFeature } from "./supports-feature";
|
||||||
import { formatDuration, UNIT_TO_SECOND_CONVERT } from "../datetime/duration";
|
|
||||||
|
|
||||||
export const computeStateDisplay = (
|
export const computeStateDisplay = (
|
||||||
localize: LocalizeFunc,
|
localize: LocalizeFunc,
|
||||||
@@ -29,27 +28,11 @@ export const computeStateDisplay = (
|
|||||||
|
|
||||||
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
// Entities with a `unit_of_measurement` or `state_class` are numeric values and should use `formatNumber`
|
||||||
if (isNumericState(stateObj)) {
|
if (isNumericState(stateObj)) {
|
||||||
// state is duration
|
|
||||||
if (
|
|
||||||
stateObj.attributes.device_class === "duration" &&
|
|
||||||
stateObj.attributes.unit_of_measurement &&
|
|
||||||
UNIT_TO_SECOND_CONVERT[stateObj.attributes.unit_of_measurement]
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
return formatDuration(
|
|
||||||
compareState,
|
|
||||||
stateObj.attributes.unit_of_measurement
|
|
||||||
);
|
|
||||||
} catch (_err) {
|
|
||||||
// fallback to default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stateObj.attributes.device_class === "monetary") {
|
if (stateObj.attributes.device_class === "monetary") {
|
||||||
try {
|
try {
|
||||||
return formatNumber(compareState, locale, {
|
return formatNumber(compareState, locale, {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
currency: stateObj.attributes.unit_of_measurement,
|
currency: stateObj.attributes.unit_of_measurement,
|
||||||
minimumFractionDigits: 2,
|
|
||||||
});
|
});
|
||||||
} catch (_err) {
|
} catch (_err) {
|
||||||
// fallback to default
|
// fallback to default
|
||||||
|
@@ -8,25 +8,26 @@ import {
|
|||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCast,
|
mdiCast,
|
||||||
mdiCastConnected,
|
mdiCastConnected,
|
||||||
mdiCheckCircleOutline,
|
|
||||||
mdiClock,
|
mdiClock,
|
||||||
mdiCloseCircleOutline,
|
|
||||||
mdiGestureTapButton,
|
mdiGestureTapButton,
|
||||||
mdiLanConnect,
|
mdiLanConnect,
|
||||||
mdiLanDisconnect,
|
mdiLanDisconnect,
|
||||||
|
mdiLightSwitch,
|
||||||
mdiLock,
|
mdiLock,
|
||||||
mdiLockAlert,
|
mdiLockAlert,
|
||||||
mdiLockClock,
|
mdiLockClock,
|
||||||
mdiLockOpen,
|
mdiLockOpen,
|
||||||
mdiPackage,
|
|
||||||
mdiPackageDown,
|
|
||||||
mdiPackageUp,
|
mdiPackageUp,
|
||||||
mdiPowerPlug,
|
mdiPowerPlug,
|
||||||
mdiPowerPlugOff,
|
mdiPowerPlugOff,
|
||||||
mdiRestart,
|
mdiRestart,
|
||||||
mdiToggleSwitchVariant,
|
mdiToggleSwitch,
|
||||||
mdiToggleSwitchVariantOff,
|
mdiToggleSwitchOff,
|
||||||
|
mdiCheckCircleOutline,
|
||||||
|
mdiCloseCircleOutline,
|
||||||
mdiWeatherNight,
|
mdiWeatherNight,
|
||||||
|
mdiPackage,
|
||||||
|
mdiPackageDown,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
import { updateIsInstalling, UpdateEntity } from "../../data/update";
|
||||||
@@ -108,11 +109,9 @@ export const domainIcon = (
|
|||||||
case "outlet":
|
case "outlet":
|
||||||
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
|
return compareState === "on" ? mdiPowerPlug : mdiPowerPlugOff;
|
||||||
case "switch":
|
case "switch":
|
||||||
return compareState === "on"
|
return compareState === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||||
? mdiToggleSwitchVariant
|
|
||||||
: mdiToggleSwitchVariantOff;
|
|
||||||
default:
|
default:
|
||||||
return mdiToggleSwitchVariant;
|
return mdiLightSwitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "sensor": {
|
case "sensor": {
|
||||||
|
244
src/common/string/filter/char-code.ts
Normal file
244
src/common/string/filter/char-code.ts
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// MIT License
|
||||||
|
|
||||||
|
// Copyright (c) 2015 - present Microsoft Corporation
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
// Names from https://blog.codinghorror.com/ascii-pronunciation-rules-for-programmers/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An inlined enum containing useful character codes (to be used with String.charCodeAt).
|
||||||
|
* Please leave the const keyword such that it gets inlined when compiled to JavaScript!
|
||||||
|
*/
|
||||||
|
export enum CharCode {
|
||||||
|
Null = 0,
|
||||||
|
/**
|
||||||
|
* The `\b` character.
|
||||||
|
*/
|
||||||
|
Backspace = 8,
|
||||||
|
/**
|
||||||
|
* The `\t` character.
|
||||||
|
*/
|
||||||
|
Tab = 9,
|
||||||
|
/**
|
||||||
|
* The `\n` character.
|
||||||
|
*/
|
||||||
|
LineFeed = 10,
|
||||||
|
/**
|
||||||
|
* The `\r` character.
|
||||||
|
*/
|
||||||
|
CarriageReturn = 13,
|
||||||
|
Space = 32,
|
||||||
|
/**
|
||||||
|
* The `!` character.
|
||||||
|
*/
|
||||||
|
ExclamationMark = 33,
|
||||||
|
/**
|
||||||
|
* The `"` character.
|
||||||
|
*/
|
||||||
|
DoubleQuote = 34,
|
||||||
|
/**
|
||||||
|
* The `#` character.
|
||||||
|
*/
|
||||||
|
Hash = 35,
|
||||||
|
/**
|
||||||
|
* The `$` character.
|
||||||
|
*/
|
||||||
|
DollarSign = 36,
|
||||||
|
/**
|
||||||
|
* The `%` character.
|
||||||
|
*/
|
||||||
|
PercentSign = 37,
|
||||||
|
/**
|
||||||
|
* The `&` character.
|
||||||
|
*/
|
||||||
|
Ampersand = 38,
|
||||||
|
/**
|
||||||
|
* The `'` character.
|
||||||
|
*/
|
||||||
|
SingleQuote = 39,
|
||||||
|
/**
|
||||||
|
* The `(` character.
|
||||||
|
*/
|
||||||
|
OpenParen = 40,
|
||||||
|
/**
|
||||||
|
* The `)` character.
|
||||||
|
*/
|
||||||
|
CloseParen = 41,
|
||||||
|
/**
|
||||||
|
* The `*` character.
|
||||||
|
*/
|
||||||
|
Asterisk = 42,
|
||||||
|
/**
|
||||||
|
* The `+` character.
|
||||||
|
*/
|
||||||
|
Plus = 43,
|
||||||
|
/**
|
||||||
|
* The `,` character.
|
||||||
|
*/
|
||||||
|
Comma = 44,
|
||||||
|
/**
|
||||||
|
* The `-` character.
|
||||||
|
*/
|
||||||
|
Dash = 45,
|
||||||
|
/**
|
||||||
|
* The `.` character.
|
||||||
|
*/
|
||||||
|
Period = 46,
|
||||||
|
/**
|
||||||
|
* The `/` character.
|
||||||
|
*/
|
||||||
|
Slash = 47,
|
||||||
|
|
||||||
|
Digit0 = 48,
|
||||||
|
Digit1 = 49,
|
||||||
|
Digit2 = 50,
|
||||||
|
Digit3 = 51,
|
||||||
|
Digit4 = 52,
|
||||||
|
Digit5 = 53,
|
||||||
|
Digit6 = 54,
|
||||||
|
Digit7 = 55,
|
||||||
|
Digit8 = 56,
|
||||||
|
Digit9 = 57,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `:` character.
|
||||||
|
*/
|
||||||
|
Colon = 58,
|
||||||
|
/**
|
||||||
|
* The `;` character.
|
||||||
|
*/
|
||||||
|
Semicolon = 59,
|
||||||
|
/**
|
||||||
|
* The `<` character.
|
||||||
|
*/
|
||||||
|
LessThan = 60,
|
||||||
|
/**
|
||||||
|
* The `=` character.
|
||||||
|
*/
|
||||||
|
Equals = 61,
|
||||||
|
/**
|
||||||
|
* The `>` character.
|
||||||
|
*/
|
||||||
|
GreaterThan = 62,
|
||||||
|
/**
|
||||||
|
* The `?` character.
|
||||||
|
*/
|
||||||
|
QuestionMark = 63,
|
||||||
|
/**
|
||||||
|
* The `@` character.
|
||||||
|
*/
|
||||||
|
AtSign = 64,
|
||||||
|
|
||||||
|
A = 65,
|
||||||
|
B = 66,
|
||||||
|
C = 67,
|
||||||
|
D = 68,
|
||||||
|
E = 69,
|
||||||
|
F = 70,
|
||||||
|
G = 71,
|
||||||
|
H = 72,
|
||||||
|
I = 73,
|
||||||
|
J = 74,
|
||||||
|
K = 75,
|
||||||
|
L = 76,
|
||||||
|
M = 77,
|
||||||
|
N = 78,
|
||||||
|
O = 79,
|
||||||
|
P = 80,
|
||||||
|
Q = 81,
|
||||||
|
R = 82,
|
||||||
|
S = 83,
|
||||||
|
T = 84,
|
||||||
|
U = 85,
|
||||||
|
V = 86,
|
||||||
|
W = 87,
|
||||||
|
X = 88,
|
||||||
|
Y = 89,
|
||||||
|
Z = 90,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `[` character.
|
||||||
|
*/
|
||||||
|
OpenSquareBracket = 91,
|
||||||
|
/**
|
||||||
|
* The `\` character.
|
||||||
|
*/
|
||||||
|
Backslash = 92,
|
||||||
|
/**
|
||||||
|
* The `]` character.
|
||||||
|
*/
|
||||||
|
CloseSquareBracket = 93,
|
||||||
|
/**
|
||||||
|
* The `^` character.
|
||||||
|
*/
|
||||||
|
Caret = 94,
|
||||||
|
/**
|
||||||
|
* The `_` character.
|
||||||
|
*/
|
||||||
|
Underline = 95,
|
||||||
|
/**
|
||||||
|
* The ``(`)`` character.
|
||||||
|
*/
|
||||||
|
BackTick = 96,
|
||||||
|
|
||||||
|
a = 97,
|
||||||
|
b = 98,
|
||||||
|
c = 99,
|
||||||
|
d = 100,
|
||||||
|
e = 101,
|
||||||
|
f = 102,
|
||||||
|
g = 103,
|
||||||
|
h = 104,
|
||||||
|
i = 105,
|
||||||
|
j = 106,
|
||||||
|
k = 107,
|
||||||
|
l = 108,
|
||||||
|
m = 109,
|
||||||
|
n = 110,
|
||||||
|
o = 111,
|
||||||
|
p = 112,
|
||||||
|
q = 113,
|
||||||
|
r = 114,
|
||||||
|
s = 115,
|
||||||
|
t = 116,
|
||||||
|
u = 117,
|
||||||
|
v = 118,
|
||||||
|
w = 119,
|
||||||
|
x = 120,
|
||||||
|
y = 121,
|
||||||
|
z = 122,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `{` character.
|
||||||
|
*/
|
||||||
|
OpenCurlyBrace = 123,
|
||||||
|
/**
|
||||||
|
* The `|` character.
|
||||||
|
*/
|
||||||
|
Pipe = 124,
|
||||||
|
/**
|
||||||
|
* The `}` character.
|
||||||
|
*/
|
||||||
|
CloseCurlyBrace = 125,
|
||||||
|
/**
|
||||||
|
* The `~` character.
|
||||||
|
*/
|
||||||
|
Tilde = 126,
|
||||||
|
}
|
551
src/common/string/filter/filter.ts
Normal file
551
src/common/string/filter/filter.ts
Normal file
@@ -0,0 +1,551 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
// MIT License
|
||||||
|
|
||||||
|
// Copyright (c) 2015 - present Microsoft Corporation
|
||||||
|
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
// of this software and associated documentation files (the "Software"), to deal
|
||||||
|
// in the Software without restriction, including without limitation the rights
|
||||||
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
// copies of the Software, and to permit persons to whom the Software is
|
||||||
|
// furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
// SOFTWARE.
|
||||||
|
|
||||||
|
import { CharCode } from "./char-code";
|
||||||
|
|
||||||
|
const _debug = false;
|
||||||
|
|
||||||
|
export interface Match {
|
||||||
|
start: number;
|
||||||
|
end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _maxLen = 128;
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
|
const table: number[][] = [];
|
||||||
|
const row: number[] = [];
|
||||||
|
for (let i = 0; i <= _maxLen; i++) {
|
||||||
|
row[i] = 0;
|
||||||
|
}
|
||||||
|
for (let i = 0; i <= _maxLen; i++) {
|
||||||
|
table.push(row.slice(0));
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSeparatorAtPos(value: string, index: number): boolean {
|
||||||
|
if (index < 0 || index >= value.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const code = value.codePointAt(index);
|
||||||
|
switch (code) {
|
||||||
|
case CharCode.Underline:
|
||||||
|
case CharCode.Dash:
|
||||||
|
case CharCode.Period:
|
||||||
|
case CharCode.Space:
|
||||||
|
case CharCode.Slash:
|
||||||
|
case CharCode.Backslash:
|
||||||
|
case CharCode.SingleQuote:
|
||||||
|
case CharCode.DoubleQuote:
|
||||||
|
case CharCode.Colon:
|
||||||
|
case CharCode.DollarSign:
|
||||||
|
case CharCode.LessThan:
|
||||||
|
case CharCode.OpenParen:
|
||||||
|
case CharCode.OpenSquareBracket:
|
||||||
|
return true;
|
||||||
|
case undefined:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
if (isEmojiImprecise(code)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWhitespaceAtPos(value: string, index: number): boolean {
|
||||||
|
if (index < 0 || index >= value.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const code = value.charCodeAt(index);
|
||||||
|
switch (code) {
|
||||||
|
case CharCode.Space:
|
||||||
|
case CharCode.Tab:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUpperCaseAtPos(pos: number, word: string, wordLow: string): boolean {
|
||||||
|
return word[pos] !== wordLow[pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPatternInWord(
|
||||||
|
patternLow: string,
|
||||||
|
patternPos: number,
|
||||||
|
patternLen: number,
|
||||||
|
wordLow: string,
|
||||||
|
wordPos: number,
|
||||||
|
wordLen: number,
|
||||||
|
fillMinWordPosArr = false
|
||||||
|
): boolean {
|
||||||
|
while (patternPos < patternLen && wordPos < wordLen) {
|
||||||
|
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||||
|
if (fillMinWordPosArr) {
|
||||||
|
// Remember the min word position for each pattern position
|
||||||
|
_minWordMatchPos[patternPos] = wordPos;
|
||||||
|
}
|
||||||
|
patternPos += 1;
|
||||||
|
}
|
||||||
|
wordPos += 1;
|
||||||
|
}
|
||||||
|
return patternPos === patternLen; // pattern must be exhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Arrow {
|
||||||
|
Diag = 1,
|
||||||
|
Left = 2,
|
||||||
|
LeftLeft = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array representing a fuzzy match.
|
||||||
|
*
|
||||||
|
* 0. the score
|
||||||
|
* 1. the offset at which matching started
|
||||||
|
* 2. `<match_pos_N>`
|
||||||
|
* 3. `<match_pos_1>`
|
||||||
|
* 4. `<match_pos_0>` etc
|
||||||
|
*/
|
||||||
|
// export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]];// [number, number, number];
|
||||||
|
export type FuzzyScore = Array<number>;
|
||||||
|
|
||||||
|
export function fuzzyScore(
|
||||||
|
pattern: string,
|
||||||
|
patternLow: string,
|
||||||
|
patternStart: number,
|
||||||
|
word: string,
|
||||||
|
wordLow: string,
|
||||||
|
wordStart: number,
|
||||||
|
firstMatchCanBeWeak: boolean
|
||||||
|
): FuzzyScore | undefined {
|
||||||
|
const patternLen = pattern.length > _maxLen ? _maxLen : pattern.length;
|
||||||
|
const wordLen = word.length > _maxLen ? _maxLen : word.length;
|
||||||
|
|
||||||
|
if (
|
||||||
|
patternStart >= patternLen ||
|
||||||
|
wordStart >= wordLen ||
|
||||||
|
patternLen - patternStart > wordLen - wordStart
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run a simple check if the characters of pattern occur
|
||||||
|
// (in order) at all in word. If that isn't the case we
|
||||||
|
// stop because no match will be possible
|
||||||
|
if (
|
||||||
|
!isPatternInWord(
|
||||||
|
patternLow,
|
||||||
|
patternStart,
|
||||||
|
patternLen,
|
||||||
|
wordLow,
|
||||||
|
wordStart,
|
||||||
|
wordLen,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the max matching word position for each pattern position
|
||||||
|
// NOTE: the min matching word position was filled in above, in the `isPatternInWord` call
|
||||||
|
_fillInMaxWordMatchPos(
|
||||||
|
patternLen,
|
||||||
|
wordLen,
|
||||||
|
patternStart,
|
||||||
|
wordStart,
|
||||||
|
patternLow,
|
||||||
|
wordLow
|
||||||
|
);
|
||||||
|
|
||||||
|
let row: number;
|
||||||
|
let column = 1;
|
||||||
|
let patternPos: number;
|
||||||
|
let wordPos: number;
|
||||||
|
|
||||||
|
const hasStrongFirstMatch = [false];
|
||||||
|
|
||||||
|
// There will be a match, fill in tables
|
||||||
|
for (
|
||||||
|
row = 1, patternPos = patternStart;
|
||||||
|
patternPos < patternLen;
|
||||||
|
row++, patternPos++
|
||||||
|
) {
|
||||||
|
// Reduce search space to possible matching word positions and to possible access from next row
|
||||||
|
const minWordMatchPos = _minWordMatchPos[patternPos];
|
||||||
|
const maxWordMatchPos = _maxWordMatchPos[patternPos];
|
||||||
|
const nextMaxWordMatchPos =
|
||||||
|
patternPos + 1 < patternLen ? _maxWordMatchPos[patternPos + 1] : wordLen;
|
||||||
|
|
||||||
|
for (
|
||||||
|
column = minWordMatchPos - wordStart + 1, wordPos = minWordMatchPos;
|
||||||
|
wordPos < nextMaxWordMatchPos;
|
||||||
|
column++, wordPos++
|
||||||
|
) {
|
||||||
|
let score = Number.MIN_SAFE_INTEGER;
|
||||||
|
let canComeDiag = false;
|
||||||
|
|
||||||
|
if (wordPos <= maxWordMatchPos) {
|
||||||
|
score = _doScore(
|
||||||
|
pattern,
|
||||||
|
patternLow,
|
||||||
|
patternPos,
|
||||||
|
patternStart,
|
||||||
|
word,
|
||||||
|
wordLow,
|
||||||
|
wordPos,
|
||||||
|
wordLen,
|
||||||
|
wordStart,
|
||||||
|
_diag[row - 1][column - 1] === 0,
|
||||||
|
hasStrongFirstMatch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let diagScore = 0;
|
||||||
|
if (score !== Number.MAX_SAFE_INTEGER) {
|
||||||
|
canComeDiag = true;
|
||||||
|
diagScore = score + _table[row - 1][column - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const canComeLeft = wordPos > minWordMatchPos;
|
||||||
|
const leftScore = canComeLeft
|
||||||
|
? _table[row][column - 1] + (_diag[row][column - 1] > 0 ? -5 : 0)
|
||||||
|
: 0; // penalty for a gap start
|
||||||
|
|
||||||
|
const canComeLeftLeft =
|
||||||
|
wordPos > minWordMatchPos + 1 && _diag[row][column - 1] > 0;
|
||||||
|
const leftLeftScore = canComeLeftLeft
|
||||||
|
? _table[row][column - 2] + (_diag[row][column - 2] > 0 ? -5 : 0)
|
||||||
|
: 0; // penalty for a gap start
|
||||||
|
|
||||||
|
if (
|
||||||
|
canComeLeftLeft &&
|
||||||
|
(!canComeLeft || leftLeftScore >= leftScore) &&
|
||||||
|
(!canComeDiag || leftLeftScore >= diagScore)
|
||||||
|
) {
|
||||||
|
// always prefer choosing left left to jump over a diagonal because that means a match is earlier in the word
|
||||||
|
_table[row][column] = leftLeftScore;
|
||||||
|
_arrows[row][column] = Arrow.LeftLeft;
|
||||||
|
_diag[row][column] = 0;
|
||||||
|
} else if (canComeLeft && (!canComeDiag || leftScore >= diagScore)) {
|
||||||
|
// always prefer choosing left since that means a match is earlier in the word
|
||||||
|
_table[row][column] = leftScore;
|
||||||
|
_arrows[row][column] = Arrow.Left;
|
||||||
|
_diag[row][column] = 0;
|
||||||
|
} else if (canComeDiag) {
|
||||||
|
_table[row][column] = diagScore;
|
||||||
|
_arrows[row][column] = Arrow.Diag;
|
||||||
|
_diag[row][column] = _diag[row - 1][column - 1] + 1;
|
||||||
|
} else {
|
||||||
|
throw new Error(`not possible`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_debug) {
|
||||||
|
printTables(pattern, patternStart, word, wordStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasStrongFirstMatch[0] && !firstMatchCanBeWeak) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
row--;
|
||||||
|
column--;
|
||||||
|
|
||||||
|
const result: FuzzyScore = [_table[row][column], wordStart];
|
||||||
|
|
||||||
|
let backwardsDiagLength = 0;
|
||||||
|
let maxMatchColumn = 0;
|
||||||
|
|
||||||
|
while (row >= 1) {
|
||||||
|
// Find the column where we go diagonally up
|
||||||
|
let diagColumn = column;
|
||||||
|
do {
|
||||||
|
const arrow = _arrows[row][diagColumn];
|
||||||
|
if (arrow === Arrow.LeftLeft) {
|
||||||
|
diagColumn -= 2;
|
||||||
|
} else if (arrow === Arrow.Left) {
|
||||||
|
diagColumn -= 1;
|
||||||
|
} else {
|
||||||
|
// found the diagonal
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (diagColumn >= 1);
|
||||||
|
|
||||||
|
// Overturn the "forwards" decision if keeping the "backwards" diagonal would give a better match
|
||||||
|
if (
|
||||||
|
backwardsDiagLength > 1 && // only if we would have a contiguous match of 3 characters
|
||||||
|
patternLow[patternStart + row - 1] === wordLow[wordStart + column - 1] && // only if we can do a contiguous match diagonally
|
||||||
|
!isUpperCaseAtPos(diagColumn + wordStart - 1, word, wordLow) && // only if the forwards chose diagonal is not an uppercase
|
||||||
|
backwardsDiagLength + 1 > _diag[row][diagColumn] // only if our contiguous match would be longer than the "forwards" contiguous match
|
||||||
|
) {
|
||||||
|
diagColumn = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagColumn === column) {
|
||||||
|
// this is a contiguous match
|
||||||
|
backwardsDiagLength++;
|
||||||
|
} else {
|
||||||
|
backwardsDiagLength = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!maxMatchColumn) {
|
||||||
|
// remember the last matched column
|
||||||
|
maxMatchColumn = diagColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
row--;
|
||||||
|
column = diagColumn - 1;
|
||||||
|
result.push(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordLen === patternLen) {
|
||||||
|
// the word matches the pattern with all characters!
|
||||||
|
// giving the score a total match boost (to come up ahead other words)
|
||||||
|
result[0] += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add 1 penalty for each skipped character in the word
|
||||||
|
const skippedCharsCount = maxMatchColumn - patternLen;
|
||||||
|
result[0] -= skippedCharsCount;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _doScore(
|
||||||
|
pattern: string,
|
||||||
|
patternLow: string,
|
||||||
|
patternPos: number,
|
||||||
|
patternStart: number,
|
||||||
|
word: string,
|
||||||
|
wordLow: string,
|
||||||
|
wordPos: number,
|
||||||
|
wordLen: number,
|
||||||
|
wordStart: number,
|
||||||
|
newMatchStart: boolean,
|
||||||
|
outFirstMatchStrong: boolean[]
|
||||||
|
): number {
|
||||||
|
if (patternLow[patternPos] !== wordLow[wordPos]) {
|
||||||
|
return Number.MIN_SAFE_INTEGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
let score = 1;
|
||||||
|
let isGapLocation = false;
|
||||||
|
if (wordPos === patternPos - patternStart) {
|
||||||
|
// common prefix: `foobar <-> foobaz`
|
||||||
|
// ^^^^^
|
||||||
|
score = pattern[patternPos] === word[wordPos] ? 7 : 5;
|
||||||
|
} else if (
|
||||||
|
isUpperCaseAtPos(wordPos, word, wordLow) &&
|
||||||
|
(wordPos === 0 || !isUpperCaseAtPos(wordPos - 1, word, wordLow))
|
||||||
|
) {
|
||||||
|
// hitting upper-case: `foo <-> forOthers`
|
||||||
|
// ^^ ^
|
||||||
|
score = pattern[patternPos] === word[wordPos] ? 7 : 5;
|
||||||
|
isGapLocation = true;
|
||||||
|
} else if (
|
||||||
|
isSeparatorAtPos(wordLow, wordPos) &&
|
||||||
|
(wordPos === 0 || !isSeparatorAtPos(wordLow, wordPos - 1))
|
||||||
|
) {
|
||||||
|
// hitting a separator: `. <-> foo.bar`
|
||||||
|
// ^
|
||||||
|
score = 5;
|
||||||
|
} else if (
|
||||||
|
isSeparatorAtPos(wordLow, wordPos - 1) ||
|
||||||
|
isWhitespaceAtPos(wordLow, wordPos - 1)
|
||||||
|
) {
|
||||||
|
// post separator: `foo <-> bar_foo`
|
||||||
|
// ^^^
|
||||||
|
score = 5;
|
||||||
|
isGapLocation = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score > 1 && patternPos === patternStart) {
|
||||||
|
outFirstMatchStrong[0] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isGapLocation) {
|
||||||
|
isGapLocation =
|
||||||
|
isUpperCaseAtPos(wordPos, word, wordLow) ||
|
||||||
|
isSeparatorAtPos(wordLow, wordPos - 1) ||
|
||||||
|
isWhitespaceAtPos(wordLow, wordPos - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
if (patternPos === patternStart) {
|
||||||
|
// first character in pattern
|
||||||
|
if (wordPos > wordStart) {
|
||||||
|
// the first pattern character would match a word character that is not at the word start
|
||||||
|
// so introduce a penalty to account for the gap preceding this match
|
||||||
|
score -= isGapLocation ? 3 : 5;
|
||||||
|
}
|
||||||
|
} else if (newMatchStart) {
|
||||||
|
// this would be the beginning of a new match (i.e. there would be a gap before this location)
|
||||||
|
score += isGapLocation ? 2 : 0;
|
||||||
|
} else {
|
||||||
|
// this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a prefered gap location
|
||||||
|
score += isGapLocation ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wordPos + 1 === wordLen) {
|
||||||
|
// we always penalize gaps, but this gives unfair advantages to a match that would match the last character in the word
|
||||||
|
// so pretend there is a gap after the last character in the word to normalize things
|
||||||
|
score -= isGapLocation ? 3 : 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTable(
|
||||||
|
table: number[][],
|
||||||
|
pattern: string,
|
||||||
|
patternLen: number,
|
||||||
|
word: string,
|
||||||
|
wordLen: number
|
||||||
|
): string {
|
||||||
|
function pad(s: string, n: number, _pad = " ") {
|
||||||
|
while (s.length < n) {
|
||||||
|
s = _pad + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
let ret = ` | |${word
|
||||||
|
.split("")
|
||||||
|
.map((c) => pad(c, 3))
|
||||||
|
.join("|")}\n`;
|
||||||
|
|
||||||
|
for (let i = 0; i <= patternLen; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
ret += " |";
|
||||||
|
} else {
|
||||||
|
ret += `${pattern[i - 1]}|`;
|
||||||
|
}
|
||||||
|
ret +=
|
||||||
|
table[i]
|
||||||
|
.slice(0, wordLen + 1)
|
||||||
|
.map((n) => pad(n.toString(), 3))
|
||||||
|
.join("|") + "\n";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printTables(
|
||||||
|
pattern: string,
|
||||||
|
patternStart: number,
|
||||||
|
word: string,
|
||||||
|
wordStart: number
|
||||||
|
): void {
|
||||||
|
pattern = pattern.substr(patternStart);
|
||||||
|
word = word.substr(wordStart);
|
||||||
|
console.log(printTable(_table, pattern, pattern.length, word, word.length));
|
||||||
|
console.log(printTable(_arrows, pattern, pattern.length, word, word.length));
|
||||||
|
console.log(printTable(_diag, pattern, pattern.length, word, word.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
const _minWordMatchPos = initArr(2 * _maxLen); // min word position for a certain pattern position
|
||||||
|
const _maxWordMatchPos = initArr(2 * _maxLen); // max word position for a certain pattern position
|
||||||
|
const _diag = initTable(); // the length of a contiguous diagonal match
|
||||||
|
const _table = initTable();
|
||||||
|
const _arrows = <Arrow[][]>initTable();
|
||||||
|
|
||||||
|
function initArr(maxLen: number) {
|
||||||
|
const row: number[] = [];
|
||||||
|
for (let i = 0; i <= maxLen; i++) {
|
||||||
|
row[i] = 0;
|
||||||
|
}
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _fillInMaxWordMatchPos(
|
||||||
|
patternLen: number,
|
||||||
|
wordLen: number,
|
||||||
|
patternStart: number,
|
||||||
|
wordStart: number,
|
||||||
|
patternLow: string,
|
||||||
|
wordLow: string
|
||||||
|
) {
|
||||||
|
let patternPos = patternLen - 1;
|
||||||
|
let wordPos = wordLen - 1;
|
||||||
|
while (patternPos >= patternStart && wordPos >= wordStart) {
|
||||||
|
if (patternLow[patternPos] === wordLow[wordPos]) {
|
||||||
|
_maxWordMatchPos[patternPos] = wordPos;
|
||||||
|
patternPos--;
|
||||||
|
}
|
||||||
|
wordPos--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FuzzyScorer {
|
||||||
|
(
|
||||||
|
pattern: string,
|
||||||
|
lowPattern: string,
|
||||||
|
patternPos: number,
|
||||||
|
word: string,
|
||||||
|
lowWord: string,
|
||||||
|
wordPos: number,
|
||||||
|
firstMatchCanBeWeak: boolean
|
||||||
|
): FuzzyScore | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMatches(score: undefined | FuzzyScore): Match[] {
|
||||||
|
if (typeof score === "undefined") {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const res: Match[] = [];
|
||||||
|
const wordPos = score[1];
|
||||||
|
for (let i = score.length - 1; i > 1; i--) {
|
||||||
|
const pos = score[i] + wordPos;
|
||||||
|
const last = res[res.length - 1];
|
||||||
|
if (last && last.end === pos) {
|
||||||
|
last.end = pos + 1;
|
||||||
|
} else {
|
||||||
|
res.push({ start: pos, end: pos + 1 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fast function (therefore imprecise) to check if code points are emojis.
|
||||||
|
* Generated using https://github.com/alexdima/unicode-utils/blob/master/generate-emoji-test.js
|
||||||
|
*/
|
||||||
|
export function isEmojiImprecise(x: number): boolean {
|
||||||
|
return (
|
||||||
|
(x >= 0x1f1e6 && x <= 0x1f1ff) ||
|
||||||
|
x === 8986 ||
|
||||||
|
x === 8987 ||
|
||||||
|
x === 9200 ||
|
||||||
|
x === 9203 ||
|
||||||
|
(x >= 9728 && x <= 10175) ||
|
||||||
|
x === 11088 ||
|
||||||
|
x === 11093 ||
|
||||||
|
(x >= 127744 && x <= 128591) ||
|
||||||
|
(x >= 128640 && x <= 128764) ||
|
||||||
|
(x >= 128992 && x <= 129003) ||
|
||||||
|
(x >= 129280 && x <= 129535) ||
|
||||||
|
(x >= 129648 && x <= 129750)
|
||||||
|
);
|
||||||
|
}
|
@@ -1,4 +1,52 @@
|
|||||||
import fuzzysort from "fuzzysort";
|
import { fuzzyScore } from "./filter";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether a sequence of letters exists in another string,
|
||||||
|
* in that order, allowing for skipping. Ex: "chdr" exists in "chandelier")
|
||||||
|
*
|
||||||
|
* @param {string} filter - Sequence of letters to check for
|
||||||
|
* @param {ScorableTextItem} item - Item against whose strings will be checked
|
||||||
|
*
|
||||||
|
* @return {number} Score representing how well the word matches the filter. Return of 0 means no match.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const fuzzySequentialMatch = (
|
||||||
|
filter: string,
|
||||||
|
item: ScorableTextItem
|
||||||
|
) => {
|
||||||
|
let topScore = Number.NEGATIVE_INFINITY;
|
||||||
|
|
||||||
|
for (const word of item.strings) {
|
||||||
|
const scores = fuzzyScore(
|
||||||
|
filter,
|
||||||
|
filter.toLowerCase(),
|
||||||
|
0,
|
||||||
|
word,
|
||||||
|
word.toLowerCase(),
|
||||||
|
0,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!scores) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The VS Code implementation of filter returns a 0 for a weak match.
|
||||||
|
// But if .filter() sees a "0", it considers that a failed match and will remove it.
|
||||||
|
// So, we set score to 1 in these cases so the match will be included, and mostly respect correct ordering.
|
||||||
|
const score = scores[0] === 0 ? 1 : scores[0];
|
||||||
|
|
||||||
|
if (score > topScore) {
|
||||||
|
topScore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topScore === Number.NEGATIVE_INFINITY) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return topScore;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface that objects must extend in order to use the fuzzy sequence matcher
|
* An interface that objects must extend in order to use the fuzzy sequence matcher
|
||||||
@@ -18,48 +66,18 @@ export interface ScorableTextItem {
|
|||||||
strings: string[];
|
strings: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FuzzyFilterSort = <T extends ScorableTextItem>(
|
type FuzzyFilterSort = <T extends ScorableTextItem>(
|
||||||
filter: string,
|
filter: string,
|
||||||
items: T[]
|
items: T[]
|
||||||
) => T[];
|
) => T[];
|
||||||
|
|
||||||
export function fuzzyMatcher(search: string | null): (string) => boolean {
|
export const fuzzyFilterSort: FuzzyFilterSort = (filter, items) =>
|
||||||
const scorer = fuzzyScorer(search);
|
items
|
||||||
return (value: string) => scorer([value]) !== Number.NEGATIVE_INFINITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fuzzyScorer(
|
|
||||||
search: string | null
|
|
||||||
): (values: string[]) => number {
|
|
||||||
const searchTerms = (search || "").match(/("[^"]+"|[^"\s]+)/g);
|
|
||||||
if (!searchTerms) {
|
|
||||||
return () => 0;
|
|
||||||
}
|
|
||||||
return (values) =>
|
|
||||||
searchTerms
|
|
||||||
.map((term) => {
|
|
||||||
const resultsForTerm = fuzzysort.go(term, values, {
|
|
||||||
allowTypo: true,
|
|
||||||
});
|
|
||||||
if (resultsForTerm.length > 0) {
|
|
||||||
return Math.max(...resultsForTerm.map((result) => result.score));
|
|
||||||
}
|
|
||||||
return Number.NEGATIVE_INFINITY;
|
|
||||||
})
|
|
||||||
.reduce((partial, current) => partial + current, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fuzzySortFilterSort: FuzzyFilterSort = (filter, items) => {
|
|
||||||
const scorer = fuzzyScorer(filter);
|
|
||||||
return items
|
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
item.score = scorer(item.strings);
|
item.score = fuzzySequentialMatch(filter, item);
|
||||||
return item;
|
return item;
|
||||||
})
|
})
|
||||||
.filter((item) => item.score !== undefined && item.score > -100000)
|
.filter((item) => item.score !== undefined)
|
||||||
.sort(({ score: scoreA = 0 }, { score: scoreB = 0 }) =>
|
.sort(({ score: scoreA = 0 }, { score: scoreB = 0 }) =>
|
||||||
scoreA > scoreB ? -1 : scoreA < scoreB ? 1 : 0
|
scoreA > scoreB ? -1 : scoreA < scoreB ? 1 : 0
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
export const defaultFuzzyFilterSort = fuzzySortFilterSort;
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export const promiseTimeout = (ms: number, promise: Promise<any> | any) => {
|
export const promiseTimeout = (ms: number, promise: Promise<any>) => {
|
||||||
const timeout = new Promise((_resolve, reject) => {
|
const timeout = new Promise((_resolve, reject) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reject(`Timed out in ${ms} ms.`);
|
reject(`Timed out in ${ms} ms.`);
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
import { HomeAssistant } from "../../types";
|
|
||||||
|
|
||||||
export const subscribePollingCollection = (
|
|
||||||
hass: HomeAssistant,
|
|
||||||
updateData: (hass: HomeAssistant) => void,
|
|
||||||
interval: number
|
|
||||||
) => {
|
|
||||||
let timeout;
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
await updateData(hass);
|
|
||||||
} finally {
|
|
||||||
timeout = setTimeout(() => fetchData(), interval);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchData();
|
|
||||||
return () => clearTimeout(timeout);
|
|
||||||
};
|
|
@@ -347,8 +347,8 @@ class StatisticsChart extends LitElement {
|
|||||||
statTypes.forEach((type) => {
|
statTypes.forEach((type) => {
|
||||||
let val: number | null;
|
let val: number | null;
|
||||||
if (type === "sum") {
|
if (type === "sum") {
|
||||||
if (initVal === null) {
|
if (!initVal) {
|
||||||
initVal = val = stat.state || 0;
|
initVal = val = stat.state;
|
||||||
prevSum = stat.sum;
|
prevSum = stat.sum;
|
||||||
} else {
|
} else {
|
||||||
val = initVal + ((stat.sum || 0) - prevSum!);
|
val = initVal + ((stat.sum || 0) - prevSum!);
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
export const currencies = [
|
export const createCurrencyListEl = () => {
|
||||||
|
const list = document.createElement("datalist");
|
||||||
|
list.id = "currencies";
|
||||||
|
for (const currency of [
|
||||||
"AED",
|
"AED",
|
||||||
"AFN",
|
"AFN",
|
||||||
"ALL",
|
"ALL",
|
||||||
@@ -156,12 +159,7 @@ export const currencies = [
|
|||||||
"ZAR",
|
"ZAR",
|
||||||
"ZMK",
|
"ZMK",
|
||||||
"ZWL",
|
"ZWL",
|
||||||
];
|
]) {
|
||||||
|
|
||||||
export const createCurrencyListEl = () => {
|
|
||||||
const list = document.createElement("datalist");
|
|
||||||
list.id = "currencies";
|
|
||||||
for (const currency of currencies) {
|
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = currency;
|
option.value = currency;
|
||||||
option.innerHTML = currency;
|
option.innerHTML = currency;
|
||||||
|
@@ -7,26 +7,25 @@ import type {
|
|||||||
SortableColumnContainer,
|
SortableColumnContainer,
|
||||||
SortingDirection,
|
SortingDirection,
|
||||||
} from "./ha-data-table";
|
} from "./ha-data-table";
|
||||||
import { fuzzyMatcher } from "../../common/string/filter/sequence-matching";
|
|
||||||
|
|
||||||
const filterData = (
|
const filterData = (
|
||||||
data: DataTableRowData[],
|
data: DataTableRowData[],
|
||||||
columns: SortableColumnContainer,
|
columns: SortableColumnContainer,
|
||||||
filter: string
|
filter: string
|
||||||
) => {
|
) => {
|
||||||
const matcher = fuzzyMatcher(filter);
|
filter = filter.toUpperCase();
|
||||||
return data.filter((row) =>
|
return data.filter((row) =>
|
||||||
Object.entries(columns).some((columnEntry) => {
|
Object.entries(columns).some((columnEntry) => {
|
||||||
const [key, column] = columnEntry;
|
const [key, column] = columnEntry;
|
||||||
if (column.filterable) {
|
if (column.filterable) {
|
||||||
if (
|
if (
|
||||||
matcher(
|
|
||||||
String(
|
String(
|
||||||
column.filterKey
|
column.filterKey
|
||||||
? row[column.valueColumn || key][column.filterKey]
|
? row[column.valueColumn || key][column.filterKey]
|
||||||
: row[column.valueColumn || key]
|
: row[column.valueColumn || key]
|
||||||
)
|
)
|
||||||
)
|
.toUpperCase()
|
||||||
|
.includes(filter)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -52,8 +52,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property() public devices?: DeviceRegistryEntry[];
|
@property() public devices?: DeviceRegistryEntry[];
|
||||||
|
|
||||||
@property() public areas?: AreaRegistryEntry[];
|
@property() public areas?: AreaRegistryEntry[];
|
||||||
@@ -198,8 +196,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
this.hass,
|
this.hass,
|
||||||
deviceEntityLookup[device.id]
|
deviceEntityLookup[device.id]
|
||||||
),
|
),
|
||||||
area:
|
area: device.area_id
|
||||||
device.area_id && areaLookup[device.area_id]
|
|
||||||
? areaLookup[device.area_id].name
|
? areaLookup[device.area_id].name
|
||||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||||
}));
|
}));
|
||||||
@@ -272,7 +269,6 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
? this.hass.localize("ui.components.device-picker.device")
|
? this.hass.localize("ui.components.device-picker.device")
|
||||||
: this.label}
|
: this.label}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.helper=${this.helper}
|
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
|
@@ -4,7 +4,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "./ha-device-picker";
|
import "./ha-device-picker";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "./ha-device-picker";
|
|
||||||
|
|
||||||
@customElement("ha-devices-picker")
|
@customElement("ha-devices-picker")
|
||||||
class HaDevicesPicker extends LitElement {
|
class HaDevicesPicker extends LitElement {
|
||||||
@@ -12,10 +11,6 @@ class HaDevicesPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public value?: string[];
|
@property() public value?: string[];
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled?: boolean;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public required?: boolean;
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,8 +37,6 @@ class HaDevicesPicker extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
|
@property({ attribute: "pick-device-label" }) public pickDeviceLabel?: string;
|
||||||
|
|
||||||
@property() public deviceFilter?: HaDevicePickerDeviceFilterFunc;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass) {
|
if (!this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
@@ -58,13 +51,11 @@ class HaDevicesPicker extends LitElement {
|
|||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
.curValue=${entityId}
|
.curValue=${entityId}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.deviceFilter=${this.deviceFilter}
|
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
.value=${entityId}
|
.value=${entityId}
|
||||||
.label=${this.pickedDeviceLabel}
|
.label=${this.pickedDeviceLabel}
|
||||||
.disabled=${this.disabled}
|
|
||||||
@value-changed=${this._deviceChanged}
|
@value-changed=${this._deviceChanged}
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,15 +63,11 @@ class HaDevicesPicker extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<ha-device-picker
|
<ha-device-picker
|
||||||
allow-custom-entity
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.helper=${this.helper}
|
|
||||||
.deviceFilter=${this.deviceFilter}
|
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
.label=${this.pickDeviceLabel}
|
.label=${this.pickDeviceLabel}
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${this.required && !currentDevices.length}
|
.required=${this.required && !currentDevices.length}
|
||||||
@value-changed=${this._addDevice}
|
@value-changed=${this._addDevice}
|
||||||
></ha-device-picker>
|
></ha-device-picker>
|
||||||
|
@@ -14,12 +14,8 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Array }) public value?: string[];
|
@property({ type: Array }) public value?: string[];
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled?: boolean;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public required?: boolean;
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show entities from specific domains.
|
* Show entities from specific domains.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@@ -98,7 +94,6 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
.entityFilter=${this._entityFilter}
|
.entityFilter=${this._entityFilter}
|
||||||
.value=${entityId}
|
.value=${entityId}
|
||||||
.label=${this.pickedEntityLabel}
|
.label=${this.pickedEntityLabel}
|
||||||
.disabled=${this.disabled}
|
|
||||||
@value-changed=${this._entityChanged}
|
@value-changed=${this._entityChanged}
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,7 +101,6 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<ha-entity-picker
|
<ha-entity-picker
|
||||||
allow-custom-entity
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
@@ -116,8 +110,6 @@ class HaEntitiesPickerLight extends LitElement {
|
|||||||
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
.includeUnitOfMeasurement=${this.includeUnitOfMeasurement}
|
||||||
.entityFilter=${this._entityFilter}
|
.entityFilter=${this._entityFilter}
|
||||||
.label=${this.pickEntityLabel}
|
.label=${this.pickEntityLabel}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${this.required && !currentEntities.length}
|
.required=${this.required && !currentEntities.length}
|
||||||
@value-changed=${this._addEntity}
|
@value-changed=${this._addEntity}
|
||||||
></ha-entity-picker>
|
></ha-entity-picker>
|
||||||
|
@@ -28,8 +28,6 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) private _opened = false;
|
@property({ type: Boolean }) private _opened = false;
|
||||||
|
|
||||||
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
@query("ha-combo-box", true) private _comboBox!: HaComboBox;
|
||||||
@@ -66,7 +64,6 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.disabled=${this.disabled || !this.entityId}
|
.disabled=${this.disabled || !this.entityId}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.helper=${this.helper}
|
|
||||||
.allowCustomValue=${this.allowCustomValue}
|
.allowCustomValue=${this.allowCustomValue}
|
||||||
item-value-path="value"
|
item-value-path="value"
|
||||||
item-label-path="label"
|
item-label-path="label"
|
||||||
|
@@ -15,7 +15,6 @@ import type { HaComboBox } from "../ha-combo-box";
|
|||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
import { defaultFuzzyFilterSort } from "../../common/string/filter/sequence-matching";
|
|
||||||
|
|
||||||
interface HassEntityWithCachedName extends HassEntity {
|
interface HassEntityWithCachedName extends HassEntity {
|
||||||
friendly_name: string;
|
friendly_name: string;
|
||||||
@@ -49,8 +48,6 @@ export class HaEntityPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show entities from specific domains.
|
* Show entities from specific domains.
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -307,7 +304,6 @@ export class HaEntityPicker extends LitElement {
|
|||||||
.label=${this.label === undefined
|
.label=${this.label === undefined
|
||||||
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
? this.hass.localize("ui.components.entity.entity-picker.entity")
|
||||||
: this.label}
|
: this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.allowCustomValue=${this.allowCustomEntity}
|
.allowCustomValue=${this.allowCustomEntity}
|
||||||
.filteredItems=${this._states}
|
.filteredItems=${this._states}
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
@@ -337,18 +333,11 @@ export class HaEntityPicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _filterChanged(ev: CustomEvent): void {
|
private _filterChanged(ev: CustomEvent): void {
|
||||||
const filterString = ev.detail.value;
|
const filterString = ev.detail.value.toLowerCase();
|
||||||
|
(this.comboBox as any).filteredItems = this._states.filter(
|
||||||
const sortableEntityStates = this._states.map((entityState) => ({
|
(entityState) =>
|
||||||
strings: [entityState.entity_id, computeStateName(entityState)],
|
entityState.entity_id.toLowerCase().includes(filterString) ||
|
||||||
entityState: entityState,
|
computeStateName(entityState).toLowerCase().includes(filterString)
|
||||||
}));
|
|
||||||
const sortedEntityStates = defaultFuzzyFilterSort(
|
|
||||||
filterString,
|
|
||||||
sortableEntityStates
|
|
||||||
);
|
|
||||||
(this.comboBox as any).filteredItems = sortedEntityStates.map(
|
|
||||||
(sortableItem) => sortableItem.entityState
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,8 +29,6 @@ class HaAddonPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public value = "";
|
@property() public value = "";
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@state() private _addons?: HassioAddonInfo[];
|
@state() private _addons?: HassioAddonInfo[];
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -64,7 +62,6 @@ class HaAddonPicker extends LitElement {
|
|||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this.helper}
|
|
||||||
.renderer=${rowRenderer}
|
.renderer=${rowRenderer}
|
||||||
.items=${this._addons}
|
.items=${this._addons}
|
||||||
item-value-path="slug"
|
item-value-path="slug"
|
||||||
|
@@ -2,12 +2,12 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
|
import { Analytics, AnalyticsPreferences } from "../data/analytics";
|
||||||
import { haStyle } from "../resources/styles";
|
import { haStyle } from "../resources/styles";
|
||||||
import type { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import "./ha-checkbox";
|
||||||
|
import type { HaCheckbox } from "./ha-checkbox";
|
||||||
import "./ha-settings-row";
|
import "./ha-settings-row";
|
||||||
import "./ha-switch";
|
|
||||||
import type { HaSwitch } from "./ha-switch";
|
|
||||||
|
|
||||||
const ADDITIONAL_PREFERENCES = [
|
const ADDITIONAL_PREFERENCES = [
|
||||||
{
|
{
|
||||||
@@ -40,62 +40,62 @@ export class HaAnalytics extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading" data-for="base"> Basic analytics </span>
|
<span slot="prefix">
|
||||||
<span slot="description" data-for="base">
|
<ha-checkbox
|
||||||
This includes information about your system.
|
@change=${this._handleRowCheckboxClick}
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
@change=${this._handleRowClick}
|
|
||||||
.checked=${baseEnabled}
|
.checked=${baseEnabled}
|
||||||
.preference=${"base"}
|
.preference=${"base"}
|
||||||
.disabled=${loading}
|
.disabled=${loading}
|
||||||
name="base"
|
name="base"
|
||||||
>
|
>
|
||||||
</ha-switch>
|
</ha-checkbox>
|
||||||
|
</span>
|
||||||
|
<span slot="heading" data-for="base"> Basic analytics </span>
|
||||||
|
<span slot="description" data-for="base">
|
||||||
|
This includes information about your system.
|
||||||
|
</span>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
${ADDITIONAL_PREFERENCES.map(
|
${ADDITIONAL_PREFERENCES.map(
|
||||||
(preference) =>
|
(preference) =>
|
||||||
html`
|
html`<ha-settings-row>
|
||||||
<ha-settings-row>
|
<span slot="prefix">
|
||||||
|
<ha-checkbox
|
||||||
|
@change=${this._handleRowCheckboxClick}
|
||||||
|
.checked=${this.analytics?.preferences[preference.key]}
|
||||||
|
.preference=${preference.key}
|
||||||
|
name=${preference.key}
|
||||||
|
>
|
||||||
|
</ha-checkbox>
|
||||||
|
${!baseEnabled
|
||||||
|
? html`<paper-tooltip animation-delay="0" position="right">
|
||||||
|
You need to enable basic analytics for this option to be
|
||||||
|
available
|
||||||
|
</paper-tooltip>`
|
||||||
|
: ""}
|
||||||
|
</span>
|
||||||
<span slot="heading" data-for=${preference.key}>
|
<span slot="heading" data-for=${preference.key}>
|
||||||
${preference.title}
|
${preference.title}
|
||||||
</span>
|
</span>
|
||||||
<span slot="description" data-for=${preference.key}>
|
<span slot="description" data-for=${preference.key}>
|
||||||
${preference.description}
|
${preference.description}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
</ha-settings-row>`
|
||||||
<ha-switch
|
|
||||||
@change=${this._handleRowClick}
|
|
||||||
.checked=${this.analytics?.preferences[preference.key]}
|
|
||||||
.preference=${preference.key}
|
|
||||||
name=${preference.key}
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
${!baseEnabled
|
|
||||||
? html`
|
|
||||||
<paper-tooltip animation-delay="0" position="right">
|
|
||||||
You need to enable basic analytics for this option to be
|
|
||||||
available
|
|
||||||
</paper-tooltip>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</span>
|
|
||||||
</ha-settings-row>
|
|
||||||
`
|
|
||||||
)}
|
)}
|
||||||
<ha-settings-row>
|
<ha-settings-row>
|
||||||
<span slot="heading" data-for="diagnostics"> Diagnostics </span>
|
<span slot="prefix">
|
||||||
<span slot="description" data-for="diagnostics">
|
<ha-checkbox
|
||||||
Share crash reports when unexpected errors occur.
|
@change=${this._handleRowCheckboxClick}
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
@change=${this._handleRowClick}
|
|
||||||
.checked=${this.analytics?.preferences.diagnostics}
|
.checked=${this.analytics?.preferences.diagnostics}
|
||||||
.preference=${"diagnostics"}
|
.preference=${"diagnostics"}
|
||||||
.disabled=${loading}
|
.disabled=${loading}
|
||||||
name="diagnostics"
|
name="diagnostics"
|
||||||
>
|
>
|
||||||
</ha-switch>
|
</ha-checkbox>
|
||||||
|
</span>
|
||||||
|
<span slot="heading" data-for="diagnostics"> Diagnostics </span>
|
||||||
|
<span slot="description" data-for="diagnostics">
|
||||||
|
Share crash reports when unexpected errors occur.
|
||||||
|
</span>
|
||||||
</ha-settings-row>
|
</ha-settings-row>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -120,23 +120,23 @@ export class HaAnalytics extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleRowClick(ev: Event) {
|
private _handleRowCheckboxClick(ev: Event) {
|
||||||
const target = ev.currentTarget as HaSwitch;
|
const checkbox = ev.currentTarget as HaCheckbox;
|
||||||
const preference = (target as any).preference;
|
const preference = (checkbox as any).preference;
|
||||||
const preferences = this.analytics ? { ...this.analytics.preferences } : {};
|
const preferences = this.analytics ? { ...this.analytics.preferences } : {};
|
||||||
|
|
||||||
if (preferences[preference] === target.checked) {
|
if (preferences[preference] === checkbox.checked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
preferences[preference] = target.checked;
|
preferences[preference] = checkbox.checked;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) &&
|
ADDITIONAL_PREFERENCES.some((entry) => entry.key === preference) &&
|
||||||
target.checked
|
checkbox.checked
|
||||||
) {
|
) {
|
||||||
preferences.base = true;
|
preferences.base = true;
|
||||||
} else if (preference === "base" && !target.checked) {
|
} else if (preference === "base" && !checkbox.checked) {
|
||||||
preferences.usage = false;
|
preferences.usage = false;
|
||||||
preferences.statistics = false;
|
preferences.statistics = false;
|
||||||
}
|
}
|
||||||
|
@@ -49,8 +49,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public value?: string;
|
@property() public value?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property() public placeholder?: string;
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-add" })
|
@property({ type: Boolean, attribute: "no-add" })
|
||||||
@@ -314,7 +312,6 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
return html`
|
return html`
|
||||||
<ha-combo-box
|
<ha-combo-box
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.helper=${this.helper}
|
|
||||||
item-value-path="area_id"
|
item-value-path="area_id"
|
||||||
item-id-path="area_id"
|
item-id-path="area_id"
|
||||||
item-label-path="name"
|
item-label-path="name"
|
||||||
@@ -409,7 +406,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
name,
|
name,
|
||||||
});
|
});
|
||||||
this._areas = [...this._areas!, area];
|
this._areas = [...this._areas!, area];
|
||||||
(this.comboBox as any).filteredItems = this._getAreas(
|
(this.comboBox as any).items = this._getAreas(
|
||||||
this._areas!,
|
this._areas!,
|
||||||
this._devices!,
|
this._devices!,
|
||||||
this._entities!,
|
this._entities!,
|
||||||
|
@@ -15,8 +15,6 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public value?: string[];
|
@property() public value?: string[];
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property() public placeholder?: string;
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-add" })
|
@property({ type: Boolean, attribute: "no-add" })
|
||||||
@@ -92,7 +90,6 @@ export class HaAreasPicker extends SubscribeMixin(LitElement) {
|
|||||||
.noAdd=${this.noAdd}
|
.noAdd=${this.noAdd}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.pickAreaLabel}
|
.label=${this.pickAreaLabel}
|
||||||
.helper=${this.helper}
|
|
||||||
.includeDomains=${this.includeDomains}
|
.includeDomains=${this.includeDomains}
|
||||||
.excludeDomains=${this.excludeDomains}
|
.excludeDomains=${this.excludeDomains}
|
||||||
.includeDeviceClasses=${this.includeDeviceClasses}
|
.includeDeviceClasses=${this.includeDeviceClasses}
|
||||||
|
@@ -5,7 +5,6 @@ import { fireEvent } from "../common/dom/fire_event";
|
|||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import "./ha-select";
|
import "./ha-select";
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
import "./ha-input-helper-text";
|
|
||||||
|
|
||||||
export interface TimeChangedEvent {
|
export interface TimeChangedEvent {
|
||||||
days?: number;
|
days?: number;
|
||||||
@@ -131,7 +130,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.label
|
${this.label
|
||||||
? html`<label>${this.label}${this.required ? " *" : ""}</label>`
|
? html`<label>${this.label}${this.required ? "*" : ""}</label>`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="time-input-wrap">
|
<div class="time-input-wrap">
|
||||||
${this.enableDay
|
${this.enableDay
|
||||||
@@ -254,9 +253,7 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
<mwc-list-item value="PM">PM</mwc-list-item>
|
<mwc-list-item value="PM">PM</mwc-list-item>
|
||||||
</ha-select>`}
|
</ha-select>`}
|
||||||
</div>
|
</div>
|
||||||
${this.helper
|
${this.helper ? html`<div class="helper">${this.helper}</div>` : ""}
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +307,6 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
border-radius: var(--mdc-shape-small, 4px) var(--mdc-shape-small, 4px) 0 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
direction: ltr;
|
|
||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@@ -354,6 +350,13 @@ export class HaBaseTimeInput extends LitElement {
|
|||||||
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, 0.87));
|
||||||
padding-left: 4px;
|
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;
|
--mdc-shape-small: 4px;
|
||||||
border-right-width: 1px;
|
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;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,68 +0,0 @@
|
|||||||
import { ListItemBase } from "@material/mwc-list/mwc-list-item-base";
|
|
||||||
import { styles } from "@material/mwc-list/mwc-list-item.css";
|
|
||||||
import { css, CSSResult, html } from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
|
||||||
|
|
||||||
@customElement("ha-clickable-list-item")
|
|
||||||
export class HaClickableListItem extends ListItemBase {
|
|
||||||
@property() public href?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disableHref = false;
|
|
||||||
|
|
||||||
// property used only in css
|
|
||||||
@property({ type: Boolean, reflect: true }) public rtl = false;
|
|
||||||
|
|
||||||
@query("a") private _anchor!: HTMLAnchorElement;
|
|
||||||
|
|
||||||
public render() {
|
|
||||||
const r = super.render();
|
|
||||||
const href = this.href || "";
|
|
||||||
|
|
||||||
return html`${this.disableHref
|
|
||||||
? html`<a aria-role="option">${r}</a>`
|
|
||||||
: html`<a aria-role="option" href=${href}>${r}</a>`}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
firstUpdated() {
|
|
||||||
super.firstUpdated();
|
|
||||||
this.addEventListener("keydown", (ev) => {
|
|
||||||
if (ev.key === "Enter" || ev.key === " ") {
|
|
||||||
this._anchor.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [
|
|
||||||
styles,
|
|
||||||
css`
|
|
||||||
:host {
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
:host([rtl]) span {
|
|
||||||
margin-left: var(--mdc-list-item-graphic-margin, 20px) !important;
|
|
||||||
margin-right: 0px !important;
|
|
||||||
}
|
|
||||||
:host([graphic="avatar"]:not([twoLine])),
|
|
||||||
:host([graphic="icon"]:not([twoLine])) {
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: var(--mdc-list-side-padding, 20px);
|
|
||||||
padding-right: var(--mdc-list-side-padding, 20px);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-clickable-list-item": HaClickableListItem;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -64,8 +64,6 @@ export class HaComboBox extends LitElement {
|
|||||||
|
|
||||||
@property() public validationMessage?: string;
|
@property() public validationMessage?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public invalid?: boolean;
|
@property({ type: Boolean }) public invalid?: boolean;
|
||||||
@@ -149,8 +147,6 @@ export class HaComboBox extends LitElement {
|
|||||||
.suffix=${html`<div style="width: 28px;"></div>`}
|
.suffix=${html`<div style="width: 28px;"></div>`}
|
||||||
.icon=${this.icon}
|
.icon=${this.icon}
|
||||||
.invalid=${this.invalid}
|
.invalid=${this.invalid}
|
||||||
.helper=${this.helper}
|
|
||||||
helperPersistent
|
|
||||||
>
|
>
|
||||||
<slot name="icon" slot="leadingIcon"></slot>
|
<slot name="icon" slot="leadingIcon"></slot>
|
||||||
</ha-textfield>
|
</ha-textfield>
|
||||||
@@ -250,18 +246,6 @@ export class HaComboBox extends LitElement {
|
|||||||
top: -7px;
|
top: -7px;
|
||||||
right: 36px;
|
right: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context([style*="direction: rtl;"]) .toggle-button {
|
|
||||||
left: 12px;
|
|
||||||
right: auto;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
:host-context([style*="direction: rtl;"]) .clear-button {
|
|
||||||
--mdc-icon-size: 20px;
|
|
||||||
top: -7px;
|
|
||||||
left: 36px;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,15 +39,11 @@ export class HaDateInput extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<ha-textfield
|
return html`<ha-textfield
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
iconTrailing
|
iconTrailing
|
||||||
helperPersistent
|
|
||||||
@click=${this._openDialog}
|
@click=${this._openDialog}
|
||||||
.value=${this.value
|
.value=${this.value
|
||||||
? formatDateNumeric(new Date(this.value), this.locale)
|
? formatDateNumeric(new Date(this.value), this.locale)
|
||||||
|
@@ -98,10 +98,6 @@ export class HaDialog extends DialogBase {
|
|||||||
margin-left: 40px;
|
margin-left: 40px;
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
:host-context([style*="direction: rtl;"]) .dialog-actions {
|
|
||||||
left: 0px !important;
|
|
||||||
right: auto !important;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { Fab } from "@material/mwc-fab";
|
import { Fab } from "@material/mwc-fab";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { css } from "lit";
|
|
||||||
|
|
||||||
@customElement("ha-fab")
|
@customElement("ha-fab")
|
||||||
export class HaFab extends Fab {
|
export class HaFab extends Fab {
|
||||||
@@ -8,17 +7,6 @@ export class HaFab extends Fab {
|
|||||||
super.firstUpdated(changedProperties);
|
super.firstUpdated(changedProperties);
|
||||||
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
||||||
}
|
}
|
||||||
|
|
||||||
static override styles = Fab.styles.concat([
|
|
||||||
css`
|
|
||||||
:host-context([style*="direction: rtl;"])
|
|
||||||
.mdc-fab--extended
|
|
||||||
.mdc-fab__icon {
|
|
||||||
margin-left: 12px !important;
|
|
||||||
margin-right: calc(12px - 20px) !important;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -176,24 +176,10 @@ export class HaFileUpload extends LitElement {
|
|||||||
.mdc-text-field__icon--leading {
|
.mdc-text-field__icon--leading {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
:host-context([style*="direction: rtl;"])
|
|
||||||
.mdc-text-field__icon--leading {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
.mdc-text-field--filled .mdc-floating-label--float-above {
|
.mdc-text-field--filled .mdc-floating-label--float-above {
|
||||||
transform: scale(0.75);
|
transform: scale(0.75);
|
||||||
top: 8px;
|
top: 8px;
|
||||||
}
|
}
|
||||||
:host-context([style*="direction: rtl;"]) .mdc-floating-label {
|
|
||||||
left: initial;
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
:host-context([style*="direction: rtl;"])
|
|
||||||
.mdc-text-field--filled
|
|
||||||
.mdc-floating-label {
|
|
||||||
left: initial;
|
|
||||||
right: 48px;
|
|
||||||
}
|
|
||||||
.dragged:before {
|
.dragged:before {
|
||||||
position: var(--layout-fit_-_position);
|
position: var(--layout-fit_-_position);
|
||||||
top: var(--layout-fit_-_top);
|
top: var(--layout-fit_-_top);
|
||||||
|
@@ -77,7 +77,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="root" part="root">
|
<div class="root">
|
||||||
${this.error && this.error.base
|
${this.error && this.error.base
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="error">
|
<ha-alert alert-type="error">
|
||||||
@@ -173,6 +173,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
// .root has overflow: auto to avoid margin collapse
|
||||||
return css`
|
return css`
|
||||||
.root {
|
.root {
|
||||||
margin-bottom: -24px;
|
margin-bottom: -24px;
|
||||||
|
@@ -31,8 +31,6 @@ export class HaIconPicker extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property() public placeholder?: string;
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property() public fallbackPath?: string;
|
@property() public fallbackPath?: string;
|
||||||
@@ -59,7 +57,6 @@ export class HaIconPicker extends LitElement {
|
|||||||
allow-custom-value
|
allow-custom-value
|
||||||
.filteredItems=${iconItems}
|
.filteredItems=${iconItems}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.placeholder=${this.placeholder}
|
.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}}"
|
value="{{value}}"
|
||||||
></ha-slider>
|
></ha-slider>
|
||||||
</div>
|
</div>
|
||||||
<template is="dom-if" if="[[helper]]">
|
|
||||||
<ha-input-helper-text>[[helper]]</ha-input-helper-text>
|
|
||||||
</template>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTitle() {
|
_getTitle() {
|
||||||
return `${this.caption}${this.caption && this.required ? " *" : ""}`;
|
return `${this.caption}${this.required ? "*" : ""}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@@ -65,7 +62,6 @@ class HaLabeledSlider extends PolymerElement {
|
|||||||
max: Number,
|
max: Number,
|
||||||
pin: Boolean,
|
pin: Boolean,
|
||||||
step: Number,
|
step: Number,
|
||||||
helper: String,
|
|
||||||
|
|
||||||
extra: {
|
extra: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@@ -3,6 +3,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { computeDomain } from "../common/entity/compute_domain";
|
||||||
import { subscribeNotifications } from "../data/persistent_notification";
|
import { subscribeNotifications } from "../data/persistent_notification";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
@@ -42,15 +43,18 @@ class HaMenuButton extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const hasNotifications =
|
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`
|
return html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
.label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||||
.path=${mdiMenu}
|
.path=${mdiMenu}
|
||||||
@click=${this._toggleMenu}
|
@click=${this._toggleMenu}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
${hasNotifications ? html`<div class="dot"></div>` : ""}
|
${hasNotifications ? html` <div class="dot"></div> ` : ""}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,79 +0,0 @@
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { roundWithOneDecimal } from "../util/calculate";
|
|
||||||
import "./ha-bar";
|
|
||||||
import "./ha-settings-row";
|
|
||||||
|
|
||||||
@customElement("ha-metric")
|
|
||||||
class HaMetric extends LitElement {
|
|
||||||
@property({ type: Number }) public value!: number;
|
|
||||||
|
|
||||||
@property({ type: String }) public heading!: string;
|
|
||||||
|
|
||||||
@property({ type: String }) public tooltip?: string;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
const roundedValue = roundWithOneDecimal(this.value);
|
|
||||||
return html`
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading"> ${this.heading} </span>
|
|
||||||
<div slot="description" .title=${this.tooltip ?? ""}>
|
|
||||||
<span class="value"> ${roundedValue} % </span>
|
|
||||||
<ha-bar
|
|
||||||
class=${classMap({
|
|
||||||
"target-warning": roundedValue > 50,
|
|
||||||
"target-critical": roundedValue > 85,
|
|
||||||
})}
|
|
||||||
.value=${this.value}
|
|
||||||
></ha-bar>
|
|
||||||
</div>
|
|
||||||
</ha-settings-row>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
ha-settings-row {
|
|
||||||
padding: 0;
|
|
||||||
height: 54px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
ha-settings-row > div[slot="description"] {
|
|
||||||
white-space: normal;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
ha-bar {
|
|
||||||
--ha-bar-primary-color: var(
|
|
||||||
--metric-bar-ok-color,
|
|
||||||
var(--success-color)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
.target-warning {
|
|
||||||
--ha-bar-primary-color: var(
|
|
||||||
--metric-bar-warning-color,
|
|
||||||
var(--warning-color)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
.target-critical {
|
|
||||||
--ha-bar-primary-color: var(
|
|
||||||
--metric-bar-critical-color,
|
|
||||||
var(--error-color)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
width: 48px;
|
|
||||||
padding-right: 4px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-metric": HaMetric;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,97 +0,0 @@
|
|||||||
import "@material/mwc-list/mwc-list";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import type { PageNavigation } from "../layouts/hass-tabs-subpage";
|
|
||||||
import type { HomeAssistant } from "../types";
|
|
||||||
import "./ha-clickable-list-item";
|
|
||||||
import "./ha-icon-next";
|
|
||||||
import "./ha-svg-icon";
|
|
||||||
|
|
||||||
@customElement("ha-navigation-list")
|
|
||||||
class HaNavigationList extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public pages!: PageNavigation[];
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public hasSecondary = false;
|
|
||||||
|
|
||||||
public render(): TemplateResult {
|
|
||||||
return html`
|
|
||||||
<mwc-list>
|
|
||||||
${this.pages.map(
|
|
||||||
(page) => html`
|
|
||||||
<ha-clickable-list-item
|
|
||||||
graphic="avatar"
|
|
||||||
.twoline=${this.hasSecondary}
|
|
||||||
.hasMeta=${!this.narrow}
|
|
||||||
@click=${this._entryClicked}
|
|
||||||
href=${page.path}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
slot="graphic"
|
|
||||||
class=${page.iconColor ? "icon-background" : ""}
|
|
||||||
.style="background-color: ${page.iconColor || "undefined"}"
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${page.iconPath}></ha-svg-icon>
|
|
||||||
</div>
|
|
||||||
<span>${page.name}</span>
|
|
||||||
${this.hasSecondary
|
|
||||||
? html`<span slot="secondary">${page.description}</span>`
|
|
||||||
: ""}
|
|
||||||
${!this.narrow
|
|
||||||
? html`<ha-icon-next slot="meta"></ha-icon-next>`
|
|
||||||
: ""}
|
|
||||||
</ha-clickable-list-item>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
</mwc-list>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _entryClicked(ev) {
|
|
||||||
ev.currentTarget.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles: CSSResultGroup = css`
|
|
||||||
:host {
|
|
||||||
--mdc-list-vertical-padding: 0;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
ha-svg-icon,
|
|
||||||
ha-icon-next {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
ha-svg-icon {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
.icon-background {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.icon-background ha-svg-icon {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
ha-clickable-list-item {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: var(--navigation-list-item-title-font-size);
|
|
||||||
padding: var(--navigation-list-item-padding) 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-navigation-list": HaNavigationList;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -163,9 +163,6 @@ export class HaNetwork extends LitElement {
|
|||||||
|
|
||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
--paper-time-input-justify-content: flex-end;
|
|
||||||
--settings-row-content-display: contents;
|
|
||||||
--settings-row-prefix-display: contents;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
span[slot="heading"],
|
span[slot="heading"],
|
||||||
|
@@ -47,10 +47,6 @@ export class HaSelect extends SelectBase {
|
|||||||
.mdc-select__anchor {
|
.mdc-select__anchor {
|
||||||
width: var(--ha-select-min-width, 200px);
|
width: var(--ha-select-min-width, 200px);
|
||||||
}
|
}
|
||||||
:host-context([style*="direction: rtl;"]) .mdc-floating-label {
|
|
||||||
right: 16px !important;
|
|
||||||
left: initial !important;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,6 @@ export class HaAddonSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -25,7 +23,6 @@ export class HaAddonSelector extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
|
@@ -18,8 +18,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@state() public _configEntries?: ConfigEntry[];
|
@state() public _configEntries?: ConfigEntry[];
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -49,7 +47,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
no-add
|
no-add
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.entityFilter=${this._filterEntities}
|
.entityFilter=${this._filterEntities}
|
||||||
@@ -69,7 +66,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
<ha-areas-picker
|
<ha-areas-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
|
||||||
.pickAreaLabel=${this.label}
|
.pickAreaLabel=${this.label}
|
||||||
no-add
|
no-add
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
|
@@ -16,8 +16,6 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -34,7 +32,6 @@ export class HaSelectorAttribute extends SubscribeMixin(LitElement) {
|
|||||||
this.context?.filter_entity}
|
this.context?.filter_entity}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
allow-custom-value
|
allow-custom-value
|
||||||
|
@@ -4,7 +4,6 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-formfield";
|
import "../ha-formfield";
|
||||||
import "../ha-switch";
|
import "../ha-switch";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
|
|
||||||
@customElement("ha-selector-boolean")
|
@customElement("ha-selector-boolean")
|
||||||
export class HaBooleanSelector extends LitElement {
|
export class HaBooleanSelector extends LitElement {
|
||||||
@@ -14,23 +13,16 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
||||||
<ha-formfield alignEnd spaceBetween .label=${this.label}>
|
|
||||||
<ha-switch
|
<ha-switch
|
||||||
.checked=${this.value}
|
.checked=${this.value}
|
||||||
@change=${this._handleChange}
|
@change=${this._handleChange}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-formfield>
|
</ha-formfield>`;
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev) {
|
private _handleChange(ev) {
|
||||||
@@ -43,10 +35,12 @@ export class HaBooleanSelector extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-formfield {
|
:host {
|
||||||
display: flex;
|
|
||||||
height: 56px;
|
height: 56px;
|
||||||
align-items: center;
|
display: flex;
|
||||||
|
}
|
||||||
|
ha-formfield {
|
||||||
|
width: 100%;
|
||||||
--mdc-typography-body2-font-size: 1em;
|
--mdc-typography-body2-font-size: 1em;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -16,8 +16,6 @@ export class HaColorRGBSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -26,11 +24,9 @@ export class HaColorRGBSelector extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
type="color"
|
type="color"
|
||||||
helperPersistent
|
|
||||||
.value=${this.value ? rgb2hex(this.value as any) : ""}
|
.value=${this.value ? rgb2hex(this.value as any) : ""}
|
||||||
.label=${this.label || ""}
|
.label=${this.label || ""}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.helper=${this.helper}
|
|
||||||
.disalbled=${this.disabled}
|
.disalbled=${this.disabled}
|
||||||
@change=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
|
@@ -15,8 +15,6 @@ export class HaColorTempSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -26,12 +24,11 @@ export class HaColorTempSelector extends LitElement {
|
|||||||
<ha-labeled-slider
|
<ha-labeled-slider
|
||||||
pin
|
pin
|
||||||
icon="hass:thermometer"
|
icon="hass:thermometer"
|
||||||
.caption=${this.label || ""}
|
.caption=${this.label}
|
||||||
.min=${this.selector.color_temp.min_mireds ?? 153}
|
.min=${this.selector.color_temp.min_mireds ?? 153}
|
||||||
.max=${this.selector.color_temp.max_mireds ?? 500}
|
.max=${this.selector.color_temp.max_mireds ?? 500}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this.helper}
|
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
@change=${this._valueChanged}
|
@change=${this._valueChanged}
|
||||||
></ha-labeled-slider>
|
></ha-labeled-slider>
|
||||||
|
@@ -14,8 +14,6 @@ export class HaDateSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -28,7 +26,6 @@ export class HaDateSelector extends LitElement {
|
|||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.helper=${this.helper}
|
|
||||||
>
|
>
|
||||||
</ha-date-input>
|
</ha-date-input>
|
||||||
`;
|
`;
|
||||||
|
@@ -6,7 +6,6 @@ import type { HomeAssistant } from "../../types";
|
|||||||
import "../ha-date-input";
|
import "../ha-date-input";
|
||||||
import type { HaDateInput } from "../ha-date-input";
|
import type { HaDateInput } from "../ha-date-input";
|
||||||
import "../ha-time-input";
|
import "../ha-time-input";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
import type { HaTimeInput } from "../ha-time-input";
|
import type { HaTimeInput } from "../ha-time-input";
|
||||||
|
|
||||||
@customElement("ha-selector-datetime")
|
@customElement("ha-selector-datetime")
|
||||||
@@ -19,8 +18,6 @@ export class HaDateTimeSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -33,7 +30,6 @@ export class HaDateTimeSelector extends LitElement {
|
|||||||
const values = this.value?.split(" ");
|
const values = this.value?.split(" ");
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="input">
|
|
||||||
<ha-date-input
|
<ha-date-input
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
@@ -51,10 +47,6 @@ export class HaDateTimeSelector extends LitElement {
|
|||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
</div>
|
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +58,7 @@ export class HaDateTimeSelector extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
.input {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@@ -17,8 +17,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@state() public _configEntries?: ConfigEntry[];
|
@state() public _configEntries?: ConfigEntry[];
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -45,7 +43,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||||
? [this.selector.device.entity.device_class]
|
? [this.selector.device.entity.device_class]
|
||||||
@@ -65,15 +62,12 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
<ha-devices-picker
|
<ha-devices-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
|
||||||
.deviceFilter=${this._filterDevices}
|
|
||||||
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
.includeDeviceClasses=${this.selector.device.entity?.device_class
|
||||||
? [this.selector.device.entity.device_class]
|
? [this.selector.device.entity.device_class]
|
||||||
: undefined}
|
: undefined}
|
||||||
.includeDomains=${this.selector.device.entity?.domain
|
.includeDomains=${this.selector.device.entity?.domain
|
||||||
? [this.selector.device.entity.domain]
|
? [this.selector.device.entity.domain]
|
||||||
: undefined}
|
: undefined}
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
></ha-devices-picker>
|
></ha-devices-picker>
|
||||||
`;
|
`;
|
||||||
|
@@ -23,8 +23,6 @@ export class HaEntitySelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -35,7 +33,6 @@ export class HaEntitySelector extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.includeEntities=${this.selector.entity.include_entities}
|
.includeEntities=${this.selector.entity.include_entities}
|
||||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||||
.entityFilter=${this._filterEntities}
|
.entityFilter=${this._filterEntities}
|
||||||
@@ -50,11 +47,9 @@ export class HaEntitySelector extends LitElement {
|
|||||||
<ha-entities-picker
|
<ha-entities-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
.entityFilter=${this._filterEntities}
|
||||||
.includeEntities=${this.selector.entity.include_entities}
|
.includeEntities=${this.selector.entity.include_entities}
|
||||||
.excludeEntities=${this.selector.entity.exclude_entities}
|
.excludeEntities=${this.selector.entity.exclude_entities}
|
||||||
.entityFilter=${this._filterEntities}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
></ha-entities-picker>
|
></ha-entities-picker>
|
||||||
`;
|
`;
|
||||||
|
@@ -15,8 +15,6 @@ export class HaIconSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
@property({ type: Boolean }) public required = true;
|
||||||
@@ -28,7 +26,6 @@ export class HaIconSelector extends LitElement {
|
|||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this.helper}
|
|
||||||
.fallbackPath=${this.selector.icon.fallbackPath}
|
.fallbackPath=${this.selector.icon.fallbackPath}
|
||||||
.placeholder=${this.selector.icon.placeholder}
|
.placeholder=${this.selector.icon.placeholder}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { css, html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
@@ -20,8 +20,6 @@ export class HaLocationSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -29,7 +27,6 @@ export class HaLocationSelector extends LitElement {
|
|||||||
<ha-locations-editor
|
<ha-locations-editor
|
||||||
class="flex"
|
class="flex"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.helper=${this.helper}
|
|
||||||
.locations=${this._location(this.selector, this.value)}
|
.locations=${this._location(this.selector, this.value)}
|
||||||
@location-updated=${this._locationChanged}
|
@location-updated=${this._locationChanged}
|
||||||
@radius-updated=${this._radiusChanged}
|
@radius-updated=${this._radiusChanged}
|
||||||
@@ -76,13 +73,6 @@ export class HaLocationSelector extends LitElement {
|
|||||||
const radius = ev.detail.radius;
|
const radius = ev.detail.radius;
|
||||||
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
|
fireEvent(this, "value-changed", { value: { ...this.value, radius } });
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -33,8 +33,6 @@ export class HaMediaSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean, reflect: true }) public required = true;
|
@property({ type: Boolean, reflect: true }) public required = true;
|
||||||
@@ -88,7 +86,6 @@ export class HaMediaSelector extends LitElement {
|
|||||||
.label=${this.label ||
|
.label=${this.label ||
|
||||||
this.hass.localize("ui.components.selectors.media.pick_media_player")}
|
this.hass.localize("ui.components.selectors.media.pick_media_player")}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.helper=${this.helper}
|
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
include-domains='["media_player"]'
|
include-domains='["media_player"]'
|
||||||
allow-custom-entity
|
allow-custom-entity
|
||||||
|
@@ -6,7 +6,6 @@ import { NumberSelector } from "../../data/selector";
|
|||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-slider";
|
import "../ha-slider";
|
||||||
import "../ha-textfield";
|
import "../ha-textfield";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
|
|
||||||
@customElement("ha-selector-number")
|
@customElement("ha-selector-number")
|
||||||
export class HaNumberSelector extends LitElement {
|
export class HaNumberSelector extends LitElement {
|
||||||
@@ -27,13 +26,8 @@ export class HaNumberSelector extends LitElement {
|
|||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const isBox = this.selector.number.mode === "box";
|
return html`${this.selector.number.mode !== "box"
|
||||||
|
? html`${this.label}${this.required ? "*" : ""}<ha-slider
|
||||||
return html`
|
|
||||||
${this.label ? html`${this.label}${this.required ? " *" : ""}` : ""}
|
|
||||||
<div class="input">
|
|
||||||
${!isBox
|
|
||||||
? html`<ha-slider
|
|
||||||
.min=${this.selector.number.min}
|
.min=${this.selector.number.min}
|
||||||
.max=${this.selector.number.max}
|
.max=${this.selector.number.max}
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
@@ -57,7 +51,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
.value=${this.value ?? ""}
|
.value=${this.value ?? ""}
|
||||||
.step=${this.selector.number.step ?? 1}
|
.step=${this.selector.number.step ?? 1}
|
||||||
helperPersistent
|
helperPersistent
|
||||||
.helper=${isBox ? this.helper : undefined}
|
.helper=${this.helper}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.suffix=${this.selector.number.unit_of_measurement}
|
.suffix=${this.selector.number.unit_of_measurement}
|
||||||
@@ -66,12 +60,7 @@ export class HaNumberSelector extends LitElement {
|
|||||||
?no-spinner=${this.selector.number.mode !== "box"}
|
?no-spinner=${this.selector.number.mode !== "box"}
|
||||||
@input=${this._handleInputChange}
|
@input=${this._handleInputChange}
|
||||||
>
|
>
|
||||||
</ha-textfield>
|
</ha-textfield>`;
|
||||||
</div>
|
|
||||||
${!isBox && this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
@@ -103,11 +92,10 @@ export class HaNumberSelector extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.input {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
direction: ltr;
|
|
||||||
}
|
}
|
||||||
ha-slider {
|
ha-slider {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@@ -3,7 +3,6 @@ import { customElement, property } from "lit/decorators";
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-yaml-editor";
|
import "../ha-yaml-editor";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
|
|
||||||
@customElement("ha-selector-object")
|
@customElement("ha-selector-object")
|
||||||
export class HaObjectSelector extends LitElement {
|
export class HaObjectSelector extends LitElement {
|
||||||
@@ -13,8 +12,6 @@ export class HaObjectSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property() public placeholder?: string;
|
@property() public placeholder?: string;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
@@ -25,15 +22,11 @@ export class HaObjectSelector extends LitElement {
|
|||||||
return html`<ha-yaml-editor
|
return html`<ha-yaml-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.readonly=${this.disabled}
|
.readonly=${this.disabled}
|
||||||
.label=${this.label}
|
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.placeholder=${this.placeholder}
|
.placeholder=${this.placeholder}
|
||||||
.defaultValue=${this.value}
|
.defaultValue=${this.value}
|
||||||
@value-changed=${this._handleChange}
|
@value-changed=${this._handleChange}
|
||||||
></ha-yaml-editor>
|
></ha-yaml-editor>`;
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""} `;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleChange(ev) {
|
private _handleChange(ev) {
|
||||||
|
@@ -58,7 +58,6 @@ export class HaSelectSelector extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
${this._renderHelper()}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +76,6 @@ export class HaSelectSelector extends LitElement {
|
|||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
${this._renderHelper()}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +107,6 @@ export class HaSelectSelector extends LitElement {
|
|||||||
item-label-path="label"
|
item-label-path="label"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required && !value.length}
|
.required=${this.required && !value.length}
|
||||||
.value=${this._filter}
|
.value=${this._filter}
|
||||||
@@ -134,7 +131,6 @@ export class HaSelectSelector extends LitElement {
|
|||||||
item-label-path="label"
|
item-label-path="label"
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.helper=${this.helper}
|
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.items=${options}
|
.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" {
|
private get _mode(): "list" | "dropdown" {
|
||||||
return (
|
return (
|
||||||
this.selector.select.mode ||
|
this.selector.select.mode ||
|
||||||
|
@@ -26,8 +26,6 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@state() private _entityPlaformLookup?: Record<string, string>;
|
@state() private _entityPlaformLookup?: Record<string, string>;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
@state() private _configEntries?: ConfigEntry[];
|
||||||
@@ -66,7 +64,6 @@ export class HaTargetSelector extends SubscribeMixin(LitElement) {
|
|||||||
return html`<ha-target-picker
|
return html`<ha-target-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this.value}
|
.value=${this.value}
|
||||||
.helper=${this.helper}
|
|
||||||
.deviceFilter=${this._filterDevices}
|
.deviceFilter=${this._filterDevices}
|
||||||
.entityRegFilter=${this._filterRegEntities}
|
.entityRegFilter=${this._filterRegEntities}
|
||||||
.entityFilter=${this._filterEntities}
|
.entityFilter=${this._filterEntities}
|
||||||
|
@@ -1,56 +0,0 @@
|
|||||||
import { html, LitElement } from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import "../ha-code-editor";
|
|
||||||
import "../ha-input-helper-text";
|
|
||||||
|
|
||||||
@customElement("ha-selector-template")
|
|
||||||
export class HaTemplateSelector extends LitElement {
|
|
||||||
@property() public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property() public value?: string;
|
|
||||||
|
|
||||||
@property() public label?: string;
|
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = true;
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
return html`
|
|
||||||
${this.label
|
|
||||||
? html`<p>${this.label}${this.required ? " *" : ""}</p>`
|
|
||||||
: ""}
|
|
||||||
<ha-code-editor
|
|
||||||
mode="jinja2"
|
|
||||||
.hass=${this.hass}
|
|
||||||
.value=${this.value}
|
|
||||||
.readOnly=${this.disabled}
|
|
||||||
autofocus
|
|
||||||
autocomplete-entities
|
|
||||||
@value-changed=${this._handleChange}
|
|
||||||
dir="ltr"
|
|
||||||
></ha-code-editor>
|
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleChange(ev) {
|
|
||||||
const value = ev.target.value;
|
|
||||||
if (this.value === value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fireEvent(this, "value-changed", { value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-selector-template": HaTemplateSelector;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,8 +14,6 @@ export class HaTimeSelector extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
@@ -27,7 +25,6 @@ export class HaTimeSelector extends LitElement {
|
|||||||
.locale=${this.hass.locale}
|
.locale=${this.hass.locale}
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.helper=${this.helper}
|
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
enable-second
|
enable-second
|
||||||
></ha-time-input>
|
></ha-time-input>
|
||||||
|
@@ -18,7 +18,6 @@ import "./ha-selector-number";
|
|||||||
import "./ha-selector-object";
|
import "./ha-selector-object";
|
||||||
import "./ha-selector-select";
|
import "./ha-selector-select";
|
||||||
import "./ha-selector-target";
|
import "./ha-selector-target";
|
||||||
import "./ha-selector-template";
|
|
||||||
import "./ha-selector-text";
|
import "./ha-selector-text";
|
||||||
import "./ha-selector-time";
|
import "./ha-selector-time";
|
||||||
import "./ha-selector-icon";
|
import "./ha-selector-icon";
|
||||||
|
@@ -472,7 +472,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
ha-settings-row {
|
ha-settings-row {
|
||||||
--paper-time-input-justify-content: flex-end;
|
--paper-time-input-justify-content: flex-end;
|
||||||
--settings-row-content-width: 100%;
|
--settings-row-content-width: 100%;
|
||||||
--settings-row-prefix-display: contents;
|
|
||||||
border-top: var(
|
border-top: var(
|
||||||
--service-control-items-border-top,
|
--service-control-items-border-top,
|
||||||
1px solid var(--divider-color)
|
1px solid var(--divider-color)
|
||||||
|
@@ -47,7 +47,7 @@ export class HaSettingsRow extends LitElement {
|
|||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
:host(:not([narrow])) .content {
|
:host(:not([narrow])) .content {
|
||||||
display: var(--settings-row-content-display, flex);
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
@@ -68,7 +68,7 @@ export class HaSettingsRow extends LitElement {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
.prefix-wrap {
|
.prefix-wrap {
|
||||||
display: var(--settings-row-prefix-display);
|
display: contents;
|
||||||
}
|
}
|
||||||
:host([narrow]) .prefix-wrap {
|
:host([narrow]) .prefix-wrap {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@@ -36,9 +36,10 @@ import memoizeOne from "memoize-one";
|
|||||||
import { LocalStorage } from "../common/decorators/local-storage";
|
import { LocalStorage } from "../common/decorators/local-storage";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
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 { stringCompare } from "../common/string/compare";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import { throttle } from "../common/util/throttle";
|
|
||||||
import { ActionHandlerDetail } from "../data/lovelace";
|
import { ActionHandlerDetail } from "../data/lovelace";
|
||||||
import {
|
import {
|
||||||
PersistentNotification,
|
PersistentNotification,
|
||||||
@@ -293,7 +294,11 @@ class HaSidebar extends LitElement {
|
|||||||
toggleAttribute(this, "rtl", computeRTL(this.hass));
|
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) {
|
if (!SUPPORT_SCROLL_IF_NEEDED) {
|
||||||
return;
|
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() {
|
private _renderHeader() {
|
||||||
return html`<div
|
return html`<div
|
||||||
class="menu"
|
class="menu"
|
||||||
@@ -529,9 +519,14 @@ class HaSidebar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _renderNotifications() {
|
private _renderNotifications() {
|
||||||
const notificationCount = this._notifications
|
let notificationCount = this._notifications
|
||||||
? this._notifications.length
|
? this._notifications.length
|
||||||
: 0;
|
: 0;
|
||||||
|
for (const entityId in this.hass.states) {
|
||||||
|
if (computeDomain(entityId) === "configurator") {
|
||||||
|
notificationCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return html`<div
|
return html`<div
|
||||||
class="notifications-container"
|
class="notifications-container"
|
||||||
@@ -1039,8 +1034,6 @@ class HaSidebar extends LitElement {
|
|||||||
|
|
||||||
.notification-badge,
|
.notification-badge,
|
||||||
.configuration-badge {
|
.configuration-badge {
|
||||||
left: calc(var(--app-drawer-width) - 42px);
|
|
||||||
position: absolute;
|
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@@ -1051,6 +1044,9 @@ class HaSidebar extends LitElement {
|
|||||||
padding: 0px 6px;
|
padding: 0px 6px;
|
||||||
color: var(--text-accent-color, var(--text-primary-color));
|
color: var(--text-accent-color, var(--text-primary-color));
|
||||||
}
|
}
|
||||||
|
.configuration-badge {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
ha-svg-icon + .notification-badge,
|
ha-svg-icon + .notification-badge,
|
||||||
ha-svg-icon + .configuration-badge {
|
ha-svg-icon + .configuration-badge {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@@ -43,7 +43,6 @@ import type { HaEntityPickerEntityFilterFunc } from "./entity/ha-entity-picker";
|
|||||||
import "./ha-area-picker";
|
import "./ha-area-picker";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
import "./ha-input-helper-text";
|
|
||||||
|
|
||||||
@customElement("ha-target-picker")
|
@customElement("ha-target-picker")
|
||||||
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||||
@@ -53,8 +52,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show only targets with entities from specific domains.
|
* Show only targets with entities from specific domains.
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -216,11 +213,7 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>`;
|
||||||
|
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""} `;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _showPicker(ev) {
|
private async _showPicker(ev) {
|
||||||
@@ -616,10 +609,6 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
|||||||
opacity: var(--light-disabled-opacity);
|
opacity: var(--light-disabled-opacity);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
:host-context([style*="direction: rtl;"]) .mdc-chip__icon {
|
|
||||||
margin-right: -14px !important;
|
|
||||||
margin-left: 4px !important;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -91,19 +91,6 @@ export class HaTextField extends TextFieldBase {
|
|||||||
.mdc-text-field {
|
.mdc-text-field {
|
||||||
overflow: var(--text-field-overflow);
|
overflow: var(--text-field-overflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context([style*="direction: rtl;"]) .mdc-floating-label {
|
|
||||||
right: 10px !important;
|
|
||||||
left: initial !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context([style*="direction: rtl;"])
|
|
||||||
.mdc-text-field--with-leading-icon.mdc-text-field--filled
|
|
||||||
.mdc-floating-label {
|
|
||||||
max-width: calc(100% - 48px);
|
|
||||||
right: 48px !important;
|
|
||||||
left: initial !important;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -14,8 +14,6 @@ export class HaTimeInput extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property({ type: Boolean }) public required = false;
|
@property({ type: Boolean }) public required = false;
|
||||||
@@ -48,7 +46,6 @@ export class HaTimeInput extends LitElement {
|
|||||||
@value-changed=${this._timeChanged}
|
@value-changed=${this._timeChanged}
|
||||||
.enableSecond=${this.enableSecond}
|
.enableSecond=${this.enableSecond}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.helper=${this.helper}
|
|
||||||
></ha-base-time-input>
|
></ha-base-time-input>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
import { mdiLightbulbOutline } from "@mdi/js";
|
|
||||||
import { css, html, LitElement } from "lit";
|
|
||||||
import { customElement } from "lit/decorators";
|
|
||||||
|
|
||||||
import "./ha-svg-icon";
|
|
||||||
|
|
||||||
@customElement("ha-tip")
|
|
||||||
class HaTip extends LitElement {
|
|
||||||
public render() {
|
|
||||||
return html`
|
|
||||||
<ha-svg-icon .path=${mdiLightbulbOutline}></ha-svg-icon>
|
|
||||||
<span class="prefix">Tip!</span>
|
|
||||||
<span class="text"><slot></slot></span>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-left: 2px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.prefix {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-tip": HaTip;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -61,9 +61,7 @@ export class HaYamlEditor extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
${this.label
|
${this.label ? html`<p>${this.label}${this.required ? "*" : ""}</p>` : ""}
|
||||||
? html`<p>${this.label}${this.required ? " *" : ""}</p>`
|
|
||||||
: ""}
|
|
||||||
<ha-code-editor
|
<ha-code-editor
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._yaml}
|
.value=${this._yaml}
|
||||||
|
@@ -19,7 +19,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
|
import type { LeafletModuleType } from "../../common/dom/setup-leaflet-map";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-input-helper-text";
|
|
||||||
import "./ha-map";
|
import "./ha-map";
|
||||||
import type { HaMap } from "./ha-map";
|
import type { HaMap } from "./ha-map";
|
||||||
|
|
||||||
@@ -51,8 +50,6 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public locations?: MarkerLocation[];
|
@property({ attribute: false }) public locations?: MarkerLocation[];
|
||||||
|
|
||||||
@property() public helper?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public autoFit = false;
|
@property({ type: Boolean }) public autoFit = false;
|
||||||
|
|
||||||
@property({ type: Number }) public zoom = 16;
|
@property({ type: Number }) public zoom = 16;
|
||||||
@@ -105,18 +102,13 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`<ha-map
|
||||||
<ha-map
|
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.layers=${this._getLayers(this._circles, this._locationMarkers)}
|
.layers=${this._getLayers(this._circles, this._locationMarkers)}
|
||||||
.zoom=${this.zoom}
|
.zoom=${this.zoom}
|
||||||
.autoFit=${this.autoFit}
|
.autoFit=${this.autoFit}
|
||||||
.darkMode=${this.darkMode}
|
.darkMode=${this.darkMode}
|
||||||
></ha-map>
|
></ha-map>`;
|
||||||
${this.helper
|
|
||||||
? html`<ha-input-helper-text>${this.helper}</ha-input-helper-text>`
|
|
||||||
: ""}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getLayers = memoizeOne(
|
private _getLayers = memoizeOne(
|
||||||
@@ -295,8 +287,11 @@ export class HaLocationsEditor extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-map {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
ha-map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@@ -151,7 +151,6 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
ha-media-player-browse {
|
ha-media-player-browse {
|
||||||
--media-browser-max-height: calc(100vh - 65px);
|
--media-browser-max-height: calc(100vh - 65px);
|
||||||
height: calc(100vh - 65px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
@media (min-width: 800px) {
|
||||||
@@ -164,7 +163,6 @@ class DialogMediaPlayerBrowse extends LitElement {
|
|||||||
ha-media-player-browse {
|
ha-media-player-browse {
|
||||||
position: initial;
|
position: initial;
|
||||||
--media-browser-max-height: 100vh - 137px;
|
--media-browser-max-height: 100vh - 137px;
|
||||||
height: 100vh - 137px;
|
|
||||||
width: 700px;
|
width: 700px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,8 +3,6 @@ import "@material/mwc-list/mwc-list";
|
|||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiArrowUpRight, mdiPlay, mdiPlus } from "@mdi/js";
|
import { mdiArrowUpRight, mdiPlay, mdiPlus } from "@mdi/js";
|
||||||
import "@polymer/paper-tooltip/paper-tooltip";
|
import "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { grid } from "@lit-labs/virtualizer/layouts/grid";
|
|
||||||
import "@lit-labs/virtualizer";
|
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -18,13 +16,16 @@ import {
|
|||||||
eventOptions,
|
eventOptions,
|
||||||
property,
|
property,
|
||||||
query,
|
query,
|
||||||
|
queryAll,
|
||||||
state,
|
state,
|
||||||
} from "lit/decorators";
|
} from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { until } from "lit/directives/until";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||||
import { debounce } from "../../common/util/debounce";
|
import { debounce } from "../../common/util/debounce";
|
||||||
|
import { getSignedPath } from "../../data/auth";
|
||||||
import type { MediaPlayerItem } from "../../data/media-player";
|
import type { MediaPlayerItem } from "../../data/media-player";
|
||||||
import {
|
import {
|
||||||
browseMediaPlayer,
|
browseMediaPlayer,
|
||||||
@@ -39,18 +40,18 @@ import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
|||||||
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
|
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
||||||
import { documentationUrl } from "../../util/documentation-url";
|
import { documentationUrl } from "../../util/documentation-url";
|
||||||
import "../entity/ha-entity-picker";
|
import "../entity/ha-entity-picker";
|
||||||
import "../ha-button-menu";
|
import "../ha-button-menu";
|
||||||
import "../ha-card";
|
import "../ha-card";
|
||||||
|
import type { HaCard } from "../ha-card";
|
||||||
import "../ha-circular-progress";
|
import "../ha-circular-progress";
|
||||||
import "../ha-fab";
|
import "../ha-fab";
|
||||||
import "../ha-icon-button";
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./ha-browse-media-tts";
|
import "./ha-browse-media-tts";
|
||||||
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
import type { TtsMediaPickedEvent } from "./ha-browse-media-tts";
|
||||||
import { getSignedPath } from "../../data/auth";
|
|
||||||
import { brandsUrl, extractDomainFromBrandUrl } from "../../util/brands-url";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@@ -100,6 +101,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
|
|
||||||
@query(".content") private _content?: HTMLDivElement;
|
@query(".content") private _content?: HTMLDivElement;
|
||||||
|
|
||||||
|
@queryAll(".lazythumbnail") private _thumbnails?: HaCard[];
|
||||||
|
|
||||||
private _headerOffsetHeight = 0;
|
private _headerOffsetHeight = 0;
|
||||||
|
|
||||||
private _resizeObserver?: ResizeObserver;
|
private _resizeObserver?: ResizeObserver;
|
||||||
@@ -145,6 +148,326 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (this._error) {
|
||||||
|
return html`
|
||||||
|
<div class="container">${this._renderError(this._error)}</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._currentItem) {
|
||||||
|
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentItem = this._currentItem;
|
||||||
|
|
||||||
|
const subtitle = this.hass.localize(
|
||||||
|
`ui.components.media-browser.class.${currentItem.media_class}`
|
||||||
|
);
|
||||||
|
const children = currentItem.children || [];
|
||||||
|
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
||||||
|
const childrenMediaClass = currentItem.children_media_class
|
||||||
|
? MediaClassBrowserSettings[currentItem.children_media_class]
|
||||||
|
: MediaClassBrowserSettings.directory;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${
|
||||||
|
currentItem.can_play
|
||||||
|
? html` <div
|
||||||
|
class="header ${classMap({
|
||||||
|
"no-img": !currentItem.thumbnail,
|
||||||
|
"no-dialog": !this.dialog,
|
||||||
|
})}"
|
||||||
|
@transitionend=${this._setHeaderHeight}
|
||||||
|
>
|
||||||
|
<div class="header-content">
|
||||||
|
${currentItem.thumbnail
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class="img"
|
||||||
|
style=${styleMap({
|
||||||
|
backgroundImage: currentItem.thumbnail
|
||||||
|
? `url(${currentItem.thumbnail})`
|
||||||
|
: "none",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
${this._narrow && currentItem?.can_play
|
||||||
|
? html`
|
||||||
|
<ha-fab
|
||||||
|
mini
|
||||||
|
.item=${currentItem}
|
||||||
|
@click=${this._actionClicked}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}-media`
|
||||||
|
)}
|
||||||
|
.path=${this.action === "play"
|
||||||
|
? mdiPlay
|
||||||
|
: mdiPlus}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}`
|
||||||
|
)}
|
||||||
|
</ha-fab>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html``}
|
||||||
|
<div class="header-info">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<h1 class="title">${currentItem.title}</h1>
|
||||||
|
${subtitle
|
||||||
|
? html` <h2 class="subtitle">${subtitle}</h2> `
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
${currentItem.can_play &&
|
||||||
|
(!currentItem.thumbnail || !this._narrow)
|
||||||
|
? html`
|
||||||
|
<mwc-button
|
||||||
|
raised
|
||||||
|
.item=${currentItem}
|
||||||
|
@click=${this._actionClicked}
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}-media`
|
||||||
|
)}
|
||||||
|
.path=${this.action === "play"
|
||||||
|
? mdiPlay
|
||||||
|
: mdiPlus}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}`
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
<div
|
||||||
|
class="content"
|
||||||
|
@scroll=${this._scroll}
|
||||||
|
@touchmove=${this._scroll}
|
||||||
|
>
|
||||||
|
${
|
||||||
|
this._error
|
||||||
|
? html`
|
||||||
|
<div class="container">
|
||||||
|
${this._renderError(this._error)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: isTTSMediaSource(currentItem.media_content_id)
|
||||||
|
? html`
|
||||||
|
<ha-browse-media-tts
|
||||||
|
.item=${currentItem}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.action=${this.action}
|
||||||
|
@tts-picked=${this._ttsPicked}
|
||||||
|
></ha-browse-media-tts>
|
||||||
|
`
|
||||||
|
: !children.length && !currentItem.not_shown
|
||||||
|
? html`
|
||||||
|
<div class="container no-items">
|
||||||
|
${currentItem.media_content_id ===
|
||||||
|
"media-source://media_source/local/."
|
||||||
|
? html`
|
||||||
|
<div class="highlight-add-button">
|
||||||
|
<span>
|
||||||
|
<ha-svg-icon
|
||||||
|
.path=${mdiArrowUpRight}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.file_management.highlight_button"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: this.hass.localize(
|
||||||
|
"ui.components.media-browser.no_items"
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: childrenMediaClass.layout === "grid"
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class="children ${classMap({
|
||||||
|
portrait:
|
||||||
|
childrenMediaClass.thumbnail_ratio === "portrait",
|
||||||
|
})}"
|
||||||
|
>
|
||||||
|
${children.map(
|
||||||
|
(child) => html`
|
||||||
|
<div
|
||||||
|
class="child"
|
||||||
|
.item=${child}
|
||||||
|
@click=${this._childClicked}
|
||||||
|
>
|
||||||
|
<ha-card outlined>
|
||||||
|
<div class="thumbnail">
|
||||||
|
${child.thumbnail
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
class="${["app", "directory"].includes(
|
||||||
|
child.media_class
|
||||||
|
)
|
||||||
|
? "centered-image"
|
||||||
|
: ""} image lazythumbnail"
|
||||||
|
data-src=${child.thumbnail}
|
||||||
|
></div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<div class="icon-holder image">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="folder"
|
||||||
|
.path=${MediaClassBrowserSettings[
|
||||||
|
child.media_class === "directory"
|
||||||
|
? child.children_media_class ||
|
||||||
|
child.media_class
|
||||||
|
: child.media_class
|
||||||
|
].icon}
|
||||||
|
></ha-svg-icon>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
${child.can_play
|
||||||
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
|
class="play ${classMap({
|
||||||
|
can_expand: child.can_expand,
|
||||||
|
})}"
|
||||||
|
.item=${child}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}-media`
|
||||||
|
)}
|
||||||
|
.path=${this.action === "play"
|
||||||
|
? mdiPlay
|
||||||
|
: mdiPlus}
|
||||||
|
@click=${this._actionClicked}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<div class="title">
|
||||||
|
${child.title}
|
||||||
|
<paper-tooltip
|
||||||
|
fitToVisibleBounds
|
||||||
|
position="top"
|
||||||
|
offset="4"
|
||||||
|
>${child.title}</paper-tooltip
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
${currentItem.not_shown
|
||||||
|
? html`
|
||||||
|
<div class="grid not-shown">
|
||||||
|
<div class="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.not_shown",
|
||||||
|
{ count: currentItem.not_shown }
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<mwc-list>
|
||||||
|
${children.map(
|
||||||
|
(child) => html`
|
||||||
|
<mwc-list-item
|
||||||
|
@click=${this._childClicked}
|
||||||
|
.item=${child}
|
||||||
|
.graphic=${mediaClass.show_list_images
|
||||||
|
? "medium"
|
||||||
|
: "avatar"}
|
||||||
|
dir=${computeRTLDirection(this.hass)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=${classMap({
|
||||||
|
graphic: true,
|
||||||
|
lazythumbnail:
|
||||||
|
mediaClass.show_list_images === true,
|
||||||
|
})}
|
||||||
|
data-src=${ifDefined(
|
||||||
|
mediaClass.show_list_images && child.thumbnail
|
||||||
|
? child.thumbnail
|
||||||
|
: undefined
|
||||||
|
)}
|
||||||
|
slot="graphic"
|
||||||
|
>
|
||||||
|
<ha-icon-button
|
||||||
|
class="play ${classMap({
|
||||||
|
show:
|
||||||
|
!mediaClass.show_list_images ||
|
||||||
|
!child.thumbnail,
|
||||||
|
})}"
|
||||||
|
.item=${child}
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
`ui.components.media-browser.${this.action}-media`
|
||||||
|
)}
|
||||||
|
.path=${this.action === "play"
|
||||||
|
? mdiPlay
|
||||||
|
: mdiPlus}
|
||||||
|
@click=${this._actionClicked}
|
||||||
|
></ha-icon-button>
|
||||||
|
</div>
|
||||||
|
<span class="title">${child.title}</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
<li divider role="separator"></li>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
${currentItem.not_shown
|
||||||
|
? html`
|
||||||
|
<mwc-list-item
|
||||||
|
noninteractive
|
||||||
|
class="not-shown"
|
||||||
|
.graphic=${mediaClass.show_list_images
|
||||||
|
? "medium"
|
||||||
|
: "avatar"}
|
||||||
|
dir=${computeRTLDirection(this.hass)}
|
||||||
|
>
|
||||||
|
<span class="title">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.components.media-browser.not_shown",
|
||||||
|
{ count: currentItem.not_shown }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</mwc-list-item>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</mwc-list>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this._measureCard();
|
||||||
|
this._attachResizeObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
|
if (changedProps.size > 1 || !changedProps.has("hass")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const oldHass = changedProps.get("hass") as this["hass"];
|
||||||
|
return oldHass === undefined || oldHass.localize !== this.hass.localize;
|
||||||
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues<this>): void {
|
public willUpdate(changedProps: PropertyValues<this>): void {
|
||||||
super.willUpdate(changedProps);
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
@@ -260,19 +583,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
|
||||||
if (changedProps.size > 1 || !changedProps.has("hass")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const oldHass = changedProps.get("hass") as this["hass"];
|
|
||||||
return oldHass === undefined || oldHass.localize !== this.hass.localize;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected firstUpdated(): void {
|
|
||||||
this._measureCard();
|
|
||||||
this._attachResizeObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
|
|
||||||
@@ -280,368 +590,16 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
this._animateHeaderHeight();
|
this._animateHeaderHeight();
|
||||||
} else if (changedProps.has("_currentItem")) {
|
} else if (changedProps.has("_currentItem")) {
|
||||||
this._setHeaderHeight();
|
this._setHeaderHeight();
|
||||||
|
this._attachIntersectionObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
private _actionClicked(ev: MouseEvent): void {
|
||||||
if (this._error) {
|
|
||||||
return html`
|
|
||||||
<div class="container">${this._renderError(this._error)}</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._currentItem) {
|
|
||||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentItem = this._currentItem;
|
|
||||||
|
|
||||||
const subtitle = this.hass.localize(
|
|
||||||
`ui.components.media-browser.class.${currentItem.media_class}`
|
|
||||||
);
|
|
||||||
const children = currentItem.children || [];
|
|
||||||
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
|
||||||
const childrenMediaClass = currentItem.children_media_class
|
|
||||||
? MediaClassBrowserSettings[currentItem.children_media_class]
|
|
||||||
: MediaClassBrowserSettings.directory;
|
|
||||||
|
|
||||||
const backgroundImage = currentItem.thumbnail
|
|
||||||
? this._getSignedThumbnail(currentItem.thumbnail).then(
|
|
||||||
(value) => `url(${value})`
|
|
||||||
)
|
|
||||||
: "none";
|
|
||||||
|
|
||||||
return html`
|
|
||||||
${
|
|
||||||
currentItem.can_play
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="header ${classMap({
|
|
||||||
"no-img": !currentItem.thumbnail,
|
|
||||||
"no-dialog": !this.dialog,
|
|
||||||
})}"
|
|
||||||
@transitionend=${this._setHeaderHeight}
|
|
||||||
>
|
|
||||||
<div class="header-content">
|
|
||||||
${currentItem.thumbnail
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="img"
|
|
||||||
style="background-image: ${until(
|
|
||||||
backgroundImage,
|
|
||||||
""
|
|
||||||
)}"
|
|
||||||
>
|
|
||||||
${this._narrow && currentItem?.can_play
|
|
||||||
? html`
|
|
||||||
<ha-fab
|
|
||||||
mini
|
|
||||||
.item=${currentItem}
|
|
||||||
@click=${this._actionClicked}
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="icon"
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}-media`
|
|
||||||
)}
|
|
||||||
.path=${this.action === "play"
|
|
||||||
? mdiPlay
|
|
||||||
: mdiPlus}
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}`
|
|
||||||
)}
|
|
||||||
</ha-fab>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: html``}
|
|
||||||
<div class="header-info">
|
|
||||||
<div class="breadcrumb">
|
|
||||||
<h1 class="title">${currentItem.title}</h1>
|
|
||||||
${subtitle
|
|
||||||
? html` <h2 class="subtitle">${subtitle}</h2> `
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
${currentItem.can_play &&
|
|
||||||
(!currentItem.thumbnail || !this._narrow)
|
|
||||||
? html`
|
|
||||||
<mwc-button
|
|
||||||
raised
|
|
||||||
.item=${currentItem}
|
|
||||||
@click=${this._actionClicked}
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}-media`
|
|
||||||
)}
|
|
||||||
.path=${this.action === "play"
|
|
||||||
? mdiPlay
|
|
||||||
: mdiPlus}
|
|
||||||
></ha-svg-icon>
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}`
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
<div
|
|
||||||
class="content"
|
|
||||||
@scroll=${this._scroll}
|
|
||||||
@touchmove=${this._scroll}
|
|
||||||
>
|
|
||||||
${
|
|
||||||
this._error
|
|
||||||
? html`
|
|
||||||
<div class="container">
|
|
||||||
${this._renderError(this._error)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: isTTSMediaSource(currentItem.media_content_id)
|
|
||||||
? html`
|
|
||||||
<ha-browse-media-tts
|
|
||||||
.item=${currentItem}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.action=${this.action}
|
|
||||||
@tts-picked=${this._ttsPicked}
|
|
||||||
></ha-browse-media-tts>
|
|
||||||
`
|
|
||||||
: !children.length && !currentItem.not_shown
|
|
||||||
? html`
|
|
||||||
<div class="container no-items">
|
|
||||||
${currentItem.media_content_id ===
|
|
||||||
"media-source://media_source/local/."
|
|
||||||
? html`
|
|
||||||
<div class="highlight-add-button">
|
|
||||||
<span>
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiArrowUpRight}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.media-browser.file_management.highlight_button"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: this.hass.localize(
|
|
||||||
"ui.components.media-browser.no_items"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: childrenMediaClass.layout === "grid"
|
|
||||||
? html`
|
|
||||||
<lit-virtualizer
|
|
||||||
scroller
|
|
||||||
.layout=${grid({
|
|
||||||
itemSize: {
|
|
||||||
width: "175px",
|
|
||||||
height: "225px",
|
|
||||||
},
|
|
||||||
gap: "16px",
|
|
||||||
flex: { preserve: "aspect-ratio" },
|
|
||||||
justify: "space-evenly",
|
|
||||||
direction: "vertical",
|
|
||||||
})}
|
|
||||||
.items=${children}
|
|
||||||
.renderItem=${this._renderGridItem}
|
|
||||||
class="children ${classMap({
|
|
||||||
portrait:
|
|
||||||
childrenMediaClass.thumbnail_ratio === "portrait",
|
|
||||||
not_shown: !!currentItem.not_shown,
|
|
||||||
})}"
|
|
||||||
></lit-virtualizer>
|
|
||||||
${currentItem.not_shown
|
|
||||||
? html`
|
|
||||||
<div class="grid not-shown">
|
|
||||||
<div class="title">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.media-browser.not_shown",
|
|
||||||
{ count: currentItem.not_shown }
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<mwc-list>
|
|
||||||
<lit-virtualizer
|
|
||||||
scroller
|
|
||||||
.items=${children}
|
|
||||||
.renderItem=${this._renderListItem}
|
|
||||||
></lit-virtualizer>
|
|
||||||
${currentItem.not_shown
|
|
||||||
? html`
|
|
||||||
<mwc-list-item
|
|
||||||
noninteractive
|
|
||||||
class="not-shown"
|
|
||||||
.graphic=${mediaClass.show_list_images
|
|
||||||
? "medium"
|
|
||||||
: "avatar"}
|
|
||||||
dir=${computeRTLDirection(this.hass)}
|
|
||||||
>
|
|
||||||
<span class="title">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.components.media-browser.not_shown",
|
|
||||||
{ count: currentItem.not_shown }
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</mwc-list-item>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</mwc-list>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _renderGridItem = (child: MediaPlayerItem): TemplateResult => {
|
|
||||||
const backgroundImage = child.thumbnail
|
|
||||||
? this._getSignedThumbnail(child.thumbnail).then(
|
|
||||||
(value) => `url(${value})`
|
|
||||||
)
|
|
||||||
: "none";
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="child" .item=${child} @click=${this._childClicked}>
|
|
||||||
<ha-card outlined>
|
|
||||||
<div class="thumbnail">
|
|
||||||
${child.thumbnail
|
|
||||||
? html`
|
|
||||||
<div
|
|
||||||
class="${["app", "directory"].includes(child.media_class)
|
|
||||||
? "centered-image"
|
|
||||||
: ""} image"
|
|
||||||
style="background-image: ${until(backgroundImage, "")}"
|
|
||||||
></div>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<div class="icon-holder image">
|
|
||||||
<ha-svg-icon
|
|
||||||
class="folder"
|
|
||||||
.path=${MediaClassBrowserSettings[
|
|
||||||
child.media_class === "directory"
|
|
||||||
? child.children_media_class || child.media_class
|
|
||||||
: child.media_class
|
|
||||||
].icon}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</div>
|
|
||||||
`}
|
|
||||||
${child.can_play
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
class="play ${classMap({
|
|
||||||
can_expand: child.can_expand,
|
|
||||||
})}"
|
|
||||||
.item=${child}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}-media`
|
|
||||||
)}
|
|
||||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
|
||||||
@click=${this._actionClicked}
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
<div class="title">
|
|
||||||
${child.title}
|
|
||||||
<paper-tooltip fitToVisibleBounds position="top" offset="4"
|
|
||||||
>${child.title}</paper-tooltip
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
private _renderListItem = (child: MediaPlayerItem): TemplateResult => {
|
|
||||||
const currentItem = this._currentItem;
|
|
||||||
const mediaClass = MediaClassBrowserSettings[currentItem!.media_class];
|
|
||||||
|
|
||||||
const backgroundImage =
|
|
||||||
mediaClass.show_list_images && child.thumbnail
|
|
||||||
? this._getSignedThumbnail(child.thumbnail).then(
|
|
||||||
(value) => `url(${value})`
|
|
||||||
)
|
|
||||||
: "none";
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<mwc-list-item
|
|
||||||
@click=${this._childClicked}
|
|
||||||
.item=${child}
|
|
||||||
.graphic=${mediaClass.show_list_images ? "medium" : "avatar"}
|
|
||||||
dir=${computeRTLDirection(this.hass)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class=${classMap({
|
|
||||||
graphic: true,
|
|
||||||
thumbnail: mediaClass.show_list_images === true,
|
|
||||||
})}
|
|
||||||
style="background-image: ${until(backgroundImage, "")}"
|
|
||||||
slot="graphic"
|
|
||||||
>
|
|
||||||
<ha-icon-button
|
|
||||||
class="play ${classMap({
|
|
||||||
show: !mediaClass.show_list_images || !child.thumbnail,
|
|
||||||
})}"
|
|
||||||
.item=${child}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
`ui.components.media-browser.${this.action}-media`
|
|
||||||
)}
|
|
||||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
|
||||||
@click=${this._actionClicked}
|
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
|
||||||
<span class="title">${child.title}</span>
|
|
||||||
</mwc-list-item>
|
|
||||||
<li divider role="separator"></li>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
private async _getSignedThumbnail(
|
|
||||||
thumbnailUrl: string | undefined
|
|
||||||
): Promise<string> {
|
|
||||||
if (!thumbnailUrl) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbnailUrl.startsWith("/")) {
|
|
||||||
// Thumbnails served by local API require authentication
|
|
||||||
return (await getSignedPath(this.hass, thumbnailUrl)).path;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (thumbnailUrl.startsWith("https://brands.home-assistant.io")) {
|
|
||||||
// The backend is not aware of the theme used by the users,
|
|
||||||
// so we rewrite the URL to show a proper icon
|
|
||||||
thumbnailUrl = brandsUrl({
|
|
||||||
domain: extractDomainFromBrandUrl(thumbnailUrl),
|
|
||||||
type: "icon",
|
|
||||||
useFallback: true,
|
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return thumbnailUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _actionClicked = (ev: MouseEvent): void => {
|
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const item = (ev.currentTarget as any).item;
|
const item = (ev.currentTarget as any).item;
|
||||||
|
|
||||||
this._runAction(item);
|
this._runAction(item);
|
||||||
};
|
}
|
||||||
|
|
||||||
private _runAction(item: MediaPlayerItem): void {
|
private _runAction(item: MediaPlayerItem): void {
|
||||||
fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds });
|
fireEvent(this, "media-picked", { item, navigateIds: this.navigateIds });
|
||||||
@@ -657,7 +615,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _childClicked = async (ev: MouseEvent): Promise<void> => {
|
private async _childClicked(ev: MouseEvent): Promise<void> {
|
||||||
const target = ev.currentTarget as any;
|
const target = ev.currentTarget as any;
|
||||||
const item: MediaPlayerItem = target.item;
|
const item: MediaPlayerItem = target.item;
|
||||||
|
|
||||||
@@ -673,7 +631,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
fireEvent(this, "media-browsed", {
|
fireEvent(this, "media-browsed", {
|
||||||
ids: [...this.navigateIds, item],
|
ids: [...this.navigateIds, item],
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
private async _fetchData(
|
private async _fetchData(
|
||||||
entityId: string,
|
entityId: string,
|
||||||
@@ -700,6 +658,55 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
this._resizeObserver.observe(this);
|
this._resizeObserver.observe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load thumbnails for images on demand as they become visible.
|
||||||
|
*/
|
||||||
|
private async _attachIntersectionObserver(): Promise<void> {
|
||||||
|
if (!("IntersectionObserver" in window) || !this._thumbnails) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._intersectionObserver) {
|
||||||
|
this._intersectionObserver = new IntersectionObserver(
|
||||||
|
async (entries, observer) => {
|
||||||
|
await Promise.all(
|
||||||
|
entries.map(async (entry) => {
|
||||||
|
if (!entry.isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const thumbnailCard = entry.target as HTMLElement;
|
||||||
|
let thumbnailUrl = thumbnailCard.dataset.src;
|
||||||
|
if (!thumbnailUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thumbnailUrl.startsWith("/")) {
|
||||||
|
// Thumbnails served by local API require authentication
|
||||||
|
const signedPath = await getSignedPath(this.hass, thumbnailUrl);
|
||||||
|
thumbnailUrl = signedPath.path;
|
||||||
|
} else if (
|
||||||
|
thumbnailUrl.startsWith("https://brands.home-assistant.io")
|
||||||
|
) {
|
||||||
|
// The backend is not aware of the theme used by the users,
|
||||||
|
// so we rewrite the URL to show a proper icon
|
||||||
|
thumbnailUrl = brandsUrl({
|
||||||
|
domain: extractDomainFromBrandUrl(thumbnailUrl),
|
||||||
|
type: "icon",
|
||||||
|
useFallback: true,
|
||||||
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
thumbnailCard.style.backgroundImage = `url(${thumbnailUrl})`;
|
||||||
|
observer.unobserve(thumbnailCard); // loaded, so no need to observe anymore
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const observer = this._intersectionObserver!;
|
||||||
|
for (const thumbnailCard of this._thumbnails) {
|
||||||
|
observer.observe(thumbnailCard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _closeDialogAction(): void {
|
private _closeDialogAction(): void {
|
||||||
fireEvent(this, "close-dialog");
|
fireEvent(this, "close-dialog");
|
||||||
}
|
}
|
||||||
@@ -834,7 +841,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
.content {
|
.content {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* HEADER */
|
/* HEADER */
|
||||||
@@ -920,7 +926,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
.not-shown {
|
.not-shown {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
padding: 8px 16px 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid.not-shown {
|
.grid.not-shown {
|
||||||
@@ -946,11 +951,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
border-bottom-color: var(--divider-color);
|
border-bottom-color: var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-list-item {
|
.children {
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.children {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(
|
grid-template-columns: repeat(
|
||||||
auto-fit,
|
auto-fit,
|
||||||
@@ -987,7 +988,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
padding-bottom: 100%;
|
padding-bottom: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.portrait ha-card .thumbnail {
|
.portrait.children ha-card .thumbnail {
|
||||||
padding-bottom: 150%;
|
padding-bottom: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1061,6 +1062,10 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-card:hover .lazythumbnail {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.child .title {
|
.child .title {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
@@ -1122,7 +1127,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([narrow]) div.children {
|
:host([narrow]) .children {
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1227,16 +1232,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
|||||||
--mdc-fab-box-shadow: none;
|
--mdc-fab-box-shadow: none;
|
||||||
--mdc-theme-secondary: rgba(var(--rgb-primary-color), 0.5);
|
--mdc-theme-secondary: rgba(var(--rgb-primary-color), 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
lit-virtualizer {
|
|
||||||
height: 100%;
|
|
||||||
overflow: overlay !important;
|
|
||||||
contain: size layout !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
lit-virtualizer.not_shown {
|
|
||||||
height: calc(100% - 36px);
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,6 @@ import { BlueprintInput } from "./blueprint";
|
|||||||
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||||
import { Action, MODES } from "./script";
|
import { Action, MODES } from "./script";
|
||||||
|
|
||||||
export const AUTOMATION_DEFAULT_MODE: ManualAutomationConfig["mode"] = "single";
|
|
||||||
|
|
||||||
export interface AutomationEntity extends HassEntityBase {
|
export interface AutomationEntity extends HassEntityBase {
|
||||||
attributes: HassEntityAttributeBase & {
|
attributes: HassEntityAttributeBase & {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -69,7 +67,7 @@ export interface BaseTrigger {
|
|||||||
|
|
||||||
export interface StateTrigger extends BaseTrigger {
|
export interface StateTrigger extends BaseTrigger {
|
||||||
platform: "state";
|
platform: "state";
|
||||||
entity_id: string | string[];
|
entity_id: string;
|
||||||
attribute?: string;
|
attribute?: string;
|
||||||
from?: string | number;
|
from?: string | number;
|
||||||
to?: string | string[] | number;
|
to?: string | string[] | number;
|
||||||
@@ -152,12 +150,6 @@ export interface EventTrigger extends BaseTrigger {
|
|||||||
context?: ContextConstraint;
|
context?: ContextConstraint;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarTrigger extends BaseTrigger {
|
|
||||||
platform: "calendar";
|
|
||||||
event: "start" | "end";
|
|
||||||
entity_id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Trigger =
|
export type Trigger =
|
||||||
| StateTrigger
|
| StateTrigger
|
||||||
| MqttTrigger
|
| MqttTrigger
|
||||||
@@ -172,8 +164,7 @@ export type Trigger =
|
|||||||
| TimeTrigger
|
| TimeTrigger
|
||||||
| TemplateTrigger
|
| TemplateTrigger
|
||||||
| EventTrigger
|
| EventTrigger
|
||||||
| DeviceTrigger
|
| DeviceTrigger;
|
||||||
| CalendarTrigger;
|
|
||||||
|
|
||||||
interface BaseCondition {
|
interface BaseCondition {
|
||||||
condition: string;
|
condition: string;
|
||||||
@@ -233,20 +224,6 @@ export interface TriggerCondition extends BaseCondition {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShorthandBaseCondition = Omit<BaseCondition, "condition">;
|
|
||||||
|
|
||||||
export interface ShorthandAndCondition extends ShorthandBaseCondition {
|
|
||||||
and: Condition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShorthandOrCondition extends ShorthandBaseCondition {
|
|
||||||
or: Condition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ShorthandNotCondition extends ShorthandBaseCondition {
|
|
||||||
not: Condition[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Condition =
|
export type Condition =
|
||||||
| StateCondition
|
| StateCondition
|
||||||
| NumericStateCondition
|
| NumericStateCondition
|
||||||
@@ -258,12 +235,6 @@ export type Condition =
|
|||||||
| LogicalCondition
|
| LogicalCondition
|
||||||
| TriggerCondition;
|
| TriggerCondition;
|
||||||
|
|
||||||
export type ConditionWithShorthand =
|
|
||||||
| Condition
|
|
||||||
| ShorthandAndCondition
|
|
||||||
| ShorthandOrCondition
|
|
||||||
| ShorthandNotCondition;
|
|
||||||
|
|
||||||
export const triggerAutomationActions = (
|
export const triggerAutomationActions = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string
|
entityId: string
|
||||||
|
@@ -1,9 +1,4 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export interface LogProvider {
|
|
||||||
key: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchErrorLog = (hass: HomeAssistant) =>
|
export const fetchErrorLog = (hass: HomeAssistant) =>
|
||||||
hass.callApi<string>("GET", "error_log");
|
hass.callApi<string>("GET", "error_log");
|
||||||
|
@@ -21,8 +21,7 @@ export type AddonState = "started" | "stopped" | null;
|
|||||||
export type AddonRepository = "core" | "local" | string;
|
export type AddonRepository = "core" | "local" | string;
|
||||||
|
|
||||||
interface AddonTranslations {
|
interface AddonTranslations {
|
||||||
network?: Record<string, string>;
|
[key: string]: Record<string, Record<string, Record<string, string>>>;
|
||||||
configuration?: Record<string, { name?: string; description?: string }>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HassioAddonInfo {
|
export interface HassioAddonInfo {
|
||||||
@@ -92,7 +91,7 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
|||||||
slug: string;
|
slug: string;
|
||||||
startup: AddonStartup;
|
startup: AddonStartup;
|
||||||
stdin: boolean;
|
stdin: boolean;
|
||||||
translations: Record<string, AddonTranslations>;
|
translations: AddonTranslations;
|
||||||
watchdog: null | boolean;
|
watchdog: null | boolean;
|
||||||
webui: null | string;
|
webui: null | string;
|
||||||
}
|
}
|
||||||
|
@@ -194,24 +194,11 @@ export interface ChooseAction {
|
|||||||
default?: Action | Action[];
|
default?: Action | Action[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IfAction {
|
|
||||||
alias?: string;
|
|
||||||
if: string | Condition[];
|
|
||||||
then: Action | Action[];
|
|
||||||
else?: Action | Action[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VariablesAction {
|
export interface VariablesAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
variables: Record<string, unknown>;
|
variables: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StopAction {
|
|
||||||
alias?: string;
|
|
||||||
stop: string;
|
|
||||||
error?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UnknownAction {
|
interface UnknownAction {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
@@ -228,10 +215,8 @@ export type Action =
|
|||||||
| WaitForTriggerAction
|
| WaitForTriggerAction
|
||||||
| RepeatAction
|
| RepeatAction
|
||||||
| ChooseAction
|
| ChooseAction
|
||||||
| IfAction
|
|
||||||
| VariablesAction
|
| VariablesAction
|
||||||
| PlayMediaAction
|
| PlayMediaAction
|
||||||
| StopAction
|
|
||||||
| UnknownAction;
|
| UnknownAction;
|
||||||
|
|
||||||
export interface ActionTypes {
|
export interface ActionTypes {
|
||||||
@@ -243,12 +228,10 @@ export interface ActionTypes {
|
|||||||
activate_scene: SceneAction;
|
activate_scene: SceneAction;
|
||||||
repeat: RepeatAction;
|
repeat: RepeatAction;
|
||||||
choose: ChooseAction;
|
choose: ChooseAction;
|
||||||
if: IfAction;
|
|
||||||
wait_for_trigger: WaitForTriggerAction;
|
wait_for_trigger: WaitForTriggerAction;
|
||||||
variables: VariablesAction;
|
variables: VariablesAction;
|
||||||
service: ServiceAction;
|
service: ServiceAction;
|
||||||
play_media: PlayMediaAction;
|
play_media: PlayMediaAction;
|
||||||
stop: StopAction;
|
|
||||||
unknown: UnknownAction;
|
unknown: UnknownAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,18 +299,12 @@ export const getActionType = (action: Action): ActionType => {
|
|||||||
if ("choose" in action) {
|
if ("choose" in action) {
|
||||||
return "choose";
|
return "choose";
|
||||||
}
|
}
|
||||||
if ("if" in action) {
|
|
||||||
return "if";
|
|
||||||
}
|
|
||||||
if ("wait_for_trigger" in action) {
|
if ("wait_for_trigger" in action) {
|
||||||
return "wait_for_trigger";
|
return "wait_for_trigger";
|
||||||
}
|
}
|
||||||
if ("variables" in action) {
|
if ("variables" in action) {
|
||||||
return "variables";
|
return "variables";
|
||||||
}
|
}
|
||||||
if ("stop" in action) {
|
|
||||||
return "stop";
|
|
||||||
}
|
|
||||||
if ("service" in action) {
|
if ("service" in action) {
|
||||||
if ("metadata" in action) {
|
if ("metadata" in action) {
|
||||||
if (is(action, activateSceneActionStruct)) {
|
if (is(action, activateSceneActionStruct)) {
|
||||||
|
@@ -19,7 +19,6 @@ export type Selector =
|
|||||||
| SelectSelector
|
| SelectSelector
|
||||||
| StringSelector
|
| StringSelector
|
||||||
| TargetSelector
|
| TargetSelector
|
||||||
| TemplateSelector
|
|
||||||
| ThemeSelector
|
| ThemeSelector
|
||||||
| TimeSelector;
|
| TimeSelector;
|
||||||
|
|
||||||
@@ -214,11 +213,6 @@ export interface TargetSelector {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TemplateSelector {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
||||||
template: {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ThemeSelector {
|
export interface ThemeSelector {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
theme: {};
|
theme: {};
|
||||||
|
@@ -1,15 +1,10 @@
|
|||||||
import type {
|
import type {
|
||||||
HassEntities,
|
|
||||||
HassEntityAttributeBase,
|
HassEntityAttributeBase,
|
||||||
HassEntityBase,
|
HassEntityBase,
|
||||||
} from "home-assistant-js-websocket";
|
} from "home-assistant-js-websocket";
|
||||||
import { BINARY_STATE_ON } from "../common/const";
|
import { BINARY_STATE_ON } from "../common/const";
|
||||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
|
||||||
import { supportsFeature } from "../common/entity/supports-feature";
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
import { caseInsensitiveStringCompare } from "../common/string/compare";
|
|
||||||
import { showAlertDialog } from "../dialogs/generic/show-dialog-box";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { showToast } from "../util/toast";
|
|
||||||
|
|
||||||
export const UPDATE_SUPPORT_INSTALL = 1;
|
export const UPDATE_SUPPORT_INSTALL = 1;
|
||||||
export const UPDATE_SUPPORT_SPECIFIC_VERSION = 2;
|
export const UPDATE_SUPPORT_SPECIFIC_VERSION = 2;
|
||||||
@@ -36,12 +31,8 @@ export const updateUsesProgress = (entity: UpdateEntity): boolean =>
|
|||||||
supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) &&
|
supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) &&
|
||||||
typeof entity.attributes.in_progress === "number";
|
typeof entity.attributes.in_progress === "number";
|
||||||
|
|
||||||
export const updateCanInstall = (
|
export const updateCanInstall = (entity: UpdateEntity): boolean =>
|
||||||
entity: UpdateEntity,
|
entity.state === BINARY_STATE_ON &&
|
||||||
showSkipped = false
|
|
||||||
): boolean =>
|
|
||||||
(entity.state === BINARY_STATE_ON ||
|
|
||||||
(showSkipped && Boolean(entity.attributes.skipped_version))) &&
|
|
||||||
supportsFeature(entity, UPDATE_SUPPORT_INSTALL);
|
supportsFeature(entity, UPDATE_SUPPORT_INSTALL);
|
||||||
|
|
||||||
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
export const updateIsInstalling = (entity: UpdateEntity): boolean =>
|
||||||
@@ -52,75 +43,3 @@ export const updateReleaseNotes = (hass: HomeAssistant, entityId: string) =>
|
|||||||
type: "update/release_notes",
|
type: "update/release_notes",
|
||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const filterUpdateEntities = (entities: HassEntities) =>
|
|
||||||
(
|
|
||||||
Object.values(entities).filter(
|
|
||||||
(entity) => computeStateDomain(entity) === "update"
|
|
||||||
) as UpdateEntity[]
|
|
||||||
).sort((a, b) => {
|
|
||||||
if (a.attributes.title === "Home Assistant Core") {
|
|
||||||
return -3;
|
|
||||||
}
|
|
||||||
if (b.attributes.title === "Home Assistant Core") {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
if (a.attributes.title === "Home Assistant Operating System") {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
if (b.attributes.title === "Home Assistant Operating System") {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
if (a.attributes.title === "Home Assistant Supervisor") {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (b.attributes.title === "Home Assistant Supervisor") {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return caseInsensitiveStringCompare(
|
|
||||||
a.attributes.title || a.attributes.friendly_name || "",
|
|
||||||
b.attributes.title || b.attributes.friendly_name || ""
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const filterUpdateEntitiesWithInstall = (
|
|
||||||
entities: HassEntities,
|
|
||||||
showSkipped = false
|
|
||||||
) =>
|
|
||||||
filterUpdateEntities(entities).filter((entity) =>
|
|
||||||
updateCanInstall(entity, showSkipped)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const checkForEntityUpdates = async (
|
|
||||||
element: HTMLElement,
|
|
||||||
hass: HomeAssistant
|
|
||||||
) => {
|
|
||||||
const entities = filterUpdateEntities(hass.states).map(
|
|
||||||
(entity) => entity.entity_id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!entities.length) {
|
|
||||||
showAlertDialog(element, {
|
|
||||||
title: hass.localize("ui.panel.config.updates.no_update_entities.title"),
|
|
||||||
text: hass.localize(
|
|
||||||
"ui.panel.config.updates.no_update_entities.description"
|
|
||||||
),
|
|
||||||
warning: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await hass.callService("homeassistant", "update_entity", {
|
|
||||||
entity_id: entities,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filterUpdateEntitiesWithInstall(hass.states).length) {
|
|
||||||
showToast(element, {
|
|
||||||
message: hass.localize("ui.panel.config.updates.updates_refreshed"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showToast(element, {
|
|
||||||
message: hass.localize("ui.panel.config.updates.no_new_updates"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@@ -20,7 +20,6 @@ export const subscribeRenderTemplate = (
|
|||||||
entity_ids?: string | string[];
|
entity_ids?: string | string[];
|
||||||
variables?: Record<string, unknown>;
|
variables?: Record<string, unknown>;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
strict?: boolean;
|
|
||||||
}
|
}
|
||||||
): Promise<UnsubscribeFunc> =>
|
): Promise<UnsubscribeFunc> =>
|
||||||
conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
|
conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
|
||||||
|
@@ -127,7 +127,7 @@ export interface ZWaveJSClient {
|
|||||||
|
|
||||||
export interface ZWaveJSController {
|
export interface ZWaveJSController {
|
||||||
home_id: number;
|
home_id: number;
|
||||||
sdk_version: string;
|
library_version: string;
|
||||||
type: number;
|
type: number;
|
||||||
own_node_id: number;
|
own_node_id: number;
|
||||||
is_secondary: boolean;
|
is_secondary: boolean;
|
||||||
@@ -136,7 +136,7 @@ export interface ZWaveJSController {
|
|||||||
was_real_primary: boolean;
|
was_real_primary: boolean;
|
||||||
is_static_update_controller: boolean;
|
is_static_update_controller: boolean;
|
||||||
is_slave: boolean;
|
is_slave: boolean;
|
||||||
firmware_version: string;
|
serial_api_version: string;
|
||||||
manufacturer_id: number;
|
manufacturer_id: number;
|
||||||
product_id: number;
|
product_id: number;
|
||||||
product_type: number;
|
product_type: number;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user