mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-14 11:49:26 +00:00
Compare commits
3 Commits
fix-action
...
20200901.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ba3cc7df0f | ||
![]() |
090ad34f78 | ||
![]() |
b2460cbc3d |
60
.github/workflows/codeql-analysis.yml
vendored
60
.github/workflows/codeql-analysis.yml
vendored
@@ -1,60 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [dev, master]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [dev]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# Override automatic language detection by changing the below list
|
||||
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
|
||||
language: ['javascript']
|
||||
# Learn more...
|
||||
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import "../../../src/state-summary/state-card-content";
|
||||
import "./more-info-content";
|
||||
|
||||
class DemoMoreInfo extends PolymerElement {
|
||||
static get template() {
|
||||
|
@@ -1,73 +0,0 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
||||
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
|
||||
import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-automation";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-camera";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-climate";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-configurator";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-counter";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-cover";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-default";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-fan";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-group";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-light";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-lock";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-media_player";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-person";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-script";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-sun";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-timer";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
|
||||
import "../../../src/dialogs/more-info/controls/more-info-weather";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
class MoreInfoContent extends UpdatingElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
private _detachedChild?: ChildNode;
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.style.position = "relative";
|
||||
this.style.display = "block";
|
||||
}
|
||||
|
||||
// This is not a lit element, but an updating element, so we implement update
|
||||
protected update(changedProps: PropertyValues): void {
|
||||
super.update(changedProps);
|
||||
const stateObj = this.stateObj;
|
||||
const hass = this.hass;
|
||||
|
||||
if (!stateObj || !hass) {
|
||||
if (this.lastChild) {
|
||||
this._detachedChild = this.lastChild;
|
||||
// Detach child to prevent it from doing work.
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._detachedChild) {
|
||||
this.appendChild(this._detachedChild);
|
||||
this._detachedChild = undefined;
|
||||
}
|
||||
|
||||
const moreInfoType =
|
||||
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
|
||||
? stateObj.attributes.custom_ui_more_info
|
||||
: "more-info-" + stateMoreInfoType(stateObj);
|
||||
|
||||
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
|
||||
hass,
|
||||
stateObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-content", MoreInfoContent);
|
@@ -3,10 +3,10 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-card";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
|
||||
import "../../../src/dialogs/more-info/more-info-content";
|
||||
import { getEntity } from "../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||
import "../components/demo-more-infos";
|
||||
import "../components/more-info-content";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("light", "bed_light", "on", {
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
@@ -20,13 +19,13 @@ import {
|
||||
HassioAddonRepository,
|
||||
reloadHassioAddons,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import "./hassio-addon-repository";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
|
||||
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
if (a.slug === "local") {
|
||||
@@ -180,7 +179,7 @@ class HassioAddonStore extends LitElement {
|
||||
this._repos.sort(sortRepos);
|
||||
this._addons = addonsInfo.addons;
|
||||
} catch (err) {
|
||||
alert(extractApiErrorMessage(err));
|
||||
alert("Failed to fetch add-on info");
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,6 @@ import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
|
||||
@customElement("hassio-addon-audio")
|
||||
class HassioAddonAudio extends LitElement {
|
||||
@@ -92,9 +91,7 @@ class HassioAddonAudio extends LitElement {
|
||||
</paper-dropdown-menu>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._saveSettings}>
|
||||
Save
|
||||
</ha-progress-button>
|
||||
<mwc-button @click=${this._saveSettings}>Save</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@@ -175,10 +172,7 @@ class HassioAddonAudio extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _saveSettings(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _saveSettings(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
audio_input:
|
||||
@@ -188,14 +182,12 @@ class HassioAddonAudio extends LitElement {
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch {
|
||||
this._error = "Failed to set addon audio device";
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,15 +5,14 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||
@@ -22,7 +21,6 @@ import {
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -57,103 +55,20 @@ class HassioAddonConfig extends LitElement {
|
||||
${valid ? "" : html` <div class="errors">Invalid YAML</div> `}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
<mwc-button class="warning" @click=${this._resetTapped}>
|
||||
Reset to defaults
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this._configHasChanged || !valid}
|
||||
>
|
||||
Save
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
this._editor.setValue(this.addon.options);
|
||||
}
|
||||
}
|
||||
|
||||
private _configChanged(): void {
|
||||
this._configHasChanged = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to reset all your options?",
|
||||
confirmText: "reset options",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
options: null,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "options",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to reset addon configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
let data: HassioAddonSetOptionParams;
|
||||
this._error = undefined;
|
||||
try {
|
||||
data = {
|
||||
options: this._editor.value,
|
||||
};
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "options",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to save addon configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -183,6 +98,80 @@ class HassioAddonConfig extends LitElement {
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
this._editor.setValue(this.addon.options);
|
||||
}
|
||||
}
|
||||
|
||||
private _configChanged(): void {
|
||||
this._configHasChanged = true;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private async _resetTapped(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to reset all your options?",
|
||||
confirmText: "reset options",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
options: null,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "options",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to reset addon configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _saveTapped(): Promise<void> {
|
||||
let data: HassioAddonSetOptionParams;
|
||||
this._error = undefined;
|
||||
try {
|
||||
data = {
|
||||
options: this._editor.value,
|
||||
};
|
||||
} catch (err) {
|
||||
this._error = err;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
path: "options",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to save addon configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -4,21 +4,19 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
@@ -87,17 +85,38 @@ class HassioAddonNetwork extends LitElement {
|
||||
</table>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button class="warning" @click=${this._resetTapped}>
|
||||
<mwc-button class="warning" @click=${this._resetTapped}>
|
||||
Reset to defaults
|
||||
</ha-progress-button>
|
||||
<ha-progress-button @click=${this._saveTapped}>
|
||||
Save
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._saveTapped}>Save</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected update(changedProperties: PropertyValues): void {
|
||||
super.update(changedProperties);
|
||||
if (changedProperties.has("addon")) {
|
||||
@@ -130,10 +149,7 @@ class HassioAddonNetwork extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _resetTapped(): Promise<void> {
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
network: null,
|
||||
};
|
||||
@@ -146,22 +162,17 @@ class HassioAddonNetwork extends LitElement {
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _saveTapped(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const networkconfiguration = {};
|
||||
this._config!.forEach((item) => {
|
||||
@@ -180,38 +191,14 @@ class HassioAddonNetwork extends LitElement {
|
||||
path: "option",
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
if (this.addon?.state === "started") {
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
if (!this._error && this.addon?.state === "started") {
|
||||
await suggestAddonRestart(this, this.hass, this.addon);
|
||||
}
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
color: var(--error-color);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,19 +3,18 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import {
|
||||
fetchHassioAddonDocumentation,
|
||||
HassioAddonDetails,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import "../../../../src/layouts/hass-loading-screen";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -81,9 +80,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
this.addon!.slug
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon documentation, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = `Failed to get addon documentation, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -38,22 +38,15 @@ import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
fetchHassioAddonChangelog,
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
installHassioAddon,
|
||||
setHassioAddonOption,
|
||||
setHassioAddonSecurity,
|
||||
startHassioAddon,
|
||||
uninstallHassioAddon,
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-card-content";
|
||||
@@ -133,6 +126,8 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
@internalProperty() private _error?: string;
|
||||
|
||||
@property({ type: Boolean }) private _installing = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this._computeUpdateAvailable
|
||||
@@ -405,7 +400,7 @@ class HassioAddonInfo extends LitElement {
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
|
||||
${this.addon.startup !== "once"
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<ha-settings-row ?three-line=${this.narrow}>
|
||||
<span slot="heading">
|
||||
@@ -503,9 +498,12 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-call-api-button>
|
||||
`
|
||||
: html`
|
||||
<ha-progress-button @click=${this._startClicked}>
|
||||
<ha-call-api-button
|
||||
.hass=${this.hass}
|
||||
.path="hassio/addons/${this.addon.slug}/start"
|
||||
>
|
||||
Start
|
||||
</ha-progress-button>
|
||||
</ha-call-api-button>
|
||||
`}
|
||||
${this._computeShowWebUI
|
||||
? html`
|
||||
@@ -529,12 +527,12 @@ class HassioAddonInfo extends LitElement {
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
class=" right warning"
|
||||
@click=${this._uninstallClicked}
|
||||
>
|
||||
Uninstall
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
${this.addon.build
|
||||
? html`
|
||||
<ha-call-api-button
|
||||
@@ -556,7 +554,8 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
.disabled=${!this.addon.available || this._installing}
|
||||
.progress=${this._installing}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
Install
|
||||
@@ -663,9 +662,7 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,9 +680,7 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -703,9 +698,7 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,9 +716,9 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon security option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = `Failed to set addon security option, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,13 +736,12 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
this._error = `Failed to set addon option, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
this._error = `Failed to set addon option, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _openChangelog(): Promise<void> {
|
||||
this._error = undefined;
|
||||
try {
|
||||
const content = await fetchHassioAddonChangelog(
|
||||
this.hass,
|
||||
@@ -760,17 +752,15 @@ class HassioAddonInfo extends LitElement {
|
||||
content,
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get addon changelog",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
this._error = `Failed to get addon changelog, ${
|
||||
err.body?.message || err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async _installClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _installClicked(): Promise<void> {
|
||||
this._error = undefined;
|
||||
this._installing = true;
|
||||
try {
|
||||
await installHassioAddon(this.hass, this.addon.slug);
|
||||
const eventdata = {
|
||||
@@ -780,62 +770,12 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to install addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
this._error = `Failed to install addon, ${err.body?.message || err}`;
|
||||
}
|
||||
button.progress = false;
|
||||
this._installing = false;
|
||||
}
|
||||
|
||||
private async _startClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
try {
|
||||
const validate = await validateHassioAddonOption(
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
if (!validate.data.valid) {
|
||||
await showConfirmationDialog(this, {
|
||||
title: "Failed to start addon - configruation validation faled!",
|
||||
text: validate.data.message.split(" Got ")[0],
|
||||
confirm: () => this._openConfiguration(),
|
||||
confirmText: "Go to configruation",
|
||||
dismissText: "Cancel",
|
||||
});
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to validate addon configuration",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await startHassioAddon(this.hass, this.addon.slug);
|
||||
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to start addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private _openConfiguration(): void {
|
||||
navigate(this, `/hassio/addon/${this.addon.slug}/config`);
|
||||
}
|
||||
|
||||
private async _uninstallClicked(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _uninstallClicked(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.addon.name,
|
||||
text: "Are you sure you want to uninstall this add-on?",
|
||||
@@ -844,7 +784,6 @@ class HassioAddonInfo extends LitElement {
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -858,12 +797,8 @@ class HassioAddonInfo extends LitElement {
|
||||
};
|
||||
fireEvent(this, "hass-api-called", eventdata);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to uninstall addon",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
this._error = `Failed to uninstall addon, ${err.body?.message || err}`;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
|
@@ -4,9 +4,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-card";
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
fetchHassioAddonLogs,
|
||||
HassioAddonDetails,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-ansi-to-html";
|
||||
@@ -76,7 +75,7 @@ class HassioAddonLogs extends LitElement {
|
||||
try {
|
||||
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get addon logs, ${extractApiErrorMessage(err)}`;
|
||||
this._error = `Failed to get addon logs, ${err.body?.message || err}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,31 +5,27 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
HassioResponse,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { HassioHassOSInfo } from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { HassioResponse } from "../../../src/data/hassio/common";
|
||||
|
||||
@customElement("hassio-update")
|
||||
export class HassioUpdate extends LitElement {
|
||||
@@ -149,7 +145,7 @@ export class HassioUpdate extends LitElement {
|
||||
}
|
||||
|
||||
private async _confirmUpdate(ev): Promise<void> {
|
||||
const item = ev.currentTarget;
|
||||
const item = ev.target;
|
||||
item.progress = true;
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: `Update ${item.name}`,
|
||||
@@ -165,12 +161,16 @@ export class HassioUpdate extends LitElement {
|
||||
try {
|
||||
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath);
|
||||
} catch (err) {
|
||||
// Only show an error if the status code was not expected (user behind proxy)
|
||||
// or no status at all(connection terminated)
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
// Only show an error if the status code was not 504 (timeout reported by proxies)
|
||||
if (err.status_code !== 504) {
|
||||
showAlertDialog(this, {
|
||||
title: "Update failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
text:
|
||||
typeof err === "object"
|
||||
? typeof err.body === "object"
|
||||
? err.body.message
|
||||
: err.body || "Unkown error"
|
||||
: err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,42 +1,43 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button";
|
||||
import "@material/mwc-tab";
|
||||
import "@material/mwc-tab-bar";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@material/mwc-tab";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
|
||||
import {
|
||||
updateNetworkInterface,
|
||||
NetworkInterface,
|
||||
} from "../../../../src/data/hassio/network";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import type { HaRadio } from "../../../../src/components/ha-radio";
|
||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-header-bar";
|
||||
import "../../../../src/components/ha-radio";
|
||||
import type { HaRadio } from "../../../../src/components/ha-radio";
|
||||
import "../../../../src/components/ha-related-items";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
NetworkInterface,
|
||||
updateNetworkInterface,
|
||||
} from "../../../../src/data/hassio/network";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioNetworkDialogParams } from "./show-dialog-network";
|
||||
|
||||
@customElement("dialog-hassio-network")
|
||||
export class DialogHassioNetwork extends LitElement implements HassDialog {
|
||||
@@ -200,7 +201,8 @@ export class DialogHassioNetwork extends LitElement implements HassDialog {
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to change network settings",
|
||||
text: extractApiErrorMessage(err),
|
||||
text:
|
||||
typeof err === "object" ? err.body.message || "Unkown error" : err,
|
||||
});
|
||||
this._prosessing = false;
|
||||
return;
|
||||
|
@@ -5,26 +5,25 @@ import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonRepository,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
|
||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
@@ -191,7 +190,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
input.value = "";
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._error = err.message;
|
||||
}
|
||||
this._prosessing = false;
|
||||
}
|
||||
@@ -223,7 +222,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
|
||||
await this._dialogParams!.loadData();
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._error = err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,7 +15,6 @@ import {
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { getSignedPath } from "../../../../src/data/auth";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
fetchHassioSnapshotInfo,
|
||||
HassioSnapshotDetail,
|
||||
@@ -380,7 +379,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
|
||||
);
|
||||
} catch (err) {
|
||||
alert(`Error: ${extractApiErrorMessage(err)}`);
|
||||
alert(`Error: ${err.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,6 @@ import {
|
||||
HassioAddonDetails,
|
||||
restartHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -27,7 +26,7 @@ export const suggestAddonRestart = async (
|
||||
} catch (err) {
|
||||
showAlertDialog(element, {
|
||||
title: "Failed to restart",
|
||||
text: extractApiErrorMessage(err),
|
||||
text: err.body.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -13,17 +13,15 @@ import {
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
import {
|
||||
createHassioFullSnapshot,
|
||||
createHassioPartialSnapshot,
|
||||
@@ -82,6 +80,8 @@ class HassioSnapshots extends LitElement {
|
||||
{ slug: "addons/local", name: "Local add-ons", checked: true },
|
||||
];
|
||||
|
||||
@internalProperty() private _creatingSnapshot = false;
|
||||
|
||||
@internalProperty() private _error = "";
|
||||
|
||||
public async refreshData() {
|
||||
@@ -192,9 +192,12 @@ class HassioSnapshots extends LitElement {
|
||||
: undefined}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._createSnapshot}>
|
||||
<mwc-button
|
||||
.disabled=${this._creatingSnapshot}
|
||||
@click=${this._createSnapshot}
|
||||
>
|
||||
Create
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
@@ -227,7 +230,7 @@ class HassioSnapshots extends LitElement {
|
||||
.icon=${snapshot.type === "full"
|
||||
? mdiPackageVariantClosed
|
||||
: mdiPackageVariant}
|
||||
icon-class="snapshot"
|
||||
.icon-class="snapshot"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -290,20 +293,17 @@ class HassioSnapshots extends LitElement {
|
||||
this._snapshots = await fetchHassioSnapshots(this.hass);
|
||||
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._error = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
private async _createSnapshot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
private async _createSnapshot() {
|
||||
this._error = "";
|
||||
if (this._snapshotHasPassword && !this._snapshotPassword.length) {
|
||||
this._error = "Please enter a password.";
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
this._creatingSnapshot = true;
|
||||
await this.updateComplete;
|
||||
|
||||
const name =
|
||||
@@ -343,9 +343,10 @@ class HassioSnapshots extends LitElement {
|
||||
this._updateSnapshots();
|
||||
fireEvent(this, "hass-api-called", { success: true, response: null });
|
||||
} catch (err) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._error = err.message;
|
||||
} finally {
|
||||
this._creatingSnapshot = false;
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private _computeDetails(snapshot: HassioSnapshot) {
|
||||
|
@@ -14,15 +14,9 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-button-menu";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import {
|
||||
extractApiErrorMessage,
|
||||
ignoredStatusCodes,
|
||||
} from "../../../src/data/hassio/common";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
@@ -85,8 +79,7 @@ class HassioHostInfo extends LitElement {
|
||||
</mwc-button>
|
||||
</ha-settings-row>`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("network") &&
|
||||
atLeastVersion(this.hass.config.version, 0, 115)
|
||||
${this.hostInfo.features.includes("network")
|
||||
? html` <ha-settings-row>
|
||||
<span slot="heading">
|
||||
IP address
|
||||
@@ -113,12 +106,12 @@ class HassioHostInfo extends LitElement {
|
||||
${this.hostInfo.version !== this.hostInfo.version_latest &&
|
||||
this.hostInfo.features.includes("hassos")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
title="Update the host OS"
|
||||
label="Update"
|
||||
@click=${this._osUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
@@ -146,24 +139,24 @@ class HassioHostInfo extends LitElement {
|
||||
<div class="card-actions">
|
||||
${this.hostInfo.features.includes("reboot")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
title="Reboot the host OS"
|
||||
label="Reboot"
|
||||
class="warning"
|
||||
@click=${this._hostReboot}
|
||||
>
|
||||
Reboot
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
${this.hostInfo.features.includes("shutdown")
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
title="Shutdown the host OS"
|
||||
label="Shutdown"
|
||||
class="warning"
|
||||
@click=${this._hostShutdown}
|
||||
>
|
||||
Shutdown
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
@@ -190,177 +183,6 @@ class HassioHostInfo extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
|
||||
if (!network_info) {
|
||||
return "";
|
||||
}
|
||||
return Object.keys(network_info?.interfaces)
|
||||
.map((device) => network_info.interfaces[device])
|
||||
.find((device) => device.primary)?.ip_address;
|
||||
});
|
||||
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
await this._showHardware();
|
||||
break;
|
||||
case 1:
|
||||
await this._importFromUSB();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async _showHardware(): Promise<void> {
|
||||
try {
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get Hardware list",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _hostReboot(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to reboot the host?",
|
||||
confirmText: "reboot host",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _hostShutdown(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Shutdown",
|
||||
text: "Are you sure you want to shutdown the host?",
|
||||
confirmText: "shutdown host",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
// Ignore connection errors, these are all expected
|
||||
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _osUpdate(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update",
|
||||
text: "Are you sure you want to update the OS?",
|
||||
confirmText: "update os",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _changeNetworkClicked(): Promise<void> {
|
||||
showNetworkDialog(this, {
|
||||
network: this._networkInfo!,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
const curHostname: string = this.hostInfo.hostname;
|
||||
const hostname = await showPromptDialog(this, {
|
||||
title: "Change hostname",
|
||||
inputLabel: "Please enter a new hostname:",
|
||||
inputType: "string",
|
||||
defaultValue: curHostname,
|
||||
});
|
||||
|
||||
if (hostname && hostname !== curHostname) {
|
||||
try {
|
||||
await changeHostOptions(this.hass, { hostname });
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._networkInfo = await fetchNetworkInfo(this.hass);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -416,6 +238,162 @@ class HassioHostInfo extends LitElement {
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => {
|
||||
if (!network_info) {
|
||||
return "";
|
||||
}
|
||||
return Object.keys(network_info?.interfaces)
|
||||
.map((device) => network_info.interfaces[device])
|
||||
.find((device) => device.primary)?.ip_address;
|
||||
});
|
||||
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
await this._showHardware();
|
||||
break;
|
||||
case 1:
|
||||
await this._importFromUSB();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async _showHardware(): Promise<void> {
|
||||
try {
|
||||
const content = await fetchHassioHardwareInfo(this.hass);
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: "Hardware",
|
||||
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`,
|
||||
});
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to get Hardware list",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _hostReboot(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Reboot",
|
||||
text: "Are you sure you want to reboot the host?",
|
||||
confirmText: "reboot host",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await rebootHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reboot",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _hostShutdown(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Shutdown",
|
||||
text: "Are you sure you want to shutdown the host?",
|
||||
confirmText: "shutdown host",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await shutdownHost(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to shutdown",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _osUpdate(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update",
|
||||
text: "Are you sure you want to update the OS?",
|
||||
confirmText: "update os",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateOS(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _changeNetworkClicked(): Promise<void> {
|
||||
showNetworkDialog(this, {
|
||||
network: this._networkInfo!,
|
||||
loadData: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
private async _changeHostnameClicked(): Promise<void> {
|
||||
const curHostname: string = this.hostInfo.hostname;
|
||||
const hostname = await showPromptDialog(this, {
|
||||
title: "Change hostname",
|
||||
inputLabel: "Please enter a new hostname:",
|
||||
inputType: "string",
|
||||
defaultValue: curHostname,
|
||||
});
|
||||
|
||||
if (hostname && hostname !== curHostname) {
|
||||
try {
|
||||
await changeHostOptions(this.hass, { hostname });
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Setting hostname failed",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _importFromUSB(): Promise<void> {
|
||||
try {
|
||||
await configSyncOS(this.hass);
|
||||
this.hostInfo = await fetchHassioHostInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to import from USB",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._networkInfo = await fetchNetworkInfo(this.hass);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-settings-row";
|
||||
import "../../../src/components/ha-switch";
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
setSupervisorOption,
|
||||
SupervisorOptions,
|
||||
updateSupervisor,
|
||||
fetchHassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
@@ -27,7 +26,6 @@ import {
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
class HassioSupervisorInfo extends LitElement {
|
||||
@@ -58,12 +56,12 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</span>
|
||||
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
title="Update the supervisor"
|
||||
label="Update"
|
||||
@click=${this._supervisorUpdate}
|
||||
>
|
||||
Update
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
@@ -76,21 +74,21 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</span>
|
||||
${this.supervisorInfo.channel === "beta"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
@click=${this._toggleBeta}
|
||||
label="Leave beta channel"
|
||||
title="Get stable updates for Home Assistant, supervisor and host"
|
||||
>
|
||||
Leave beta channel
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
`
|
||||
: this.supervisorInfo.channel === "stable"
|
||||
? html`
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
@click=${this._toggleBeta}
|
||||
label="Join beta channel"
|
||||
title="Get beta updates for Home Assistant (RCs), supervisor and host"
|
||||
>
|
||||
Join beta channel
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
</ha-settings-row>
|
||||
@@ -133,136 +131,17 @@ class HassioSupervisorInfo extends LitElement {
|
||||
</div>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button
|
||||
<mwc-button
|
||||
@click=${this._supervisorReload}
|
||||
title="Reload parts of the supervisor."
|
||||
label="Reload"
|
||||
>
|
||||
Reload
|
||||
</ha-progress-button>
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _toggleBeta(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
if (this.supervisorInfo.channel === "stable") {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data: Partial<SupervisorOptions> = {
|
||||
channel: this.supervisorInfo.channel === "stable" ? "beta" : "stable",
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
await reloadSupervisor(this.hass);
|
||||
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _supervisorReload(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
try {
|
||||
await reloadSupervisor(this.hass);
|
||||
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reload the supervisor",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _supervisorUpdate(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "Update supervisor",
|
||||
text: `Are you sure you want to upgrade supervisor to version ${this.supervisorInfo.version_latest}?`,
|
||||
confirmText: "update",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
button.progress = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _diagnosticsInformationDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
title: "Help Improve Home Assistant",
|
||||
text: html`Would you want to automatically share crash reports and
|
||||
diagnostic information when the supervisor encounters unexpected errors?
|
||||
<br /><br />
|
||||
This will allow us to fix the problems, the information is only
|
||||
accessible to the Home Assistant Core team and will not be shared with
|
||||
others.
|
||||
<br /><br />
|
||||
The data does not include any private/sensitive information and you can
|
||||
disable this in settings at any time you want.`,
|
||||
});
|
||||
}
|
||||
|
||||
private async _toggleDiagnostics(): Promise<void> {
|
||||
try {
|
||||
const data: SupervisorOptions = {
|
||||
diagnostics: !this.supervisorInfo?.diagnostics,
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text: extractApiErrorMessage(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -292,13 +171,109 @@ class HassioSupervisorInfo extends LitElement {
|
||||
ha-settings-row[three-line] {
|
||||
height: 74px;
|
||||
}
|
||||
ha-settings-row > div[slot="description"] {
|
||||
ha-settings-row > span[slot="description"] {
|
||||
white-space: normal;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private async _toggleBeta(): Promise<void> {
|
||||
if (this.supervisorInfo.channel === "stable") {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: "WARNING",
|
||||
text: html` Beta releases are for testers and early adopters and can
|
||||
contain unstable code changes.
|
||||
<br />
|
||||
<b>
|
||||
Make sure you have backups of your data before you activate this
|
||||
feature.
|
||||
</b>
|
||||
<br /><br />
|
||||
This includes beta releases for:
|
||||
<li>Home Assistant Core</li>
|
||||
<li>Home Assistant Supervisor</li>
|
||||
<li>Home Assistant Operating System</li>
|
||||
<br />
|
||||
Do you want to join the beta channel?`,
|
||||
confirmText: "join beta",
|
||||
dismissText: "no",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const data: Partial<SupervisorOptions> = {
|
||||
channel: this.supervisorInfo.channel !== "stable" ? "beta" : "stable",
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
await reloadSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _supervisorReload(): Promise<void> {
|
||||
try {
|
||||
await reloadSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to reload the supervisor",
|
||||
text:
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _supervisorUpdate(): Promise<void> {
|
||||
try {
|
||||
await updateSupervisor(this.hass);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to update the supervisor",
|
||||
text:
|
||||
typeof err === "object" ? err.body.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async _diagnosticsInformationDialog(): Promise<void> {
|
||||
await showAlertDialog(this, {
|
||||
title: "Help Improve Home Assistant",
|
||||
text: html`Would you want to automatically share crash reports and
|
||||
diagnostic information when the supervisor encounters unexpected errors?
|
||||
<br /><br />
|
||||
This will allow us to fix the problems, the information is only
|
||||
accessible to the Home Assistant Core team and will not be shared with
|
||||
others.
|
||||
<br /><br />
|
||||
The data does not include any private/sensitive information and you can
|
||||
disable this in settings at any time you want.`,
|
||||
});
|
||||
}
|
||||
|
||||
private async _toggleDiagnostics(): Promise<void> {
|
||||
try {
|
||||
const data: SupervisorOptions = {
|
||||
diagnostics: !this.supervisorInfo?.diagnostics,
|
||||
};
|
||||
await setSupervisorOption(this.hass, data);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: "Failed to set supervisor option",
|
||||
text:
|
||||
typeof err === "object" ? err.body.message || "Unkown error" : err,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -12,15 +12,15 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/layouts/hass-loading-screen";
|
||||
import "../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface LogProvider {
|
||||
key: string;
|
||||
@@ -104,42 +104,12 @@ class HassioSupervisorLog extends LitElement {
|
||||
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`}
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-progress-button @click=${this._refresh}>
|
||||
Refresh
|
||||
</ha-progress-button>
|
||||
<mwc-button @click=${this._loadData}>Refresh</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
this._selectedLogProvider = provider;
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
private async _refresh(ev: CustomEvent): Promise<void> {
|
||||
const button = ev.currentTarget as any;
|
||||
button.progress = true;
|
||||
await this._loadData();
|
||||
button.progress = false;
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
|
||||
try {
|
||||
this._content = await fetchHassioLogs(
|
||||
this.hass,
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${extractApiErrorMessage(
|
||||
err
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -163,6 +133,27 @@ class HassioSupervisorLog extends LitElement {
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private async _setLogProvider(ev): Promise<void> {
|
||||
const provider = ev.detail.item.getAttribute("provider");
|
||||
this._selectedLogProvider = provider;
|
||||
await this._loadData();
|
||||
}
|
||||
|
||||
private async _loadData(): Promise<void> {
|
||||
this._error = undefined;
|
||||
|
||||
try {
|
||||
this._content = await fetchHassioLogs(
|
||||
this.hass,
|
||||
this._selectedLogProvider
|
||||
);
|
||||
} catch (err) {
|
||||
this._error = `Failed to get supervisor logs, ${
|
||||
typeof err === "object" ? err.body?.message || "Unkown error" : err
|
||||
}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -79,7 +79,6 @@
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@thomasloven/round-slider": "0.5.0",
|
||||
"@types/chromecast-caf-sender": "^1.0.3",
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200912.0",
|
||||
version="20200901.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -1,9 +0,0 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
/** Return an array of domains with the service. */
|
||||
export const componentsWithService = (
|
||||
hass: HomeAssistant,
|
||||
service: string
|
||||
): Array<string> =>
|
||||
hass &&
|
||||
Object.keys(hass.services).filter((key) => service in hass.services[key]);
|
@@ -44,6 +44,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
"updater",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
"weather",
|
||||
|
@@ -1,33 +1,7 @@
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { PropertyDeclaration, UpdatingElement } from "lit-element";
|
||||
import type { ClassElement } from "../../types";
|
||||
|
||||
type Callback = (oldValue: any, newValue: any) => void;
|
||||
|
||||
class Storage {
|
||||
constructor() {
|
||||
window.addEventListener("storage", (ev: StorageEvent) => {
|
||||
if (ev.key && this.hasKey(ev.key)) {
|
||||
this._storage[ev.key] = ev.newValue
|
||||
? JSON.parse(ev.newValue)
|
||||
: ev.newValue;
|
||||
if (this._listeners[ev.key]) {
|
||||
this._listeners[ev.key].forEach((listener) =>
|
||||
listener(
|
||||
ev.oldValue ? JSON.parse(ev.oldValue) : ev.oldValue,
|
||||
this._storage[ev.key!]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _storage: { [storageKey: string]: any } = {};
|
||||
|
||||
private _listeners: {
|
||||
[storageKey: string]: Callback[];
|
||||
} = {};
|
||||
private _storage: any = {};
|
||||
|
||||
public addFromStorage(storageKey: any): void {
|
||||
if (!this._storage[storageKey]) {
|
||||
@@ -38,30 +12,6 @@ class Storage {
|
||||
}
|
||||
}
|
||||
|
||||
public subscribeChanges(
|
||||
storageKey: string,
|
||||
callback: Callback
|
||||
): UnsubscribeFunc {
|
||||
if (this._listeners[storageKey]) {
|
||||
this._listeners[storageKey].push(callback);
|
||||
} else {
|
||||
this._listeners[storageKey] = [callback];
|
||||
}
|
||||
return () => {
|
||||
this.unsubscribeChanges(storageKey, callback);
|
||||
};
|
||||
}
|
||||
|
||||
public unsubscribeChanges(storageKey: string, callback: Callback) {
|
||||
if (!(storageKey in this._listeners)) {
|
||||
return;
|
||||
}
|
||||
const index = this._listeners[storageKey].indexOf(callback);
|
||||
if (index !== -1) {
|
||||
this._listeners[storageKey].splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public hasKey(storageKey: string): any {
|
||||
return storageKey in this._storage;
|
||||
}
|
||||
@@ -82,49 +32,30 @@ class Storage {
|
||||
|
||||
const storage = new Storage();
|
||||
|
||||
export const LocalStorage = (
|
||||
storageKey?: string,
|
||||
property?: boolean,
|
||||
propertyOptions?: PropertyDeclaration
|
||||
): any => {
|
||||
return (clsElement: ClassElement) => {
|
||||
const key = String(clsElement.key);
|
||||
storageKey = storageKey || String(clsElement.key);
|
||||
const initVal = clsElement.initializer
|
||||
? clsElement.initializer()
|
||||
: undefined;
|
||||
export const LocalStorage = (key?: string) => {
|
||||
return (element: ClassElement, propName: string) => {
|
||||
const storageKey = key || propName;
|
||||
const initVal = element.initializer ? element.initializer() : undefined;
|
||||
|
||||
storage.addFromStorage(storageKey);
|
||||
|
||||
const subscribe = (el: UpdatingElement): UnsubscribeFunc =>
|
||||
storage.subscribeChanges(storageKey!, (oldValue) => {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
});
|
||||
|
||||
const getValue = (): any => {
|
||||
return storage.hasKey(storageKey!)
|
||||
? storage.getValue(storageKey!)
|
||||
return storage.hasKey(storageKey)
|
||||
? storage.getValue(storageKey)
|
||||
: initVal;
|
||||
};
|
||||
|
||||
const setValue = (el: UpdatingElement, value: any) => {
|
||||
let oldValue: unknown | undefined;
|
||||
if (property) {
|
||||
oldValue = getValue();
|
||||
}
|
||||
storage.setValue(storageKey!, value);
|
||||
if (property) {
|
||||
el.requestUpdate(clsElement.key, oldValue);
|
||||
}
|
||||
const setValue = (val: any) => {
|
||||
storage.setValue(storageKey, val);
|
||||
};
|
||||
|
||||
return {
|
||||
kind: "method",
|
||||
placement: "prototype",
|
||||
key: clsElement.key,
|
||||
placement: "own",
|
||||
key: element.key,
|
||||
descriptor: {
|
||||
set(this: UpdatingElement, value: unknown) {
|
||||
setValue(this, value);
|
||||
set(value) {
|
||||
setValue(value);
|
||||
},
|
||||
get() {
|
||||
return getValue();
|
||||
@@ -132,24 +63,6 @@ export const LocalStorage = (
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
},
|
||||
finisher(cls: typeof UpdatingElement) {
|
||||
if (property) {
|
||||
const connectedCallback = cls.prototype.connectedCallback;
|
||||
const disconnectedCallback = cls.prototype.disconnectedCallback;
|
||||
cls.prototype.connectedCallback = function () {
|
||||
connectedCallback.call(this);
|
||||
this[`__unbsubLocalStorage${key}`] = subscribe(this);
|
||||
};
|
||||
cls.prototype.disconnectedCallback = function () {
|
||||
disconnectedCallback.call(this);
|
||||
this[`__unbsubLocalStorage${key}`]();
|
||||
};
|
||||
cls.createProperty(clsElement.key, {
|
||||
noAccessor: true,
|
||||
...propertyOptions,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@@ -105,12 +105,12 @@ const processTheme = (
|
||||
const keys = {};
|
||||
for (const key of Object.keys(combinedTheme)) {
|
||||
const prefixedKey = `--${key}`;
|
||||
const value = String(combinedTheme[key]!);
|
||||
const value = combinedTheme[key]!;
|
||||
styles[prefixedKey] = value;
|
||||
keys[prefixedKey] = "";
|
||||
|
||||
// Try to create a rgb value for this key if it is not a var
|
||||
if (value.startsWith("#")) {
|
||||
if (!value.startsWith("#")) {
|
||||
// Can't convert non hex value
|
||||
continue;
|
||||
}
|
||||
|
@@ -3,51 +3,49 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
/** Return an icon representing a binary sensor state. */
|
||||
|
||||
export const binarySensorIcon = (state: HassEntity) => {
|
||||
const is_off = state.state && state.state === "off";
|
||||
const activated = state.state && state.state === "off";
|
||||
switch (state.attributes.device_class) {
|
||||
case "battery":
|
||||
return is_off ? "hass:battery" : "hass:battery-outline";
|
||||
case "battery_charging":
|
||||
return is_off ? "hass:battery" : "hass:battery-charging";
|
||||
return activated ? "hass:battery" : "hass:battery-outline";
|
||||
case "cold":
|
||||
return is_off ? "hass:thermometer" : "hass:snowflake";
|
||||
return activated ? "hass:thermometer" : "hass:snowflake";
|
||||
case "connectivity":
|
||||
return is_off ? "hass:server-network-off" : "hass:server-network";
|
||||
return activated ? "hass:server-network-off" : "hass:server-network";
|
||||
case "door":
|
||||
return is_off ? "hass:door-closed" : "hass:door-open";
|
||||
return activated ? "hass:door-closed" : "hass:door-open";
|
||||
case "garage_door":
|
||||
return is_off ? "hass:garage" : "hass:garage-open";
|
||||
return activated ? "hass:garage" : "hass:garage-open";
|
||||
case "gas":
|
||||
case "power":
|
||||
case "problem":
|
||||
case "safety":
|
||||
case "smoke":
|
||||
return is_off ? "hass:shield-check" : "hass:alert";
|
||||
return activated ? "hass:shield-check" : "hass:alert";
|
||||
case "heat":
|
||||
return is_off ? "hass:thermometer" : "hass:fire";
|
||||
return activated ? "hass:thermometer" : "hass:fire";
|
||||
case "light":
|
||||
return is_off ? "hass:brightness-5" : "hass:brightness-7";
|
||||
return activated ? "hass:brightness-5" : "hass:brightness-7";
|
||||
case "lock":
|
||||
return is_off ? "hass:lock" : "hass:lock-open";
|
||||
return activated ? "hass:lock" : "hass:lock-open";
|
||||
case "moisture":
|
||||
return is_off ? "hass:water-off" : "hass:water";
|
||||
return activated ? "hass:water-off" : "hass:water";
|
||||
case "motion":
|
||||
return is_off ? "hass:walk" : "hass:run";
|
||||
return activated ? "hass:walk" : "hass:run";
|
||||
case "occupancy":
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
return activated ? "hass:home-outline" : "hass:home";
|
||||
case "opening":
|
||||
return is_off ? "hass:square" : "hass:square-outline";
|
||||
return activated ? "hass:square" : "hass:square-outline";
|
||||
case "plug":
|
||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
||||
return activated ? "hass:power-plug-off" : "hass:power-plug";
|
||||
case "presence":
|
||||
return is_off ? "hass:home-outline" : "hass:home";
|
||||
return activated ? "hass:home-outline" : "hass:home";
|
||||
case "sound":
|
||||
return is_off ? "hass:music-note-off" : "hass:music-note";
|
||||
return activated ? "hass:music-note-off" : "hass:music-note";
|
||||
case "vibration":
|
||||
return is_off ? "hass:crop-portrait" : "hass:vibrate";
|
||||
return activated ? "hass:crop-portrait" : "hass:vibrate";
|
||||
case "window":
|
||||
return is_off ? "hass:window-closed" : "hass:window-open";
|
||||
return activated ? "hass:window-closed" : "hass:window-open";
|
||||
default:
|
||||
return is_off ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
|
||||
return activated ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
|
||||
}
|
||||
};
|
||||
|
@@ -3,10 +3,9 @@ import { HomeAssistant } from "../../types";
|
||||
import { DOMAINS_WITH_CARD } from "../const";
|
||||
import { canToggleState } from "./can_toggle_state";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { UNAVAILABLE } from "../../data/entity";
|
||||
|
||||
export const stateCardType = (hass: HomeAssistant, stateObj: HassEntity) => {
|
||||
if (stateObj.state === UNAVAILABLE) {
|
||||
if (stateObj.state === "unavailable") {
|
||||
return "display";
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,7 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import durationToSeconds from "../datetime/duration_to_seconds";
|
||||
|
||||
export const timerTimeRemaining = (
|
||||
stateObj: HassEntity
|
||||
): undefined | number => {
|
||||
if (!stateObj.attributes.remaining) {
|
||||
return undefined;
|
||||
}
|
||||
export const timerTimeRemaining = (stateObj: HassEntity) => {
|
||||
let timeRemaining = durationToSeconds(stateObj.attributes.remaining);
|
||||
|
||||
if (stateObj.state === "active") {
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import {
|
||||
ListItem,
|
||||
RequestSelectedDetail,
|
||||
ListItem,
|
||||
} from "@material/mwc-list/mwc-list-item";
|
||||
|
||||
export const shouldHandleRequestSelectedEvent = (
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): boolean => {
|
||||
if (!ev.detail.selected || ev.detail.source !== "property") {
|
||||
if (!ev.detail.selected && ev.detail.source !== "property") {
|
||||
return false;
|
||||
}
|
||||
(ev.currentTarget as ListItem).selected = false;
|
||||
(ev.target as ListItem).selected = false;
|
||||
return true;
|
||||
};
|
||||
|
@@ -1,50 +0,0 @@
|
||||
// From: underscore.js https://github.com/jashkenas/underscore/blob/master/underscore.js
|
||||
|
||||
// Returns a function, that, when invoked, will only be triggered at most once
|
||||
// during a given window of time. Normally, the throttled function will run
|
||||
// as much as it can, without ever going more than once per `wait` duration;
|
||||
// but if you'd like to disable the execution on the leading edge, pass
|
||||
// `false for leading`. To disable execution on the trailing edge, ditto.
|
||||
export const throttle = <T extends Function>(
|
||||
func: T,
|
||||
wait: number,
|
||||
leading = true,
|
||||
trailing = true
|
||||
): T => {
|
||||
let timeout: number | undefined;
|
||||
let previous = 0;
|
||||
let context: any;
|
||||
let args: any;
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
func.apply(context, args);
|
||||
if (!timeout) {
|
||||
context = null;
|
||||
args = null;
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
return function (...argmnts) {
|
||||
// @ts-ignore
|
||||
// @typescript-eslint/no-this-alias
|
||||
context = this;
|
||||
args = argmnts;
|
||||
|
||||
const now = Date.now();
|
||||
if (!previous && leading === false) {
|
||||
previous = now;
|
||||
}
|
||||
const remaining = wait - (now - previous);
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = undefined;
|
||||
}
|
||||
previous = now;
|
||||
func.apply(context, args);
|
||||
} else if (!timeout && trailing !== false) {
|
||||
timeout = window.setTimeout(later, remaining);
|
||||
}
|
||||
};
|
||||
};
|
@@ -1,178 +0,0 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@vaadin/vaadin-combo-box/theme/material/vaadin-combo-box-light";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-button";
|
||||
import "./state-badge";
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
|
||||
const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => {
|
||||
if (!root.firstElementChild) {
|
||||
root.innerHTML = `
|
||||
<style>
|
||||
paper-item {
|
||||
margin: -10px;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<paper-item></paper-item>
|
||||
`;
|
||||
}
|
||||
root.querySelector("paper-item")!.textContent = model.item;
|
||||
};
|
||||
|
||||
@customElement("ha-entity-attribute-picker")
|
||||
class HaEntityAttributePicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entityId?: string;
|
||||
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-custom-value" })
|
||||
public allowCustomValue;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string;
|
||||
|
||||
@property({ type: Boolean }) private _opened = false;
|
||||
|
||||
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("_opened") && this._opened) {
|
||||
const state = this.entityId ? this.hass.states[this.entityId] : undefined;
|
||||
(this._comboBox as any).items = state
|
||||
? Object.keys(state.attributes)
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
.value=${this._value}
|
||||
.allowCustomValue=${this.allowCustomValue}
|
||||
.renderer=${rowRenderer}
|
||||
@opened-changed=${this._openedChanged}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
<paper-input
|
||||
.autofocus=${this.autofocus}
|
||||
.label=${this.label ??
|
||||
this.hass.localize(
|
||||
"ui.components.entity.entity-attribute-picker.attribute"
|
||||
)}
|
||||
.value=${this._value}
|
||||
.disabled=${this.disabled || !this.entityId}
|
||||
class="input"
|
||||
autocapitalize="none"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
${this.value
|
||||
? html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.entity.entity-picker.clear"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
icon="hass:close"
|
||||
@click=${this._clearValue}
|
||||
no-ripple
|
||||
>
|
||||
Clear
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.components.entity.entity-attribute-picker.show_attributes"
|
||||
)}
|
||||
slot="suffix"
|
||||
class="toggle-button"
|
||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
||||
>
|
||||
Toggle
|
||||
</ha-icon-button>
|
||||
</paper-input>
|
||||
</vaadin-combo-box-light>
|
||||
`;
|
||||
}
|
||||
|
||||
private _clearValue(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._setValue("");
|
||||
}
|
||||
|
||||
private get _value() {
|
||||
return this.value || "";
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||
this._opened = ev.detail.value;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: PolymerChangedEvent<string>) {
|
||||
const newValue = ev.detail.value;
|
||||
if (newValue !== this._value) {
|
||||
this._setValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue(value: string) {
|
||||
this.value = value;
|
||||
setTimeout(() => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "change");
|
||||
}, 0);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-input > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 0px 2px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-attribute-picker": HaEntityAttributePicker;
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
import "../ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@@ -6,7 +7,6 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
@@ -20,7 +20,6 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-icon-button";
|
||||
import "./state-badge";
|
||||
|
||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||
@@ -52,8 +51,7 @@ const rowRenderer = (
|
||||
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
|
||||
};
|
||||
|
||||
@customElement("ha-entity-picker")
|
||||
export class HaEntityPicker extends LitElement {
|
||||
class HaEntityPicker extends LitElement {
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@property({ type: Boolean }) public disabled?: boolean;
|
||||
@@ -97,8 +95,6 @@ export class HaEntityPicker extends LitElement {
|
||||
|
||||
@query("vaadin-combo-box-light") private _comboBox!: HTMLElement;
|
||||
|
||||
private _initedStates = false;
|
||||
|
||||
private _getStates = memoizeOne(
|
||||
(
|
||||
_opened: boolean,
|
||||
@@ -152,18 +148,11 @@ export class HaEntityPicker extends LitElement {
|
||||
);
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues) {
|
||||
if (
|
||||
changedProps.has("value") ||
|
||||
changedProps.has("label") ||
|
||||
changedProps.has("disabled")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (!this._initedStates || (changedProps.has("_opened") && this._opened)) {
|
||||
if (changedProps.has("_opened") && this._opened) {
|
||||
const states = this._getStates(
|
||||
this._opened,
|
||||
this.hass,
|
||||
@@ -173,7 +162,6 @@ export class HaEntityPicker extends LitElement {
|
||||
this.includeDeviceClasses
|
||||
);
|
||||
(this._comboBox as any).items = states;
|
||||
this._initedStates = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +169,7 @@ export class HaEntityPicker extends LitElement {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<vaadin-combo-box-light
|
||||
item-value-path="entity_id"
|
||||
@@ -278,6 +267,8 @@ export class HaEntityPicker extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-picker", HaEntityPicker);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-picker": HaEntityPicker;
|
||||
|
@@ -20,7 +20,6 @@ import { stateIcon } from "../../common/entity/state_icon";
|
||||
import { timerTimeRemaining } from "../../common/entity/timer_time_remaining";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../ha-label-badge";
|
||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||
|
||||
@customElement("ha-state-label-badge")
|
||||
export class HaStateLabelBadge extends LitElement {
|
||||
@@ -82,8 +81,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
? ""
|
||||
: this.image
|
||||
? this.image
|
||||
: state.attributes.entity_picture_local ||
|
||||
state.attributes.entity_picture}"
|
||||
: state.attributes.entity_picture_local || state.attributes.entity_picture}"
|
||||
.label="${this._computeLabel(domain, state, this._timerTimeRemaining)}"
|
||||
.description="${this.name ? this.name : computeStateName(state)}"
|
||||
></ha-label-badge>
|
||||
@@ -110,7 +108,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
return null;
|
||||
case "sensor":
|
||||
default:
|
||||
return state.state === UNKNOWN
|
||||
return state.state === "unknown"
|
||||
? "-"
|
||||
: state.attributes.unit_of_measurement
|
||||
? state.state
|
||||
@@ -123,7 +121,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
}
|
||||
|
||||
private _computeIcon(domain: string, state: HassEntity) {
|
||||
if (state.state === UNAVAILABLE) {
|
||||
if (state.state === "unavailable") {
|
||||
return null;
|
||||
}
|
||||
switch (domain) {
|
||||
@@ -168,7 +166,7 @@ export class HaStateLabelBadge extends LitElement {
|
||||
|
||||
private _computeLabel(domain, state, _timerTimeRemaining) {
|
||||
if (
|
||||
state.state === UNAVAILABLE ||
|
||||
state.state === "unavailable" ||
|
||||
["device_tracker", "alarm_control_panel", "person"].includes(domain)
|
||||
) {
|
||||
// Localize the state with a special state_badge namespace, which has variations of
|
||||
|
@@ -3,43 +3,56 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { supportsFeature } from "../common/entity/supports-feature";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import { getExternalConfig } from "../external_app/external_config";
|
||||
import {
|
||||
CAMERA_SUPPORT_STREAM,
|
||||
computeMJPEGStreamUrl,
|
||||
fetchStreamUrl,
|
||||
} from "../data/camera";
|
||||
import { CameraEntity, HomeAssistant } from "../types";
|
||||
import "./ha-hls-player";
|
||||
|
||||
type HLSModule = typeof import("hls.js");
|
||||
|
||||
@customElement("ha-camera-stream")
|
||||
class HaCameraStream extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: CameraEntity;
|
||||
@property() public stateObj?: CameraEntity;
|
||||
|
||||
@property({ type: Boolean, attribute: "controls" })
|
||||
public controls = false;
|
||||
@property({ type: Boolean }) public showControls = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "muted" })
|
||||
public muted = false;
|
||||
@internalProperty() private _attached = false;
|
||||
|
||||
// We keep track if we should force MJPEG with a string
|
||||
// that way it automatically resets if we change entity.
|
||||
@internalProperty() private _forceMJPEG?: string;
|
||||
@internalProperty() private _forceMJPEG: string | undefined = undefined;
|
||||
|
||||
@internalProperty() private _url?: string;
|
||||
private _hlsPolyfillInstance?: Hls;
|
||||
|
||||
private _useExoPlayer = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._attached = true;
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._attached = false;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.stateObj) {
|
||||
if (!this.stateObj || !this._attached) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
@@ -56,25 +69,51 @@ class HaCameraStream extends LitElement {
|
||||
)} camera.`}
|
||||
/>
|
||||
`
|
||||
: this._url
|
||||
? html`
|
||||
<ha-hls-player
|
||||
: html`
|
||||
<video
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
.muted=${this.muted}
|
||||
.controls=${this.controls}
|
||||
.hass=${this.hass}
|
||||
.url=${this._url}
|
||||
></ha-hls-player>
|
||||
`
|
||||
: ""}
|
||||
?controls=${this.showControls}
|
||||
@loadeddata=${this._elementResized}
|
||||
></video>
|
||||
`}
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("stateObj") && !this._shouldRenderMJPEG) {
|
||||
this._forceMJPEG = undefined;
|
||||
this._getStreamUrl();
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
const stateObjChanged = changedProps.has("stateObj");
|
||||
const attachedChanged = changedProps.has("_attached");
|
||||
|
||||
const oldState = changedProps.get("stateObj") as this["stateObj"];
|
||||
const oldEntityId = oldState ? oldState.entity_id : undefined;
|
||||
const curEntityId = this.stateObj ? this.stateObj.entity_id : undefined;
|
||||
|
||||
if (
|
||||
(!stateObjChanged && !attachedChanged) ||
|
||||
(stateObjChanged && oldEntityId === curEntityId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are no longer attached, destroy polyfill.
|
||||
if (attachedChanged && !this._attached) {
|
||||
this._destroyPolyfill();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nothing to do if we are render MJPEG.
|
||||
if (this._shouldRenderMJPEG) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tear down existing polyfill, if available
|
||||
this._destroyPolyfill();
|
||||
|
||||
if (curEntityId) {
|
||||
this._startHls();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,35 +125,136 @@ class HaCameraStream extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private async _getStreamUrl(): Promise<void> {
|
||||
private get _videoEl(): HTMLVideoElement {
|
||||
return this.shadowRoot!.querySelector("video")!;
|
||||
}
|
||||
|
||||
private async _getUseExoPlayer(): Promise<boolean> {
|
||||
if (!this.hass!.auth.external) {
|
||||
return false;
|
||||
}
|
||||
const externalConfig = await getExternalConfig(this.hass!.auth.external);
|
||||
return externalConfig && externalConfig.hasExoPlayer;
|
||||
}
|
||||
|
||||
private async _startHls(): Promise<void> {
|
||||
// eslint-disable-next-line
|
||||
let hls;
|
||||
const videoEl = this._videoEl;
|
||||
this._useExoPlayer = await this._getUseExoPlayer();
|
||||
if (!this._useExoPlayer) {
|
||||
hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
|
||||
.default as HLSModule;
|
||||
let hlsSupported = hls.isSupported();
|
||||
|
||||
if (!hlsSupported) {
|
||||
hlsSupported =
|
||||
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
|
||||
}
|
||||
|
||||
if (!hlsSupported) {
|
||||
this._forceMJPEG = this.stateObj!.entity_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { url } = await fetchStreamUrl(
|
||||
this.hass!,
|
||||
this.stateObj!.entity_id
|
||||
);
|
||||
|
||||
this._url = url;
|
||||
if (this._useExoPlayer) {
|
||||
this._renderHLSExoPlayer(url);
|
||||
} else if (hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, hls, url);
|
||||
} else {
|
||||
this._renderHLSNative(videoEl, url);
|
||||
}
|
||||
return;
|
||||
} catch (err) {
|
||||
// Fails if we were unable to get a stream
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
|
||||
this._forceMJPEG = this.stateObj!.entity_id;
|
||||
}
|
||||
}
|
||||
|
||||
private async _renderHLSExoPlayer(url: string) {
|
||||
window.addEventListener("resize", this._resizeExoPlayer);
|
||||
this.updateComplete.then(() => nextRender()).then(this._resizeExoPlayer);
|
||||
this._videoEl.style.visibility = "hidden";
|
||||
await this.hass!.auth.external!.sendMessage({
|
||||
type: "exoplayer/play_hls",
|
||||
payload: new URL(url, window.location.href).toString(),
|
||||
});
|
||||
}
|
||||
|
||||
private _resizeExoPlayer = () => {
|
||||
const rect = this._videoEl.getBoundingClientRect();
|
||||
this.hass!.auth.external!.fireMessage({
|
||||
type: "exoplayer/resize",
|
||||
payload: {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
|
||||
videoEl.src = url;
|
||||
await new Promise((resolve) =>
|
||||
videoEl.addEventListener("loadedmetadata", resolve)
|
||||
);
|
||||
videoEl.play();
|
||||
}
|
||||
|
||||
private async _renderHLSPolyfill(
|
||||
videoEl: HTMLVideoElement,
|
||||
// eslint-disable-next-line
|
||||
Hls: HLSModule,
|
||||
url: string
|
||||
) {
|
||||
const hls = new Hls({
|
||||
liveBackBufferLength: 60,
|
||||
fragLoadingTimeOut: 30000,
|
||||
manifestLoadingTimeOut: 30000,
|
||||
levelLoadingTimeOut: 30000,
|
||||
});
|
||||
this._hlsPolyfillInstance = hls;
|
||||
hls.attachMedia(videoEl);
|
||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||
hls.loadSource(url);
|
||||
});
|
||||
}
|
||||
|
||||
private _elementResized() {
|
||||
fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
private _destroyPolyfill() {
|
||||
if (this._hlsPolyfillInstance) {
|
||||
this._hlsPolyfillInstance.destroy();
|
||||
this._hlsPolyfillInstance = undefined;
|
||||
}
|
||||
if (this._useExoPlayer) {
|
||||
window.removeEventListener("resize", this._resizeExoPlayer);
|
||||
this.hass!.auth.external!.fireMessage({ type: "exoplayer/stop" });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host,
|
||||
img {
|
||||
img,
|
||||
video {
|
||||
display: block;
|
||||
}
|
||||
|
||||
img {
|
||||
img,
|
||||
video {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
@@ -97,7 +97,6 @@ export class HaCodeEditor extends UpdatingElement {
|
||||
.CodeMirror {
|
||||
height: var(--code-mirror-height, auto);
|
||||
direction: var(--code-mirror-direction, ltr);
|
||||
font-family: var(--code-font-family, monospace);
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
max-height: var(--code-mirror-max-height, --code-mirror-height);
|
||||
|
@@ -176,11 +176,6 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
||||
this.drawColorWheel();
|
||||
this.drawMarker();
|
||||
|
||||
if (this.desiredHsColor) {
|
||||
this.setMarkerOnColor(this.desiredHsColor);
|
||||
this.applyColorToCanvas(this.desiredHsColor);
|
||||
}
|
||||
|
||||
this.interactionLayer.addEventListener("mousedown", (ev) =>
|
||||
this.onMouseDown(ev)
|
||||
);
|
||||
@@ -324,9 +319,6 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
||||
|
||||
// set marker position to the given color
|
||||
setMarkerOnColor(hs) {
|
||||
if (!this.marker || !this.tooltip) {
|
||||
return;
|
||||
}
|
||||
const dist = hs.s * this.radius;
|
||||
const theta = ((hs.h - 180) / 180) * Math.PI;
|
||||
const markerdX = -dist * Math.cos(theta);
|
||||
@@ -338,9 +330,6 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
|
||||
|
||||
// apply given color to interface elements
|
||||
applyColorToCanvas(hs) {
|
||||
if (!this.interactionLayer) {
|
||||
return;
|
||||
}
|
||||
// we're not really converting hs to hsl here, but we keep it cheap
|
||||
// setting the color on the interactionLayer, the svg elements can inherit
|
||||
this.interactionLayer.style.color = `hsl(${hs.h}, 100%, ${
|
||||
|
@@ -10,7 +10,7 @@ import "./ha-icon-button";
|
||||
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||
|
||||
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
<span class="header_title">${title}</span>
|
||||
${title}
|
||||
<mwc-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
dialogAction="close"
|
||||
@@ -64,7 +64,6 @@ export class HaDialog extends MwcDialog {
|
||||
}
|
||||
.mdc-dialog .mdc-dialog__surface {
|
||||
position: var(--dialog-surface-position, relative);
|
||||
top: var(--dialog-surface-top);
|
||||
min-height: var(--mdc-dialog-min-height, auto);
|
||||
}
|
||||
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
|
||||
@@ -78,17 +77,10 @@ export class HaDialog extends MwcDialog {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.header_title {
|
||||
margin-right: 40px;
|
||||
}
|
||||
[dir="rtl"].header_button {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
[dir="rtl"].header_title {
|
||||
margin-left: 40px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -54,8 +54,7 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-paper-slider
|
||||
pin
|
||||
editable
|
||||
pin=""
|
||||
.value=${this._value}
|
||||
.min=${this.schema.valueMin}
|
||||
.max=${this.schema.valueMax}
|
||||
@@ -112,10 +111,6 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
ha-paper-slider {
|
||||
width: 100%;
|
||||
margin-right: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form";
|
||||
|
||||
@customElement("ha-form-select")
|
||||
@@ -24,7 +24,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
@property() public suffix!: string;
|
||||
|
||||
@query("ha-paper-dropdown-menu") private _input?: HTMLElement;
|
||||
@query("paper-dropdown-menu") private _input?: HTMLElement;
|
||||
|
||||
public focus() {
|
||||
if (this._input) {
|
||||
@@ -34,7 +34,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-paper-dropdown-menu .label=${this.label}>
|
||||
<paper-dropdown-menu .label=${this.label}>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-value"
|
||||
@@ -51,7 +51,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
</paper-dropdown-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-paper-dropdown-menu {
|
||||
paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { customElement, LitElement, html, unsafeCSS, css } from "lit-element";
|
||||
// @ts-ignore
|
||||
import topAppBarStyles from "@material/top-app-bar/dist/mdc.top-app-bar.min.css";
|
||||
import { css, customElement, html, LitElement, unsafeCSS } from "lit-element";
|
||||
|
||||
@customElement("ha-header-bar")
|
||||
export class HaHeaderBar extends LitElement {
|
||||
|
@@ -1,216 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import { getExternalConfig } from "../external_app/external_config";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
type HLSModule = typeof import("hls.js");
|
||||
|
||||
@customElement("ha-hls-player")
|
||||
class HaHLSPlayer extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public url!: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "controls" })
|
||||
public controls = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "muted" })
|
||||
public muted = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "autoplay" })
|
||||
public autoPlay = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "playsinline" })
|
||||
public playsInline = false;
|
||||
|
||||
@query("video") private _videoEl!: HTMLVideoElement;
|
||||
|
||||
@internalProperty() private _attached = false;
|
||||
|
||||
private _hlsPolyfillInstance?: Hls;
|
||||
|
||||
private _useExoPlayer = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._attached = true;
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._attached = false;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._attached) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<video
|
||||
?autoplay=${this.autoPlay}
|
||||
.muted=${this.muted}
|
||||
?playsinline=${this.playsInline}
|
||||
?controls=${this.controls}
|
||||
@loadeddata=${this._elementResized}
|
||||
></video>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
const attachedChanged = changedProps.has("_attached");
|
||||
const urlChanged = changedProps.has("url");
|
||||
|
||||
if (!urlChanged && !attachedChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are no longer attached, destroy polyfill
|
||||
if (attachedChanged && !this._attached) {
|
||||
// Tear down existing polyfill, if available
|
||||
this._destroyPolyfill();
|
||||
return;
|
||||
}
|
||||
|
||||
this._destroyPolyfill();
|
||||
this._startHls();
|
||||
}
|
||||
|
||||
private async _getUseExoPlayer(): Promise<boolean> {
|
||||
if (!this.hass!.auth.external) {
|
||||
return false;
|
||||
}
|
||||
const externalConfig = await getExternalConfig(this.hass!.auth.external);
|
||||
return externalConfig && externalConfig.hasExoPlayer;
|
||||
}
|
||||
|
||||
private async _startHls(): Promise<void> {
|
||||
let hls: any;
|
||||
const videoEl = this._videoEl;
|
||||
this._useExoPlayer = await this._getUseExoPlayer();
|
||||
if (!this._useExoPlayer) {
|
||||
hls = ((await import(/* webpackChunkName: "hls.js" */ "hls.js")) as any)
|
||||
.default as HLSModule;
|
||||
let hlsSupported = hls.isSupported();
|
||||
|
||||
if (!hlsSupported) {
|
||||
hlsSupported =
|
||||
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
|
||||
}
|
||||
|
||||
if (!hlsSupported) {
|
||||
this._videoEl.innerHTML = this.hass.localize(
|
||||
"ui.components.media-browser.video_not_supported"
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const url = this.url;
|
||||
|
||||
if (this._useExoPlayer) {
|
||||
this._renderHLSExoPlayer(url);
|
||||
} else if (hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, hls, url);
|
||||
} else {
|
||||
this._renderHLSNative(videoEl, url);
|
||||
}
|
||||
}
|
||||
|
||||
private async _renderHLSExoPlayer(url: string) {
|
||||
window.addEventListener("resize", this._resizeExoPlayer);
|
||||
this.updateComplete.then(() => nextRender()).then(this._resizeExoPlayer);
|
||||
this._videoEl.style.visibility = "hidden";
|
||||
await this.hass!.auth.external!.sendMessage({
|
||||
type: "exoplayer/play_hls",
|
||||
payload: new URL(url, window.location.href).toString(),
|
||||
});
|
||||
}
|
||||
|
||||
private _resizeExoPlayer = () => {
|
||||
const rect = this._videoEl.getBoundingClientRect();
|
||||
this.hass!.auth.external!.fireMessage({
|
||||
type: "exoplayer/resize",
|
||||
payload: {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
private async _renderHLSPolyfill(
|
||||
videoEl: HTMLVideoElement,
|
||||
Hls: HLSModule,
|
||||
url: string
|
||||
) {
|
||||
const hls = new Hls({
|
||||
liveBackBufferLength: 60,
|
||||
fragLoadingTimeOut: 30000,
|
||||
manifestLoadingTimeOut: 30000,
|
||||
levelLoadingTimeOut: 30000,
|
||||
});
|
||||
this._hlsPolyfillInstance = hls;
|
||||
hls.attachMedia(videoEl);
|
||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||
hls.loadSource(url);
|
||||
});
|
||||
}
|
||||
|
||||
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
|
||||
videoEl.src = url;
|
||||
await new Promise((resolve) =>
|
||||
videoEl.addEventListener("loadedmetadata", resolve)
|
||||
);
|
||||
videoEl.play();
|
||||
}
|
||||
|
||||
private _elementResized() {
|
||||
fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
private _destroyPolyfill() {
|
||||
if (this._hlsPolyfillInstance) {
|
||||
this._hlsPolyfillInstance.destroy();
|
||||
this._hlsPolyfillInstance = undefined;
|
||||
}
|
||||
if (this._useExoPlayer) {
|
||||
window.removeEventListener("resize", this._resizeExoPlayer);
|
||||
this.hass!.auth.external!.fireMessage({ type: "exoplayer/stop" });
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host,
|
||||
video {
|
||||
display: block;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-hls-player": HaHLSPlayer;
|
||||
}
|
||||
}
|
@@ -68,10 +68,6 @@ class HaPaperSlider extends PaperSliderClass {
|
||||
-webkit-transform: scale(1) translate(0, -17px) scaleX(-1) !important;
|
||||
transform: scale(1) translate(0, -17px) scaleX(-1) !important;
|
||||
}
|
||||
|
||||
.slider-input {
|
||||
width: 54px;
|
||||
}
|
||||
`;
|
||||
tpl.content.appendChild(styleEl);
|
||||
return tpl;
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -8,6 +7,7 @@ import {
|
||||
property,
|
||||
SVGTemplateResult,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
@customElement("ha-settings-row")
|
||||
export class HaSettingsRow extends LitElement {
|
||||
@@ -49,9 +49,6 @@ export class HaSettingsRow extends LitElement {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
::slotted(ha-switch) {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
77
src/components/ha-sidebar-sort-styles.ts
Normal file
77
src/components/ha-sidebar-sort-styles.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { html } from "lit-element";
|
||||
|
||||
export const sortStyles = html`
|
||||
<style>
|
||||
#sortable a:nth-of-type(2n) paper-icon-item {
|
||||
animation-name: keyframes1;
|
||||
animation-iteration-count: infinite;
|
||||
transform-origin: 50% 10%;
|
||||
animation-delay: -0.75s;
|
||||
animation-duration: 0.25s;
|
||||
}
|
||||
|
||||
#sortable a:nth-of-type(2n-1) paper-icon-item {
|
||||
animation-name: keyframes2;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: alternate;
|
||||
transform-origin: 30% 5%;
|
||||
animation-delay: -0.5s;
|
||||
animation-duration: 0.33s;
|
||||
}
|
||||
|
||||
#sortable {
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.sortable-fallback {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes keyframes1 {
|
||||
0% {
|
||||
transform: rotate(-1deg);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(1.5deg);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes keyframes2 {
|
||||
0% {
|
||||
transform: rotate(1deg);
|
||||
animation-timing-function: ease-in;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: rotate(-1.5deg);
|
||||
animation-timing-function: ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-panel {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
:host([expanded]) .hide-panel {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
paper-icon-item.hidden-panel,
|
||||
paper-icon-item.hidden-panel span,
|
||||
paper-icon-item.hidden-panel ha-icon[slot="item-icon"] {
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
`;
|
@@ -23,6 +23,7 @@ import {
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { guard } from "lit-html/directives/guard";
|
||||
@@ -42,7 +43,6 @@ import {
|
||||
getExternalConfig,
|
||||
} from "../external_app/external_config";
|
||||
import { actionHandler } from "../panels/lovelace/common/directives/action-handler-directive";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant, PanelInfo } from "../types";
|
||||
import "./ha-icon";
|
||||
import "./ha-menu-button";
|
||||
@@ -159,13 +159,13 @@ const computePanels = memoizeOne(
|
||||
|
||||
let Sortable;
|
||||
|
||||
let sortStyles: CSSResult;
|
||||
let sortStyles: TemplateResult;
|
||||
|
||||
@customElement("ha-sidebar")
|
||||
class HaSidebar extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public alwaysExpand = false;
|
||||
|
||||
@@ -190,15 +190,11 @@ class HaSidebar extends LitElement {
|
||||
private _recentKeydownActiveUntil = 0;
|
||||
|
||||
// @ts-ignore
|
||||
@LocalStorage("sidebarPanelOrder", true, {
|
||||
attribute: false,
|
||||
})
|
||||
@LocalStorage("sidebarPanelOrder")
|
||||
private _panelOrder: string[] = [];
|
||||
|
||||
// @ts-ignore
|
||||
@LocalStorage("sidebarHiddenPanels", true, {
|
||||
attribute: false,
|
||||
})
|
||||
@LocalStorage("sidebarHiddenPanels")
|
||||
private _hiddenPanels: string[] = [];
|
||||
|
||||
private _sortable?;
|
||||
@@ -227,21 +223,8 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
${this._editMode
|
||||
? html`
|
||||
<style>
|
||||
${sortStyles?.cssText}
|
||||
</style>
|
||||
`
|
||||
: ""}
|
||||
<div
|
||||
class="menu"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: !this._editMode,
|
||||
disabled: this._editMode,
|
||||
})}
|
||||
>
|
||||
${this._editMode ? sortStyles : ""}
|
||||
<div class="menu">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
@@ -259,19 +242,23 @@ class HaSidebar extends LitElement {
|
||||
<div class="title">
|
||||
${this._editMode
|
||||
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
||||
${hass.localize("ui.sidebar.done")}
|
||||
DONE
|
||||
</mwc-button>`
|
||||
: "Home Assistant"}
|
||||
</div>
|
||||
</div>
|
||||
<paper-listbox
|
||||
attr-for-selected="data-panel"
|
||||
class="ha-scrollbar"
|
||||
.selected=${hass.panelUrl}
|
||||
@focusin=${this._listboxFocusIn}
|
||||
@focusout=${this._listboxFocusOut}
|
||||
@scroll=${this._listboxScroll}
|
||||
@keydown=${this._listboxKeydown}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: !this._editMode,
|
||||
disabled: this._editMode,
|
||||
})}
|
||||
>
|
||||
${this._editMode
|
||||
? html`<div id="sortable">
|
||||
@@ -287,29 +274,27 @@ class HaSidebar extends LitElement {
|
||||
? html`
|
||||
${this._hiddenPanels.map((url) => {
|
||||
const panel = this.hass.panels[url];
|
||||
if (!panel) {
|
||||
return "";
|
||||
}
|
||||
return html`<paper-icon-item
|
||||
@click=${this._unhidePanel}
|
||||
class="hidden-panel"
|
||||
.panel=${url}
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${panel.url_path === this.hass.defaultPanel
|
||||
.icon=${panel.url_path === "lovelace"
|
||||
? "mdi:view-dashboard"
|
||||
: panel.icon}
|
||||
></ha-icon>
|
||||
<span class="item-text"
|
||||
>${panel.url_path === this.hass.defaultPanel
|
||||
>${panel.url_path === "lovelace"
|
||||
? hass.localize("panel.states")
|
||||
: hass.localize(`panel.${panel.title}`) ||
|
||||
panel.title}</span
|
||||
>
|
||||
<mwc-icon-button class="hide-panel">
|
||||
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<ha-svg-icon
|
||||
class="hide-panel"
|
||||
.panel=${url}
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
</paper-icon-item>`;
|
||||
})}
|
||||
<div class="spacer" disabled></div>
|
||||
@@ -389,11 +374,7 @@ class HaSidebar extends LitElement {
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
<ha-user-badge
|
||||
slot="item-icon"
|
||||
.user=${hass.user}
|
||||
.hass=${hass}
|
||||
></ha-user-badge>
|
||||
<ha-user-badge slot="item-icon" .user=${hass.user}></ha-user-badge>
|
||||
|
||||
<span class="item-text">
|
||||
${hass.user ? hass.user.name : ""}
|
||||
@@ -413,9 +394,7 @@ class HaSidebar extends LitElement {
|
||||
changedProps.has("_externalConfig") ||
|
||||
changedProps.has("_notifications") ||
|
||||
changedProps.has("_editMode") ||
|
||||
changedProps.has("_renderEmptySortable") ||
|
||||
changedProps.has("_hiddenPanels") ||
|
||||
(changedProps.has("_panelOrder") && !this._editMode)
|
||||
changedProps.has("_renderEmptySortable")
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -449,9 +428,6 @@ class HaSidebar extends LitElement {
|
||||
subscribeNotifications(this.hass.connection, (notifications) => {
|
||||
this._notifications = notifications;
|
||||
});
|
||||
window.addEventListener("hass-edit-sidebar", () =>
|
||||
this._activateEditMode()
|
||||
);
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
@@ -484,22 +460,18 @@ class HaSidebar extends LitElement {
|
||||
return this.shadowRoot!.querySelector(".tooltip")! as HTMLDivElement;
|
||||
}
|
||||
|
||||
private _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
private async _handleAction(ev: CustomEvent<ActionHandlerDetail>) {
|
||||
if (ev.detail.action !== "hold") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._activateEditMode();
|
||||
}
|
||||
|
||||
private async _activateEditMode() {
|
||||
if (!Sortable) {
|
||||
const [sortableImport, sortStylesImport] = await Promise.all([
|
||||
import("sortablejs/modular/sortable.core.esm"),
|
||||
import("../resources/ha-sortable-style"),
|
||||
import("./ha-sidebar-sort-styles"),
|
||||
]);
|
||||
|
||||
sortStyles = sortStylesImport.sortableStyles;
|
||||
sortStyles = sortStylesImport.sortStyles;
|
||||
|
||||
Sortable = sortableImport.Sortable;
|
||||
Sortable.mount(sortableImport.OnSpill);
|
||||
@@ -507,8 +479,6 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
this._editMode = true;
|
||||
|
||||
fireEvent(this, "hass-open-menu");
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
this._createSortable();
|
||||
@@ -533,7 +503,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
private async _hidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const panel = (ev.currentTarget as any).panel;
|
||||
const panel = (ev.target as any).panel;
|
||||
if (this._hiddenPanels.includes(panel)) {
|
||||
return;
|
||||
}
|
||||
@@ -546,7 +516,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
private async _unhidePanel(ev: Event) {
|
||||
ev.preventDefault();
|
||||
const index = this._hiddenPanels.indexOf((ev.currentTarget as any).panel);
|
||||
const index = this._hiddenPanels.indexOf((ev.target as any).panel);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
@@ -658,13 +628,11 @@ class HaSidebar extends LitElement {
|
||||
return panels.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
panel.url_path === this.hass.defaultPanel
|
||||
? panel.title || this.hass.localize("panel.states")
|
||||
panel.url_path === "lovelace"
|
||||
? this.hass.localize("panel.states")
|
||||
: this.hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
panel.url_path === this.hass.defaultPanel && !panel.icon
|
||||
? mdiViewDashboard
|
||||
: undefined
|
||||
panel.url_path === "lovelace" ? undefined : panel.icon,
|
||||
panel.url_path === "lovelace" ? mdiViewDashboard : undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -678,8 +646,8 @@ class HaSidebar extends LitElement {
|
||||
return html`
|
||||
<a
|
||||
aria-role="option"
|
||||
href=${`/${urlPath}`}
|
||||
data-panel=${urlPath}
|
||||
href="${`/${urlPath}`}"
|
||||
data-panel="${urlPath}"
|
||||
tabindex="-1"
|
||||
@mouseenter=${this._itemMouseEnter}
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
@@ -693,23 +661,20 @@ class HaSidebar extends LitElement {
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<span class="item-text">${title}</span>
|
||||
${this._editMode
|
||||
? html`<mwc-icon-button
|
||||
? html`<ha-svg-icon
|
||||
class="hide-panel"
|
||||
.panel=${urlPath}
|
||||
@click=${this._hidePanel}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>`
|
||||
.path=${mdiClose}
|
||||
></ha-svg-icon>`
|
||||
: ""}
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
height: 100%;
|
||||
display: block;
|
||||
@@ -768,9 +733,6 @@ class HaSidebar extends LitElement {
|
||||
width: 100%;
|
||||
display: none;
|
||||
}
|
||||
:host([narrow]) .title {
|
||||
padding: 0 16px;
|
||||
}
|
||||
:host([expanded]) .title {
|
||||
display: initial;
|
||||
}
|
||||
@@ -778,13 +740,27 @@ class HaSidebar extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
paper-listbox::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
}
|
||||
|
||||
paper-listbox::-webkit-scrollbar-thumb {
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
background: var(--scrollbar-thumb-color);
|
||||
}
|
||||
|
||||
paper-listbox {
|
||||
padding: 4px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
height: calc(100% - 196px - env(safe-area-inset-bottom));
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
scrollbar-color: var(--scrollbar-thumb-color) transparent;
|
||||
scrollbar-width: thin;
|
||||
background: none;
|
||||
margin-left: env(safe-area-inset-left);
|
||||
}
|
||||
@@ -984,8 +960,7 @@ class HaSidebar extends LitElement {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -279,7 +279,6 @@ class LocationEditor extends LitElement {
|
||||
}
|
||||
#map {
|
||||
height: 100%;
|
||||
background: inherit;
|
||||
}
|
||||
.leaflet-edit-move {
|
||||
border-radius: 50%;
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent, HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
@@ -33,17 +33,16 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
|
||||
@internalProperty() private _params?: MediaPlayerBrowseDialogParams;
|
||||
|
||||
public showDialog(params: MediaPlayerBrowseDialogParams): void {
|
||||
public async showDialog(
|
||||
params: MediaPlayerBrowseDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._entityId = this._params.entityId;
|
||||
this._mediaContentId = this._params.mediaContentId;
|
||||
this._mediaContentType = this._params.mediaContentType;
|
||||
this._action = this._params.action || "play";
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -58,7 +57,7 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
flexContent
|
||||
@closed=${this.closeDialog}
|
||||
@closed=${this._closeDialog}
|
||||
>
|
||||
<ha-media-player-browse
|
||||
dialog
|
||||
@@ -67,17 +66,21 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
.action=${this._action!}
|
||||
.mediaContentId=${this._mediaContentId}
|
||||
.mediaContentType=${this._mediaContentType}
|
||||
@close-dialog=${this.closeDialog}
|
||||
@close-dialog=${this._closeDialog}
|
||||
@media-picked=${this._mediaPicked}
|
||||
></ha-media-player-browse>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
private _mediaPicked(ev: HASSDomEvent<MediaPickedEvent>): void {
|
||||
this._params!.mediaPickedCallback(ev.detail);
|
||||
if (this._action !== "play") {
|
||||
this.closeDialog();
|
||||
this._closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +93,17 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
ha-header-bar {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 800px;
|
||||
--dialog-surface-position: fixed;
|
||||
--dialog-surface-top: 40px;
|
||||
--mdc-dialog-max-height: calc(100% - 72px);
|
||||
}
|
||||
ha-media-player-browse {
|
||||
width: 700px;
|
||||
|
@@ -2,7 +2,7 @@ import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-fab/mwc-fab";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowLeft, mdiClose, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import { mdiArrowLeft, mdiClose, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
@@ -18,20 +18,12 @@ import {
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import {
|
||||
browseLocalMediaPlayer,
|
||||
browseMediaPlayer,
|
||||
BROWSER_SOURCE,
|
||||
MediaClassBrowserSettings,
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
import { browseMediaPlayer, MediaPickedEvent } from "../../data/media-player";
|
||||
import type { MediaPlayerItem } from "../../data/media-player";
|
||||
import { showAlertDialog } from "../../dialogs/generic/show-dialog-box";
|
||||
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -58,7 +50,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
@property() public mediaContentType?: string;
|
||||
|
||||
@property() public action: MediaPlayerBrowseAction = "play";
|
||||
@property() public action: "pick" | "play" = "play";
|
||||
|
||||
@property({ type: Boolean }) public hideBack = false;
|
||||
|
||||
@property({ type: Boolean }) public hideTitle = false;
|
||||
|
||||
@property({ type: Boolean }) public dialog = false;
|
||||
|
||||
@@ -67,8 +63,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
@internalProperty() private _loading = false;
|
||||
|
||||
@internalProperty() private _error?: { message: string; code: string };
|
||||
|
||||
@internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = [];
|
||||
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
@@ -94,70 +88,52 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._loading) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
if (this._error && !this._mediaPlayerItems.length) {
|
||||
if (this.dialog) {
|
||||
this._closeDialogAction();
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.components.media-browser.media_browsing_error"
|
||||
),
|
||||
text: this._renderError(this._error),
|
||||
});
|
||||
} else {
|
||||
return html`<div class="container">
|
||||
${this._renderError(this._error)}
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._mediaPlayerItems.length) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const currentItem = this._mediaPlayerItems[
|
||||
if (this._loading) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
const mostRecentItem = this._mediaPlayerItems[
|
||||
this._mediaPlayerItems.length - 1
|
||||
];
|
||||
|
||||
const previousItem: MediaPlayerItem | undefined =
|
||||
const previousItem =
|
||||
this._mediaPlayerItems.length > 1
|
||||
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
|
||||
: undefined;
|
||||
|
||||
const subtitle = this.hass.localize(
|
||||
`ui.components.media-browser.class.${currentItem.media_class}`
|
||||
const hasExpandableChildren:
|
||||
| MediaPlayerItem
|
||||
| undefined = this._hasExpandableChildren(mostRecentItem.children);
|
||||
|
||||
const showImages = mostRecentItem.children?.some(
|
||||
(child) => child.thumbnail && child.thumbnail !== mostRecentItem.thumbnail
|
||||
);
|
||||
|
||||
const mediaType = this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${mostRecentItem.media_content_type}`
|
||||
);
|
||||
const mediaClass = MediaClassBrowserSettings[currentItem.media_class];
|
||||
const childrenMediaClass =
|
||||
MediaClassBrowserSettings[currentItem.children_media_class];
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="header ${classMap({
|
||||
"no-img": !currentItem.thumbnail,
|
||||
"no-dialog": !this.dialog,
|
||||
"no-img": !mostRecentItem.thumbnail,
|
||||
})}"
|
||||
>
|
||||
<div class="header-wrapper">
|
||||
<div class="header-content">
|
||||
${currentItem.thumbnail
|
||||
${mostRecentItem.thumbnail
|
||||
? html`
|
||||
<div
|
||||
class="img"
|
||||
style=${styleMap({
|
||||
backgroundImage: currentItem.thumbnail
|
||||
? `url(${currentItem.thumbnail})`
|
||||
: "none",
|
||||
})}
|
||||
style="background-image: url(${mostRecentItem.thumbnail})"
|
||||
>
|
||||
${this._narrow && currentItem?.can_play
|
||||
${this._narrow && mostRecentItem?.can_play
|
||||
? html`
|
||||
<mwc-fab
|
||||
mini
|
||||
.item=${currentItem}
|
||||
.item=${mostRecentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
@@ -165,9 +141,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play"
|
||||
? mdiPlay
|
||||
: mdiPlus}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
@@ -179,33 +153,39 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
`
|
||||
: html``}
|
||||
<div class="header-info">
|
||||
${this.hideTitle && (this._narrow || !mostRecentItem.thumbnail)
|
||||
? ""
|
||||
: html`<div class="breadcrumb-overflow">
|
||||
<div class="breadcrumb">
|
||||
${previousItem
|
||||
${!this.hideBack && previousItem
|
||||
? html`
|
||||
<div class="previous-title" @click=${this.navigateBack}>
|
||||
<div
|
||||
class="previous-title"
|
||||
@click=${this.navigateBack}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
||||
${previousItem.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${currentItem.title}</h1>
|
||||
${subtitle
|
||||
? html`
|
||||
<h2 class="subtitle">
|
||||
${subtitle}
|
||||
</h2>
|
||||
`
|
||||
<h1 class="title">${mostRecentItem.title}</h1>
|
||||
${mediaType
|
||||
? html`<h2 class="subtitle">
|
||||
${mediaType}
|
||||
</h2>`
|
||||
: ""}
|
||||
</div>
|
||||
${currentItem.can_play &&
|
||||
(!currentItem.thumbnail || !this._narrow)
|
||||
</div>`}
|
||||
${mostRecentItem?.can_play &&
|
||||
(!mostRecentItem.thumbnail || !this._narrow)
|
||||
? html`
|
||||
<mwc-button
|
||||
raised
|
||||
.item=${currentItem}
|
||||
.item=${mostRecentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
@@ -227,52 +207,34 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
class="header_button"
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
${this._error
|
||||
${mostRecentItem.children?.length
|
||||
? hasExpandableChildren
|
||||
? html`
|
||||
<div class="container error">
|
||||
${this._renderError(this._error)}
|
||||
</div>
|
||||
`
|
||||
: currentItem.children?.length
|
||||
? childrenMediaClass.layout === "grid"
|
||||
<div class="children">
|
||||
${mostRecentItem.children?.length
|
||||
? html`
|
||||
<div
|
||||
class="children ${classMap({
|
||||
portrait: childrenMediaClass.thumbnail_ratio === "portrait",
|
||||
})}"
|
||||
>
|
||||
${currentItem.children.map(
|
||||
${mostRecentItem.children.map(
|
||||
(child) => html`
|
||||
<div
|
||||
class="child"
|
||||
.item=${child}
|
||||
@click=${this._childClicked}
|
||||
@click=${this._navigateForward}
|
||||
>
|
||||
<div class="ha-card-parent">
|
||||
<ha-card
|
||||
outlined
|
||||
style=${styleMap({
|
||||
backgroundImage: child.thumbnail
|
||||
? `url(${child.thumbnail})`
|
||||
: "none",
|
||||
})}
|
||||
style="background-image: url(${child.thumbnail})"
|
||||
>
|
||||
${!child.thumbnail
|
||||
${child.can_expand && !child.thumbnail
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="folder"
|
||||
.path=${MediaClassBrowserSettings[
|
||||
child.media_class === "directory"
|
||||
? child.children_media_class ||
|
||||
child.media_class
|
||||
: child.media_class
|
||||
].icon}
|
||||
.path=${mdiFolder}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
@@ -280,9 +242,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
${child.can_play
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="play ${classMap({
|
||||
can_expand: child.can_expand,
|
||||
})}"
|
||||
class="play"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
@@ -307,23 +267,24 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<mwc-list>
|
||||
${currentItem.children.map(
|
||||
${mostRecentItem.children.map(
|
||||
(child) => html`
|
||||
<mwc-list-item
|
||||
@click=${this._childClicked}
|
||||
@click=${this._actionClicked}
|
||||
.item=${child}
|
||||
graphic="avatar"
|
||||
hasMeta
|
||||
dir=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<div
|
||||
class="graphic"
|
||||
style=${ifDefined(
|
||||
mediaClass.show_list_images && child.thumbnail
|
||||
showImages && child.thumbnail
|
||||
? `background-image: url(${child.thumbnail})`
|
||||
: undefined
|
||||
)}
|
||||
@@ -331,8 +292,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
>
|
||||
<mwc-icon-button
|
||||
class="play ${classMap({
|
||||
show:
|
||||
!mediaClass.show_list_images || !child.thumbnail,
|
||||
show: !showImages || !child.thumbnail,
|
||||
})}"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
@@ -345,18 +305,14 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</div>
|
||||
<span class="title">${child.title}</span>
|
||||
<span>${child.title}</span>
|
||||
</mwc-list-item>
|
||||
<li divider role="separator"></li>
|
||||
`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: html`
|
||||
<div class="container">
|
||||
${this.hass.localize("ui.components.media-browser.no_items")}
|
||||
</div>
|
||||
`}
|
||||
: this.hass.localize("ui.components.media-browser.no_items")}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -382,22 +338,11 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("entityId")) {
|
||||
this._error = undefined;
|
||||
this._mediaPlayerItems = [];
|
||||
}
|
||||
|
||||
this._fetchData(this.mediaContentId, this.mediaContentType)
|
||||
.then((itemData) => {
|
||||
if (!itemData) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fetchData(this.mediaContentId, this.mediaContentType).then(
|
||||
(itemData) => {
|
||||
this._mediaPlayerItems = [itemData];
|
||||
})
|
||||
.catch((err) => {
|
||||
this._error = err;
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _actionClicked(ev: MouseEvent): void {
|
||||
@@ -408,44 +353,27 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
private _runAction(item: MediaPlayerItem): void {
|
||||
fireEvent(this, "media-picked", { item });
|
||||
fireEvent(this, "media-picked", {
|
||||
media_content_id: item.media_content_id,
|
||||
media_content_type: item.media_content_type,
|
||||
});
|
||||
}
|
||||
|
||||
private async _childClicked(ev: MouseEvent): Promise<void> {
|
||||
private async _navigateForward(ev: MouseEvent): Promise<void> {
|
||||
const target = ev.currentTarget as any;
|
||||
const item: MediaPlayerItem = target.item;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.can_expand) {
|
||||
this._runAction(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this._navigate(item);
|
||||
}
|
||||
|
||||
private async _navigate(item: MediaPlayerItem) {
|
||||
this._error = undefined;
|
||||
|
||||
let itemData: MediaPlayerItem;
|
||||
|
||||
try {
|
||||
itemData = await this._fetchData(
|
||||
const itemData = await this._fetchData(
|
||||
item.media_content_id,
|
||||
item.media_content_type
|
||||
);
|
||||
} catch (err) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.components.media-browser.media_browsing_error"
|
||||
),
|
||||
text: this._renderError(err),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.scrollTo(0, 0);
|
||||
this._mediaPlayerItems = [...this._mediaPlayerItems, itemData];
|
||||
@@ -455,15 +383,12 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
mediaContentId?: string,
|
||||
mediaContentType?: string
|
||||
): Promise<MediaPlayerItem> {
|
||||
const itemData =
|
||||
this.entityId !== BROWSER_SOURCE
|
||||
? await browseMediaPlayer(
|
||||
const itemData = await browseMediaPlayer(
|
||||
this.hass,
|
||||
this.entityId,
|
||||
mediaContentId,
|
||||
!mediaContentId ? undefined : mediaContentId,
|
||||
mediaContentType
|
||||
)
|
||||
: await browseLocalMediaPlayer(this.hass, mediaContentId);
|
||||
);
|
||||
|
||||
return itemData;
|
||||
}
|
||||
@@ -491,38 +416,14 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
this._resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private _hasExpandableChildren = memoizeOne((children) =>
|
||||
children.find((item: MediaPlayerItem) => item.can_expand)
|
||||
);
|
||||
|
||||
private _closeDialogAction(): void {
|
||||
fireEvent(this, "close-dialog");
|
||||
}
|
||||
|
||||
private _renderError(err: { message: string; code: string }) {
|
||||
if (err.message === "Media directory does not exist.") {
|
||||
return html`
|
||||
<h2>No local media found.</h2>
|
||||
<p>
|
||||
It looks like you have not yet created a media directory.
|
||||
<br />Create a directory with the name <b>"media"</b> in the
|
||||
configuration directory of Home Assistant
|
||||
(${this.hass.config.config_dir}). <br />Place your video, audio and
|
||||
image files in this directory to be able to browse and play them in
|
||||
the browser or on supported media players.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Check the
|
||||
<a
|
||||
href="https://www.home-assistant.io/integrations/media_source/#local-media"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>
|
||||
for more info
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return html`<span class="error">err.message</span>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -535,26 +436,26 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 16px;
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.header_button {
|
||||
position: relative;
|
||||
top: 14px;
|
||||
right: -8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: block;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
background-color: var(--card-background-color);
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
padding: 20px 24px 10px;
|
||||
}
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -582,7 +483,12 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
.header-info mwc-button {
|
||||
display: block;
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb-overflow {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
@@ -626,7 +532,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
|
||||
mwc-list {
|
||||
--mdc-list-vertical-padding: 0;
|
||||
--mdc-list-item-graphic-margin: 0;
|
||||
--mdc-theme-text-icon-on-background: var(--secondary-text-color);
|
||||
margin-top: 10px;
|
||||
}
|
||||
@@ -643,18 +548,14 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--media-browse-item-size, 175px), 0.1fr)
|
||||
minmax(var(--media-browse-item-size, 175px), 0.33fr)
|
||||
);
|
||||
grid-gap: 16px;
|
||||
margin: 8px 0px;
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
:host([dialog]) .children {
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--media-browse-item-size, 175px), 0.33fr)
|
||||
);
|
||||
:host(:not([narrow])) .children {
|
||||
padding: 0px 24px;
|
||||
}
|
||||
|
||||
.child {
|
||||
@@ -668,7 +569,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.children ha-card {
|
||||
ha-card {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
@@ -676,11 +577,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: padding-bottom 0.1s ease-out;
|
||||
}
|
||||
|
||||
.portrait.children ha-card {
|
||||
padding-bottom: 150%;
|
||||
}
|
||||
|
||||
.child .folder,
|
||||
@@ -696,43 +592,24 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
.child .play {
|
||||
transition: color 0.5s;
|
||||
border-radius: 50%;
|
||||
bottom: calc(50% - 35px);
|
||||
right: calc(50% - 35px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.1s ease-out;
|
||||
}
|
||||
|
||||
.child .play:not(.can_expand) {
|
||||
--mdc-icon-button-size: 70px;
|
||||
--mdc-icon-size: 48px;
|
||||
}
|
||||
|
||||
.ha-card-parent:hover .play:not(.can_expand) {
|
||||
opacity: 1;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.child .play.can_expand {
|
||||
opacity: 1;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
transition: all 0.5s;
|
||||
background-color: rgba(var(--rgb-card-background-color), 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.child .play:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.ha-card-parent:hover ha-card {
|
||||
ha-card:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.child .title {
|
||||
font-size: 16px;
|
||||
padding-top: 8px;
|
||||
padding-left: 2px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
@@ -742,7 +619,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
.child .type {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
mwc-list-item .graphic {
|
||||
@@ -767,14 +643,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
mwc-list-item .title {
|
||||
margin-left: 16px;
|
||||
}
|
||||
mwc-list-item[dir="rtl"] .title {
|
||||
margin-right: 16px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* ============= Narrow ============= */
|
||||
|
||||
:host([narrow]) {
|
||||
@@ -789,10 +657,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .header.no-dialog {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host([narrow]) .header_button {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
@@ -832,7 +696,8 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
padding: 20px 24px 10px;
|
||||
}
|
||||
|
||||
:host([narrow]) .media-source {
|
||||
:host([narrow]) .media-source,
|
||||
:host([narrow]) .children {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
@@ -851,10 +716,6 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
:host(:not([narrow])[scroll]) .header:not(.no-img) mwc-icon-button {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
:host([scroll]) .header-info mwc-button,
|
||||
.no-img .header-info mwc-button {
|
||||
padding-right: 4px;
|
||||
|
@@ -76,8 +76,6 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
const staticColors = {
|
||||
on: 1,
|
||||
off: 0,
|
||||
home: 1,
|
||||
not_home: 0,
|
||||
unavailable: "#a0a0a0",
|
||||
unknown: "#606060",
|
||||
idle: 2,
|
||||
|
@@ -1,74 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import { Person } from "../../data/person";
|
||||
import { computeInitials } from "./ha-user-badge";
|
||||
|
||||
@customElement("ha-person-badge")
|
||||
class PersonBadge extends LitElement {
|
||||
@property({ attribute: false }) public person?: Person;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.person) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const picture = this.person.picture;
|
||||
|
||||
if (picture) {
|
||||
return html`<div
|
||||
style=${styleMap({ backgroundImage: `url(${picture})` })}
|
||||
class="picture"
|
||||
></div>`;
|
||||
}
|
||||
const initials = computeInitials(this.person.name);
|
||||
return html`<div
|
||||
class="initials ${classMap({ long: initials!.length > 2 })}"
|
||||
>
|
||||
${initials}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
.picture {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.initials {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
background-color: var(--light-primary-color);
|
||||
text-decoration: none;
|
||||
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||
overflow: hidden;
|
||||
}
|
||||
.initials.long {
|
||||
font-size: 80%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-person-badge": PersonBadge;
|
||||
}
|
||||
}
|
@@ -3,20 +3,17 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { toggleAttribute } from "../../common/dom/toggle_attribute";
|
||||
import { User } from "../../data/user";
|
||||
import { CurrentUser, HomeAssistant } from "../../types";
|
||||
import { CurrentUser } from "../../types";
|
||||
|
||||
export const computeInitials = (name: string) => {
|
||||
const computeInitials = (name: string) => {
|
||||
if (!name) {
|
||||
return "?";
|
||||
return "user";
|
||||
}
|
||||
return (
|
||||
name
|
||||
@@ -31,89 +28,27 @@ export const computeInitials = (name: string) => {
|
||||
};
|
||||
|
||||
@customElement("ha-user-badge")
|
||||
class UserBadge extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
class StateBadge extends LitElement {
|
||||
@property() public user?: User | CurrentUser;
|
||||
|
||||
@property({ attribute: false }) public user?: User | CurrentUser;
|
||||
|
||||
@internalProperty() private _personPicture?: string;
|
||||
|
||||
private _personEntityId?: string;
|
||||
protected render(): TemplateResult {
|
||||
const user = this.user;
|
||||
const initials = user ? computeInitials(user.name) : "?";
|
||||
return html` ${initials} `;
|
||||
}
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("user")) {
|
||||
this._getPersonPicture();
|
||||
return;
|
||||
}
|
||||
const oldHass = changedProps.get("hass");
|
||||
if (
|
||||
this._personEntityId &&
|
||||
oldHass &&
|
||||
this.hass.states[this._personEntityId] !==
|
||||
oldHass.states[this._personEntityId]
|
||||
) {
|
||||
const state = this.hass.states[this._personEntityId];
|
||||
if (state) {
|
||||
this._personPicture = state.attributes.entity_picture;
|
||||
} else {
|
||||
this._getPersonPicture();
|
||||
}
|
||||
} else if (!this._personEntityId && oldHass) {
|
||||
this._getPersonPicture();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.user) {
|
||||
return html``;
|
||||
}
|
||||
const picture = this._personPicture;
|
||||
|
||||
if (picture) {
|
||||
return html`<div
|
||||
style=${styleMap({ backgroundImage: `url(${picture})` })}
|
||||
class="picture"
|
||||
></div>`;
|
||||
}
|
||||
const initials = computeInitials(this.user.name);
|
||||
return html`<div
|
||||
class="initials ${classMap({ long: initials!.length > 2 })}"
|
||||
>
|
||||
${initials}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _getPersonPicture() {
|
||||
this._personEntityId = undefined;
|
||||
this._personPicture = undefined;
|
||||
if (!this.hass || !this.user) {
|
||||
return;
|
||||
}
|
||||
for (const entity of Object.values(this.hass.states)) {
|
||||
if (
|
||||
entity.attributes.user_id === this.user.id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._personEntityId = entity.entity_id;
|
||||
this._personPicture = entity.attributes.entity_picture;
|
||||
break;
|
||||
}
|
||||
}
|
||||
toggleAttribute(
|
||||
this,
|
||||
"long",
|
||||
(this.user ? computeInitials(this.user.name) : "?").length > 2
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
.picture {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.initials {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
width: 40px;
|
||||
@@ -125,7 +60,8 @@ class UserBadge extends LitElement {
|
||||
color: var(--text-light-primary-color, var(--primary-text-color));
|
||||
overflow: hidden;
|
||||
}
|
||||
.initials.long {
|
||||
|
||||
:host([long]) {
|
||||
font-size: 80%;
|
||||
}
|
||||
`;
|
||||
@@ -134,6 +70,6 @@ class UserBadge extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-user-badge": UserBadge;
|
||||
"ha-user-badge": StateBadge;
|
||||
}
|
||||
}
|
||||
|
@@ -53,11 +53,7 @@ class HaUserPicker extends LitElement {
|
||||
${this._sortedUsers(this.users).map(
|
||||
(user) => html`
|
||||
<paper-icon-item data-user-id=${user.id}>
|
||||
<ha-user-badge
|
||||
.hass=${this.hass}
|
||||
.user=${user}
|
||||
slot="item-icon"
|
||||
></ha-user-badge>
|
||||
<ha-user-badge .user=${user} slot="item-icon"></ha-user-badge>
|
||||
${user.name}
|
||||
</paper-icon-item>
|
||||
`
|
||||
|
@@ -3,7 +3,7 @@ import {
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { Context, HomeAssistant } from "../types";
|
||||
import { HomeAssistant, Context } from "../types";
|
||||
import { DeviceCondition, DeviceTrigger } from "./device_automation";
|
||||
import { Action } from "./script";
|
||||
|
||||
@@ -15,7 +15,6 @@ export interface AutomationEntity extends HassEntityBase {
|
||||
}
|
||||
|
||||
export interface AutomationConfig {
|
||||
id?: string;
|
||||
alias: string;
|
||||
description: string;
|
||||
trigger: Trigger[];
|
||||
@@ -33,8 +32,7 @@ export interface ForDict {
|
||||
|
||||
export interface StateTrigger {
|
||||
platform: "state";
|
||||
entity_id: string;
|
||||
attribute?: string;
|
||||
entity_id?: string;
|
||||
from?: string | number;
|
||||
to?: string | number;
|
||||
for?: string | number | ForDict;
|
||||
@@ -61,7 +59,6 @@ export interface HassTrigger {
|
||||
export interface NumericStateTrigger {
|
||||
platform: "numeric_state";
|
||||
entity_id: string;
|
||||
attribute?: string;
|
||||
above?: number;
|
||||
below?: number;
|
||||
value_template?: string;
|
||||
@@ -139,14 +136,12 @@ export interface LogicalCondition {
|
||||
export interface StateCondition {
|
||||
condition: "state";
|
||||
entity_id: string;
|
||||
attribute?: string;
|
||||
state: string | number;
|
||||
}
|
||||
|
||||
export interface NumericStateCondition {
|
||||
condition: "numeric_state";
|
||||
entity_id: string;
|
||||
attribute?: string;
|
||||
above?: number;
|
||||
below?: number;
|
||||
value_template?: string;
|
||||
|
@@ -13,8 +13,6 @@ export const DISCOVERY_SOURCES = [
|
||||
"discovery",
|
||||
];
|
||||
|
||||
export const ATTENTION_SOURCES = ["reauth"];
|
||||
|
||||
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
||||
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
|
||||
handler,
|
||||
|
@@ -51,7 +51,6 @@ export interface HassioAddonDetails extends HassioAddonInfo {
|
||||
changelog: boolean;
|
||||
hassio_api: boolean;
|
||||
hassio_role: "default" | "homeassistant" | "manager" | "admin";
|
||||
startup: "initialize" | "system" | "services" | "application" | "once";
|
||||
homeassistant_api: boolean;
|
||||
auth_api: boolean;
|
||||
full_access: boolean;
|
||||
@@ -159,19 +158,6 @@ export const setHassioAddonOption = async (
|
||||
);
|
||||
};
|
||||
|
||||
export const validateHassioAddonOption = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string
|
||||
) => {
|
||||
return await hass.callApi<
|
||||
HassioResponse<{ message: string; valid: boolean }>
|
||||
>("POST", `hassio/addons/${slug}/options/validate`);
|
||||
};
|
||||
|
||||
export const startHassioAddon = async (hass: HomeAssistant, slug: string) => {
|
||||
return hass.callApi<string>("POST", `hassio/addons/${slug}/start`);
|
||||
};
|
||||
|
||||
export const setHassioAddonSecurity = async (
|
||||
hass: HomeAssistant,
|
||||
slug: string,
|
||||
|
@@ -5,13 +5,3 @@ export interface HassioResponse<T> {
|
||||
|
||||
export const hassioApiResultExtractor = <T>(response: HassioResponse<T>) =>
|
||||
response.data;
|
||||
|
||||
export const extractApiErrorMessage = (error: any): string => {
|
||||
return typeof error === "object"
|
||||
? typeof error.body === "object"
|
||||
? error.body.message || "Unknown error, see logs"
|
||||
: error.body || "Unknown error, see logs"
|
||||
: error;
|
||||
};
|
||||
|
||||
export const ignoredStatusCodes = new Set([502, 503, 504]);
|
||||
|
@@ -23,8 +23,7 @@ export const getLogbookData = (
|
||||
hass: HomeAssistant,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityId?: string,
|
||||
entity_matches_only?: boolean
|
||||
entityId?: string
|
||||
) => {
|
||||
const ALL_ENTITIES = "*";
|
||||
|
||||
@@ -52,8 +51,7 @@ export const getLogbookData = (
|
||||
hass,
|
||||
startDate,
|
||||
endDate,
|
||||
entityId !== ALL_ENTITIES ? entityId : undefined,
|
||||
entity_matches_only
|
||||
entityId !== ALL_ENTITIES ? entityId : undefined
|
||||
).then((entries) => entries.reverse());
|
||||
return DATA_CACHE[cacheKey][entityId];
|
||||
};
|
||||
@@ -62,13 +60,11 @@ const getLogbookDataFromServer = async (
|
||||
hass: HomeAssistant,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityId?: string,
|
||||
entity_matches_only?: boolean
|
||||
entityId?: string
|
||||
) => {
|
||||
const url = `logbook/${startDate}?end_time=${endDate}${
|
||||
entityId ? `&entity=${entityId}` : ""
|
||||
}${entity_matches_only ? `&entity_matches_only` : ""}`;
|
||||
|
||||
}`;
|
||||
return hass.callApi<LogbookEntry[]>("GET", url);
|
||||
};
|
||||
|
||||
|
@@ -1,23 +1,5 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import {
|
||||
mdiFolder,
|
||||
mdiPlaylistMusic,
|
||||
mdiFileMusic,
|
||||
mdiAlbum,
|
||||
mdiMusic,
|
||||
mdiTelevisionClassic,
|
||||
mdiMovie,
|
||||
mdiVideo,
|
||||
mdiImage,
|
||||
mdiWeb,
|
||||
mdiGamepadVariant,
|
||||
mdiAccountMusic,
|
||||
mdiPodcast,
|
||||
mdiApplication,
|
||||
mdiAccountMusicOutline,
|
||||
mdiDramaMasks,
|
||||
} from "@mdi/js";
|
||||
|
||||
export const SUPPORT_PAUSE = 1;
|
||||
export const SUPPORT_SEEK = 2;
|
||||
@@ -38,70 +20,9 @@ export const CONTRAST_RATIO = 4.5;
|
||||
|
||||
export type MediaPlayerBrowseAction = "pick" | "play";
|
||||
|
||||
export const BROWSER_SOURCE = "browser";
|
||||
|
||||
export type MediaClassBrowserSetting = {
|
||||
icon: string;
|
||||
thumbnail_ratio?: string;
|
||||
layout?: string;
|
||||
show_list_images?: boolean;
|
||||
};
|
||||
|
||||
export const MediaClassBrowserSettings: {
|
||||
[type: string]: MediaClassBrowserSetting;
|
||||
} = {
|
||||
album: { icon: mdiAlbum, layout: "grid" },
|
||||
app: { icon: mdiApplication, layout: "grid" },
|
||||
artist: { icon: mdiAccountMusic, layout: "grid", show_list_images: true },
|
||||
channel: {
|
||||
icon: mdiTelevisionClassic,
|
||||
thumbnail_ratio: "portrait",
|
||||
layout: "grid",
|
||||
},
|
||||
composer: {
|
||||
icon: mdiAccountMusicOutline,
|
||||
layout: "grid",
|
||||
show_list_images: true,
|
||||
},
|
||||
contributing_artist: {
|
||||
icon: mdiAccountMusic,
|
||||
layout: "grid",
|
||||
show_list_images: true,
|
||||
},
|
||||
directory: { icon: mdiFolder, layout: "grid", show_list_images: true },
|
||||
episode: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
game: {
|
||||
icon: mdiGamepadVariant,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
genre: { icon: mdiDramaMasks, layout: "grid", show_list_images: true },
|
||||
image: { icon: mdiImage, layout: "grid" },
|
||||
movie: { icon: mdiMovie, thumbnail_ratio: "portrait", layout: "grid" },
|
||||
music: { icon: mdiMusic },
|
||||
playlist: { icon: mdiPlaylistMusic, layout: "grid", show_list_images: true },
|
||||
podcast: { icon: mdiPodcast, layout: "grid" },
|
||||
season: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
track: { icon: mdiFileMusic },
|
||||
tv_show: {
|
||||
icon: mdiTelevisionClassic,
|
||||
layout: "grid",
|
||||
thumbnail_ratio: "portrait",
|
||||
},
|
||||
url: { icon: mdiWeb },
|
||||
video: { icon: mdiVideo, layout: "grid" },
|
||||
};
|
||||
|
||||
export interface MediaPickedEvent {
|
||||
item: MediaPlayerItem;
|
||||
media_content_id: string;
|
||||
media_content_type: string;
|
||||
}
|
||||
|
||||
export interface MediaPlayerThumbnail {
|
||||
@@ -118,8 +39,6 @@ export interface MediaPlayerItem {
|
||||
title: string;
|
||||
media_content_type: string;
|
||||
media_content_id: string;
|
||||
media_class: string;
|
||||
children_media_class: string;
|
||||
can_play: boolean;
|
||||
can_expand: boolean;
|
||||
thumbnail?: string;
|
||||
@@ -139,15 +58,6 @@ export const browseMediaPlayer = (
|
||||
media_content_type: mediaContentType,
|
||||
});
|
||||
|
||||
export const browseLocalMediaPlayer = (
|
||||
hass: HomeAssistant,
|
||||
mediaContentId?: string
|
||||
): Promise<MediaPlayerItem> =>
|
||||
hass.callWS<MediaPlayerItem>({
|
||||
type: "media_source/browse_media",
|
||||
media_content_id: mediaContentId,
|
||||
});
|
||||
|
||||
export const getCurrentProgress = (stateObj: HassEntity): number => {
|
||||
let progress = stateObj.attributes.media_position;
|
||||
|
||||
|
@@ -14,8 +14,6 @@ export interface OZWDevice {
|
||||
is_zwave_plus: boolean;
|
||||
ozw_instance: number;
|
||||
event: string;
|
||||
node_manufacturer_name: string;
|
||||
node_product_name: string;
|
||||
}
|
||||
|
||||
export interface OZWDeviceMetaDataResponse {
|
||||
@@ -149,15 +147,6 @@ export const fetchOZWNetworkStatistics = (
|
||||
ozw_instance: ozw_instance,
|
||||
});
|
||||
|
||||
export const fetchOZWNodes = (
|
||||
hass: HomeAssistant,
|
||||
ozw_instance: number
|
||||
): Promise<OZWDevice[]> =>
|
||||
hass.callWS({
|
||||
type: "ozw/get_nodes",
|
||||
ozw_instance: ozw_instance,
|
||||
});
|
||||
|
||||
export const fetchOZWNodeStatus = (
|
||||
hass: HomeAssistant,
|
||||
ozw_instance: number,
|
||||
|
@@ -1,17 +0,0 @@
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"hass-refresh-tokens": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RefreshToken {
|
||||
client_icon?: string;
|
||||
client_id: string;
|
||||
client_name?: string;
|
||||
created_at: string;
|
||||
id: string;
|
||||
is_current: boolean;
|
||||
last_used_at?: string;
|
||||
last_used_ip?: string;
|
||||
type: "normal" | "long_lived_access_token";
|
||||
}
|
@@ -5,7 +5,7 @@ import {
|
||||
import { computeObjectId } from "../common/entity/compute_object_id";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Condition, Trigger } from "./automation";
|
||||
import { Condition } from "./automation";
|
||||
|
||||
export const MODES = ["single", "restart", "queued", "parallel"];
|
||||
export const MODES_MAX = ["queued", "parallel"];
|
||||
@@ -56,13 +56,6 @@ export interface SceneAction {
|
||||
export interface WaitAction {
|
||||
wait_template: string;
|
||||
timeout?: number;
|
||||
continue_on_timeout?: boolean;
|
||||
}
|
||||
|
||||
export interface WaitForTriggerAction {
|
||||
wait_for_trigger: Trigger[];
|
||||
timeout?: number;
|
||||
continue_on_timeout?: boolean;
|
||||
}
|
||||
|
||||
export interface RepeatAction {
|
||||
@@ -98,7 +91,6 @@ export type Action =
|
||||
| DelayAction
|
||||
| SceneAction
|
||||
| WaitAction
|
||||
| WaitForTriggerAction
|
||||
| RepeatAction
|
||||
| ChooseAction;
|
||||
|
||||
|
@@ -200,7 +200,7 @@ export const weatherSVGStyles = css`
|
||||
fill: var(--weather-icon-sun-color, #fdd93c);
|
||||
}
|
||||
.moon {
|
||||
fill: var(--weather-icon-moon-color, #fcf497);
|
||||
fill: var(--weather-icon-moon-color, #fdf9cc);
|
||||
}
|
||||
.cloud-back {
|
||||
fill: var(--weather-icon-cloud-back-color, #d4d4d4);
|
||||
|
@@ -1,27 +1,20 @@
|
||||
import { Connection, UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
export interface RenderTemplateResult {
|
||||
interface RenderTemplateResult {
|
||||
result: string;
|
||||
listeners: TemplateListeners;
|
||||
}
|
||||
|
||||
interface TemplateListeners {
|
||||
all: boolean;
|
||||
domains: string[];
|
||||
entities: string[];
|
||||
}
|
||||
|
||||
export const subscribeRenderTemplate = (
|
||||
conn: Connection,
|
||||
onChange: (result: RenderTemplateResult) => void,
|
||||
onChange: (result: string) => void,
|
||||
params: {
|
||||
template: string;
|
||||
entity_ids?: string | string[];
|
||||
variables?: object;
|
||||
}
|
||||
): Promise<UnsubscribeFunc> => {
|
||||
return conn.subscribeMessage((msg: RenderTemplateResult) => onChange(msg), {
|
||||
type: "render_template",
|
||||
...params,
|
||||
});
|
||||
return conn.subscribeMessage(
|
||||
(msg: RenderTemplateResult) => onChange(msg.result),
|
||||
{ type: "render_template", ...params }
|
||||
);
|
||||
};
|
||||
|
@@ -97,13 +97,8 @@ export const showConfigFlowDialog = (
|
||||
},
|
||||
|
||||
renderExternalStepHeader(hass, step) {
|
||||
return (
|
||||
hass.localize(
|
||||
return hass.localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.title`
|
||||
) ||
|
||||
hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.external_step.open_site"
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
|
@@ -10,8 +10,8 @@ import {
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-formfield";
|
||||
import "../../components/ha-switch";
|
||||
import "../../components/ha-formfield";
|
||||
import { domainToName } from "../../data/integration";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
@@ -68,9 +68,7 @@ class DomainTogglerDialog extends LitElement implements HassDialog {
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<mwc-button .domain=${domain[1]} @click=${this._handleReset}>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.domain_toggler.reset_entities"
|
||||
)}
|
||||
${this.hass.localize("ui.dialogs.domain_toggler.reset_entities")}
|
||||
</mwc-button>
|
||||
`
|
||||
)}
|
||||
@@ -98,8 +96,7 @@ class DomainTogglerDialog extends LitElement implements HassDialog {
|
||||
}
|
||||
div {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-row-gap: 8px;
|
||||
grid-template-columns: auto auto auto;
|
||||
align-items: center;
|
||||
}
|
||||
`,
|
||||
|
@@ -5,19 +5,19 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-switch";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { DialogParams } from "./show-dialog-box";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
@customElement("dialog-box")
|
||||
class DialogBox extends LitElement {
|
||||
@@ -57,8 +57,7 @@ class DialogBox extends LitElement {
|
||||
open
|
||||
?scrimClickAction=${this._params.prompt}
|
||||
?escapeKeyAction=${this._params.prompt}
|
||||
@closed=${this._dialogClosed}
|
||||
defaultAction="ignore"
|
||||
@closed=${this._dismiss}
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
@@ -79,10 +78,10 @@ class DialogBox extends LitElement {
|
||||
${this._params.prompt
|
||||
? html`
|
||||
<paper-input
|
||||
dialogInitialFocus
|
||||
autofocus
|
||||
.value=${this._value}
|
||||
@keyup=${this._handleKeyUp}
|
||||
@value-changed=${this._valueChanged}
|
||||
@keyup=${this._handleKeyUp}
|
||||
.label=${this._params.inputLabel
|
||||
? this._params.inputLabel
|
||||
: ""}
|
||||
@@ -101,11 +100,7 @@ class DialogBox extends LitElement {
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button
|
||||
@click=${this._confirm}
|
||||
?dialogInitialFocus=${!this._params.prompt}
|
||||
slot="primaryAction"
|
||||
>
|
||||
<mwc-button @click=${this._confirm} slot="primaryAction">
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
@@ -119,8 +114,8 @@ class DialogBox extends LitElement {
|
||||
}
|
||||
|
||||
private _dismiss(): void {
|
||||
if (this._params?.cancel) {
|
||||
this._params.cancel();
|
||||
if (this._params!.cancel) {
|
||||
this._params!.cancel();
|
||||
}
|
||||
this._close();
|
||||
}
|
||||
@@ -138,17 +133,7 @@ class DialogBox extends LitElement {
|
||||
this._close();
|
||||
}
|
||||
|
||||
private _dialogClosed(ev) {
|
||||
if (ev.detail.action === "ignore") {
|
||||
return;
|
||||
}
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
if (!this._params) {
|
||||
return;
|
||||
}
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
@@ -12,13 +12,12 @@ import {
|
||||
import "../../../components/ha-relative-time";
|
||||
import { triggerAutomation } from "../../../data/automation";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
|
||||
@customElement("more-info-automation")
|
||||
class MoreInfoAutomation extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public stateObj?: HassEntity;
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
@@ -35,10 +34,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<mwc-button
|
||||
@click=${this.handleAction}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(this.stateObj!.state)}
|
||||
>
|
||||
<mwc-button @click=${this.handleAction}>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
@@ -56,7 +52,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.actions {
|
||||
margin: 8px 0;
|
||||
margin: 36px 0 8px 0;
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
|
@@ -4,9 +4,9 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
@@ -47,8 +47,8 @@ class MoreInfoCamera extends LitElement {
|
||||
return html`
|
||||
<ha-camera-stream
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.stateObj}
|
||||
controls
|
||||
.stateObj="${this.stateObj}"
|
||||
showcontrols
|
||||
></ha-camera-stream>
|
||||
${this._cameraPrefs
|
||||
? html`
|
||||
|
@@ -61,6 +61,8 @@ class MoreInfoLight extends LitElement {
|
||||
"is-on": this.stateObj.state === "on",
|
||||
})}"
|
||||
>
|
||||
${this.stateObj.state === "on"
|
||||
? html`
|
||||
${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
@@ -73,8 +75,6 @@ class MoreInfoLight extends LitElement {
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
${this.stateObj.state === "on"
|
||||
? html`
|
||||
${supportsFeature(this.stateObj, SUPPORT_COLOR_TEMP)
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
@@ -134,7 +134,7 @@ class MoreInfoLight extends LitElement {
|
||||
attr-for-selected="item-name"
|
||||
>${this.stateObj.attributes.effect_list.map(
|
||||
(effect: string) => html`
|
||||
<paper-item .itemName=${effect}
|
||||
<paper-item itemName=${effect}
|
||||
>${effect}</paper-item
|
||||
>
|
||||
`
|
||||
@@ -170,7 +170,7 @@ class MoreInfoLight extends LitElement {
|
||||
}
|
||||
|
||||
private _effectChanged(ev: CustomEvent) {
|
||||
const newVal = ev.detail.item.itemName;
|
||||
const newVal = ev.detail.value;
|
||||
|
||||
if (!newVal || this.stateObj!.attributes.effect === newVal) {
|
||||
return;
|
||||
|
@@ -130,7 +130,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${![UNAVAILABLE, UNKNOWN, "off"].includes(stateObj.state) &&
|
||||
${stateObj.state !== "off" &&
|
||||
supportsFeature(stateObj, SUPPORT_SELECT_SOURCE) &&
|
||||
stateObj.attributes.source_list?.length
|
||||
? html`
|
||||
@@ -188,17 +188,14 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
<div class="tts">
|
||||
<paper-input
|
||||
id="ttsInput"
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.label=${this.hass.localize(
|
||||
"ui.card.media_player.text_to_speak"
|
||||
)}
|
||||
@keydown=${this._ttsCheckForEnter}
|
||||
></paper-input>
|
||||
<ha-icon-button
|
||||
icon="hass:send"
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
@click=${this._sendTTS}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button icon="hass:send" @click=${
|
||||
this._sendTTS
|
||||
}></ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -412,8 +409,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
entityId: this.stateObj!.entity_id,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) =>
|
||||
this._playMedia(
|
||||
pickedMedia.item.media_content_id,
|
||||
pickedMedia.item.media_content_type
|
||||
pickedMedia.media_content_id,
|
||||
pickedMedia.media_content_type
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@@ -26,12 +26,15 @@ class MoreInfoTimer extends LitElement {
|
||||
return html`
|
||||
<ha-attributes
|
||||
.stateObj=${this.stateObj}
|
||||
extra-filters="remaining"
|
||||
.extraFilters=${"remaining"}
|
||||
></ha-attributes>
|
||||
<div class="actions">
|
||||
${this.stateObj.state === "idle" || this.stateObj.state === "paused"
|
||||
? html`
|
||||
<mwc-button .action=${"start"} @click=${this._handleActionClick}>
|
||||
<mwc-button
|
||||
.action="${"start"}"
|
||||
@click="${this._handleActionClick}"
|
||||
>
|
||||
${this.hass!.localize("ui.card.timer.actions.start")}
|
||||
</mwc-button>
|
||||
`
|
||||
@@ -39,7 +42,7 @@ class MoreInfoTimer extends LitElement {
|
||||
${this.stateObj.state === "active"
|
||||
? html`
|
||||
<mwc-button
|
||||
.action=${"pause"}
|
||||
.action="${"pause"}"
|
||||
@click="${this._handleActionClick}"
|
||||
>
|
||||
${this.hass!.localize("ui.card.timer.actions.pause")}
|
||||
@@ -49,13 +52,13 @@ class MoreInfoTimer extends LitElement {
|
||||
${this.stateObj.state === "active" || this.stateObj.state === "paused"
|
||||
? html`
|
||||
<mwc-button
|
||||
.action=${"cancel"}
|
||||
.action="${"cancel"}"
|
||||
@click="${this._handleActionClick}"
|
||||
>
|
||||
${this.hass!.localize("ui.card.timer.actions.cancel")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
.action=${"finish"}
|
||||
.action="${"finish"}"
|
||||
@click="${this._handleActionClick}"
|
||||
>
|
||||
${this.hass!.localize("ui.card.timer.actions.finish")}
|
||||
|
@@ -1,72 +1,38 @@
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-icon-button";
|
||||
import "@material/mwc-tab";
|
||||
import "@material/mwc-tab-bar";
|
||||
import { mdiClose, mdiCog, mdiPencil } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import {
|
||||
DOMAINS_MORE_INFO_NO_HISTORY,
|
||||
DOMAINS_WITH_MORE_INFO,
|
||||
} from "../../common/const";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { stateMoreInfoType } from "../../common/entity/state_more_info_type";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-header-bar";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/state-history-charts";
|
||||
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import "../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import "./ha-more-info-history";
|
||||
import "./ha-more-info-logbook";
|
||||
import "./more-info-content";
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
css,
|
||||
html,
|
||||
} from "lit-element";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { getRecentWithCache } from "../../data/cached-history";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { mdiClose, mdiCog, mdiPencil } from "@mdi/js";
|
||||
import { HistoryResult } from "../../data/history";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator"];
|
||||
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
const EDITABLE_DOMAINS = ["script"];
|
||||
|
||||
const MORE_INFO_CONTROL_IMPORT = {
|
||||
alarm_control_panel: () => import("./controls/more-info-alarm_control_panel"),
|
||||
automation: () => import("./controls/more-info-automation"),
|
||||
camera: () => import("./controls/more-info-camera"),
|
||||
climate: () => import("./controls/more-info-climate"),
|
||||
configurator: () => import("./controls/more-info-configurator"),
|
||||
counter: () => import("./controls/more-info-counter"),
|
||||
cover: () => import("./controls/more-info-cover"),
|
||||
fan: () => import("./controls/more-info-fan"),
|
||||
group: () => import("./controls/more-info-group"),
|
||||
humidifier: () => import("./controls/more-info-humidifier"),
|
||||
input_datetime: () => import("./controls/more-info-input_datetime"),
|
||||
light: () => import("./controls/more-info-light"),
|
||||
lock: () => import("./controls/more-info-lock"),
|
||||
media_player: () => import("./controls/more-info-media_player"),
|
||||
person: () => import("./controls/more-info-person"),
|
||||
script: () => import("./controls/more-info-script"),
|
||||
sun: () => import("./controls/more-info-sun"),
|
||||
timer: () => import("./controls/more-info-timer"),
|
||||
vacuum: () => import("./controls/more-info-vacuum"),
|
||||
water_heater: () => import("./controls/more-info-water_heater"),
|
||||
weather: () => import("./controls/more-info-weather"),
|
||||
hidden: () => {},
|
||||
default: () => import("./controls/more-info-default"),
|
||||
};
|
||||
|
||||
export interface MoreInfoDialogParams {
|
||||
entityId: string | null;
|
||||
}
|
||||
@@ -77,11 +43,11 @@ export class MoreInfoDialog extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public large = false;
|
||||
|
||||
@internalProperty() private _stateHistory?: HistoryResult;
|
||||
|
||||
@internalProperty() private _entityId?: string | null;
|
||||
|
||||
@internalProperty() private _moreInfoType?: string;
|
||||
|
||||
@internalProperty() private _currTabIndex = 0;
|
||||
private _historyRefreshInterval?: number;
|
||||
|
||||
public showDialog(params: MoreInfoDialogParams) {
|
||||
this._entityId = params.entityId;
|
||||
@@ -89,31 +55,24 @@ export class MoreInfoDialog extends LitElement {
|
||||
this.closeDialog();
|
||||
}
|
||||
this.large = false;
|
||||
this._stateHistory = undefined;
|
||||
if (this._computeShowHistoryComponent(this._entityId)) {
|
||||
this._getStateHistory();
|
||||
clearInterval(this._historyRefreshInterval);
|
||||
this._historyRefreshInterval = window.setInterval(() => {
|
||||
this._getStateHistory();
|
||||
}, 60 * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._entityId = undefined;
|
||||
this._currTabIndex = 0;
|
||||
this._stateHistory = undefined;
|
||||
clearInterval(this._historyRefreshInterval);
|
||||
this._historyRefreshInterval = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected updated(changedProperties) {
|
||||
if (!this.hass || !this._entityId || !changedProperties.has("_entityId")) {
|
||||
return;
|
||||
}
|
||||
const stateObj = this.hass.states[this._entityId];
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
if (stateObj.attributes && "custom_ui_more_info" in stateObj.attributes) {
|
||||
this._moreInfoType = stateObj.attributes.custom_ui_more_info;
|
||||
} else {
|
||||
const type = stateMoreInfoType(stateObj);
|
||||
this._moreInfoType = `more-info-${type}`;
|
||||
MORE_INFO_CONTROL_IMPORT[type]();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._entityId) {
|
||||
return html``;
|
||||
@@ -134,14 +93,11 @@ export class MoreInfoDialog extends LitElement {
|
||||
hideActions
|
||||
data-domain=${domain}
|
||||
>
|
||||
<div slot="heading" class="heading">
|
||||
<ha-header-bar>
|
||||
<ha-header-bar slot="heading">
|
||||
<mwc-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.dialogs.more_info_control.dismiss")}
|
||||
dialogAction="cancel"
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.dismiss"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
@@ -149,8 +105,7 @@ export class MoreInfoDialog extends LitElement {
|
||||
${computeStateName(stateObj)}
|
||||
</div>
|
||||
${this.hass.user!.is_admin
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
? html`<mwc-icon-button
|
||||
slot="actionItems"
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.settings"
|
||||
@@ -158,15 +113,13 @@ export class MoreInfoDialog extends LitElement {
|
||||
@click=${this._gotoSettings}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiCog}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
</mwc-icon-button>`
|
||||
: ""}
|
||||
${this.hass.user!.is_admin &&
|
||||
((EDITABLE_DOMAINS_WITH_ID.includes(domain) &&
|
||||
stateObj.attributes.id) ||
|
||||
EDITABLE_DOMAINS.includes(domain))
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
? html` <mwc-icon-button
|
||||
slot="actionItems"
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.edit"
|
||||
@@ -174,64 +127,36 @@ export class MoreInfoDialog extends LitElement {
|
||||
@click=${this._gotoEdit}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
</mwc-icon-button>`
|
||||
: ""}
|
||||
</ha-header-bar>
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) &&
|
||||
this._computeShowHistoryComponent(entityId)
|
||||
? html`
|
||||
<mwc-tab-bar
|
||||
.activeIndex=${this._currTabIndex}
|
||||
@MDCTabBar:activated=${this._handleTabChanged}
|
||||
>
|
||||
<mwc-tab
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.details"
|
||||
)}
|
||||
></mwc-tab>
|
||||
<mwc-tab
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.history"
|
||||
)}
|
||||
></mwc-tab>
|
||||
</mwc-tab-bar>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="content">
|
||||
${cache(
|
||||
this._currTabIndex === 0
|
||||
? html`
|
||||
${DOMAINS_NO_INFO.includes(domain)
|
||||
? ""
|
||||
: html`
|
||||
<state-card-content
|
||||
in-dialog
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
in-dialog
|
||||
></state-card-content>
|
||||
`}
|
||||
${DOMAINS_WITH_MORE_INFO.includes(domain) ||
|
||||
!this._computeShowHistoryComponent(entityId)
|
||||
? ""
|
||||
: html`<ha-more-info-history
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history>
|
||||
<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-logbook>`}
|
||||
${this._moreInfoType
|
||||
? dynamicElement(this._moreInfoType, {
|
||||
hass: this.hass,
|
||||
stateObj,
|
||||
})
|
||||
: ""}
|
||||
${stateObj.attributes.restored
|
||||
${this._computeShowHistoryComponent(entityId)
|
||||
? html`
|
||||
<p>
|
||||
<state-history-charts
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
up-to-now
|
||||
.isLoadingData=${!this._stateHistory}
|
||||
></state-history-charts>
|
||||
`
|
||||
: ""}
|
||||
<more-info-content
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
></more-info-content>
|
||||
|
||||
${stateObj.attributes.restored
|
||||
? html`<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.restored.not_provided"
|
||||
)}
|
||||
@@ -241,28 +166,12 @@ export class MoreInfoDialog extends LitElement {
|
||||
"ui.dialogs.more_info_control.restored.remove_intro"
|
||||
)}
|
||||
</p>
|
||||
<mwc-button
|
||||
class="warning"
|
||||
@click=${this._removeEntity}
|
||||
>
|
||||
<mwc-button class="warning" @click=${this._removeEntity}>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.restored.remove_action"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
</mwc-button>`
|
||||
: ""}
|
||||
`
|
||||
: html`
|
||||
<ha-more-info-history
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-history>
|
||||
<ha-more-info-logbook
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
></ha-more-info-logbook>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
@@ -272,10 +181,26 @@ export class MoreInfoDialog extends LitElement {
|
||||
this.large = !this.large;
|
||||
}
|
||||
|
||||
private async _getStateHistory(): Promise<void> {
|
||||
if (!this._entityId) {
|
||||
return;
|
||||
}
|
||||
this._stateHistory = await getRecentWithCache(
|
||||
this.hass!,
|
||||
this._entityId,
|
||||
{
|
||||
refresh: 60,
|
||||
cacheKey: `more_info.${this._entityId}`,
|
||||
hoursToShow: 24,
|
||||
},
|
||||
this.hass!.localize,
|
||||
this.hass!.language
|
||||
);
|
||||
}
|
||||
|
||||
private _computeShowHistoryComponent(entityId) {
|
||||
return (
|
||||
(isComponentLoaded(this.hass, "history") ||
|
||||
isComponentLoaded(this.hass, "logbook")) &&
|
||||
isComponentLoaded(this.hass, "history") &&
|
||||
!DOMAINS_MORE_INFO_NO_HISTORY.includes(computeDomain(entityId))
|
||||
);
|
||||
}
|
||||
@@ -318,15 +243,6 @@ export class MoreInfoDialog extends LitElement {
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _handleTabChanged(ev: CustomEvent): void {
|
||||
const newTab = ev.detail.index;
|
||||
if (newTab === this._currTabIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._currTabIndex = ev.detail.index;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyleDialog,
|
||||
@@ -340,7 +256,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
--mdc-theme-on-primary: var(--primary-text-color);
|
||||
--mdc-theme-primary: var(--mdc-theme-surface);
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
@@ -351,11 +268,6 @@ export class MoreInfoDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
border-bottom: 1px solid
|
||||
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
|
||||
}
|
||||
|
||||
@media all and (min-width: 451px) and (min-height: 501px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 90vw;
|
||||
@@ -394,7 +306,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
state-card-content {
|
||||
state-card-content,
|
||||
state-history-charts {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@@ -402,9 +315,3 @@ export class MoreInfoDialog extends LitElement {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-dialog": MoreInfoDialog;
|
||||
}
|
||||
}
|
||||
|
@@ -1,109 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/state-history-charts";
|
||||
import { getRecentWithCache } from "../../data/cached-history";
|
||||
import { HistoryResult } from "../../data/history";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-more-info-history")
|
||||
export class MoreInfoHistory extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entityId!: string;
|
||||
|
||||
@internalProperty() private _stateHistory?: HistoryResult;
|
||||
|
||||
private _throttleGetStateHistory = throttle(() => {
|
||||
this._getStateHistory();
|
||||
}, 10000);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entityId) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`${isComponentLoaded(this.hass, "history")
|
||||
? html`<state-history-charts
|
||||
up-to-now
|
||||
.hass=${this.hass}
|
||||
.historyData=${this._stateHistory}
|
||||
.isLoadingData=${!this._stateHistory}
|
||||
></state-history-charts>`
|
||||
: ""} `;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("entityId")) {
|
||||
this._stateHistory = undefined;
|
||||
|
||||
if (!this.entityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._throttleGetStateHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.entityId || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (
|
||||
oldHass &&
|
||||
this.hass.states[this.entityId] !== oldHass?.states[this.entityId]
|
||||
) {
|
||||
// wait for commit of data (we only account for the default setting of 1 sec)
|
||||
setTimeout(this._throttleGetStateHistory, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private async _getStateHistory(): Promise<void> {
|
||||
if (!isComponentLoaded(this.hass, "history")) {
|
||||
return;
|
||||
}
|
||||
this._stateHistory = await getRecentWithCache(
|
||||
this.hass!,
|
||||
this.entityId,
|
||||
{
|
||||
refresh: 60,
|
||||
cacheKey: `more_info.${this.entityId}`,
|
||||
hoursToShow: 24,
|
||||
},
|
||||
this.hass!.localize,
|
||||
this.hass!.language
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
state-history-charts {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-history": MoreInfoHistory;
|
||||
}
|
||||
}
|
@@ -1,171 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/state-history-charts";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import "../../panels/logbook/ha-logbook";
|
||||
import { haStyle, haStyleScrollbar } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-more-info-logbook")
|
||||
export class MoreInfoLogbook extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entityId!: string;
|
||||
|
||||
@internalProperty() private _logbookEntries?: LogbookEntry[];
|
||||
|
||||
@internalProperty() private _persons = {};
|
||||
|
||||
private _lastLogbookDate?: Date;
|
||||
|
||||
private _throttleGetLogbookEntries = throttle(() => {
|
||||
this._getLogBookData();
|
||||
}, 10000);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entityId) {
|
||||
return html``;
|
||||
}
|
||||
const stateObj = this.hass.states[this.entityId];
|
||||
|
||||
if (!stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${isComponentLoaded(this.hass, "logbook")
|
||||
? !this._logbookEntries
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt=${this.hass.localize("ui.common.loading")}
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: this._logbookEntries.length
|
||||
? html`
|
||||
<ha-logbook
|
||||
class="ha-scrollbar"
|
||||
narrow
|
||||
no-icon
|
||||
no-name
|
||||
.hass=${this.hass}
|
||||
.entries=${this._logbookEntries}
|
||||
.userIdToName=${this._persons}
|
||||
></ha-logbook>
|
||||
`
|
||||
: html`<div class="no-entries">
|
||||
${this.hass.localize("ui.components.logbook.entries_not_found")}
|
||||
</div>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._fetchPersonNames();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("entityId")) {
|
||||
this._lastLogbookDate = undefined;
|
||||
this._logbookEntries = undefined;
|
||||
|
||||
if (!this.entityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._throttleGetLogbookEntries();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.entityId || !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
|
||||
if (
|
||||
oldHass &&
|
||||
this.hass.states[this.entityId] !== oldHass?.states[this.entityId]
|
||||
) {
|
||||
// wait for commit of data (we only account for the default setting of 1 sec)
|
||||
setTimeout(this._throttleGetLogbookEntries, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private async _getLogBookData() {
|
||||
if (!isComponentLoaded(this.hass, "logbook")) {
|
||||
return;
|
||||
}
|
||||
const lastDate =
|
||||
this._lastLogbookDate ||
|
||||
new Date(new Date().getTime() - 24 * 60 * 60 * 1000);
|
||||
const now = new Date();
|
||||
const newEntries = await getLogbookData(
|
||||
this.hass,
|
||||
lastDate.toISOString(),
|
||||
now.toISOString(),
|
||||
this.entityId,
|
||||
true
|
||||
);
|
||||
this._logbookEntries = this._logbookEntries
|
||||
? [...newEntries, ...this._logbookEntries]
|
||||
: newEntries;
|
||||
this._lastLogbookDate = now;
|
||||
}
|
||||
|
||||
private _fetchPersonNames() {
|
||||
Object.values(this.hass.states).forEach((entity) => {
|
||||
if (
|
||||
entity.attributes.user_id &&
|
||||
computeStateDomain(entity) === "person"
|
||||
) {
|
||||
this._persons[entity.attributes.user_id] =
|
||||
entity.attributes.friendly_name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
.no-entries {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-logbook {
|
||||
max-height: 250px;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-circular-progress {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-more-info-logbook": MoreInfoLogbook;
|
||||
}
|
||||
}
|
73
src/dialogs/more-info/more-info-content.ts
Normal file
73
src/dialogs/more-info/more-info-content.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { property, PropertyValues, UpdatingElement } from "lit-element";
|
||||
import dynamicContentUpdater from "../../common/dom/dynamic_content_updater";
|
||||
import { stateMoreInfoType } from "../../common/entity/state_more_info_type";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./controls/more-info-alarm_control_panel";
|
||||
import "./controls/more-info-automation";
|
||||
import "./controls/more-info-camera";
|
||||
import "./controls/more-info-climate";
|
||||
import "./controls/more-info-configurator";
|
||||
import "./controls/more-info-counter";
|
||||
import "./controls/more-info-cover";
|
||||
import "./controls/more-info-default";
|
||||
import "./controls/more-info-fan";
|
||||
import "./controls/more-info-group";
|
||||
import "./controls/more-info-humidifier";
|
||||
import "./controls/more-info-input_datetime";
|
||||
import "./controls/more-info-light";
|
||||
import "./controls/more-info-lock";
|
||||
import "./controls/more-info-media_player";
|
||||
import "./controls/more-info-person";
|
||||
import "./controls/more-info-script";
|
||||
import "./controls/more-info-sun";
|
||||
import "./controls/more-info-timer";
|
||||
import "./controls/more-info-vacuum";
|
||||
import "./controls/more-info-water_heater";
|
||||
import "./controls/more-info-weather";
|
||||
|
||||
class MoreInfoContent extends UpdatingElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
private _detachedChild?: ChildNode;
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this.style.position = "relative";
|
||||
this.style.display = "block";
|
||||
}
|
||||
|
||||
// This is not a lit element, but an updating element, so we implement update
|
||||
protected update(changedProps: PropertyValues): void {
|
||||
super.update(changedProps);
|
||||
const stateObj = this.stateObj;
|
||||
const hass = this.hass;
|
||||
|
||||
if (!stateObj || !hass) {
|
||||
if (this.lastChild) {
|
||||
this._detachedChild = this.lastChild;
|
||||
// Detach child to prevent it from doing work.
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._detachedChild) {
|
||||
this.appendChild(this._detachedChild);
|
||||
this._detachedChild = undefined;
|
||||
}
|
||||
|
||||
const moreInfoType =
|
||||
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
|
||||
? stateObj.attributes.custom_ui_more_info
|
||||
: "more-info-" + stateMoreInfoType(stateObj);
|
||||
|
||||
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
|
||||
hass,
|
||||
stateObj,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-content", MoreInfoContent);
|
@@ -7,3 +7,5 @@ import "../util/legacy-support";
|
||||
setPassiveTouchGestures(true);
|
||||
|
||||
(window as any).frontendVersion = __VERSION__;
|
||||
|
||||
import("../resources/html-import/polyfill");
|
||||
|
@@ -48,7 +48,7 @@
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #111111;
|
||||
background-color: var(--primary-background-color, #111111);
|
||||
}
|
||||
#ha-init-skeleton::before {
|
||||
background-color: #1c1c1c;
|
||||
@@ -100,5 +100,9 @@
|
||||
{% endfor -%}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% for extra_url in extra_urls -%}
|
||||
<link rel="import" href="{{ extra_url }}" async />
|
||||
{% endfor -%}
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -5,20 +5,6 @@
|
||||
<link rel="preload" href="<%= latestPageJS %>" as="script" crossorigin="use-credentials" />
|
||||
<%= renderTemplate('_header') %>
|
||||
<style>
|
||||
html {
|
||||
color: var(--primary-text-color, #212121);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: #111111;
|
||||
color: #e1e1e1;
|
||||
}
|
||||
ha-onboarding {
|
||||
--primary-text-color: #e1e1e1;
|
||||
--secondary-text-color: #9b9b9b;
|
||||
--disabled-text-color: #6f6f6f;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding: 20px 16px;
|
||||
max-width: 400px;
|
||||
@@ -37,6 +23,14 @@
|
||||
.header img {
|
||||
margin-right: 16px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #111111;
|
||||
color: #e1e1e1;
|
||||
--primary-text-color: #e1e1e1;
|
||||
--secondary-text-color: #9b9b9b;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@@ -63,7 +63,6 @@ class HassErrorScreen extends LitElement {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.content {
|
||||
color: var(--primary-text-color);
|
||||
height: calc(100% - 64px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@@ -3,26 +3,26 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
eventOptions,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
eventOptions,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-tab";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-tab";
|
||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
|
||||
export interface PageNavigation {
|
||||
path: string;
|
||||
@@ -132,7 +132,7 @@ class HassTabsSubpage extends LitElement {
|
||||
this.hass.language,
|
||||
this.narrow
|
||||
);
|
||||
const showTabs = tabs.length > 1 || !this.narrow;
|
||||
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
${this.mainPage
|
||||
@@ -152,7 +152,7 @@ class HassTabsSubpage extends LitElement {
|
||||
${this.narrow
|
||||
? html` <div class="main-title"><slot name="header"></slot></div> `
|
||||
: ""}
|
||||
${showTabs
|
||||
${tabs.length > 1 || !this.narrow
|
||||
? html`
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${tabs}
|
||||
@@ -163,15 +163,10 @@ class HassTabsSubpage extends LitElement {
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content ${classMap({ tabs: showTabs })}"
|
||||
@scroll=${this._saveScrollPos}
|
||||
>
|
||||
<div class="content" @scroll=${this._saveScrollPos}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div id="fab" class="${classMap({ tabs: showTabs })}">
|
||||
<slot name="fab"></slot>
|
||||
</div>
|
||||
<div id="fab"><slot name="fab"></slot></div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -279,13 +274,12 @@ class HassTabsSubpage extends LitElement {
|
||||
margin-left: env(safe-area-inset-left);
|
||||
margin-right: env(safe-area-inset-right);
|
||||
height: calc(100% - 65px);
|
||||
height: calc(100% - 65px - env(safe-area-inset-bottom));
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
:host([narrow]) .content.tabs {
|
||||
:host([narrow]) .content {
|
||||
height: calc(100% - 128px);
|
||||
height: calc(100% - 128px - env(safe-area-inset-bottom));
|
||||
}
|
||||
@@ -296,7 +290,7 @@ class HassTabsSubpage extends LitElement {
|
||||
bottom: calc(16px + env(safe-area-inset-bottom));
|
||||
z-index: 1;
|
||||
}
|
||||
:host([narrow]) #fab.tabs {
|
||||
:host([narrow]) #fab {
|
||||
bottom: calc(84px + env(safe-area-inset-bottom));
|
||||
}
|
||||
#fab[is-wide] {
|
||||
|
@@ -24,7 +24,6 @@ const NON_SWIPABLE_PANELS = ["map"];
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"hass-open-menu": undefined;
|
||||
"hass-toggle-menu": undefined;
|
||||
"hass-show-notifications": undefined;
|
||||
}
|
||||
@@ -93,17 +92,6 @@ class HomeAssistantMain extends LitElement {
|
||||
protected firstUpdated() {
|
||||
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
|
||||
|
||||
this.addEventListener("hass-open-menu", () => {
|
||||
if (this._sidebarNarrow) {
|
||||
this.drawer.open();
|
||||
} else {
|
||||
fireEvent(this, "hass-dock-sidebar", {
|
||||
dock: "docked",
|
||||
});
|
||||
setTimeout(() => this.appLayout.resetLayout());
|
||||
}
|
||||
});
|
||||
|
||||
this.addEventListener("hass-toggle-menu", () => {
|
||||
if (this._sidebarNarrow) {
|
||||
if (this.drawer.opened) {
|
||||
|
@@ -1,13 +1,6 @@
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import {
|
||||
STATE_NOT_RUNNING,
|
||||
STATE_RUNNING,
|
||||
STATE_STARTING,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { customElement, property, PropertyValues } from "lit-element";
|
||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||
import { deepEqual } from "../common/util/deep-equal";
|
||||
import { CustomPanelInfo } from "../data/panel_custom";
|
||||
import { HomeAssistant, Panels } from "../types";
|
||||
import { removeInitSkeleton } from "../util/init-skeleton";
|
||||
import {
|
||||
@@ -15,6 +8,13 @@ import {
|
||||
RouteOptions,
|
||||
RouterOptions,
|
||||
} from "./hass-router-page";
|
||||
import {
|
||||
STATE_STARTING,
|
||||
STATE_NOT_RUNNING,
|
||||
STATE_RUNNING,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { CustomPanelInfo } from "../data/panel_custom";
|
||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||
|
||||
const CACHE_URL_PATHS = ["lovelace", "developer-tools"];
|
||||
const COMPONENTS = {
|
||||
@@ -64,10 +64,6 @@ const COMPONENTS = {
|
||||
import(
|
||||
/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"
|
||||
),
|
||||
"media-browser": () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-media-browser" */ "../panels/media-browser/ha-panel-media-browser"
|
||||
),
|
||||
};
|
||||
|
||||
const getRoutes = (panels: Panels): RouterOptions => {
|
||||
|
@@ -6,9 +6,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiArrowDown, mdiArrowUp, mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "@material/mwc-icon-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { mdiDotsVertical, mdiArrowUp, mdiArrowDown } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
@@ -11,31 +12,29 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { Action } from "../../../../data/script";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
|
||||
import "./types/ha-automation-action-choose";
|
||||
import "./types/ha-automation-action-condition";
|
||||
import "./types/ha-automation-action-delay";
|
||||
import "./types/ha-automation-action-device_id";
|
||||
import "./types/ha-automation-action-event";
|
||||
import "./types/ha-automation-action-repeat";
|
||||
import "./types/ha-automation-action-scene";
|
||||
import "./types/ha-automation-action-service";
|
||||
import "./types/ha-automation-action-wait_for_trigger";
|
||||
import "./types/ha-automation-action-wait_template";
|
||||
import "./types/ha-automation-action-repeat";
|
||||
import "./types/ha-automation-action-choose";
|
||||
import { handleStructError } from "../../../lovelace/common/structs/handle-errors";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
|
||||
const OPTIONS = [
|
||||
"condition",
|
||||
@@ -45,7 +44,6 @@ const OPTIONS = [
|
||||
"scene",
|
||||
"service",
|
||||
"wait_template",
|
||||
"wait_for_trigger",
|
||||
"repeat",
|
||||
"choose",
|
||||
];
|
||||
@@ -168,12 +166,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item disabled>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
<mwc-list-item>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
@@ -263,7 +261,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
case 1:
|
||||
fireEvent(this, "duplicate");
|
||||
break;
|
||||
case 2:
|
||||
this._onDelete();
|
||||
@@ -336,6 +333,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
.warning {
|
||||
color: var(--warning-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.warning ul {
|
||||
|
@@ -28,7 +28,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
.index=${idx}
|
||||
.totalActions=${this.actions.length}
|
||||
.action=${action}
|
||||
@duplicate=${this._duplicateAction}
|
||||
@move-action=${this._move}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
@@ -79,14 +78,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
}
|
||||
|
||||
private _duplicateAction(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.actions.concat(this.actions[index]),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-automation-action-row,
|
||||
|
@@ -1,21 +1,22 @@
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { Condition } from "../../../../../data/automation";
|
||||
import { Action, ChooseAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
import "../../condition/ha-automation-condition-editor";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../ha-automation-action";
|
||||
import { Condition } from "../../../../../data/automation";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
|
||||
@customElement("ha-automation-action-choose")
|
||||
export class HaChooseAction extends LitElement implements ActionElement {
|
||||
|
@@ -1,21 +1,22 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { CSSResult, customElement, LitElement, property } from "lit-element";
|
||||
import { customElement, LitElement, property, CSSResult } from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import {
|
||||
RepeatAction,
|
||||
Action,
|
||||
CountRepeat,
|
||||
RepeatAction,
|
||||
UntilRepeat,
|
||||
WhileRepeat,
|
||||
UntilRepeat,
|
||||
} from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import "../ha-automation-action";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
import "../../condition/ha-automation-condition-editor";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../ha-automation-action";
|
||||
import { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
|
||||
const OPTIONS = ["count", "while", "until"];
|
||||
|
||||
|
@@ -8,7 +8,6 @@ import {
|
||||
} from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { any, assert, object, optional, string } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../../../common/entity/compute_domain";
|
||||
import { computeObjectId } from "../../../../../common/entity/compute_object_id";
|
||||
@@ -19,13 +18,14 @@ import type { HaYamlEditor } from "../../../../../components/ha-yaml-editor";
|
||||
import { ServiceAction } from "../../../../../data/script";
|
||||
import type { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { EntityId } from "../../../../lovelace/common/structs/is-entity-id";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
import { assert, optional, object, string } from "superstruct";
|
||||
import { EntityId } from "../../../../lovelace/common/structs/is-entity-id";
|
||||
|
||||
const actionStruct = object({
|
||||
service: optional(string()),
|
||||
entity_id: optional(EntityId),
|
||||
data: optional(any()),
|
||||
data: optional(object()),
|
||||
});
|
||||
|
||||
@customElement("ha-automation-action-service")
|
||||
|
@@ -1,70 +0,0 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { customElement, LitElement, property } from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import { WaitForTriggerAction } from "../../../../../data/script";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../trigger/ha-automation-trigger";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-wait_for_trigger")
|
||||
export class HaWaitForTriggerAction extends LitElement
|
||||
implements ActionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public action!: WaitForTriggerAction;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { wait_for_trigger: [], timeout: "" };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { wait_for_trigger, continue_on_timeout, timeout } = this.action;
|
||||
|
||||
return html`
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
|
||||
)}
|
||||
.name=${"timeout"}
|
||||
.value=${timeout}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.wait_for_trigger.timeout"
|
||||
)}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${continue_on_timeout}
|
||||
@change=${this._continueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-automation-trigger
|
||||
.triggers=${wait_for_trigger}
|
||||
.hass=${this.hass}
|
||||
.name=${"wait_for_trigger"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-automation-trigger>
|
||||
`;
|
||||
}
|
||||
|
||||
private _continueChanged(ev) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, continue_on_timeout: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-action-wait_for_trigger": HaWaitForTriggerAction;
|
||||
}
|
||||
}
|
@@ -2,7 +2,6 @@ import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { customElement, LitElement, property } from "lit-element";
|
||||
import { html } from "lit-html";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { WaitAction } from "../../../../../data/script";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
@@ -14,11 +13,11 @@ export class HaWaitAction extends LitElement implements ActionElement {
|
||||
@property() public action!: WaitAction;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { wait_template: "" };
|
||||
return { wait_template: "", timeout: "" };
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { wait_template, timeout, continue_on_timeout } = this.action;
|
||||
const { wait_template, timeout } = this.action;
|
||||
|
||||
return html`
|
||||
<paper-textarea
|
||||
@@ -38,24 +37,9 @@ export class HaWaitAction extends LitElement implements ActionElement {
|
||||
.value=${timeout}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<br />
|
||||
<ha-formfield
|
||||
.label=${this.hass.localize("ui.panel.config.automation.editor.actions.type.wait_template.continue_timeout")}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${continue_on_timeout}
|
||||
@change=${this._continueChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
`;
|
||||
}
|
||||
|
||||
private _continueChanged(ev) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.action, continue_on_timeout: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
@@ -1,25 +1,24 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import { Condition } from "../../../../data/automation";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-condition-editor";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
|
||||
export interface ConditionElement extends LitElement {
|
||||
condition: Condition;
|
||||
@@ -82,12 +81,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item disabled>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
<mwc-list-item>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
@@ -110,7 +109,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
case 1:
|
||||
fireEvent(this, "duplicate");
|
||||
break;
|
||||
case 2:
|
||||
this._onDelete();
|
||||
@@ -135,10 +133,8 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._yamlMode = !this._yamlMode;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.card-menu {
|
||||
float: right;
|
||||
z-index: 3;
|
||||
@@ -150,8 +146,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
mwc-list-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,6 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-card";
|
||||
@@ -21,43 +20,13 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
@property() public conditions!: Condition[];
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("conditions")) {
|
||||
return;
|
||||
}
|
||||
let updatedConditions: Condition[] | undefined;
|
||||
if (!Array.isArray(this.conditions)) {
|
||||
updatedConditions = [this.conditions];
|
||||
}
|
||||
|
||||
(updatedConditions || this.conditions).forEach((condition, index) => {
|
||||
if (typeof condition === "string") {
|
||||
updatedConditions = updatedConditions || [...this.conditions];
|
||||
updatedConditions[index] = {
|
||||
condition: "template",
|
||||
value_template: condition,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if (updatedConditions) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: updatedConditions,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!Array.isArray(this.conditions)) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
${this.conditions.map(
|
||||
(cond, idx) => html`
|
||||
<ha-automation-condition-row
|
||||
.index=${idx}
|
||||
.condition=${cond}
|
||||
@duplicate=${this._duplicateCondition}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition-row>
|
||||
@@ -99,14 +68,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
}
|
||||
|
||||
private _duplicateCondition(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.conditions.concat(this.conditions[index]),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-automation-condition-row,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import { NumericStateCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
@@ -18,34 +19,16 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
value_template,
|
||||
entity_id,
|
||||
attribute,
|
||||
below,
|
||||
above,
|
||||
} = this.condition;
|
||||
protected render() {
|
||||
const { value_template, entity_id, below, above } = this.condition;
|
||||
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
.value=${entity_id}
|
||||
.name=${"entity_id"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.value="${entity_id}"
|
||||
@value-changed="${this._entityPicked}"
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<ha-entity-attribute-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${entity_id}
|
||||
.value=${attribute}
|
||||
.name=${"attribute"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.attribute"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-value
|
||||
></ha-entity-attribute-picker>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.numeric_state.above"
|
||||
@@ -77,6 +60,13 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
private _entityPicked(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.condition, entity_id: ev.detail.value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import "../../../../../components/entity/ha-entity-attribute-picker";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/entity/ha-entity-picker";
|
||||
import { StateCondition } from "../../../../../data/automation";
|
||||
import { PolymerChangedEvent } from "../../../../../polymer-types";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
ConditionElement,
|
||||
@@ -20,27 +21,15 @@ export class HaStateCondition extends LitElement implements ConditionElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { entity_id, attribute, state } = this.condition;
|
||||
const { entity_id, state } = this.condition;
|
||||
|
||||
return html`
|
||||
<ha-entity-picker
|
||||
.value=${entity_id}
|
||||
.name=${"entity_id"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@value-changed=${this._entityPicked}
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<ha-entity-attribute-picker
|
||||
.hass=${this.hass}
|
||||
.entityId=${entity_id}
|
||||
.value=${attribute}
|
||||
.name=${"attribute"}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.state.attribute"
|
||||
)}
|
||||
@value-changed=${this._valueChanged}
|
||||
allow-custom-value
|
||||
></ha-entity-attribute-picker>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.state.state"
|
||||
@@ -55,6 +44,13 @@ export class HaStateCondition extends LitElement implements ConditionElement {
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
||||
private _entityPicked(ev: PolymerChangedEvent<string>) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this.condition, entity_id: ev.detail.value },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,14 +1,5 @@
|
||||
import { Radio } from "@material/mwc-radio";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "../../../../../components/ha-formfield";
|
||||
import "../../../../../components/ha-radio";
|
||||
import { customElement, html, LitElement, property } from "lit-element";
|
||||
import { TimeCondition } from "../../../../../data/automation";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import {
|
||||
@@ -16,130 +7,38 @@ import {
|
||||
handleChangeEvent,
|
||||
} from "../ha-automation-condition-row";
|
||||
|
||||
const includeDomains = ["input_datetime"];
|
||||
|
||||
@customElement("ha-automation-condition-time")
|
||||
export class HaTimeCondition extends LitElement implements ConditionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public condition!: TimeCondition;
|
||||
|
||||
@internalProperty() private _inputModeBefore?: boolean;
|
||||
|
||||
@internalProperty() private _inputModeAfter?: boolean;
|
||||
|
||||
public static get defaultConfig() {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const { after, before } = this.condition;
|
||||
|
||||
const inputModeBefore =
|
||||
this._inputModeBefore ?? before?.startsWith("input_datetime.");
|
||||
const inputModeAfter =
|
||||
this._inputModeAfter ?? after?.startsWith("input_datetime.");
|
||||
|
||||
return html`
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_value"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_after"
|
||||
value="value"
|
||||
?checked=${!inputModeAfter}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_input"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_after"
|
||||
value="input"
|
||||
?checked=${inputModeAfter}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
${inputModeAfter
|
||||
? html`<ha-entity-picker
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.after"
|
||||
)}
|
||||
.includeDomains=${includeDomains}
|
||||
.name=${"after"}
|
||||
.value=${after?.startsWith("input_datetime.") ? after : ""}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-entity-picker>`
|
||||
: html`<paper-input
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.after"
|
||||
)}
|
||||
name="after"
|
||||
.value=${after?.startsWith("input_datetime.") ? "" : after}
|
||||
.value=${after}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>`}
|
||||
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_value"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_before"
|
||||
value="value"
|
||||
?checked=${!inputModeBefore}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
<ha-formfield
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.type_input"
|
||||
)}
|
||||
>
|
||||
<ha-radio
|
||||
@change=${this._handleModeChanged}
|
||||
name="mode_before"
|
||||
value="input"
|
||||
?checked=${inputModeBefore}
|
||||
></ha-radio>
|
||||
</ha-formfield>
|
||||
${inputModeBefore
|
||||
? html`<ha-entity-picker
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.before"
|
||||
)}
|
||||
.includeDomains=${includeDomains}
|
||||
.name=${"before"}
|
||||
.value=${before?.startsWith("input_datetime.") ? before : ""}
|
||||
@value-changed=${this._valueChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-entity-picker>`
|
||||
: html`<paper-input
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.type.time.before"
|
||||
)}
|
||||
name="before"
|
||||
.value=${before?.startsWith("input_datetime.") ? "" : before}
|
||||
.value=${before}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>`}
|
||||
></paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleModeChanged(ev: Event) {
|
||||
const target = ev.target as Radio;
|
||||
if (target.getAttribute("name") === "mode_after") {
|
||||
this._inputModeAfter = target.value === "input";
|
||||
} else {
|
||||
this._inputModeBefore = target.value === "input";
|
||||
}
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
handleChangeEvent(this, ev);
|
||||
}
|
||||
|
@@ -1,32 +1,28 @@
|
||||
import "@material/mwc-fab";
|
||||
import { mdiContentDuplicate, mdiContentSave, mdiDelete } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { PaperListboxElement } from "@polymer/paper-listbox";
|
||||
import "../../../components/ha-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
Condition,
|
||||
deleteAutomation,
|
||||
getAutomationEditorInitData,
|
||||
showAutomationEditor,
|
||||
Trigger,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
@@ -46,6 +42,9 @@ import { HaDeviceAction } from "./action/types/ha-automation-action-device_id";
|
||||
import "./condition/ha-automation-condition";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
import { PaperListboxElement } from "@polymer/paper-listbox";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
const MODES = ["single", "restart", "queued", "parallel"];
|
||||
const MODES_MAX = ["queued", "parallel"];
|
||||
@@ -54,7 +53,6 @@ declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"ui-mode-not-available": Error;
|
||||
duplicate: undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,25 +92,14 @@ export class HaAutomationEditor extends LitElement {
|
||||
${!this.automationId
|
||||
? ""
|
||||
: html`
|
||||
<mwc-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.duplicate_automation"
|
||||
)}"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-icon-button
|
||||
class="warning"
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.delete_automation"
|
||||
)}"
|
||||
icon="hass:delete"
|
||||
@click=${this._deleteConfirm}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
></ha-icon-button>
|
||||
`}
|
||||
${this._config
|
||||
? html`
|
||||
@@ -486,31 +473,6 @@ export class HaAutomationEditor extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private async _duplicate() {
|
||||
if (this._dirty) {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.automation.editor.unsaved_confirm"
|
||||
),
|
||||
confirmText: this.hass!.localize("ui.common.yes"),
|
||||
dismissText: this.hass!.localize("ui.common.no"),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Wait for dialog to complate closing
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
showAutomationEditor(this, {
|
||||
...this._config,
|
||||
id: undefined,
|
||||
alias: `${this._config?.alias} (${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.duplicate"
|
||||
)})`,
|
||||
});
|
||||
}
|
||||
|
||||
private async _deleteConfirm() {
|
||||
showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
|
@@ -25,7 +25,6 @@ import {
|
||||
showAutomationEditor,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@@ -36,9 +35,9 @@ import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@property() public route!: Route;
|
||||
|
||||
@@ -59,7 +58,7 @@ class HaAutomationPicker extends LitElement {
|
||||
toggle: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (_toggle, automation: any) =>
|
||||
template: (_toggle, automation) =>
|
||||
html`
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
@@ -92,11 +91,10 @@ class HaAutomationPicker extends LitElement {
|
||||
if (!narrow) {
|
||||
columns.execute = {
|
||||
title: "",
|
||||
template: (_info, automation: any) => html`
|
||||
template: (_info, automation) => html`
|
||||
<mwc-button
|
||||
.automation=${automation}
|
||||
@click=${(ev) => this._execute(ev)}
|
||||
.disabled=${UNAVAILABLE_STATES.includes(automation.state)}
|
||||
>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
|
@@ -1,27 +1,25 @@
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import { mdiDotsVertical } from "@mdi/js";
|
||||
import type { PaperListboxElement } from "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
} from "lit-element";
|
||||
import { dynamicElement } from "../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-button-menu";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import type { Trigger } from "../../../../data/automation";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "./types/ha-automation-trigger-device";
|
||||
import "./types/ha-automation-trigger-event";
|
||||
@@ -31,12 +29,14 @@ import "./types/ha-automation-trigger-mqtt";
|
||||
import "./types/ha-automation-trigger-numeric_state";
|
||||
import "./types/ha-automation-trigger-state";
|
||||
import "./types/ha-automation-trigger-sun";
|
||||
import "./types/ha-automation-trigger-tag";
|
||||
import "./types/ha-automation-trigger-template";
|
||||
import "./types/ha-automation-trigger-time";
|
||||
import "./types/ha-automation-trigger-time_pattern";
|
||||
import "./types/ha-automation-trigger-webhook";
|
||||
import "./types/ha-automation-trigger-zone";
|
||||
import "./types/ha-automation-trigger-tag";
|
||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
|
||||
const OPTIONS = [
|
||||
"device",
|
||||
@@ -113,12 +113,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.edit_yaml"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item>
|
||||
<mwc-list-item disabled>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
</mwc-list-item>
|
||||
<mwc-list-item class="warning">
|
||||
<mwc-list-item>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
@@ -183,7 +183,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
case 1:
|
||||
fireEvent(this, "duplicate");
|
||||
break;
|
||||
case 2:
|
||||
this._onDelete();
|
||||
|
@@ -27,7 +27,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
<ha-automation-trigger-row
|
||||
.index=${idx}
|
||||
.trigger=${trg}
|
||||
@duplicate=${this._duplicateTrigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger-row>
|
||||
@@ -69,14 +68,6 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
}
|
||||
|
||||
private _duplicateTrigger(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.triggers.concat(this.triggers[index]),
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-automation-trigger-row,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user