mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-30 20:56:36 +00:00
Merge pull request #10578 from home-assistant/dev
This commit is contained in:
commit
72b9f8636d
@ -165,6 +165,7 @@ module.exports.config = {
|
||||
cast({ isProdBuild, latestBuild }) {
|
||||
const entry = {
|
||||
launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"),
|
||||
media: path.resolve(paths.cast_dir, "src/media/entrypoint.ts"),
|
||||
};
|
||||
|
||||
if (latestBuild) {
|
||||
|
@ -154,6 +154,15 @@ gulp.task("gen-index-cast-dev", (done) => {
|
||||
contentReceiver
|
||||
);
|
||||
|
||||
const contentMedia = renderCastTemplate("media", {
|
||||
latestMediaJS: "/frontend_latest/media.js",
|
||||
es5MediaJS: "/frontend_es5/media.js",
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.cast_output_root, "media.html"),
|
||||
contentMedia
|
||||
);
|
||||
|
||||
const contentFAQ = renderCastTemplate("launcher-faq", {
|
||||
latestLauncherJS: "/frontend_latest/launcher.js",
|
||||
es5LauncherJS: "/frontend_es5/launcher.js",
|
||||
@ -192,6 +201,15 @@ gulp.task("gen-index-cast-prod", (done) => {
|
||||
contentReceiver
|
||||
);
|
||||
|
||||
const contentMedia = renderCastTemplate("media", {
|
||||
latestMediaJS: latestManifest["media.js"],
|
||||
es5MediaJS: es5Manifest["media.js"],
|
||||
});
|
||||
fs.outputFileSync(
|
||||
path.resolve(paths.cast_output_root, "media.html"),
|
||||
contentMedia
|
||||
);
|
||||
|
||||
const contentFAQ = renderCastTemplate("launcher-faq", {
|
||||
latestLauncherJS: latestManifest["launcher.js"],
|
||||
es5LauncherJS: es5Manifest["launcher.js"],
|
||||
|
45
cast/src/html/media.html.template
Normal file
45
cast/src/html/media.html.template
Normal file
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
|
||||
<style>
|
||||
body {
|
||||
--logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
|
||||
--logo-repeat: no-repeat;
|
||||
--playback-logo-image: url('https://www.home-assistant.io/images/home-assistant-logo.svg');
|
||||
--theme-hue: 200;
|
||||
--progress-color: #03a9f4;
|
||||
--splash-image: url('https://home-assistant.io/images/cast/splash.png');
|
||||
--splash-size: cover;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
||||
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
||||
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<%= renderTemplate('_js_base') %>
|
||||
|
||||
<cast-media-player></cast-media-player>
|
||||
|
||||
<script>
|
||||
import("<%= latestMediaJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5MediaJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5MediaJS %>");
|
||||
<% } %>
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
25
cast/src/media/entrypoint.ts
Normal file
25
cast/src/media/entrypoint.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { CastReceiverContext } from "chromecast-caf-receiver/cast.framework";
|
||||
|
||||
const castContext =
|
||||
cast.framework.CastContext.getInstance() as unknown as CastReceiverContext;
|
||||
|
||||
const playerManager = castContext.getPlayerManager();
|
||||
|
||||
playerManager.setMessageInterceptor(
|
||||
cast.framework.messages.MessageType.LOAD,
|
||||
(loadRequestData) => {
|
||||
const media = loadRequestData.media;
|
||||
// Special handling if it came from Google Assistant
|
||||
if (media.entity) {
|
||||
media.contentId = media.entity;
|
||||
media.streamType = cast.framework.messages.StreamType.LIVE;
|
||||
media.contentType = "application/vnd.apple.mpegurl";
|
||||
// @ts-ignore
|
||||
media.hlsVideoSegmentFormat =
|
||||
cast.framework.messages.HlsVideoSegmentFormat.FMP4;
|
||||
}
|
||||
return loadRequestData;
|
||||
}
|
||||
);
|
||||
|
||||
castContext.start();
|
@ -16,11 +16,9 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
||||
|
||||
@customElement("hassio-upload-backup")
|
||||
export class HassioUploadBackup extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@state() public value: string | null = null;
|
||||
|
||||
@ -43,20 +41,6 @@ export class HassioUploadBackup extends LitElement {
|
||||
private async _uploadFile(ev) {
|
||||
const file = ev.detail.files[0];
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
showAlertDialog(this, {
|
||||
title: "Backup file is too big",
|
||||
text: html`The maximum allowed filesize is 1GB.<br />
|
||||
<a
|
||||
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
|
||||
target="_blank"
|
||||
>Have a look here on how to restore it.</a
|
||||
>`,
|
||||
confirmText: "ok",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!["application/x-tar"].includes(file.type)) {
|
||||
showAlertDialog(this, {
|
||||
title: "Unsupported file format",
|
||||
|
@ -15,7 +15,7 @@ export class DialogHassioBackupUpload
|
||||
extends LitElement
|
||||
implements HassDialog<HassioBackupUploadDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _params?: HassioBackupUploadDialogParams;
|
||||
|
||||
@ -54,7 +54,7 @@ export class DialogHassioBackupUpload
|
||||
<ha-header-bar>
|
||||
<span slot="title"> Upload backup </span>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.label=${this.hass?.localize("common.close") || "close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
|
@ -35,7 +35,7 @@ class HassioBackupDialog
|
||||
extends LitElement
|
||||
implements HassDialog<HassioBackupDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@ -77,7 +77,7 @@ class HassioBackupDialog
|
||||
<ha-header-bar>
|
||||
<span slot="title">${this._backup.name}</span>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.close")}
|
||||
.label=${this.hass?.localize("common.close") || "close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
@ -114,7 +114,7 @@ class HassioBackupDialog
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this.hass.localize("common.menu")}
|
||||
.label=${this.hass!.localize("common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
@ -192,25 +192,23 @@ class HassioBackupDialog
|
||||
}
|
||||
|
||||
if (!this._dialogParams?.onboarding) {
|
||||
this.hass
|
||||
.callApi(
|
||||
"POST",
|
||||
this.hass!.callApi(
|
||||
"POST",
|
||||
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/restore/partial`,
|
||||
backupDetails
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
this.closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
}
|
||||
);
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass!.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/restore/partial`,
|
||||
backupDetails
|
||||
).then(
|
||||
() => {
|
||||
this.closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
fireEvent(this, "restoring");
|
||||
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
|
||||
@ -244,24 +242,22 @@ class HassioBackupDialog
|
||||
}
|
||||
|
||||
if (!this._dialogParams?.onboarding) {
|
||||
this.hass
|
||||
.callApi(
|
||||
"POST",
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/restore/full`,
|
||||
backupDetails
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
this.closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
}
|
||||
);
|
||||
this.hass!.callApi(
|
||||
"POST",
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass!.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/restore/full`,
|
||||
backupDetails
|
||||
).then(
|
||||
() => {
|
||||
this.closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
fireEvent(this, "restoring");
|
||||
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
|
||||
@ -283,36 +279,33 @@ class HassioBackupDialog
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass
|
||||
|
||||
.callApi(
|
||||
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
? `backups/${this._backup!.slug}`
|
||||
: `snapshots/${this._backup!.slug}/remove`
|
||||
}`
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
if (this._dialogParams!.onDelete) {
|
||||
this._dialogParams!.onDelete();
|
||||
}
|
||||
this.closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
this.hass!.callApi(
|
||||
atLeastVersion(this.hass!.config.version, 2021, 9) ? "DELETE" : "POST",
|
||||
`hassio/${
|
||||
atLeastVersion(this.hass!.config.version, 2021, 9)
|
||||
? `backups/${this._backup!.slug}`
|
||||
: `snapshots/${this._backup!.slug}/remove`
|
||||
}`
|
||||
).then(
|
||||
() => {
|
||||
if (this._dialogParams!.onDelete) {
|
||||
this._dialogParams!.onDelete();
|
||||
}
|
||||
);
|
||||
this.closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async _downloadClicked() {
|
||||
let signedPath: { path: string };
|
||||
try {
|
||||
signedPath = await getSignedPath(
|
||||
this.hass,
|
||||
this.hass!,
|
||||
`/api/hassio/${
|
||||
atLeastVersion(this.hass.config.version, 2021, 9)
|
||||
atLeastVersion(this.hass!.config.version, 2021, 9)
|
||||
? "backups"
|
||||
: "snapshots"
|
||||
}/${this._backup!.slug}/download`
|
||||
|
@ -1,12 +1,12 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDelete } from "@mdi/js";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-form/ha-form";
|
||||
import { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||
import "../../../../src/components/ha-icon-button";
|
||||
import "../../../../src/components/ha-settings-row";
|
||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
addHassioDockerRegistry,
|
||||
@ -19,22 +19,41 @@ import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { RegistriesDialogParams } from "./show-dialog-registries";
|
||||
|
||||
const SCHEMA = [
|
||||
{
|
||||
type: "string",
|
||||
name: "registry",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "username",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "password",
|
||||
required: true,
|
||||
format: "password",
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("dialog-hassio-registries")
|
||||
class HassioRegistriesDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public supervisor!: Supervisor;
|
||||
|
||||
@property({ attribute: false }) private _registries?: {
|
||||
@state() private _registries?: {
|
||||
registry: string;
|
||||
username: string;
|
||||
}[];
|
||||
|
||||
@state() private _registry?: string;
|
||||
|
||||
@state() private _username?: string;
|
||||
|
||||
@state() private _password?: string;
|
||||
@state() private _input: {
|
||||
registry?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
} = {};
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@ -47,6 +66,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
@closed=${this.closeDialog}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._addingRegistry
|
||||
@ -54,99 +74,77 @@ class HassioRegistriesDialog extends LitElement {
|
||||
: this.supervisor.localize("dialog.registries.title_manage")
|
||||
)}
|
||||
>
|
||||
<div class="form">
|
||||
${this._addingRegistry
|
||||
? html`
|
||||
<paper-input
|
||||
@value-changed=${this._inputChanged}
|
||||
class="flex-auto"
|
||||
name="registry"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.registry"
|
||||
)}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<paper-input
|
||||
@value-changed=${this._inputChanged}
|
||||
class="flex-auto"
|
||||
name="username"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.username"
|
||||
)}
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<paper-input
|
||||
@value-changed=${this._inputChanged}
|
||||
class="flex-auto"
|
||||
name="password"
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.password"
|
||||
)}
|
||||
type="password"
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
|
||||
${this._addingRegistry
|
||||
? html`
|
||||
<ha-form
|
||||
.data=${this._input}
|
||||
.schema=${SCHEMA}
|
||||
@value-changed=${this._valueChanged}
|
||||
.computeLabel=${this._computeLabel}
|
||||
></ha-form>
|
||||
<div class="action">
|
||||
<mwc-button
|
||||
?disabled=${Boolean(
|
||||
!this._registry || !this._username || !this._password
|
||||
!this._input.registry ||
|
||||
!this._input.username ||
|
||||
!this._input.password
|
||||
)}
|
||||
@click=${this._addNewRegistry}
|
||||
>
|
||||
${this.supervisor.localize("dialog.registries.add_registry")}
|
||||
</mwc-button>
|
||||
`
|
||||
: html`${this._registries?.length
|
||||
? this._registries.map(
|
||||
(entry) => html`
|
||||
<mwc-list-item class="option" hasMeta twoline>
|
||||
<span>${entry.registry}</span>
|
||||
<span slot="secondary"
|
||||
>${this.supervisor.localize(
|
||||
"dialog.registries.username"
|
||||
)}:
|
||||
${entry.username}</span
|
||||
>
|
||||
<ha-icon-button
|
||||
.entry=${entry}
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.remove"
|
||||
)}
|
||||
.path=${mdiDelete}
|
||||
slot="meta"
|
||||
@click=${this._removeRegistry}
|
||||
></ha-icon-button>
|
||||
</mwc-list-item>
|
||||
`
|
||||
)
|
||||
: html`
|
||||
<mwc-list-item>
|
||||
<span
|
||||
>${this.supervisor.localize(
|
||||
"dialog.registries.no_registries"
|
||||
)}</span
|
||||
>
|
||||
</mwc-list-item>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
: html`${this._registries?.length
|
||||
? this._registries.map(
|
||||
(entry) => html`
|
||||
<ha-settings-row class="registry">
|
||||
<span slot="heading"> ${entry.registry} </span>
|
||||
<span slot="description">
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.username"
|
||||
)}:
|
||||
${entry.username}
|
||||
</span>
|
||||
<ha-icon-button
|
||||
.entry=${entry}
|
||||
.label=${this.supervisor.localize(
|
||||
"dialog.registries.remove"
|
||||
)}
|
||||
.path=${mdiDelete}
|
||||
@click=${this._removeRegistry}
|
||||
></ha-icon-button>
|
||||
</ha-settings-row>
|
||||
`
|
||||
)
|
||||
: html`
|
||||
<ha-alert>
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.no_registries"
|
||||
)}
|
||||
</ha-alert>
|
||||
`}
|
||||
<div class="action">
|
||||
<mwc-button @click=${this._addRegistry}>
|
||||
${this.supervisor.localize(
|
||||
"dialog.registries.add_new_registry"
|
||||
)}
|
||||
</mwc-button> `}
|
||||
</div>
|
||||
</mwc-button>
|
||||
</div> `}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _inputChanged(ev: Event) {
|
||||
const target = ev.currentTarget as PaperInputElement;
|
||||
this[`_${target.name}`] = target.value;
|
||||
private _computeLabel = (schema: HaFormSchema) =>
|
||||
this.supervisor.localize(`dialog.registries.${schema.name}`) || schema.name;
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._input = ev.detail.value;
|
||||
}
|
||||
|
||||
public async showDialog(dialogParams: RegistriesDialogParams): Promise<void> {
|
||||
this._opened = true;
|
||||
this._input = {};
|
||||
this.supervisor = dialogParams.supervisor;
|
||||
await this._loadRegistries();
|
||||
await this.updateComplete;
|
||||
@ -155,6 +153,7 @@ class HassioRegistriesDialog extends LitElement {
|
||||
public closeDialog(): void {
|
||||
this._addingRegistry = false;
|
||||
this._opened = false;
|
||||
this._input = {};
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
@ -179,15 +178,16 @@ class HassioRegistriesDialog extends LitElement {
|
||||
|
||||
private async _addNewRegistry(): Promise<void> {
|
||||
const data = {};
|
||||
data[this._registry!] = {
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
data[this._input.registry!] = {
|
||||
username: this._input.username,
|
||||
password: this._input.password,
|
||||
};
|
||||
|
||||
try {
|
||||
await addHassioDockerRegistry(this.hass, data);
|
||||
await this._loadRegistries();
|
||||
this._addingRegistry = false;
|
||||
this._input = {};
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor.localize("dialog.registries.failed_to_add"),
|
||||
@ -215,32 +215,20 @@ class HassioRegistriesDialog extends LitElement {
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog.button-left {
|
||||
--justify-action-buttons: flex-start;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.option {
|
||||
.registry {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
.action {
|
||||
margin-top: 24px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
ha-icon-button {
|
||||
color: var(--error-color);
|
||||
margin: -10px;
|
||||
}
|
||||
mwc-list-item {
|
||||
cursor: default;
|
||||
}
|
||||
mwc-list-item span[slot="secondary"] {
|
||||
color: var(--secondary-text-color);
|
||||
margin-right: -10px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20211103.0",
|
||||
version="20211108.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/frontend",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -21,7 +21,11 @@ class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
|
||||
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>
|
||||
${this.authProviders.map(
|
||||
(provider) => html`
|
||||
<paper-item .auth_provider=${provider} @click=${this._handlePick}>
|
||||
<paper-item
|
||||
role="button"
|
||||
.auth_provider=${provider}
|
||||
@click=${this._handlePick}
|
||||
>
|
||||
<paper-item-body>${provider.name}</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
|
@ -76,7 +76,6 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
configurator: mdiCog,
|
||||
conversation: mdiTextToSpeech,
|
||||
counter: mdiCounter,
|
||||
device_tracker: mdiAccount,
|
||||
fan: mdiFan,
|
||||
google_assistant: mdiGoogleAssistant,
|
||||
group: mdiGoogleCirclesCommunities,
|
||||
@ -104,7 +103,6 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
siren: mdiBullhorn,
|
||||
simple_alarm: mdiBell,
|
||||
sun: mdiWhiteBalanceSunny,
|
||||
switch: mdiFlash,
|
||||
timer: mdiTimerOutline,
|
||||
updater: mdiCloudUpload,
|
||||
vacuum: mdiRobotVacuum,
|
||||
@ -145,6 +143,7 @@ export const FIXED_DEVICE_CLASS_ICONS = {
|
||||
|
||||
/** Domains that have a state card. */
|
||||
export const DOMAINS_WITH_CARD = [
|
||||
"button",
|
||||
"climate",
|
||||
"cover",
|
||||
"configurator",
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
mdiBrightness5,
|
||||
mdiBrightness7,
|
||||
mdiCheckboxMarkedCircle,
|
||||
mdiCheckNetworkOutline,
|
||||
mdiCloseNetworkOutline,
|
||||
mdiCheckCircle,
|
||||
mdiCropPortrait,
|
||||
mdiDoorClosed,
|
||||
@ -26,8 +28,6 @@ import {
|
||||
mdiPowerPlugOff,
|
||||
mdiRadioboxBlank,
|
||||
mdiRun,
|
||||
mdiServerNetwork,
|
||||
mdiServerNetworkOff,
|
||||
mdiSmoke,
|
||||
mdiSnowflake,
|
||||
mdiSquare,
|
||||
@ -55,7 +55,7 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
||||
case "cold":
|
||||
return is_off ? mdiThermometer : mdiSnowflake;
|
||||
case "connectivity":
|
||||
return is_off ? mdiServerNetworkOff : mdiServerNetwork;
|
||||
return is_off ? mdiCloseNetworkOutline : mdiCheckNetworkOutline;
|
||||
case "door":
|
||||
return is_off ? mdiDoorClosed : mdiDoorOpen;
|
||||
case "garage_door":
|
||||
|
@ -116,6 +116,14 @@ export const computeStateDisplay = (
|
||||
return formatNumber(compareState, locale);
|
||||
}
|
||||
|
||||
// state of button is a timestamp
|
||||
if (
|
||||
domain === "button" ||
|
||||
(domain === "sensor" && stateObj.attributes.device_class === "timestamp")
|
||||
) {
|
||||
return formatDateTime(new Date(compareState), locale);
|
||||
}
|
||||
|
||||
return (
|
||||
// Return device class translation
|
||||
(stateObj.attributes.device_class &&
|
||||
|
@ -1,6 +1,13 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiAccountArrowRight,
|
||||
mdiAirHumidifierOff,
|
||||
mdiAirHumidifier,
|
||||
mdiFlash,
|
||||
mdiBluetooth,
|
||||
mdiBluetoothConnect,
|
||||
mdiLanConnect,
|
||||
mdiLanDisconnect,
|
||||
mdiLockOpen,
|
||||
mdiLockAlert,
|
||||
mdiLockClock,
|
||||
@ -8,8 +15,12 @@ import {
|
||||
mdiCastConnected,
|
||||
mdiCast,
|
||||
mdiEmoticonDead,
|
||||
mdiPowerPlug,
|
||||
mdiPowerPlugOff,
|
||||
mdiSleep,
|
||||
mdiTimerSand,
|
||||
mdiToggleSwitch,
|
||||
mdiToggleSwitchOff,
|
||||
mdiZWave,
|
||||
mdiClock,
|
||||
mdiCalendar,
|
||||
@ -44,6 +55,17 @@ export const domainIcon = (
|
||||
case "cover":
|
||||
return coverIcon(compareState, stateObj);
|
||||
|
||||
case "device_tracker":
|
||||
if (stateObj?.attributes.source_type === "router") {
|
||||
return compareState === "home" ? mdiLanConnect : mdiLanDisconnect;
|
||||
}
|
||||
if (
|
||||
["bluetooth", "bluetooth_le"].includes(stateObj?.attributes.source_type)
|
||||
) {
|
||||
return compareState === "home" ? mdiBluetoothConnect : mdiBluetooth;
|
||||
}
|
||||
return compareState === "not_home" ? mdiAccountArrowRight : mdiAccount;
|
||||
|
||||
case "humidifier":
|
||||
return state && state === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
|
||||
|
||||
@ -63,6 +85,16 @@ export const domainIcon = (
|
||||
case "media_player":
|
||||
return compareState === "playing" ? mdiCastConnected : mdiCast;
|
||||
|
||||
case "switch":
|
||||
switch (stateObj?.attributes.device_class) {
|
||||
case "outlet":
|
||||
return state === "on" ? mdiPowerPlug : mdiPowerPlugOff;
|
||||
case "switch":
|
||||
return state === "on" ? mdiToggleSwitch : mdiToggleSwitchOff;
|
||||
default:
|
||||
return mdiFlash;
|
||||
}
|
||||
|
||||
case "zwave":
|
||||
switch (compareState) {
|
||||
case "dead":
|
||||
|
@ -32,6 +32,7 @@ if (__BUILD__ === "latest") {
|
||||
}
|
||||
if (shouldPolyfillDateTime()) {
|
||||
polyfills.push(import("@formatjs/intl-datetimeformat/polyfill"));
|
||||
polyfills.push(import("@formatjs/intl-datetimeformat/add-all-tz"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ declare global {
|
||||
|
||||
@customElement("ha-file-upload")
|
||||
export class HaFileUpload extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property() public accept!: string;
|
||||
|
||||
@ -88,7 +88,8 @@ export class HaFileUpload extends LitElement {
|
||||
<ha-icon-button
|
||||
slot="suffix"
|
||||
@click=${this._clearValue}
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.label=${this.hass?.localize("ui.common.close") ||
|
||||
"close"}
|
||||
.path=${mdiClose}
|
||||
></ha-icon-button>
|
||||
`
|
||||
|
@ -47,12 +47,19 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const source = ev.target as TextField;
|
||||
const rawValue = source.value;
|
||||
const rawValue = source.value.replace(",", ".");
|
||||
|
||||
let value: number | undefined;
|
||||
|
||||
if (rawValue.endsWith(".")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rawValue !== "") {
|
||||
value = parseFloat(rawValue);
|
||||
if (isNaN(value)) {
|
||||
value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Detect anything changed
|
||||
@ -61,7 +68,6 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
||||
const newRawValue = value === undefined ? "" : String(value);
|
||||
if (source.value !== newRawValue) {
|
||||
source.value = newRawValue;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -60,6 +60,8 @@ export class HaIconPicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public invalid = false;
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@query("vaadin-combo-box-light", true) private comboBox!: HTMLElement;
|
||||
@ -86,6 +88,8 @@ export class HaIconPicker extends LitElement {
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
.errorMessage=${this.errorMessage}
|
||||
.invalid=${this.invalid}
|
||||
>
|
||||
${this._value || this.placeholder
|
||||
? html`
|
||||
|
@ -502,6 +502,9 @@ export class HaTargetPicker extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _entityRegMeetsFilter(entity: EntityRegistryEntry): boolean {
|
||||
if (entity.entity_category) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
this.includeDomains &&
|
||||
!this.includeDomains.includes(computeDomain(entity.entity_id))
|
||||
|
@ -79,7 +79,7 @@ export const fetchHassioBackups = async (
|
||||
};
|
||||
|
||||
export const fetchHassioBackupInfo = async (
|
||||
hass: HomeAssistant,
|
||||
hass: HomeAssistant | undefined,
|
||||
backup: string
|
||||
): Promise<HassioBackupDetail> => {
|
||||
if (hass) {
|
||||
@ -202,7 +202,7 @@ export const createHassioPartialBackup = async (
|
||||
};
|
||||
|
||||
export const uploadBackup = async (
|
||||
hass: HomeAssistant,
|
||||
hass: HomeAssistant | undefined,
|
||||
file: File
|
||||
): Promise<HassioResponse<HassioBackup>> => {
|
||||
const fd = new FormData();
|
||||
|
@ -12,7 +12,7 @@ export interface Zone {
|
||||
}
|
||||
|
||||
export interface ZoneMutableParams {
|
||||
icon: string;
|
||||
icon?: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
name: string;
|
||||
|
@ -45,7 +45,7 @@ class StepFlowPickFlow extends LitElement {
|
||||
domain: flow.handler,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
@ -102,7 +102,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
domain: handler.slug,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
@ -33,7 +33,7 @@
|
||||
<body>
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<img src="/static/icons/favicon-192x192.png" height="52" />
|
||||
<img src="/static/icons/favicon-192x192.png" height="52" alt="" />
|
||||
Home Assistant
|
||||
</div>
|
||||
<ha-authorize><p>Initializing</p></ha-authorize>
|
||||
@ -70,4 +70,4 @@
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -64,7 +64,7 @@
|
||||
<body id="particles">
|
||||
<div class="content">
|
||||
<div class="header">
|
||||
<img src="/static/icons/favicon-192x192.png" height="52" width="52" />
|
||||
<img src="/static/icons/favicon-192x192.png" height="52" width="52" alt="" />
|
||||
Home Assistant
|
||||
</div>
|
||||
|
||||
|
@ -81,7 +81,7 @@ class OnboardingIntegrations extends LitElement {
|
||||
.domain=${entry.domain}
|
||||
.title=${title}
|
||||
.badgeIcon=${mdiCheck}
|
||||
.darkOptimizedIcon=${this.hass.selectedTheme?.dark}
|
||||
.darkOptimizedIcon=${this.hass.themes?.darkMode}
|
||||
></integration-badge>
|
||||
`,
|
||||
];
|
||||
@ -98,7 +98,7 @@ class OnboardingIntegrations extends LitElement {
|
||||
clickable
|
||||
.domain=${flow.handler}
|
||||
.title=${title}
|
||||
.darkOptimizedIcon=${this.hass.selectedTheme?.dark}
|
||||
.darkOptimizedIcon=${this.hass.themes?.darkMode}
|
||||
></integration-badge>
|
||||
</button>
|
||||
`,
|
||||
|
@ -1,19 +1,20 @@
|
||||
import { mdiPencil, mdiPlusCircle, mdiOpenInNew } from "@mdi/js";
|
||||
import { mdiOpenInNew, mdiPencil, mdiPlusCircle } from "@mdi/js";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { stringCompare } from "../../../common/string/compare";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { groupBy } from "../../../common/util/group-by";
|
||||
import "../../../components/entity/ha-battery-icon";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import {
|
||||
@ -52,7 +53,6 @@ import {
|
||||
loadDeviceRegistryDetailDialog,
|
||||
showDeviceRegistryDetailDialog,
|
||||
} from "./device-registry-detail/show-dialog-device-registry-detail";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
@ -293,7 +293,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
src=${brandsUrl({
|
||||
domain: integrations[0],
|
||||
type: "logo",
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
@load=${this._onImageLoad}
|
||||
@ -407,41 +407,45 @@ export class HaConfigDevicePage extends LitElement {
|
||||
></ha-icon-button>
|
||||
</h1>
|
||||
${this._related?.automation?.length
|
||||
? this._related.automation.map((automation) => {
|
||||
const entityState = this.hass.states[automation];
|
||||
return entityState
|
||||
? html`
|
||||
<div>
|
||||
<a
|
||||
href=${ifDefined(
|
||||
entityState.attributes.id
|
||||
? `/config/automation/edit/${entityState.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-item
|
||||
.automation=${entityState}
|
||||
.disabled=${!entityState.attributes.id}
|
||||
? html`
|
||||
<div class="items">
|
||||
${this._related.automation.map((automation) => {
|
||||
const entityState =
|
||||
this.hass.states[automation];
|
||||
return entityState
|
||||
? html`<div>
|
||||
<a
|
||||
href=${ifDefined(
|
||||
entityState.attributes.id
|
||||
? `/config/automation/edit/${entityState.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-item-body>
|
||||
${computeStateName(entityState)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
${!entityState.attributes.id
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: "";
|
||||
})
|
||||
<paper-item
|
||||
.automation=${entityState}
|
||||
.disabled=${!entityState.attributes
|
||||
.id}
|
||||
>
|
||||
<paper-item-body>
|
||||
${computeStateName(entityState)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
${!entityState.attributes.id
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
</div> `
|
||||
: "";
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
@ -479,43 +483,49 @@ export class HaConfigDevicePage extends LitElement {
|
||||
.path=${mdiPlusCircle}
|
||||
></ha-icon-button>
|
||||
</h1>
|
||||
|
||||
${this._related?.scene?.length
|
||||
? this._related.scene.map((scene) => {
|
||||
const entityState = this.hass.states[scene];
|
||||
return entityState
|
||||
? html`
|
||||
<div>
|
||||
<a
|
||||
href=${ifDefined(
|
||||
entityState.attributes.id
|
||||
? `/config/scene/edit/${entityState.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-item
|
||||
.scene=${entityState}
|
||||
.disabled=${!entityState.attributes.id}
|
||||
>
|
||||
<paper-item-body>
|
||||
${computeStateName(entityState)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
${!entityState.attributes.id
|
||||
? html`
|
||||
<paper-tooltip animation-delay="0">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: "";
|
||||
})
|
||||
? html`
|
||||
<div class="items">
|
||||
${this._related.scene.map((scene) => {
|
||||
const entityState = this.hass.states[scene];
|
||||
return entityState
|
||||
? html`
|
||||
<div>
|
||||
<a
|
||||
href=${ifDefined(
|
||||
entityState.attributes.id
|
||||
? `/config/scene/edit/${entityState.attributes.id}`
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<paper-item
|
||||
.scene=${entityState}
|
||||
.disabled=${!entityState.attributes
|
||||
.id}
|
||||
>
|
||||
<paper-item-body>
|
||||
${computeStateName(entityState)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
${!entityState.attributes.id
|
||||
? html`
|
||||
<paper-tooltip
|
||||
animation-delay="0"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.cant_edit"
|
||||
)}
|
||||
</paper-tooltip>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: "";
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
@ -553,23 +563,27 @@ export class HaConfigDevicePage extends LitElement {
|
||||
></ha-icon-button>
|
||||
</h1>
|
||||
${this._related?.script?.length
|
||||
? this._related.script.map((script) => {
|
||||
const entityState = this.hass.states[script];
|
||||
return entityState
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/script/edit/${entityState.entity_id}`}
|
||||
>
|
||||
<paper-item .script=${script}>
|
||||
<paper-item-body>
|
||||
${computeStateName(entityState)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
`
|
||||
: "";
|
||||
})
|
||||
? html`
|
||||
<div class="items">
|
||||
${this._related.script.map((script) => {
|
||||
const entityState = this.hass.states[script];
|
||||
return entityState
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/script/edit/${entityState.entity_id}`}
|
||||
>
|
||||
<paper-item .script=${script}>
|
||||
<paper-item-body>
|
||||
${computeStateName(entityState)}
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
`
|
||||
: "";
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div class="card-content">
|
||||
${this.hass.localize(
|
||||
@ -869,6 +883,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.card-header ha-icon-button {
|
||||
@ -978,6 +993,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
ha-svg-icon[slot="trailingIcon"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.items {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -202,7 +202,7 @@ export class EnergyGridSettings extends LitElement {
|
||||
src=${brandsUrl({
|
||||
domain: "co2signal",
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
/>
|
||||
<span class="content">${entry.title}</span>
|
||||
@ -223,7 +223,7 @@ export class EnergyGridSettings extends LitElement {
|
||||
src=${brandsUrl({
|
||||
domain: "co2signal",
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
/>
|
||||
<mwc-button @click=${this._addCO2Sensor}>
|
||||
|
@ -136,7 +136,7 @@ export class DialogEnergySolarSettings
|
||||
src=${brandsUrl({
|
||||
domain: entry.domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
/>${entry.title}
|
||||
</div>`}
|
||||
|
@ -98,7 +98,7 @@ class IntegrationsCard extends LitElement {
|
||||
domain: domain,
|
||||
type: "icon",
|
||||
useFallback: true,
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
@ -84,7 +84,7 @@ export class HaIntegrationHeader extends LitElement {
|
||||
src=${brandsUrl({
|
||||
domain: this.domain,
|
||||
type: "icon",
|
||||
darkOptimized: this.hass.selectedTheme?.dark,
|
||||
darkOptimized: this.hass.themes?.darkMode,
|
||||
})}
|
||||
referrerpolicy="no-referrer"
|
||||
@error=${this._onImageError}
|
||||
|
@ -8,6 +8,7 @@ import { addDistanceToCoord } from "../../../common/location/add_distance_to_coo
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-icon-picker";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
import type { MarkerLocation } from "../../../components/map/ha-locations-editor";
|
||||
@ -77,14 +78,18 @@ class DialogZoneDetail extends LitElement {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
const nameValid = this._name.trim() === "";
|
||||
const iconValid = !this._icon.trim().includes(":");
|
||||
const latValid = String(this._latitude) === "";
|
||||
const lngValid = String(this._longitude) === "";
|
||||
const radiusValid = String(this._radius) === "";
|
||||
const nameInvalid = this._name.trim() === "";
|
||||
const iconInvalid = Boolean(this._icon && !this._icon.trim().includes(":"));
|
||||
const latInvalid = String(this._latitude) === "";
|
||||
const lngInvalid = String(this._longitude) === "";
|
||||
const radiusInvalid = String(this._radius) === "";
|
||||
|
||||
const valid =
|
||||
!nameValid && !iconValid && !latValid && !lngValid && !radiusValid;
|
||||
!nameInvalid &&
|
||||
!iconInvalid &&
|
||||
!latInvalid &&
|
||||
!lngInvalid &&
|
||||
!radiusInvalid;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
@ -114,7 +119,7 @@ class DialogZoneDetail extends LitElement {
|
||||
required
|
||||
auto-validate
|
||||
></paper-input>
|
||||
<paper-input
|
||||
<ha-icon-picker
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
@ -122,8 +127,8 @@ class DialogZoneDetail extends LitElement {
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.icon_error_msg"
|
||||
)}
|
||||
.invalid=${iconValid}
|
||||
></paper-input>
|
||||
.invalid=${iconInvalid}
|
||||
></ha-icon-picker>
|
||||
<ha-locations-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
@ -148,7 +153,7 @@ class DialogZoneDetail extends LitElement {
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
.invalid=${latValid}
|
||||
.invalid=${latInvalid}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._longitude}
|
||||
@ -160,7 +165,7 @@ class DialogZoneDetail extends LitElement {
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
.invalid=${lngValid}
|
||||
.invalid=${lngInvalid}
|
||||
></paper-input>
|
||||
</div>
|
||||
<paper-input
|
||||
@ -173,7 +178,7 @@ class DialogZoneDetail extends LitElement {
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.zone.detail.required_error_msg"
|
||||
)}
|
||||
.invalid=${radiusValid}
|
||||
.invalid=${radiusInvalid}
|
||||
></paper-input>
|
||||
<p>
|
||||
${this.hass!.localize("ui.panel.config.zone.detail.passive_note")}
|
||||
@ -268,12 +273,14 @@ class DialogZoneDetail extends LitElement {
|
||||
try {
|
||||
const values: ZoneMutableParams = {
|
||||
name: this._name.trim(),
|
||||
icon: this._icon.trim(),
|
||||
latitude: this._latitude,
|
||||
longitude: this._longitude,
|
||||
passive: this._passive,
|
||||
radius: this._radius,
|
||||
};
|
||||
if (this._icon) {
|
||||
values.icon = this._icon.trim();
|
||||
}
|
||||
if (this._params!.entry) {
|
||||
await this._params!.updateEntry!(values);
|
||||
} else {
|
||||
|
@ -231,7 +231,6 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
@ -261,7 +260,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
#states > div > * {
|
||||
overflow: hidden;
|
||||
overflow: clip visible;
|
||||
}
|
||||
|
||||
#states > div {
|
||||
|
@ -241,7 +241,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
);
|
||||
const lastDate = this._lastLogbookDate || hoursToShowDate;
|
||||
const now = new Date();
|
||||
let newEntries;
|
||||
let newEntries: LogbookEntry[];
|
||||
|
||||
try {
|
||||
[newEntries] = await Promise.all([
|
||||
@ -256,6 +256,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
]);
|
||||
} catch (err: any) {
|
||||
this._error = err.message;
|
||||
return;
|
||||
}
|
||||
|
||||
const logbookEntries = this._logbookEntries
|
||||
|
@ -24,6 +24,7 @@ const ALWAYS_LOADED_TYPES = new Set([
|
||||
"call-service",
|
||||
]);
|
||||
const LAZY_LOAD_TYPES = {
|
||||
"button-entity": () => import("../entity-rows/hui-button-entity-row"),
|
||||
"climate-entity": () => import("../entity-rows/hui-climate-entity-row"),
|
||||
"cover-entity": () => import("../entity-rows/hui-cover-entity-row"),
|
||||
"group-entity": () => import("../entity-rows/hui-group-entity-row"),
|
||||
@ -53,6 +54,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
_domain_not_found: "text",
|
||||
alert: "toggle",
|
||||
automation: "toggle",
|
||||
button: "button",
|
||||
climate: "climate",
|
||||
cover: "cover",
|
||||
fan: "toggle",
|
||||
|
82
src/panels/lovelace/entity-rows/hui-button-entity-row.ts
Normal file
82
src/panels/lovelace/entity-rows/hui-button-entity-row.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { ActionRowConfig, LovelaceRow } from "./types";
|
||||
|
||||
@customElement("hui-button-entity-row")
|
||||
class HuiButtonEntityRow extends LitElement implements LovelaceRow {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: ActionRowConfig;
|
||||
|
||||
public setConfig(config: ActionRowConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config || !this.hass) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity];
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning>
|
||||
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
||||
</hui-warning>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||
<mwc-button
|
||||
@click=${this._pressButton}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
${this.hass.localize("ui.card.button.press")}
|
||||
</mwc-button>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
mwc-button:last-child {
|
||||
margin-right: -0.57em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _pressButton(ev): void {
|
||||
ev.stopPropagation();
|
||||
this.hass.callService("button", "press", {
|
||||
entity_id: this._config!.entity,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-button-entity-row": HuiButtonEntityRow;
|
||||
}
|
||||
}
|
54
src/state-summary/state-card-button.ts
Normal file
54
src/state-summary/state-card-button.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import "@material/mwc-button";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../components/entity/ha-entity-toggle";
|
||||
import "../components/entity/state-info";
|
||||
import { UNAVAILABLE } from "../data/entity";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("state-card-button")
|
||||
export class StateCardButton extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj!: HassEntity;
|
||||
|
||||
@property({ type: Boolean }) public inDialog = false;
|
||||
|
||||
protected render() {
|
||||
const stateObj = this.stateObj;
|
||||
return html`
|
||||
<div class="horizontal justified layout">
|
||||
<state-info
|
||||
.hass=${this.hass}
|
||||
.stateObj=${stateObj}
|
||||
.inDialog=${this.inDialog}
|
||||
></state-info>
|
||||
<mwc-button
|
||||
@click=${this._pressButton}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
>
|
||||
${this.hass.localize("ui.card.button.press")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _pressButton(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this.hass.callService("button", "press", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return haStyle;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"state-card-button": StateCardButton;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import dynamicContentUpdater from "../common/dom/dynamic_content_updater";
|
||||
import { stateCardType } from "../common/entity/state_card_type";
|
||||
import "./state-card-button";
|
||||
import "./state-card-climate";
|
||||
import "./state-card-configurator";
|
||||
import "./state-card-cover";
|
||||
|
@ -24,6 +24,7 @@ import { getState } from "../util/ha-pref-storage";
|
||||
import hassCallApi from "../util/hass-call-api";
|
||||
import { getLocalLanguage } from "../util/common-translation";
|
||||
import { HassBaseEl } from "./hass-base-mixin";
|
||||
import { polyfillsLoaded } from "../common/translations/localize";
|
||||
|
||||
export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
superClass: T
|
||||
@ -180,12 +181,18 @@ export const connectionMixin = <T extends Constructor<HassBaseEl>>(
|
||||
|
||||
subscribeEntities(conn, (states) => this._updateHass({ states }));
|
||||
subscribeConfig(conn, (config) => {
|
||||
if (
|
||||
this.hass?.config?.time_zone !== config.time_zone &&
|
||||
"__setDefaultTimeZone" in Intl.DateTimeFormat
|
||||
) {
|
||||
// @ts-ignore
|
||||
Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone);
|
||||
if (this.hass?.config?.time_zone !== config.time_zone) {
|
||||
if (__BUILD__ === "latest" && polyfillsLoaded) {
|
||||
polyfillsLoaded.then(() => {
|
||||
if ("__setDefaultTimeZone" in Intl.DateTimeFormat) {
|
||||
// @ts-ignore
|
||||
Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone);
|
||||
}
|
||||
});
|
||||
} else if ("__setDefaultTimeZone" in Intl.DateTimeFormat) {
|
||||
// @ts-ignore
|
||||
Intl.DateTimeFormat.__setDefaultTimeZone(config.time_zone);
|
||||
}
|
||||
}
|
||||
this._updateHass({ config });
|
||||
});
|
||||
|
@ -120,6 +120,9 @@
|
||||
"last_triggered": "Last triggered",
|
||||
"trigger": "Run Actions"
|
||||
},
|
||||
"button": {
|
||||
"press": "Press"
|
||||
},
|
||||
"camera": {
|
||||
"not_available": "Image not available"
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user