mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-26 02:36:37 +00:00
20221004.0 (#13987)
This commit is contained in:
commit
4dce9404a4
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20221003.0"
|
version = "20221004.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -7,7 +7,9 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, state, query } from "lit/decorators";
|
||||||
|
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||||
import { handleWebRtcOffer, WebRtcAnswer } from "../data/camera";
|
import { handleWebRtcOffer, WebRtcAnswer } from "../data/camera";
|
||||||
|
import { fetchWebRtcSettings } from "../data/rtsp_to_webrtc";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
|
|
||||||
@ -86,7 +88,8 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
private async _startWebRtc(): Promise<void> {
|
private async _startWebRtc(): Promise<void> {
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
|
|
||||||
const peerConnection = new RTCPeerConnection();
|
const configuration = await this._fetchPeerConfiguration();
|
||||||
|
const peerConnection = new RTCPeerConnection(configuration);
|
||||||
// Some cameras (such as nest) require a data channel to establish a stream
|
// Some cameras (such as nest) require a data channel to establish a stream
|
||||||
// however, not used by any integrations.
|
// however, not used by any integrations.
|
||||||
peerConnection.createDataChannel("dataSendChannel");
|
peerConnection.createDataChannel("dataSendChannel");
|
||||||
@ -102,12 +105,25 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
);
|
);
|
||||||
await peerConnection.setLocalDescription(offer);
|
await peerConnection.setLocalDescription(offer);
|
||||||
|
|
||||||
|
let candidates = ""; // Build an Offer SDP string with ice candidates
|
||||||
|
const iceResolver = new Promise<void>((resolve) => {
|
||||||
|
peerConnection.addEventListener("icecandidate", async (event) => {
|
||||||
|
if (!event.candidate) {
|
||||||
|
resolve(); // Gathering complete
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
candidates += `a=${event.candidate.candidate}\r\n`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await iceResolver;
|
||||||
|
const offer_sdp = offer.sdp! + candidates;
|
||||||
|
|
||||||
let webRtcAnswer: WebRtcAnswer;
|
let webRtcAnswer: WebRtcAnswer;
|
||||||
try {
|
try {
|
||||||
webRtcAnswer = await handleWebRtcOffer(
|
webRtcAnswer = await handleWebRtcOffer(
|
||||||
this.hass,
|
this.hass,
|
||||||
this.entityid,
|
this.entityid,
|
||||||
offer.sdp!
|
offer_sdp
|
||||||
);
|
);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._error = "Failed to start WebRTC stream: " + err.message;
|
this._error = "Failed to start WebRTC stream: " + err.message;
|
||||||
@ -138,6 +154,23 @@ class HaWebRtcPlayer extends LitElement {
|
|||||||
this._peerConnection = peerConnection;
|
this._peerConnection = peerConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _fetchPeerConfiguration(): Promise<RTCConfiguration> {
|
||||||
|
if (!isComponentLoaded(this.hass!, "rtsp_to_webrtc")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const settings = await fetchWebRtcSettings(this.hass!);
|
||||||
|
if (!settings || !settings.stun_server) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
iceServers: [
|
||||||
|
{
|
||||||
|
urls: [`stun:${settings.stun_server!}`],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private _cleanUp() {
|
private _cleanUp() {
|
||||||
if (this._remoteStream) {
|
if (this._remoteStream) {
|
||||||
this._remoteStream.getTracks().forEach((track) => {
|
this._remoteStream.getTracks().forEach((track) => {
|
||||||
|
10
src/data/rtsp_to_webrtc.ts
Normal file
10
src/data/rtsp_to_webrtc.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
|
export interface WebRtcSettings {
|
||||||
|
stun_server?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fetchWebRtcSettings = async (hass: HomeAssistant) =>
|
||||||
|
hass.callWS<WebRtcSettings>({
|
||||||
|
type: "rtsp_to_webrtc/get_settings",
|
||||||
|
});
|
@ -306,14 +306,19 @@ export interface ZWaveJSNodeStatusUpdatedMessage {
|
|||||||
|
|
||||||
export interface ZWaveJSNodeFirmwareUpdateProgressMessage {
|
export interface ZWaveJSNodeFirmwareUpdateProgressMessage {
|
||||||
event: "firmware update progress";
|
event: "firmware update progress";
|
||||||
|
current_file: number;
|
||||||
|
total_files: number;
|
||||||
sent_fragments: number;
|
sent_fragments: number;
|
||||||
total_fragments: number;
|
total_fragments: number;
|
||||||
|
progress: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ZWaveJSNodeFirmwareUpdateFinishedMessage {
|
export interface ZWaveJSNodeFirmwareUpdateFinishedMessage {
|
||||||
event: "firmware update finished";
|
event: "firmware update finished";
|
||||||
status: FirmwareUpdateStatus;
|
status: FirmwareUpdateStatus;
|
||||||
wait_time: number;
|
success: boolean;
|
||||||
|
wait_time?: number;
|
||||||
|
reinterview: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ZWaveJSNodeFirmwareUpdateCapabilities =
|
export type ZWaveJSNodeFirmwareUpdateCapabilities =
|
||||||
|
@ -639,21 +639,6 @@ class HaConfigAreaPage extends SubscribeMixin(LitElement) {
|
|||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
css`
|
css`
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-family: var(--paper-font-headline_-_font-family);
|
|
||||||
-webkit-font-smoothing: var(
|
|
||||||
--paper-font-headline_-_-webkit-font-smoothing
|
|
||||||
);
|
|
||||||
font-size: var(--paper-font-headline_-_font-size);
|
|
||||||
font-weight: var(--paper-font-headline_-_font-weight);
|
|
||||||
letter-spacing: var(--paper-font-headline_-_letter-spacing);
|
|
||||||
line-height: var(--paper-font-headline_-_line-height);
|
|
||||||
opacity: var(--dark-primary-opacity);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
|
@ -14,6 +14,7 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
|||||||
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { differenceInDays } from "date-fns/esm";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||||
import { relativeTime } from "../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
@ -50,8 +51,6 @@ import { documentationUrl } from "../../../util/documentation-url";
|
|||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
import { showNewAutomationDialog } from "./show-dialog-new-automation";
|
||||||
|
|
||||||
const DAY_IN_MILLISECONDS = 86400000;
|
|
||||||
|
|
||||||
@customElement("ha-automation-picker")
|
@customElement("ha-automation-picker")
|
||||||
class HaAutomationPicker extends LitElement {
|
class HaAutomationPicker extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -109,16 +108,13 @@ class HaAutomationPicker extends LitElement {
|
|||||||
? (name, automation: any) => {
|
? (name, automation: any) => {
|
||||||
const date = new Date(automation.attributes.last_triggered);
|
const date = new Date(automation.attributes.last_triggered);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
const dayDifference = differenceInDays(now, date);
|
||||||
const diff = now.getTime() - date.getTime();
|
|
||||||
const dayDiff = diff / DAY_IN_MILLISECONDS;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${name}
|
${name}
|
||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
${this.hass.localize("ui.card.automation.last_triggered")}:
|
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||||
${automation.attributes.last_triggered
|
${automation.attributes.last_triggered
|
||||||
? dayDiff > 3
|
? dayDifference > 3
|
||||||
? formatShortDateTime(date, this.hass.locale)
|
? formatShortDateTime(date, this.hass.locale)
|
||||||
: relativeTime(date, this.hass.locale)
|
: relativeTime(date, this.hass.locale)
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
: this.hass.localize("ui.components.relative_time.never")}
|
||||||
@ -136,13 +132,10 @@ class HaAutomationPicker extends LitElement {
|
|||||||
template: (last_triggered) => {
|
template: (last_triggered) => {
|
||||||
const date = new Date(last_triggered);
|
const date = new Date(last_triggered);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
const dayDifference = differenceInDays(now, date);
|
||||||
const diff = now.getTime() - date.getTime();
|
|
||||||
const dayDiff = diff / DAY_IN_MILLISECONDS;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${last_triggered
|
${last_triggered
|
||||||
? dayDiff > 3
|
? dayDifference > 3
|
||||||
? formatShortDateTime(date, this.hass.locale)
|
? formatShortDateTime(date, this.hass.locale)
|
||||||
: relativeTime(date, this.hass.locale)
|
: relativeTime(date, this.hass.locale)
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
: this.hass.localize("ui.components.relative_time.never")}
|
||||||
|
@ -187,12 +187,7 @@ class AddIntegrationDialog extends LitElement {
|
|||||||
|
|
||||||
for (const [domain, domainBrands] of Object.entries(sb)) {
|
for (const [domain, domainBrands] of Object.entries(sb)) {
|
||||||
const integration = this._findIntegration(domain);
|
const integration = this._findIntegration(domain);
|
||||||
if (
|
if (!integration) {
|
||||||
!integration ||
|
|
||||||
(!integration.config_flow &&
|
|
||||||
!integration.iot_standards &&
|
|
||||||
!integration.integrations)
|
|
||||||
) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (const [slug, name] of Object.entries(domainBrands)) {
|
for (const [slug, name] of Object.entries(domainBrands)) {
|
||||||
|
@ -36,7 +36,9 @@ class HaDomainIntegrations extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`<mwc-list>
|
return html`<mwc-list>
|
||||||
${this.flowsInProgress?.length
|
${this.flowsInProgress?.length
|
||||||
? html`<h3>We discovered the following:</h3>
|
? html`<h3>
|
||||||
|
${this.hass.localize("ui.panel.config.integrations.discovered")}
|
||||||
|
</h3>
|
||||||
${this.flowsInProgress.map(
|
${this.flowsInProgress.map(
|
||||||
(flow) => html`<mwc-list-item
|
(flow) => html`<mwc-list-item
|
||||||
graphic="medium"
|
graphic="medium"
|
||||||
@ -60,36 +62,46 @@ class HaDomainIntegrations extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
</mwc-list-item>`
|
</mwc-list-item>`
|
||||||
)}`
|
)}
|
||||||
|
<li divider role="separator"></li>
|
||||||
|
${this.integration?.integrations
|
||||||
|
? html`<h3>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.available_integrations"
|
||||||
|
)}
|
||||||
|
</h3>`
|
||||||
|
: ""}`
|
||||||
: ""}
|
: ""}
|
||||||
${this.integration?.iot_standards
|
${this.integration?.iot_standards
|
||||||
? this.integration.iot_standards.map((standard) => {
|
? this.integration.iot_standards
|
||||||
const domain: string = standardToDomain[standard] || standard;
|
.filter((standard) => standard in standardToDomain)
|
||||||
return html`<mwc-list-item
|
.map((standard) => {
|
||||||
graphic="medium"
|
const domain: string = standardToDomain[standard];
|
||||||
.domain=${domain}
|
return html`<mwc-list-item
|
||||||
@click=${this._standardPicked}
|
graphic="medium"
|
||||||
hasMeta
|
.domain=${domain}
|
||||||
>
|
@click=${this._standardPicked}
|
||||||
<img
|
hasMeta
|
||||||
slot="graphic"
|
|
||||||
loading="lazy"
|
|
||||||
src=${brandsUrl({
|
|
||||||
domain,
|
|
||||||
type: "icon",
|
|
||||||
useFallback: true,
|
|
||||||
darkOptimized: this.hass.themes?.darkMode,
|
|
||||||
})}
|
|
||||||
referrerpolicy="no-referrer"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
>${this.hass.localize(
|
|
||||||
`ui.panel.config.integrations.add_${domain}_device`
|
|
||||||
)}</span
|
|
||||||
>
|
>
|
||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
<img
|
||||||
</mwc-list-item>`;
|
slot="graphic"
|
||||||
})
|
loading="lazy"
|
||||||
|
src=${brandsUrl({
|
||||||
|
domain,
|
||||||
|
type: "icon",
|
||||||
|
useFallback: true,
|
||||||
|
darkOptimized: this.hass.themes?.darkMode,
|
||||||
|
})}
|
||||||
|
referrerpolicy="no-referrer"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
>${this.hass.localize(
|
||||||
|
`ui.panel.config.integrations.add_${domain}_device`
|
||||||
|
)}</span
|
||||||
|
>
|
||||||
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
|
</mwc-list-item>`;
|
||||||
|
})
|
||||||
: ""}
|
: ""}
|
||||||
${this.integration?.integrations
|
${this.integration?.integrations
|
||||||
? Object.entries(this.integration.integrations)
|
? Object.entries(this.integration.integrations)
|
||||||
@ -155,9 +167,11 @@ class HaDomainIntegrations extends LitElement {
|
|||||||
@click=${this._integrationPicked}
|
@click=${this._integrationPicked}
|
||||||
hasMeta
|
hasMeta
|
||||||
>
|
>
|
||||||
Setup another instance of
|
${this.hass.localize("ui.panel.config.integrations.new_flow", {
|
||||||
${this.integration.name ||
|
integration:
|
||||||
domainToName(this.hass.localize, this.domain)}
|
this.integration.name ||
|
||||||
|
domainToName(this.hass.localize, this.domain),
|
||||||
|
})}
|
||||||
<ha-icon-next slot="meta"></ha-icon-next>
|
<ha-icon-next slot="meta"></ha-icon-next>
|
||||||
</mwc-list-item>`
|
</mwc-list-item>`
|
||||||
: html`<ha-integration-list-item
|
: html`<ha-integration-list-item
|
||||||
@ -272,16 +286,25 @@ class HaDomainIntegrations extends LitElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
--mdc-list-item-graphic-size: 40px;
|
||||||
|
--mdc-list-side-padding: 24px;
|
||||||
}
|
}
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0 24px;
|
margin: 8px 24px 0;
|
||||||
color: var(--primary-text-color);
|
color: var(--secondary-text-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
h3:first-of-type {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
img {
|
img {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
li[divider] {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -104,8 +104,8 @@ export class HaIntegrationListItem extends ListItemBase {
|
|||||||
styles,
|
styles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
padding-left: var(--mdc-list-side-padding, 20px);
|
--mdc-list-side-padding: 24px;
|
||||||
padding-right: var(--mdc-list-side-padding, 20px);
|
--mdc-list-item-graphic-size: 40px;
|
||||||
}
|
}
|
||||||
:host([graphic="avatar"]:not([twoLine])),
|
:host([graphic="avatar"]:not([twoLine])),
|
||||||
:host([graphic="icon"]:not([twoLine])) {
|
:host([graphic="icon"]:not([twoLine])) {
|
||||||
|
@ -11,6 +11,7 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
|
import { differenceInDays } from "date-fns/esm";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
@ -42,7 +43,8 @@ import { HomeAssistant, Route } from "../../../types";
|
|||||||
import { documentationUrl } from "../../../util/documentation-url";
|
import { documentationUrl } from "../../../util/documentation-url";
|
||||||
import { showToast } from "../../../util/toast";
|
import { showToast } from "../../../util/toast";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||||
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||||
|
|
||||||
@customElement("ha-scene-dashboard")
|
@customElement("ha-scene-dashboard")
|
||||||
@ -109,11 +111,18 @@ class HaSceneDashboard extends LitElement {
|
|||||||
),
|
),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: "30%",
|
width: "30%",
|
||||||
template: (last_activated) => html`
|
template: (last_activated) => {
|
||||||
${last_activated && !UNAVAILABLE_STATES.includes(last_activated)
|
const date = new Date(last_activated);
|
||||||
? formatDateTime(new Date(last_activated), this.hass.locale)
|
const now = new Date();
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
const dayDifference = differenceInDays(now, date);
|
||||||
`,
|
return html`
|
||||||
|
${last_activated && !UNAVAILABLE_STATES.includes(last_activated)
|
||||||
|
? dayDifference > 3
|
||||||
|
? formatShortDateTime(date, this.hass.locale)
|
||||||
|
: relativeTime(date, this.hass.locale)
|
||||||
|
: this.hass.localize("ui.components.relative_time.never")}
|
||||||
|
`;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
columns.only_editable = {
|
columns.only_editable = {
|
||||||
|
@ -11,7 +11,9 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
import { differenceInDays } from "date-fns/esm";
|
||||||
|
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||||
|
import { relativeTime } from "../../../common/datetime/relative_time";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
@ -99,18 +101,22 @@ class HaScriptPicker extends LitElement {
|
|||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
template: narrow
|
template: narrow
|
||||||
? (name, script: any) => html`
|
? (name, script: any) => {
|
||||||
${name}
|
const date = new Date(script.attributes.last_triggered);
|
||||||
<div class="secondary">
|
const now = new Date();
|
||||||
${this.hass.localize("ui.card.automation.last_triggered")}:
|
const dayDifference = differenceInDays(now, date);
|
||||||
${script.attributes.last_triggered
|
return html`
|
||||||
? formatDateTime(
|
${name}
|
||||||
new Date(script.attributes.last_triggered),
|
<div class="secondary">
|
||||||
this.hass.locale
|
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||||
)
|
${script.attributes.last_triggered
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
? dayDifference > 3
|
||||||
</div>
|
? formatShortDateTime(date, this.hass.locale)
|
||||||
`
|
: relativeTime(date, this.hass.locale)
|
||||||
|
: this.hass.localize("ui.components.relative_time.never")}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -119,11 +125,18 @@ class HaScriptPicker extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
width: "40%",
|
width: "40%",
|
||||||
title: this.hass.localize("ui.card.automation.last_triggered"),
|
title: this.hass.localize("ui.card.automation.last_triggered"),
|
||||||
template: (last_triggered) => html`
|
template: (last_triggered) => {
|
||||||
${last_triggered
|
const date = new Date(last_triggered);
|
||||||
? formatDateTime(new Date(last_triggered), this.hass.locale)
|
const now = new Date();
|
||||||
: this.hass.localize("ui.components.relative_time.never")}
|
const dayDifference = differenceInDays(now, date);
|
||||||
`,
|
return html`
|
||||||
|
${last_triggered
|
||||||
|
? dayDifference > 3
|
||||||
|
? formatShortDateTime(date, this.hass.locale)
|
||||||
|
: relativeTime(date, this.hass.locale)
|
||||||
|
: this.hass.localize("ui.components.relative_time.never")}
|
||||||
|
`;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2850,6 +2850,8 @@
|
|||||||
"description": "Manage integrations with services or devices",
|
"description": "Manage integrations with services or devices",
|
||||||
"integration": "integration",
|
"integration": "integration",
|
||||||
"discovered": "Discovered",
|
"discovered": "Discovered",
|
||||||
|
"available_integrations": "Available integrations",
|
||||||
|
"new_flow": "Setup another instance of {integration}",
|
||||||
"attention": "Attention required",
|
"attention": "Attention required",
|
||||||
"configured": "Configured",
|
"configured": "Configured",
|
||||||
"new": "Select brand",
|
"new": "Select brand",
|
||||||
@ -2973,12 +2975,8 @@
|
|||||||
"description": "This step requires you to visit an external website to be completed.",
|
"description": "This step requires you to visit an external website to be completed.",
|
||||||
"open_site": "Open website"
|
"open_site": "Open website"
|
||||||
},
|
},
|
||||||
"pick_flow_step": {
|
|
||||||
"title": "We discovered these, want to set them up?",
|
|
||||||
"new_flow": "No, set up an other instance of {integration}"
|
|
||||||
},
|
|
||||||
"loading": {
|
"loading": {
|
||||||
"loading_flow": "Please wait while {integration} is being setup",
|
"loading_flow": "Please wait, starting configuration wizard for {integration}",
|
||||||
"loading_step": "Loading next step for {integration}",
|
"loading_step": "Loading next step for {integration}",
|
||||||
"fallback_title": "the integration"
|
"fallback_title": "the integration"
|
||||||
},
|
},
|
||||||
|
@ -116,6 +116,9 @@
|
|||||||
"lv": {
|
"lv": {
|
||||||
"nativeName": "Latviešu"
|
"nativeName": "Latviešu"
|
||||||
},
|
},
|
||||||
|
"ml": {
|
||||||
|
"nativeName": "മലയാളം"
|
||||||
|
},
|
||||||
"nl": {
|
"nl": {
|
||||||
"nativeName": "Nederlands"
|
"nativeName": "Nederlands"
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user