mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Merge pull request #6692 from home-assistant/dev
This commit is contained in:
commit
090ad34f78
@ -7,9 +7,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
@ -19,6 +19,7 @@ import {
|
||||
fetchHassioSnapshotInfo,
|
||||
HassioSnapshotDetail,
|
||||
} from "../../../../src/data/hassio/snapshot";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../../src/polymer-types";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
@ -266,8 +267,12 @@ class HassioSnapshotDialog extends LitElement {
|
||||
this._snapshotPassword = ev.detail.value;
|
||||
}
|
||||
|
||||
private _partialRestoreClicked() {
|
||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||
private async _partialRestoreClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want partially to restore this snapshot?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -312,8 +317,13 @@ class HassioSnapshotDialog extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _fullRestoreClicked() {
|
||||
if (!confirm("Are you sure you want to restore this snapshot?")) {
|
||||
private async _fullRestoreClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title:
|
||||
"Are you sure you want to wipe your system and restore this snapshot?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -338,8 +348,12 @@ class HassioSnapshotDialog extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private _deleteClicked() {
|
||||
if (!confirm("Are you sure you want to delete this snapshot?")) {
|
||||
private async _deleteClicked() {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Are you sure you want to delete this snapshot?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
19
package.json
19
package.json
@ -23,8 +23,11 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-pluralrules": "^1.5.8",
|
||||
"@fullcalendar/core": "^5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
||||
"@fullcalendar/common": "5.1.0",
|
||||
"@fullcalendar/core": "5.1.0",
|
||||
"@fullcalendar/daygrid": "5.1.0",
|
||||
"@fullcalendar/interaction": "5.1.0",
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@material/chips": "=8.0.0-canary.096a7a066.0",
|
||||
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0",
|
||||
"@material/mwc-button": "^0.18.0",
|
||||
@ -41,8 +44,8 @@
|
||||
"@material/mwc-tab": "^0.18.0",
|
||||
"@material/mwc-tab-bar": "^0.18.0",
|
||||
"@material/top-app-bar": "=8.0.0-canary.096a7a066.0",
|
||||
"@mdi/js": "5.4.55",
|
||||
"@mdi/svg": "5.4.55",
|
||||
"@mdi/js": "5.5.55",
|
||||
"@mdi/svg": "5.5.55",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-route": "^3.0.2",
|
||||
"@polymer/app-storage": "^3.0.2",
|
||||
@ -146,7 +149,7 @@
|
||||
"@types/leaflet-draw": "^1.0.1",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/memoize-one": "4.1.0",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/resize-observer-browser": "^0.1.3",
|
||||
"@types/webspeechapi": "^0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
@ -157,7 +160,7 @@
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb-typescript": "^7.2.1",
|
||||
"eslint-config-prettier": "^6.10.1",
|
||||
"eslint-import-resolver-webpack": "^0.12.1",
|
||||
"eslint-import-resolver-webpack": "^0.12.2",
|
||||
"eslint-plugin-disable": "^2.0.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-lit": "^1.2.0",
|
||||
@ -180,7 +183,7 @@
|
||||
"magic-string": "^0.25.7",
|
||||
"map-stream": "^0.0.7",
|
||||
"merge-stream": "^1.0.1",
|
||||
"mocha": "^6.0.2",
|
||||
"mocha": "^7.2.0",
|
||||
"object-hash": "^2.0.3",
|
||||
"open": "^7.0.4",
|
||||
"prettier": "^2.0.4",
|
||||
@ -197,7 +200,7 @@
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"ts-lit-plugin": "^1.2.0",
|
||||
"ts-mocha": "^6.0.0",
|
||||
"ts-mocha": "^7.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200820.0",
|
||||
version="20200824.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
9
src/common/config/is_service_loaded.ts
Normal file
9
src/common/config/is_service_loaded.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
||||
/** Return if a service is loaded. */
|
||||
export const isServiceLoaded = (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
service: string
|
||||
): boolean =>
|
||||
hass && domain in hass.services && service in hass.services[domain];
|
@ -21,6 +21,11 @@ export default function relativeTime(
|
||||
const tense = delta >= 0 ? "past" : "future";
|
||||
delta = Math.abs(delta);
|
||||
let roundedDelta = Math.round(delta);
|
||||
|
||||
if (roundedDelta === 0) {
|
||||
return localize("ui.components.relative_time.just_now");
|
||||
}
|
||||
|
||||
let unit = "week";
|
||||
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
|
@ -5,12 +5,16 @@ import { domainIcon } from "./domain_icon";
|
||||
import { batteryIcon } from "./battery_icon";
|
||||
|
||||
const fixedDeviceClassIcons = {
|
||||
current: "hass:current-ac",
|
||||
energy: "hass:flash",
|
||||
humidity: "hass:water-percent",
|
||||
illuminance: "hass:brightness-5",
|
||||
temperature: "hass:thermometer",
|
||||
pressure: "hass:gauge",
|
||||
power: "hass:flash",
|
||||
power_factor: "hass:angle-acute",
|
||||
signal_strength: "hass:wifi",
|
||||
voltage: "hass:sine-wave",
|
||||
};
|
||||
|
||||
export const sensorIcon = (state: HassEntity) => {
|
||||
|
@ -3,19 +3,21 @@ import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
eventOptions,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
eventOptions,
|
||||
} 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 { scroll } from "lit-virtualizer";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../common/search/search-input";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
@ -24,8 +26,6 @@ import "../ha-checkbox";
|
||||
import type { HaCheckbox } from "../ha-checkbox";
|
||||
import "../ha-icon";
|
||||
import { filterData, sortData } from "./sort-filter";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { restoreScroll } from "../../common/decorators/restore-scroll";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
|
@ -1,12 +1,12 @@
|
||||
/* eslint-plugin-disable lit */
|
||||
import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior";
|
||||
import "../ha-icon-button";
|
||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||
import { timeOut } from "@polymer/polymer/lib/utils/async";
|
||||
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import { formatTime } from "../../common/datetime/format_time";
|
||||
import "../ha-icon-button";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/* global Chart moment Color */
|
||||
@ -355,7 +355,7 @@ class HaChartBase extends mixinBehaviors(
|
||||
return value;
|
||||
}
|
||||
const date = new Date(values[index].value);
|
||||
return formatTime(date);
|
||||
return formatTime(date, this.hass.language);
|
||||
}
|
||||
|
||||
drawChart() {
|
||||
@ -420,7 +420,7 @@ class HaChartBase extends mixinBehaviors(
|
||||
},
|
||||
};
|
||||
options = Chart.helpers.merge(options, this.data.options);
|
||||
options.scales.xAxes[0].ticks.callback = this._formatTickValue;
|
||||
options.scales.xAxes[0].ticks.callback = this._formatTickValue.bind(this);
|
||||
if (this.data.type === "timeline") {
|
||||
this.set("isTimeline", true);
|
||||
if (this.data.colors !== undefined) {
|
||||
|
@ -1,21 +1,20 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
property,
|
||||
LitElement,
|
||||
CSSResult,
|
||||
css,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import "./ha-icon-button";
|
||||
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { ToggleButton } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-button-toggle-group")
|
||||
export class HaButtonToggleGroup extends LitElement {
|
||||
@property() public buttons!: ToggleButton[];
|
||||
@property({ attribute: false }) public buttons!: ToggleButton[];
|
||||
|
||||
@property() public active?: string;
|
||||
|
||||
@ -23,21 +22,23 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
return html`
|
||||
<div>
|
||||
${this.buttons.map(
|
||||
(button) => html` <ha-icon-button
|
||||
.label=${button.label}
|
||||
.icon=${button.icon}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
</ha-icon-button>`
|
||||
(button) => html`
|
||||
<mwc-icon-button
|
||||
.label=${button.label}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(ev): void {
|
||||
this.active = ev.target.value;
|
||||
this.active = ev.currentTarget.value;
|
||||
fireEvent(this, "value-changed", { value: this.active });
|
||||
}
|
||||
|
||||
@ -48,12 +49,13 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||
}
|
||||
ha-icon-button {
|
||||
mwc-icon-button {
|
||||
border: 1px solid var(--primary-color);
|
||||
border-right-width: 0px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-icon-button::before {
|
||||
mwc-icon-button::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
@ -65,22 +67,26 @@ export class HaButtonToggleGroup extends LitElement {
|
||||
content: "";
|
||||
transition: opacity 15ms linear, background-color 15ms linear;
|
||||
}
|
||||
ha-icon-button[active]::before {
|
||||
mwc-icon-button[active]::before {
|
||||
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
||||
}
|
||||
ha-icon-button:first-child {
|
||||
mwc-icon-button:first-child {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
ha-icon-button:last-child {
|
||||
mwc-icon-button:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
mwc-icon-button:only-child {
|
||||
border-radius: 4px;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-button-toggle-button": HaButtonToggleGroup;
|
||||
"ha-button-toggle-group": HaButtonToggleGroup;
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ import {
|
||||
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,
|
||||
@ -37,6 +39,8 @@ class HaCameraStream extends LitElement {
|
||||
|
||||
private _hlsPolyfillInstance?: Hls;
|
||||
|
||||
private _useExoPlayer = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._attached = true;
|
||||
@ -125,22 +129,33 @@ class HaCameraStream extends LitElement {
|
||||
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
|
||||
const Hls = ((await import(
|
||||
/* webpackChunkName: "hls.js" */ "hls.js"
|
||||
)) as any).default as HLSModule;
|
||||
let hlsSupported = Hls.isSupported();
|
||||
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) {
|
||||
hlsSupported =
|
||||
videoEl.canPlayType("application/vnd.apple.mpegurl") !== "";
|
||||
}
|
||||
|
||||
if (!hlsSupported) {
|
||||
this._forceMJPEG = this.stateObj!.entity_id;
|
||||
return;
|
||||
if (!hlsSupported) {
|
||||
this._forceMJPEG = this.stateObj!.entity_id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@ -149,8 +164,10 @@ class HaCameraStream extends LitElement {
|
||||
this.stateObj!.entity_id
|
||||
);
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, Hls, url);
|
||||
if (this._useExoPlayer) {
|
||||
this._renderHLSExoPlayer(url);
|
||||
} else if (hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, hls, url);
|
||||
} else {
|
||||
this._renderHLSNative(videoEl, url);
|
||||
}
|
||||
@ -163,6 +180,29 @@ class HaCameraStream extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
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) =>
|
||||
@ -194,11 +234,15 @@ class HaCameraStream extends LitElement {
|
||||
fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
private _destroyPolyfill(): void {
|
||||
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 {
|
||||
|
@ -106,6 +106,7 @@ const mdiRenameMapping = {
|
||||
pot: "pot-steam",
|
||||
ruby: "language-ruby",
|
||||
sailing: "sail-boat",
|
||||
scooter: "human-scooter",
|
||||
settings: "cog",
|
||||
"settings-box": "cog-box",
|
||||
"settings-outline": "cog-outline",
|
||||
|
@ -21,8 +21,8 @@ import {
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
LeafletModuleType,
|
||||
setupLeafletMap,
|
||||
replaceTileLayer,
|
||||
setupLeafletMap,
|
||||
} from "../../common/dom/setup-leaflet-map";
|
||||
import { nextRender } from "../../common/util/render-status";
|
||||
import { defaultRadiusColor } from "../../data/zone";
|
||||
@ -40,6 +40,8 @@ class LocationEditor extends LitElement {
|
||||
|
||||
@property() public icon?: string;
|
||||
|
||||
@property({ type: Boolean }) public darkMode?: boolean;
|
||||
|
||||
public fitZoom = 16;
|
||||
|
||||
private _iconEl?: DivIcon;
|
||||
@ -129,7 +131,7 @@ class LocationEditor extends LitElement {
|
||||
private async _initMap(): Promise<void> {
|
||||
[this._leafletMap, this.Leaflet, this._tileLayer] = await setupLeafletMap(
|
||||
this._mapEl,
|
||||
this.hass.themes?.darkMode,
|
||||
this.darkMode ?? this.hass.themes?.darkMode,
|
||||
Boolean(this.radius)
|
||||
);
|
||||
this._leafletMap.addEventListener(
|
||||
|
115
src/components/media-player/dialog-media-player-browse.ts
Normal file
115
src/components/media-player/dialog-media-player-browse.ts
Normal file
@ -0,0 +1,115 @@
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import type {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { createCloseHeading } from "../ha-dialog";
|
||||
import "./ha-media-player-browse";
|
||||
import { MediaPlayerBrowseDialogParams } from "./show-media-browser-dialog";
|
||||
|
||||
@customElement("dialog-media-player-browse")
|
||||
class DialogMediaPlayerBrowse extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _entityId!: string;
|
||||
|
||||
@internalProperty() private _mediaContentId?: string;
|
||||
|
||||
@internalProperty() private _mediaContentType?: string;
|
||||
|
||||
@internalProperty() private _action?: MediaPlayerBrowseAction;
|
||||
|
||||
@internalProperty() private _params?: MediaPlayerBrowseDialogParams;
|
||||
|
||||
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";
|
||||
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.components.media-browser.media-player-browser")
|
||||
)}
|
||||
@closed=${this._closeDialog}
|
||||
>
|
||||
<ha-media-player-browse
|
||||
.hass=${this.hass}
|
||||
.entityId=${this._entityId}
|
||||
.action=${this._action!}
|
||||
.mediaContentId=${this._mediaContentId}
|
||||
.mediaContentType=${this._mediaContentType}
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-z-index: 8;
|
||||
--dialog-content-padding: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 800px;
|
||||
}
|
||||
ha-media-player-browse {
|
||||
width: 700px;
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-media-player-browse": DialogMediaPlayerBrowse;
|
||||
}
|
||||
}
|
589
src/components/media-player/ha-media-player-browse.ts
Normal file
589
src/components/media-player/ha-media-player-browse.ts
Normal file
@ -0,0 +1,589 @@
|
||||
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, mdiFolder, mdiPlay, mdiPlus } from "@mdi/js";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { browseMediaPlayer, MediaPickedEvent } from "../../data/media-player";
|
||||
import type { MediaPlayerItem } from "../../data/media-player";
|
||||
import { installResizeObserver } from "../../panels/lovelace/common/install-resize-observer";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../entity/ha-entity-picker";
|
||||
import "../ha-button-menu";
|
||||
import "../ha-card";
|
||||
import "../ha-circular-progress";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import "../ha-svg-icon";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"media-picked": MediaPickedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-media-player-browse")
|
||||
export class HaMediaPlayerBrowse extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public entityId!: string;
|
||||
|
||||
@property() public mediaContentId?: string;
|
||||
|
||||
@property() public mediaContentType?: string;
|
||||
|
||||
@property() public action: "pick" | "play" = "play";
|
||||
|
||||
@property({ type: Boolean, attribute: "narrow", reflect: true })
|
||||
private _narrow = false;
|
||||
|
||||
@internalProperty() private _loading = false;
|
||||
|
||||
@internalProperty() private _mediaPlayerItems: MediaPlayerItem[] = [];
|
||||
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.updateComplete.then(() => this._attachObserver());
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._mediaPlayerItems.length) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (this._loading) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
const mostRecentItem = this._mediaPlayerItems[
|
||||
this._mediaPlayerItems.length - 1
|
||||
];
|
||||
const previousItem =
|
||||
this._mediaPlayerItems.length > 1
|
||||
? this._mediaPlayerItems[this._mediaPlayerItems.length - 2]
|
||||
: undefined;
|
||||
|
||||
const hasExpandableChildren:
|
||||
| MediaPlayerItem
|
||||
| undefined = this._hasExpandableChildren(mostRecentItem.children);
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="header-content">
|
||||
${mostRecentItem.thumbnail
|
||||
? html`
|
||||
<div
|
||||
class="img"
|
||||
style="background-image: url(${mostRecentItem.thumbnail})"
|
||||
>
|
||||
${this._narrow && mostRecentItem?.can_play
|
||||
? html`
|
||||
<mwc-fab
|
||||
mini
|
||||
.item=${mostRecentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
)}
|
||||
</mwc-fab>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
<div class="header-info">
|
||||
<div class="breadcrumb-overflow">
|
||||
<div class="breadcrumb">
|
||||
${previousItem
|
||||
? html`
|
||||
<div
|
||||
class="previous-title"
|
||||
.previous=${true}
|
||||
.item=${previousItem}
|
||||
@click=${this._navigate}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiArrowLeft}></ha-svg-icon>
|
||||
${previousItem.title}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<h1 class="title">${mostRecentItem.title}</h1>
|
||||
<h2 class="subtitle">
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${mostRecentItem.media_content_type}`
|
||||
)}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
${mostRecentItem?.can_play &&
|
||||
(!this._narrow || (this._narrow && !mostRecentItem.thumbnail))
|
||||
? html`
|
||||
<div class="actions">
|
||||
<mwc-button
|
||||
raised
|
||||
.item=${mostRecentItem}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}`
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
${mostRecentItem.children?.length
|
||||
? hasExpandableChildren
|
||||
? html`
|
||||
<div class="children">
|
||||
${mostRecentItem.children?.length
|
||||
? html`
|
||||
${mostRecentItem.children.map(
|
||||
(child) => html`
|
||||
<div
|
||||
class="child"
|
||||
.item=${child}
|
||||
@click=${this._navigate}
|
||||
>
|
||||
<div class="ha-card-parent">
|
||||
<ha-card
|
||||
outlined
|
||||
style="background-image: url(${child.thumbnail})"
|
||||
>
|
||||
${child.can_expand && !child.thumbnail
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
class="folder"
|
||||
.path=${mdiFolder}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: ""}
|
||||
</ha-card>
|
||||
${child.can_play
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
class="play"
|
||||
.item=${child}
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
@click=${this._actionClicked}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${this.action === "play"
|
||||
? mdiPlay
|
||||
: mdiPlus}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="title">${child.title}</div>
|
||||
<div class="type">
|
||||
${this.hass.localize(
|
||||
`ui.components.media-browser.content-type.${child.media_content_type}`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<mwc-list>
|
||||
${mostRecentItem.children.map(
|
||||
(child) => html`<mwc-list-item
|
||||
@click=${this._actionClicked}
|
||||
.item=${child}
|
||||
graphic="icon"
|
||||
>
|
||||
<span>${child.title}</span>
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.label=${this.hass.localize(
|
||||
`ui.components.media-browser.${this.action}-media`
|
||||
)}
|
||||
.path=${this.action === "play" ? mdiPlay : mdiPlus}
|
||||
></ha-svg-icon
|
||||
></mwc-list-item>
|
||||
<li divider role="separator"></li>`
|
||||
)}
|
||||
</mwc-list>
|
||||
`
|
||||
: this.hass.localize("ui.components.media-browser.no_items")}
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._measureCard();
|
||||
this._attachObserver();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (
|
||||
!changedProps.has("entityId") &&
|
||||
!changedProps.has("mediaContentId") &&
|
||||
!changedProps.has("mediaContentType") &&
|
||||
!changedProps.has("action")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fetchData(this.mediaContentId, this.mediaContentType).then(
|
||||
(itemData) => {
|
||||
this._mediaPlayerItems = [itemData];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _actionClicked(ev: MouseEvent): void {
|
||||
ev.stopPropagation();
|
||||
const item = (ev.currentTarget as any).item;
|
||||
|
||||
this._runAction(item);
|
||||
}
|
||||
|
||||
private _runAction(item: MediaPlayerItem): void {
|
||||
fireEvent(this, "media-picked", {
|
||||
media_content_id: item.media_content_id,
|
||||
media_content_type: item.media_content_type,
|
||||
});
|
||||
}
|
||||
|
||||
private async _navigate(ev: MouseEvent): Promise<void> {
|
||||
const target = ev.currentTarget as any;
|
||||
let item: MediaPlayerItem | undefined;
|
||||
|
||||
if (target.previous) {
|
||||
this._mediaPlayerItems!.pop();
|
||||
item = this._mediaPlayerItems!.pop();
|
||||
}
|
||||
|
||||
item = target.item;
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemData = await this._fetchData(
|
||||
item.media_content_id,
|
||||
item.media_content_type
|
||||
);
|
||||
this._mediaPlayerItems = [...this._mediaPlayerItems, itemData];
|
||||
}
|
||||
|
||||
private async _fetchData(
|
||||
mediaContentId?: string,
|
||||
mediaContentType?: string
|
||||
): Promise<MediaPlayerItem> {
|
||||
const itemData = await browseMediaPlayer(
|
||||
this.hass,
|
||||
this.entityId,
|
||||
!mediaContentId ? undefined : mediaContentId,
|
||||
mediaContentType
|
||||
);
|
||||
|
||||
return itemData;
|
||||
}
|
||||
|
||||
private _measureCard(): void {
|
||||
this._narrow = this.offsetWidth < 500;
|
||||
}
|
||||
|
||||
private async _attachObserver(): Promise<void> {
|
||||
if (!this._resizeObserver) {
|
||||
await installResizeObserver();
|
||||
this._resizeObserver = new ResizeObserver(
|
||||
debounce(() => this._measureCard(), 250, false)
|
||||
);
|
||||
}
|
||||
|
||||
this._resizeObserver.observe(this);
|
||||
}
|
||||
|
||||
private _hasExpandableChildren = memoizeOne((children) =>
|
||||
children.find((item: MediaPlayerItem) => item.can_expand)
|
||||
);
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.breadcrumb-overflow {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-grow: 1;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-content .img {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
margin-right: 16px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.header-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-self: stretch;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-info .actions {
|
||||
padding-top: 24px;
|
||||
--mdc-theme-primary: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.breadcrumb .title {
|
||||
font-size: 48px;
|
||||
line-height: 1.2;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.breadcrumb .previous-title {
|
||||
font-size: 14px;
|
||||
padding-bottom: 8px;
|
||||
color: var(--secondary-text-color);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
--mdc-icon-size: 14px;
|
||||
}
|
||||
|
||||
.breadcrumb .subtitle {
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.divider {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.divider::before {
|
||||
height: 1px;
|
||||
display: block;
|
||||
background-color: var(--divider-color);
|
||||
content: " ";
|
||||
}
|
||||
|
||||
/* ============= CHILDREN ============= */
|
||||
|
||||
mwc-list {
|
||||
--mdc-list-vertical-padding: 0;
|
||||
--mdc-theme-text-icon-on-background: var(--secondary-text-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
mwc-list li:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
mwc-list li[divider] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
|
||||
.children {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(var(--media-browse-item-size, 175px), 0.33fr)
|
||||
);
|
||||
grid-gap: 16px;
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
.child {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ha-card-parent {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
width: 100%;
|
||||
padding-bottom: 100%;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.child .folder,
|
||||
.child .play {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.child .folder {
|
||||
color: var(--secondary-text-color);
|
||||
top: calc(50% - (var(--mdc-icon-size) / 2));
|
||||
left: calc(50% - (var(--mdc-icon-size) / 2));
|
||||
--mdc-icon-size: calc(var(--media-browse-item-size, 175px) * 0.4);
|
||||
}
|
||||
|
||||
.child .play {
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
transition: all 0.5s;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.child .play:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-card:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.child .title {
|
||||
font-size: 16px;
|
||||
padding-top: 8px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.child .type {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
/* ============= Narrow ============= */
|
||||
|
||||
:host([narrow]) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) mwc-list {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .breadcrumb .title {
|
||||
font-size: 38px;
|
||||
}
|
||||
|
||||
:host([narrow]) .breadcrumb-overflow {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-content {
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-content .img {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
margin-right: 0;
|
||||
padding-bottom: 100%;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-content .img mwc-fab {
|
||||
position: absolute;
|
||||
--mdc-theme-secondary: var(--primary-color);
|
||||
bottom: -20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
:host([narrow]) .header-info,
|
||||
:host([narrow]) .media-source,
|
||||
:host([narrow]) .children {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
:host([narrow]) .children {
|
||||
grid-template-columns: 1fr 1fr !important;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-media-player-browse": HaMediaPlayerBrowse;
|
||||
}
|
||||
}
|
27
src/components/media-player/show-media-browser-dialog.ts
Normal file
27
src/components/media-player/show-media-browser-dialog.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
MediaPickedEvent,
|
||||
MediaPlayerBrowseAction,
|
||||
} from "../../data/media-player";
|
||||
|
||||
export interface MediaPlayerBrowseDialogParams {
|
||||
action: MediaPlayerBrowseAction;
|
||||
entityId: string;
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => void;
|
||||
mediaContentId?: string;
|
||||
mediaContentType?: string;
|
||||
}
|
||||
|
||||
export const showMediaBrowserDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: MediaPlayerBrowseDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-media-player-browse",
|
||||
dialogImport: () =>
|
||||
import(
|
||||
/* webpackChunkName: "dialog-media-player-browse" */ "./dialog-media-player-browse"
|
||||
),
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@ -19,6 +19,7 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
</style>
|
||||
<ha-chart-base
|
||||
id="chart"
|
||||
hass="[[hass]]"
|
||||
data="[[chartData]]"
|
||||
identifier="[[identifier]]"
|
||||
rendered="{{rendered}}"
|
||||
@ -28,6 +29,9 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
chartData: Object,
|
||||
data: Object,
|
||||
names: Object,
|
||||
|
@ -25,6 +25,7 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
}
|
||||
</style>
|
||||
<ha-chart-base
|
||||
hass="[[hass]]"
|
||||
data="[[chartData]]"
|
||||
rendered="{{rendered}}"
|
||||
rtl="{{rtl}}"
|
||||
|
@ -71,7 +71,11 @@ export const createHassioSession = async (hass: HomeAssistant) => {
|
||||
"POST",
|
||||
"hassio/ingress/session"
|
||||
);
|
||||
document.cookie = `ingress_session=${response.data.session};path=/api/hassio_ingress/;SameSite=Strict`;
|
||||
document.cookie = `ingress_session=${
|
||||
response.data.session
|
||||
};path=/api/hassio_ingress/;SameSite=Strict${
|
||||
location.protocol === "https:" ? ";Secure" : ""
|
||||
}`;
|
||||
};
|
||||
|
||||
export const setSupervisorOption = async (
|
||||
|
@ -7,6 +7,12 @@ export interface LogbookEntry {
|
||||
entity_id?: string;
|
||||
domain: string;
|
||||
context_user_id?: string;
|
||||
context_event_type?: string;
|
||||
context_domain?: string;
|
||||
context_service?: string;
|
||||
context_entity_id?: string;
|
||||
context_entity_id_name?: string;
|
||||
context_name?: string;
|
||||
}
|
||||
|
||||
const DATA_CACHE: {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export const SUPPORT_PAUSE = 1;
|
||||
export const SUPPORT_SEEK = 2;
|
||||
@ -14,8 +15,16 @@ export const SUPPORT_SELECT_SOURCE = 2048;
|
||||
export const SUPPORT_STOP = 4096;
|
||||
export const SUPPORTS_PLAY = 16384;
|
||||
export const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||
export const SUPPORT_BROWSE_MEDIA = 131072;
|
||||
export const CONTRAST_RATIO = 4.5;
|
||||
|
||||
export type MediaPlayerBrowseAction = "pick" | "play";
|
||||
|
||||
export interface MediaPickedEvent {
|
||||
media_content_id: string;
|
||||
media_content_type: string;
|
||||
}
|
||||
|
||||
export interface MediaPlayerThumbnail {
|
||||
content_type: string;
|
||||
content: string;
|
||||
@ -26,6 +35,29 @@ export interface ControlButton {
|
||||
action: string;
|
||||
}
|
||||
|
||||
export interface MediaPlayerItem {
|
||||
title: string;
|
||||
media_content_type: string;
|
||||
media_content_id: string;
|
||||
can_play: boolean;
|
||||
can_expand: boolean;
|
||||
thumbnail?: string;
|
||||
children?: MediaPlayerItem[];
|
||||
}
|
||||
|
||||
export const browseMediaPlayer = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
mediaContentId?: string,
|
||||
mediaContentType?: string
|
||||
): Promise<MediaPlayerItem> =>
|
||||
hass.callWS<MediaPlayerItem>({
|
||||
type: "media_player/browse_media",
|
||||
entity_id: entityId,
|
||||
media_content_id: mediaContentId,
|
||||
media_content_type: mediaContentType,
|
||||
});
|
||||
|
||||
export const getCurrentProgress = (stateObj: HassEntity): number => {
|
||||
let progress = stateObj.attributes.media_position;
|
||||
|
||||
|
@ -39,6 +39,28 @@ export interface OZWDeviceMetaData {
|
||||
ProductPicBase64: string;
|
||||
}
|
||||
|
||||
export interface OZWInstance {
|
||||
ozw_instance: number;
|
||||
OZWDaemon_Version: string;
|
||||
OpenZWave_Version: string;
|
||||
QTOpenZWave_Version: string;
|
||||
Status: string;
|
||||
getControllerPath: string;
|
||||
homeID: string;
|
||||
}
|
||||
|
||||
export interface OZWNetworkStatistics {
|
||||
ozw_instance: number;
|
||||
node_count: number;
|
||||
readCnt: number;
|
||||
writeCnt: number;
|
||||
ACKCnt: number;
|
||||
CANCnt: number;
|
||||
NAKCnt: number;
|
||||
dropped: number;
|
||||
retries: number;
|
||||
}
|
||||
|
||||
export const nodeQueryStages = [
|
||||
"ProtocolInfo",
|
||||
"Probe",
|
||||
@ -59,6 +81,26 @@ export const nodeQueryStages = [
|
||||
"Complete",
|
||||
];
|
||||
|
||||
export const networkOnlineStatuses = [
|
||||
"driverAllNodesQueried",
|
||||
"driverAllNodesQueriedSomeDead",
|
||||
"driverAwakeNodesQueried",
|
||||
];
|
||||
export const networkStartingStatuses = [
|
||||
"starting",
|
||||
"started",
|
||||
"Ready",
|
||||
"driverReady",
|
||||
];
|
||||
export const networkOfflineStatuses = [
|
||||
"Offline",
|
||||
"stopped",
|
||||
"driverFailed",
|
||||
"driverReset",
|
||||
"driverRemoved",
|
||||
"driverAllNodesOnFire",
|
||||
];
|
||||
|
||||
export const getIdentifiersFromDevice = function (
|
||||
device: DeviceRegistryEntry
|
||||
): OZWNodeIdentifiers | undefined {
|
||||
@ -80,6 +122,31 @@ export const getIdentifiersFromDevice = function (
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchOZWInstances = (
|
||||
hass: HomeAssistant
|
||||
): Promise<OZWInstance[]> =>
|
||||
hass.callWS({
|
||||
type: "ozw/get_instances",
|
||||
});
|
||||
|
||||
export const fetchOZWNetworkStatus = (
|
||||
hass: HomeAssistant,
|
||||
ozw_instance: number
|
||||
): Promise<OZWInstance> =>
|
||||
hass.callWS({
|
||||
type: "ozw/network_status",
|
||||
ozw_instance: ozw_instance,
|
||||
});
|
||||
|
||||
export const fetchOZWNetworkStatistics = (
|
||||
hass: HomeAssistant,
|
||||
ozw_instance: number
|
||||
): Promise<OZWNetworkStatistics> =>
|
||||
hass.callWS({
|
||||
type: "ozw/network_statistics",
|
||||
ozw_instance: ozw_instance,
|
||||
});
|
||||
|
||||
export const fetchOZWNodeStatus = (
|
||||
hass: HomeAssistant,
|
||||
ozw_instance: number,
|
||||
|
@ -17,7 +17,9 @@ export const setDefaultPanel = (
|
||||
};
|
||||
|
||||
export const getDefaultPanel = (hass: HomeAssistant): PanelInfo =>
|
||||
hass.panels[hass.defaultPanel];
|
||||
hass.panels[hass.defaultPanel]
|
||||
? hass.panels[hass.defaultPanel]
|
||||
: hass.panels[DEFAULT_PANEL];
|
||||
|
||||
export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
|
||||
if (!hass.panels) {
|
||||
|
@ -1,43 +1,45 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import { HomeAssistant, MediaEntity } from "../../../types";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { UNAVAILABLE_STATES, UNAVAILABLE, UNKNOWN } from "../../../data/entity";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-slider";
|
||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||
import {
|
||||
SUPPORT_TURN_ON,
|
||||
SUPPORT_TURN_OFF,
|
||||
ControlButton,
|
||||
MediaPickedEvent,
|
||||
SUPPORTS_PLAY,
|
||||
SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_PREVIOUS_TRACK,
|
||||
SUPPORT_SELECT_SOUND_MODE,
|
||||
SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON,
|
||||
SUPPORT_VOLUME_BUTTONS,
|
||||
SUPPORT_VOLUME_MUTE,
|
||||
SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_BUTTONS,
|
||||
SUPPORT_SELECT_SOURCE,
|
||||
SUPPORT_SELECT_SOUND_MODE,
|
||||
SUPPORT_PLAY_MEDIA,
|
||||
ControlButton,
|
||||
} from "../../../data/media-player";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-slider";
|
||||
import "../../../components/ha-icon";
|
||||
import { HomeAssistant, MediaEntity } from "../../../types";
|
||||
|
||||
@customElement("more-info-media_player")
|
||||
class MoreInfoMediaPlayer extends LitElement {
|
||||
@ -60,15 +62,29 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
? ""
|
||||
: html`
|
||||
<div class="controls">
|
||||
${controls!.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
action=${control.action}
|
||||
.icon=${control.icon}
|
||||
@click=${this._handleClick}
|
||||
></ha-icon-button>
|
||||
`
|
||||
)}
|
||||
<div class="basic-controls">
|
||||
${controls!.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
action=${control.action}
|
||||
.icon=${control.icon}
|
||||
@click=${this._handleClick}
|
||||
></ha-icon-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||
? html`
|
||||
<ha-icon-button
|
||||
icon="hass:folder-multiple"
|
||||
.title=${this.hass.localize(
|
||||
"ui.card.media_player.browse_media"
|
||||
)}
|
||||
@click=${this._showBrowseMedia}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`}
|
||||
${(supportsFeature(stateObj, SUPPORT_VOLUME_SET) ||
|
||||
@ -196,8 +212,16 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.basic-controls {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.volume,
|
||||
.controls,
|
||||
.source-input,
|
||||
.sound-input,
|
||||
.tts {
|
||||
@ -372,6 +396,26 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
});
|
||||
ttsInput.value = "";
|
||||
}
|
||||
|
||||
private _showBrowseMedia(): void {
|
||||
showMediaBrowserDialog(this, {
|
||||
action: "play",
|
||||
entityId: this.stateObj!.entity_id,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) =>
|
||||
this._playMedia(
|
||||
pickedMedia.media_content_id,
|
||||
pickedMedia.media_content_type
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _playMedia(media_content_id: string, media_content_type: string) {
|
||||
this.hass!.callService("media_player", "play_media", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
media_content_id,
|
||||
media_content_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -3,6 +3,7 @@ import { ExternalMessaging } from "./external_messaging";
|
||||
export interface ExternalConfig {
|
||||
hasSettingsScreen: boolean;
|
||||
canWriteTag: boolean;
|
||||
hasExoPlayer: boolean;
|
||||
}
|
||||
|
||||
export const getExternalConfig = (
|
||||
|
@ -22,6 +22,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>
|
||||
|
@ -23,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>
|
||||
|
@ -5,15 +5,15 @@ import type { AppDrawerElement } from "@polymer/app-layout/app-drawer/app-drawer
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
@ -49,7 +49,15 @@ class HomeAssistantMain extends LitElement {
|
||||
const disableSwipe =
|
||||
!sidebarNarrow || NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1;
|
||||
|
||||
// Style block in render because of the mixin that is not supported
|
||||
return html`
|
||||
<style>
|
||||
app-drawer {
|
||||
--app-drawer-content-container: {
|
||||
background-color: var(--primary-background-color, #fff);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<app-drawer-layout
|
||||
fullbleed
|
||||
.forceNarrow=${sidebarNarrow}
|
||||
|
@ -8,9 +8,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
@ -27,6 +27,7 @@ import type { PolymerChangedEvent } from "../polymer-types";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
const amsterdam = [52.3731339, 4.8903147];
|
||||
const mql = matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
@customElement("onboarding-core-config")
|
||||
class OnboardingCoreConfig extends LitElement {
|
||||
@ -93,6 +94,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.location=${this._locationValue}
|
||||
.fitZoom=${14}
|
||||
.darkMode=${mql.matches}
|
||||
@change=${this._locationChanged}
|
||||
></ha-location-editor>
|
||||
</div>
|
||||
|
@ -1,67 +1,85 @@
|
||||
import {
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
LitElement,
|
||||
CSSResult,
|
||||
html,
|
||||
css,
|
||||
unsafeCSS,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
// @ts-ignore
|
||||
import fullcalendarStyle from "@fullcalendar/common/main.css";
|
||||
import { Calendar } from "@fullcalendar/core";
|
||||
import type { CalendarOptions } from "@fullcalendar/core";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
// @ts-ignore
|
||||
import fullcalendarStyle from "@fullcalendar/core/main.css";
|
||||
// @ts-ignore
|
||||
import daygridStyle from "@fullcalendar/daygrid/main.css";
|
||||
import interactionPlugin from "@fullcalendar/interaction";
|
||||
import listPlugin from "@fullcalendar/list";
|
||||
// @ts-ignore
|
||||
import listStyle from "@fullcalendar/list/main.css";
|
||||
import "@material/mwc-button";
|
||||
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
|
||||
import type {
|
||||
CalendarViewChanged,
|
||||
CalendarEvent,
|
||||
ToggleButton,
|
||||
HomeAssistant,
|
||||
} from "../../types";
|
||||
import { mdiViewAgenda, mdiViewDay, mdiViewModule, mdiViewWeek } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
unsafeCSS,
|
||||
} from "lit-element";
|
||||
import memoize from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
import "../../components/ha-icon-button";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type {
|
||||
CalendarEvent,
|
||||
CalendarViewChanged,
|
||||
FullCalendarView,
|
||||
HomeAssistant,
|
||||
ToggleButton,
|
||||
} from "../../types";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-full-calendar": HAFullCalendar;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"view-changed": CalendarViewChanged;
|
||||
}
|
||||
}
|
||||
|
||||
const fullCalendarConfig = {
|
||||
const defaultFullCalendarConfig: CalendarOptions = {
|
||||
headerToolbar: false,
|
||||
plugins: [dayGridPlugin],
|
||||
plugins: [dayGridPlugin, listPlugin, interactionPlugin],
|
||||
initialView: "dayGridMonth",
|
||||
dayMaxEventRows: true,
|
||||
height: "parent",
|
||||
eventDisplay: "list-item",
|
||||
};
|
||||
|
||||
const viewButtons: ToggleButton[] = [
|
||||
{ label: "Month View", value: "dayGridMonth", icon: "hass:view-module" },
|
||||
{ label: "Week View", value: "dayGridWeek", icon: "hass:view-week" },
|
||||
{ label: "Day View", value: "dayGridDay", icon: "hass:view-day" },
|
||||
{ label: "Month View", value: "dayGridMonth", iconPath: mdiViewModule },
|
||||
{ label: "Week View", value: "dayGridWeek", iconPath: mdiViewWeek },
|
||||
{ label: "Day View", value: "dayGridDay", iconPath: mdiViewDay },
|
||||
{ label: "List View", value: "listWeek", iconPath: mdiViewAgenda },
|
||||
];
|
||||
|
||||
class HAFullCalendar extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property() public events: CalendarEvent[] = [];
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
@property({ attribute: false }) public events: CalendarEvent[] = [];
|
||||
|
||||
@property({ attribute: false }) public views: FullCalendarView[] = [
|
||||
"dayGridMonth",
|
||||
"dayGridWeek",
|
||||
"dayGridDay",
|
||||
];
|
||||
|
||||
@internalProperty() private calendar?: Calendar;
|
||||
|
||||
@internalProperty() private _activeView = "dayGridMonth";
|
||||
@internalProperty() private _activeView: FullCalendarView = "dayGridMonth";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const viewToggleButtons = this._viewToggleButtons(this.views);
|
||||
|
||||
return html`
|
||||
${this.calendar
|
||||
? html`
|
||||
@ -96,27 +114,12 @@ class HAFullCalendar extends LitElement {
|
||||
${this.calendar.view.title}
|
||||
</h1>
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewButtons}
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
`
|
||||
: html`
|
||||
<div class="controls">
|
||||
<mwc-button
|
||||
outlined
|
||||
class="today"
|
||||
@click=${this._handleToday}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.calendar.today"
|
||||
)}</mwc-button
|
||||
>
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<h1>
|
||||
${this.calendar.view.title}
|
||||
@ -138,6 +141,21 @@ class HAFullCalendar extends LitElement {
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<mwc-button
|
||||
outlined
|
||||
class="today"
|
||||
@click=${this._handleToday}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.calendar.today"
|
||||
)}</mwc-button
|
||||
>
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewToggleButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
@ -157,14 +175,25 @@ class HAFullCalendar extends LitElement {
|
||||
this.calendar.removeAllEventSources();
|
||||
this.calendar.addEventSource(this.events);
|
||||
}
|
||||
|
||||
if (changedProps.has("views") && !this.views.includes(this._activeView)) {
|
||||
this._activeView = this.views[0];
|
||||
this.calendar!.changeView(this._activeView);
|
||||
this._fireViewChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
const config = { ...fullCalendarConfig, locale: this.hass.language };
|
||||
const config: CalendarOptions = {
|
||||
...defaultFullCalendarConfig,
|
||||
locale: this.hass.language,
|
||||
};
|
||||
|
||||
config.dateClick = (info) => this._handleDateClick(info);
|
||||
config.eventClick = (info) => this._handleEventClick(info);
|
||||
|
||||
this.calendar = new Calendar(
|
||||
this.shadowRoot!.getElementById("calendar")!,
|
||||
// @ts-ignore
|
||||
config
|
||||
);
|
||||
|
||||
@ -172,6 +201,25 @@ class HAFullCalendar extends LitElement {
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _handleEventClick(info): void {
|
||||
if (info.view.type !== "dayGridMonth") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._activeView = "dayGridDay";
|
||||
this.calendar!.changeView("dayGridDay");
|
||||
this.calendar!.gotoDate(info.event.startStr);
|
||||
}
|
||||
|
||||
private _handleDateClick(info): void {
|
||||
if (info.view.type !== "dayGridMonth") {
|
||||
return;
|
||||
}
|
||||
this._activeView = "dayGridDay";
|
||||
this.calendar!.changeView("dayGridDay");
|
||||
this.calendar!.gotoDate(info.dateStr);
|
||||
}
|
||||
|
||||
private _handleNext(): void {
|
||||
this.calendar!.next();
|
||||
this._fireViewChanged();
|
||||
@ -201,14 +249,21 @@ class HAFullCalendar extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _viewToggleButtons = memoize((views) =>
|
||||
viewButtons.filter((button) =>
|
||||
views.includes(button.value as FullCalendarView)
|
||||
)
|
||||
);
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
${unsafeCSS(fullcalendarStyle)}
|
||||
${unsafeCSS(daygridStyle)}
|
||||
|
||||
:host {
|
||||
${unsafeCSS(listStyle)}
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
--fc-theme-standard-border-color: var(--divider-color);
|
||||
@ -262,6 +317,15 @@ class HAFullCalendar extends LitElement {
|
||||
#calendar {
|
||||
flex-grow: 1;
|
||||
background-color: var(--card-background-color);
|
||||
min-height: 400px;
|
||||
--fc-neutral-bg-color: var(--card-background-color);
|
||||
--fc-list-event-hover-bg-color: var(--card-background-color);
|
||||
--fc-theme-standard-border-color: var(--divider-color);
|
||||
--fc-border-color: var(--divider-color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.fc-theme-standard .fc-scrollgrid {
|
||||
@ -273,15 +337,20 @@ class HAFullCalendar extends LitElement {
|
||||
}
|
||||
|
||||
th.fc-col-header-cell.fc-day {
|
||||
color: #70757a;
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fc-daygrid-dot-event:hover {
|
||||
background-color: inherit
|
||||
}
|
||||
|
||||
.fc-daygrid-day-top {
|
||||
text-align: center;
|
||||
padding-top: 8px;
|
||||
padding-top: 5px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
table.fc-scrollgrid-sync-table
|
||||
@ -296,13 +365,21 @@ class HAFullCalendar extends LitElement {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
td.fc-day-today {
|
||||
.fc .fc-daygrid-day-number {
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.fc .fc-daygrid-day.fc-day-today {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
td.fc-day-today .fc-daygrid-day-top {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
td.fc-day-today .fc-daygrid-day-number {
|
||||
height: 24px;
|
||||
color: var(--text-primary-color);
|
||||
color: var(--text-primary-color) !important;
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
@ -342,6 +419,66 @@ class HAFullCalendar extends LitElement {
|
||||
.fc-popover-header {
|
||||
background-color: var(--secondary-background-color) !important;
|
||||
}
|
||||
|
||||
.fc-theme-standard .fc-list-day-frame {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.fc-list.fc-view,
|
||||
.fc-list-event.fc-event td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.fc-list-day.fc-day th {
|
||||
border-bottom: none;
|
||||
border-top: 1px solid var(--fc-theme-standard-border-color, #ddd) !important;
|
||||
}
|
||||
|
||||
.fc-list-day-text {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.fc-list-day-side-text {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.fc-list-table td,
|
||||
.fc-list-day-frame {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
:host([narrow]) .fc-dayGridMonth-view
|
||||
.fc-daygrid-dot-event
|
||||
.fc-event-time,
|
||||
:host([narrow]) .fc-dayGridMonth-view
|
||||
.fc-daygrid-dot-event
|
||||
.fc-event-title,
|
||||
:host([narrow]) .fc-dayGridMonth-view .fc-daygrid-day-bottom {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([narrow]) .fc .fc-dayGridMonth-view .fc-daygrid-event-harness-abs {
|
||||
visibility: visible !important;
|
||||
position: static;
|
||||
}
|
||||
|
||||
:host([narrow]) .fc-dayGridMonth-view .fc-daygrid-day-events {
|
||||
display: flex;
|
||||
min-height: 2em !important;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-height: 2em;
|
||||
height: 2em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host([narrow]) .fc-dayGridMonth-view .fc-scrollgrid-sync-table {
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -53,12 +53,6 @@ class PanelCalendar extends LitElement {
|
||||
selected: true,
|
||||
calendar,
|
||||
}));
|
||||
|
||||
if (!this._start || !this._end) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fetchEvents(this._start, this._end, this._selectedCalendars);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@ -88,8 +82,8 @@ class PanelCalendar extends LitElement {
|
||||
<mwc-formfield .label=${selCal.calendar.name}>
|
||||
<mwc-checkbox
|
||||
style=${styleMap({
|
||||
"--mdc-theme-secondary":
|
||||
selCal.calendar.backgroundColor,
|
||||
"--mdc-theme-secondary": selCal.calendar
|
||||
.backgroundColor!,
|
||||
})}
|
||||
.value=${selCal.calendar.entity_id}
|
||||
.checked=${selCal.selected}
|
||||
|
@ -44,6 +44,7 @@ import "./device-detail/ha-device-entities-card";
|
||||
import "./device-detail/ha-device-info-card";
|
||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||
stateName?: string | null;
|
||||
@ -307,11 +308,15 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: "";
|
||||
})
|
||||
: html`
|
||||
<paper-item class="no-link"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.devices.automation.no_automations"
|
||||
)}</paper-item
|
||||
>
|
||||
<paper-item class="no-link">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.add_prompt",
|
||||
"name",
|
||||
this.hass.localize(
|
||||
"ui.panel.config.devices.automation.automations"
|
||||
)
|
||||
)}
|
||||
</paper-item>
|
||||
`}
|
||||
</ha-card>
|
||||
`
|
||||
@ -375,11 +380,15 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: "";
|
||||
})
|
||||
: html`
|
||||
<paper-item class="no-link"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.devices.scene.no_scenes"
|
||||
)}</paper-item
|
||||
>
|
||||
<paper-item class="no-link">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.add_prompt",
|
||||
"name",
|
||||
this.hass.localize(
|
||||
"ui.panel.config.devices.scene.scenes"
|
||||
)
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
}
|
||||
</ha-card>
|
||||
@ -428,9 +437,13 @@ export class HaConfigDevicePage extends LitElement {
|
||||
: html`
|
||||
<paper-item class="no-link">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.devices.script.no_scripts"
|
||||
)}</paper-item
|
||||
>
|
||||
"ui.panel.config.devices.add_prompt",
|
||||
"name",
|
||||
this.hass.localize(
|
||||
"ui.panel.config.devices.script.scripts"
|
||||
)
|
||||
)}
|
||||
</paper-item>
|
||||
`}
|
||||
</ha-card>
|
||||
`
|
||||
@ -549,11 +562,14 @@ export class HaConfigDevicePage extends LitElement {
|
||||
|
||||
const renameEntityid =
|
||||
this.showAdvanced &&
|
||||
confirm(
|
||||
this.hass.localize(
|
||||
(await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.devices.confirm_rename_entity_ids"
|
||||
)
|
||||
);
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.devices.confirm_rename_entity_ids_warning"
|
||||
),
|
||||
}));
|
||||
|
||||
const updateProms = entities.map((entity) => {
|
||||
const name = entity.name || entity.stateName;
|
||||
@ -702,6 +718,10 @@ export class HaConfigDevicePage extends LitElement {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-card {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
ha-card a {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
@ -352,6 +352,13 @@ class HaPanelConfig extends HassRouterPage {
|
||||
/* webpackChunkName: "panel-config-mqtt" */ "./integrations/integration-panels/mqtt/mqtt-config-panel"
|
||||
),
|
||||
},
|
||||
ozw: {
|
||||
tag: "ozw-config-router",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-ozw" */ "./integrations/integration-panels/ozw/ozw-config-router"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -55,6 +55,10 @@ const integrationsWithPanel = {
|
||||
buttonLocalizeKey: "ui.panel.config.zha.button",
|
||||
path: "/config/zha/dashboard",
|
||||
},
|
||||
ozw: {
|
||||
buttonLocalizeKey: "ui.panel.config.ozw.button",
|
||||
path: "/config/ozw/dashboard",
|
||||
},
|
||||
zwave: {
|
||||
buttonLocalizeKey: "ui.panel.config.zwave.button",
|
||||
path: "/config/zwave",
|
||||
|
@ -0,0 +1,227 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { mdiCircle, mdiCheckCircle, mdiCloseCircle, mdiZWave } from "@mdi/js";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
OZWInstance,
|
||||
fetchOZWInstances,
|
||||
networkOnlineStatuses,
|
||||
networkOfflineStatuses,
|
||||
networkStartingStatuses,
|
||||
} from "../../../../../data/ozw";
|
||||
|
||||
export const ozwTabs: PageNavigation[] = [];
|
||||
|
||||
@customElement("ozw-config-dashboard")
|
||||
class OZWConfigDashboard extends LitElement {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public configEntryId?: string;
|
||||
|
||||
@internalProperty() private _instances: OZWInstance[] = [];
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.hass) {
|
||||
this._fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
this._instances = await fetchOZWInstances(this.hass!);
|
||||
if (this._instances.length === 1) {
|
||||
navigate(
|
||||
this,
|
||||
`/config/ozw/network/${this._instances[0].ozw_instance}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${ozwTabs}
|
||||
back-path="/config/integrations"
|
||||
>
|
||||
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
|
||||
<div slot="header">
|
||||
${this.hass.localize("ui.panel.config.ozw.select_instance.header")}
|
||||
</div>
|
||||
|
||||
<div slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.select_instance.introduction"
|
||||
)}
|
||||
</div>
|
||||
${this._instances.length > 0
|
||||
? html`
|
||||
${this._instances.map((instance) => {
|
||||
let status = "unknown";
|
||||
let icon = mdiCircle;
|
||||
if (networkOnlineStatuses.includes(instance.Status)) {
|
||||
status = "online";
|
||||
icon = mdiCheckCircle;
|
||||
}
|
||||
if (networkStartingStatuses.includes(instance.Status)) {
|
||||
status = "starting";
|
||||
}
|
||||
if (networkOfflineStatuses.includes(instance.Status)) {
|
||||
status = "offline";
|
||||
icon = mdiCloseCircle;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<a
|
||||
href="/config/ozw/network/${instance.ozw_instance}"
|
||||
aria-role="option"
|
||||
tabindex="-1"
|
||||
>
|
||||
<paper-icon-item>
|
||||
<ha-svg-icon .path=${mdiZWave} slot="item-icon">
|
||||
</ha-svg-icon>
|
||||
<paper-item-body>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.common.instance"
|
||||
)}
|
||||
${instance.ozw_instance}
|
||||
<div secondary>
|
||||
<ha-svg-icon
|
||||
.path=${icon}
|
||||
class="network-status-icon ${status}"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.network_status." + status
|
||||
)}
|
||||
-
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.network_status.details." +
|
||||
instance.Status.toLowerCase()
|
||||
)}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.common.controller"
|
||||
)}
|
||||
: ${instance.getControllerPath}<br />
|
||||
OZWDaemon ${instance.OZWDaemon_Version} (OpenZWave
|
||||
${instance.OpenZWave_Version})
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
`
|
||||
: ``}
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card:last-child {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-config-section {
|
||||
margin-top: -12px;
|
||||
}
|
||||
:host([narrow]) ha-config-section {
|
||||
margin-top: -20px;
|
||||
}
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
paper-item-body {
|
||||
margin: 16px 0;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
position: relative;
|
||||
display: block;
|
||||
outline: 0;
|
||||
}
|
||||
ha-svg-icon.network-status-icon {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
.online {
|
||||
color: green;
|
||||
}
|
||||
.starting {
|
||||
color: orange;
|
||||
}
|
||||
.offline {
|
||||
color: red;
|
||||
}
|
||||
ha-svg-icon,
|
||||
ha-icon-next {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.iron-selected paper-item::before,
|
||||
a:not(.iron-selected):focus::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
transition: opacity 15ms linear;
|
||||
will-change: opacity;
|
||||
}
|
||||
a:not(.iron-selected):focus::before {
|
||||
background-color: currentColor;
|
||||
opacity: var(--dark-divider-opacity);
|
||||
}
|
||||
.iron-selected paper-item:focus::before,
|
||||
.iron-selected:focus paper-item::before {
|
||||
opacity: 0.2;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ozw-config-dashboard": OZWConfigDashboard;
|
||||
}
|
||||
}
|
@ -0,0 +1,253 @@
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-icon-next";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import "../../../ha-config-section";
|
||||
import { mdiCircle, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import "../../../../../layouts/hass-tabs-subpage";
|
||||
import type { PageNavigation } from "../../../../../layouts/hass-tabs-subpage";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
OZWInstance,
|
||||
fetchOZWNetworkStatus,
|
||||
fetchOZWNetworkStatistics,
|
||||
networkOnlineStatuses,
|
||||
networkOfflineStatuses,
|
||||
networkStartingStatuses,
|
||||
OZWNetworkStatistics,
|
||||
} from "../../../../../data/ozw";
|
||||
|
||||
export const ozwTabs: PageNavigation[] = [];
|
||||
|
||||
@customElement("ozw-config-network")
|
||||
class OZWConfigNetwork extends LitElement {
|
||||
@property({ type: Object }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Object }) public route!: Route;
|
||||
|
||||
@property({ type: Boolean }) public narrow!: boolean;
|
||||
|
||||
@property({ type: Boolean }) public isWide!: boolean;
|
||||
|
||||
@property() public configEntryId?: string;
|
||||
|
||||
@property() public ozw_instance = 0;
|
||||
|
||||
@internalProperty() private _network?: OZWInstance;
|
||||
|
||||
@internalProperty() private _statistics?: OZWNetworkStatistics;
|
||||
|
||||
@internalProperty() private _status = "unknown";
|
||||
|
||||
@internalProperty() private _icon = mdiCircle;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
if (this.ozw_instance <= 0) {
|
||||
navigate(this, "/config/ozw/dashboard", true);
|
||||
}
|
||||
if (this.hass) {
|
||||
this._fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
this._network = await fetchOZWNetworkStatus(this.hass!, this.ozw_instance);
|
||||
this._statistics = await fetchOZWNetworkStatistics(
|
||||
this.hass!,
|
||||
this.ozw_instance
|
||||
);
|
||||
if (networkOnlineStatuses.includes(this._network.Status)) {
|
||||
this._status = "online";
|
||||
this._icon = mdiCheckCircle;
|
||||
}
|
||||
if (networkStartingStatuses.includes(this._network.Status)) {
|
||||
this._status = "starting";
|
||||
}
|
||||
if (networkOfflineStatuses.includes(this._network.Status)) {
|
||||
this._status = "offline";
|
||||
this._icon = mdiCloseCircle;
|
||||
}
|
||||
}
|
||||
|
||||
private _generateServiceButton(service: string) {
|
||||
return html`
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
domain="ozw"
|
||||
service="${service}"
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.ozw.services." + service)}
|
||||
</ha-call-service-button>
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
.tabs=${ozwTabs}
|
||||
>
|
||||
<ha-config-section .narrow=${this.narrow} .isWide=${this.isWide}>
|
||||
<div slot="header">
|
||||
${this.hass.localize("ui.panel.config.ozw.network.header")}
|
||||
</div>
|
||||
|
||||
<div slot="introduction">
|
||||
${this.hass.localize("ui.panel.config.ozw.network.introduction")}
|
||||
</div>
|
||||
${this._network
|
||||
? html`
|
||||
<ha-card class="content network-status">
|
||||
<div class="card-content">
|
||||
<div class="details">
|
||||
<ha-svg-icon
|
||||
.path=${this._icon}
|
||||
class="network-status-icon ${this._status}"
|
||||
slot="item-icon"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.common.network"
|
||||
)}
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.network_status." + this._status
|
||||
)}
|
||||
<br />
|
||||
<small>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.network_status.details." +
|
||||
this._network.Status.toLowerCase()
|
||||
)}
|
||||
</small>
|
||||
</div>
|
||||
<div class="secondary">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.common.ozw_instance"
|
||||
)}
|
||||
${this._network.ozw_instance}
|
||||
${this._statistics
|
||||
? html`
|
||||
•
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.network.node_count",
|
||||
"count",
|
||||
this._statistics.node_count
|
||||
)}
|
||||
`
|
||||
: ``}
|
||||
<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.ozw.common.controller"
|
||||
)}:
|
||||
${this._network.getControllerPath}<br />
|
||||
OZWDaemon ${this._network.OZWDaemon_Version} (OpenZWave
|
||||
${this._network.OpenZWave_Version})
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
${this._generateServiceButton("add_node")}
|
||||
${this._generateServiceButton("remove_node")}
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ``}
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.online {
|
||||
color: green;
|
||||
}
|
||||
.starting {
|
||||
color: orange;
|
||||
}
|
||||
.offline {
|
||||
color: red;
|
||||
}
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
position: relative;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.network-status {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.network-status div.details {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.network-status ha-svg-icon {
|
||||
display: block;
|
||||
margin: 0px auto 16px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.network-status small {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.card-actions.warning ha-call-service-button {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
.toggle-help-icon {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: 0;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
ha-service-description {
|
||||
display: block;
|
||||
color: grey;
|
||||
padding: 0 8px 12px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ozw-config-network": OZWConfigNetwork;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
import { customElement, property } from "lit-element";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../../../layouts/hass-router-page";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
|
||||
@customElement("ozw-config-router")
|
||||
class OZWConfigRouter extends HassRouterPage {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
private _configEntry = new URLSearchParams(window.location.search).get(
|
||||
"config_entry"
|
||||
);
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "dashboard",
|
||||
showLoading: true,
|
||||
routes: {
|
||||
dashboard: {
|
||||
tag: "ozw-config-dashboard",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "ozw-config-dashboard" */ "./ozw-config-dashboard"
|
||||
),
|
||||
},
|
||||
network: {
|
||||
tag: "ozw-config-network",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "ozw-config-network" */ "./ozw-config-network"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
protected updatePageEl(el): void {
|
||||
el.route = this.routeTail;
|
||||
el.hass = this.hass;
|
||||
el.isWide = this.isWide;
|
||||
el.narrow = this.narrow;
|
||||
el.configEntryId = this._configEntry;
|
||||
if (this._currentPage === "network") {
|
||||
el.ozw_instance = this.routeTail.path.substr(1);
|
||||
}
|
||||
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (this._configEntry && !searchParams.has("config_entry")) {
|
||||
searchParams.append("config_entry", this._configEntry);
|
||||
navigate(
|
||||
this,
|
||||
`${this.routeTail.prefix}${
|
||||
this.routeTail.path
|
||||
}?${searchParams.toString()}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ozw-config-router": OZWConfigRouter;
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { isServiceLoaded } from "../../../common/config/is_service_loaded";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../ha-config-section";
|
||||
@ -35,6 +35,7 @@ const reloadableDomains = [
|
||||
"input_number",
|
||||
"input_datetime",
|
||||
"input_select",
|
||||
"template",
|
||||
];
|
||||
|
||||
@customElement("ha-config-server-control")
|
||||
@ -202,7 +203,7 @@ export class HaConfigServerControl extends LitElement {
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
${reloadableDomains.map((domain) =>
|
||||
isComponentLoaded(this.hass, domain)
|
||||
isServiceLoaded(this.hass, domain, "reload")
|
||||
? html`<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
.hass=${this.hass}
|
||||
|
@ -3,23 +3,23 @@ import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
TemplateResult,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-formfield";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/map/ha-location-editor";
|
||||
import { Tag, UpdateTagParams } from "../../../data/tag";
|
||||
import { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { TagDetailDialogParams } from "./show-dialog-tag-detail";
|
||||
import { UpdateTagParams, Tag } from "../../../data/tag";
|
||||
|
||||
@customElement("dialog-tag-detail")
|
||||
class DialogTagDetail extends LitElement implements HassDialog {
|
||||
@ -162,7 +162,7 @@ class DialogTagDetail extends LitElement implements HassDialog {
|
||||
} else {
|
||||
newValue = await this._params!.createEntry(values, this._id);
|
||||
}
|
||||
this._params = undefined;
|
||||
this.closeDialog();
|
||||
} catch (err) {
|
||||
this._error = err ? err.message : "Unknown error";
|
||||
} finally {
|
||||
@ -172,11 +172,12 @@ class DialogTagDetail extends LitElement implements HassDialog {
|
||||
}
|
||||
|
||||
private async _updateWriteEntry() {
|
||||
const openWrite = this._params?.openWrite;
|
||||
const tag = await this._updateEntry();
|
||||
if (!tag) {
|
||||
if (!tag || !openWrite) {
|
||||
return;
|
||||
}
|
||||
this._params?.openWrite!(tag);
|
||||
openWrite(tag);
|
||||
}
|
||||
|
||||
private async _deleteEntry() {
|
||||
|
@ -12,6 +12,7 @@ import memoizeOne from "memoize-one";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-relative-time";
|
||||
import { showAutomationEditor, TagTrigger } from "../../../data/automation";
|
||||
import {
|
||||
createTag,
|
||||
deleteTag,
|
||||
@ -23,14 +24,13 @@ import {
|
||||
UpdateTagParams,
|
||||
} from "../../../data/tag";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { getExternalConfig } from "../../../external_app/external_config";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showTagDetailDialog } from "./show-dialog-tag-detail";
|
||||
import "./tag-image";
|
||||
import { getExternalConfig } from "../../../external_app/external_config";
|
||||
import { showAutomationEditor, TagTrigger } from "../../../data/automation";
|
||||
|
||||
export interface TagRowData extends Tag {
|
||||
last_scanned_datetime: Date | null;
|
||||
@ -70,12 +70,12 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
template: (name, tag: any) => html`${name}
|
||||
${narrow
|
||||
? html`<div class="secondary">
|
||||
${tag.last_scanned
|
||||
${tag.last_scanned_datetime
|
||||
? html`<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetimeObj=${tag.last_scanned_datetime}
|
||||
></ha-relative-time>`
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
: this.hass.localize("ui.panel.config.tags.never_scanned")}
|
||||
</div>`
|
||||
: ""}`,
|
||||
},
|
||||
@ -94,7 +94,7 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
||||
.hass=${this.hass}
|
||||
.datetimeObj=${last_scanned_datetime}
|
||||
></ha-relative-time>`
|
||||
: this.hass.localize("ui.components.relative_time.never")}
|
||||
: this.hass.localize("ui.panel.config.tags.never_scanned")}
|
||||
`,
|
||||
};
|
||||
}
|
||||
|
@ -1,204 +0,0 @@
|
||||
import "../../../components/ha-circular-progress";
|
||||
import { timeOut } from "@polymer/polymer/lib/utils/async";
|
||||
import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../components/ha-code-editor";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import "../../../styles/polymer-ha-style";
|
||||
|
||||
class HaPanelDevTemplate extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style iron-flex iron-positioning"></style>
|
||||
<style>
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.edit-pane {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.edit-pane a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.horizontal .edit-pane {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.render-pane {
|
||||
position: relative;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.render-spinner {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.rendered {
|
||||
@apply --paper-font-code1;
|
||||
clear: both;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.rendered.error {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class$="[[computeFormClasses(narrow)]]">
|
||||
<div class="edit-pane">
|
||||
<p>
|
||||
[[localize('ui.panel.developer-tools.tabs.templates.description')]]
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="http://jinja.pocoo.org/docs/dev/templates/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>[[localize('ui.panel.developer-tools.tabs.templates.jinja_documentation')]]</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/configuration/templating/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>[[localize('ui.panel.developer-tools.tabs.templates.template_extensions')]]</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<p>[[localize('ui.panel.developer-tools.tabs.templates.editor')]]</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
value="[[template]]"
|
||||
error="[[error]]"
|
||||
autofocus
|
||||
on-value-changed="templateChanged"
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
|
||||
<div class="render-pane">
|
||||
<ha-circular-progress
|
||||
class="render-spinner"
|
||||
active="[[rendering]]"
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<pre class$="[[computeRenderedClasses(error)]]">[[processed]]</pre>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
error: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
rendering: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
template: {
|
||||
type: String,
|
||||
/* eslint-disable max-len */
|
||||
value: `Imitate available variables:
|
||||
{% set my_test_json = {
|
||||
"temperature": 25,
|
||||
"unit": "°C"
|
||||
} %}
|
||||
|
||||
The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}.
|
||||
|
||||
{% if is_state("device_tracker.paulus", "home") and
|
||||
is_state("device_tracker.anne_therese", "home") -%}
|
||||
You are both home, you silly
|
||||
{%- else -%}
|
||||
Anne Therese is at {{ states("device_tracker.anne_therese") }}
|
||||
Paulus is at {{ states("device_tracker.paulus") }}
|
||||
{%- endif %}
|
||||
|
||||
For loop example:
|
||||
{% for state in states.sensor -%}
|
||||
{%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
|
||||
{{ state.name | lower }} is {{state.state_with_unit}}
|
||||
{%- endfor %}.`,
|
||||
/* eslint-enable max-len */
|
||||
},
|
||||
|
||||
processed: {
|
||||
type: String,
|
||||
value: "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.renderTemplate();
|
||||
}
|
||||
|
||||
computeFormClasses(narrow) {
|
||||
return narrow ? "content" : "content layout horizontal";
|
||||
}
|
||||
|
||||
computeRenderedClasses(error) {
|
||||
return error ? "error rendered" : "rendered";
|
||||
}
|
||||
|
||||
templateChanged(ev) {
|
||||
this.template = ev.detail.value;
|
||||
if (this.error) {
|
||||
this.error = false;
|
||||
}
|
||||
this._debouncer = Debouncer.debounce(
|
||||
this._debouncer,
|
||||
timeOut.after(500),
|
||||
() => {
|
||||
this.renderTemplate();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
renderTemplate() {
|
||||
this.rendering = true;
|
||||
|
||||
this.hass.callApi("POST", "template", { template: this.template }).then(
|
||||
function (processed) {
|
||||
this.processed = processed;
|
||||
this.rendering = false;
|
||||
}.bind(this),
|
||||
function (error) {
|
||||
this.processed =
|
||||
(error && error.body && error.body.message) ||
|
||||
this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.unknown_error_template"
|
||||
);
|
||||
this.error = true;
|
||||
this.rendering = false;
|
||||
}.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("developer-tools-template", HaPanelDevTemplate);
|
280
src/panels/developer-tools/template/developer-tools-template.ts
Normal file
280
src/panels/developer-tools/template/developer-tools-template.ts
Normal file
@ -0,0 +1,280 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "../../../components/ha-code-editor";
|
||||
import { subscribeRenderTemplate } from "../../../data/ws-templates";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
const DEMO_TEMPLATE = `{## Imitate available variables: ##}
|
||||
{% set my_test_json = {
|
||||
"temperature": 25,
|
||||
"unit": "°C"
|
||||
} %}
|
||||
|
||||
The temperature is {{ my_test_json.temperature }} {{ my_test_json.unit }}.
|
||||
|
||||
{% if is_state("sun.sun", "above_horizon") -%}
|
||||
The sun rose {{ relative_time(states.sun.sun.last_changed) }} ago.
|
||||
{%- else -%}
|
||||
The sun will rise at {{ as_timestamp(strptime(state_attr("sun.sun", "next_rising"), "")) | timestamp_local }}.
|
||||
{%- endif %}
|
||||
|
||||
For loop example getting 3 entity values:
|
||||
|
||||
{% for states in states | slice(3) -%}
|
||||
{% set state = states | first %}
|
||||
{%- if loop.first %}The {% elif loop.last %} and the {% else %}, the {% endif -%}
|
||||
{{ state.name | lower }} is {{state.state_with_unit}}
|
||||
{%- endfor %}.`;
|
||||
|
||||
@customElement("developer-tools-template")
|
||||
class HaPanelDevTemplate extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public narrow!: boolean;
|
||||
|
||||
@internalProperty() private _error = false;
|
||||
|
||||
@internalProperty() private _rendering = false;
|
||||
|
||||
@internalProperty() private _processed = "";
|
||||
|
||||
@internalProperty() private _unsubRenderTemplate?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _template = "";
|
||||
|
||||
private _inited = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this._template && !this._unsubRenderTemplate) {
|
||||
this._subscribeTemplate();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
this._unsubscribeTemplate();
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
if (localStorage && localStorage["panel-dev-template-template"]) {
|
||||
this._template = localStorage["panel-dev-template-template"];
|
||||
} else {
|
||||
this._template = DEMO_TEMPLATE;
|
||||
}
|
||||
this._subscribeTemplate();
|
||||
this._inited = true;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
class="content ${classMap({
|
||||
layout: !this.narrow,
|
||||
horizontal: !this.narrow,
|
||||
})}"
|
||||
>
|
||||
<div class="edit-pane">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.description"
|
||||
)}
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="http://jinja.pocoo.org/docs/dev/templates/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.jinja_documentation"
|
||||
)}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/configuration/templating/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.template_extensions"
|
||||
)}</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.editor"
|
||||
)}
|
||||
</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.value=${this._template}
|
||||
.error=${this._error}
|
||||
autofocus
|
||||
@value-changed=${this._templateChanged}
|
||||
></ha-code-editor>
|
||||
<mwc-button @click=${this._restoreDemo}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.reset"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
|
||||
<div class="render-pane">
|
||||
<ha-circular-progress
|
||||
class="render-spinner"
|
||||
.active=${this._rendering}
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<pre class="rendered ${classMap({ error: this._error })}">
|
||||
${this._processed}</pre
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.edit-pane {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.edit-pane a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.horizontal .edit-pane {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.render-pane {
|
||||
position: relative;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.render-spinner {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.rendered {
|
||||
@apply --paper-font-code1;
|
||||
clear: both;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.rendered.error {
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private _debounceRender = debounce(
|
||||
() => {
|
||||
this._subscribeTemplate();
|
||||
this._storeTemplate();
|
||||
},
|
||||
500,
|
||||
false
|
||||
);
|
||||
|
||||
private _templateChanged(ev) {
|
||||
this._template = ev.detail.value;
|
||||
if (this._error) {
|
||||
this._error = false;
|
||||
}
|
||||
this._debounceRender();
|
||||
}
|
||||
|
||||
private async _subscribeTemplate() {
|
||||
this._rendering = true;
|
||||
await this._unsubscribeTemplate();
|
||||
try {
|
||||
this._unsubRenderTemplate = subscribeRenderTemplate(
|
||||
this.hass.connection,
|
||||
(result) => {
|
||||
this._processed = result;
|
||||
},
|
||||
{
|
||||
template: this._template,
|
||||
}
|
||||
);
|
||||
await this._unsubRenderTemplate;
|
||||
} catch (err) {
|
||||
this._error = true;
|
||||
if (err.message) {
|
||||
this._processed = err.message;
|
||||
}
|
||||
this._unsubRenderTemplate = undefined;
|
||||
} finally {
|
||||
this._rendering = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _unsubscribeTemplate(): Promise<void> {
|
||||
if (!this._unsubRenderTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const unsub = await this._unsubRenderTemplate;
|
||||
unsub();
|
||||
this._unsubRenderTemplate = undefined;
|
||||
} catch (e) {
|
||||
if (e.code === "not_found") {
|
||||
// If we get here, the connection was probably already closed. Ignore.
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _storeTemplate() {
|
||||
if (!this._inited) {
|
||||
return;
|
||||
}
|
||||
localStorage["panel-dev-template-template"] = this._template;
|
||||
}
|
||||
|
||||
private _restoreDemo() {
|
||||
this._template = DEMO_TEMPLATE;
|
||||
this._subscribeTemplate();
|
||||
delete localStorage["panel-dev-template-template"];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"developer-tools-template": HaPanelDevTemplate;
|
||||
}
|
||||
}
|
@ -118,6 +118,26 @@ class HaLogbook extends LitElement {
|
||||
? ` (${item_username})`
|
||||
: ``}</span
|
||||
>
|
||||
${!item.context_event_type
|
||||
? ""
|
||||
: item.context_event_type === "call_service"
|
||||
? // Service Call
|
||||
html` by service ${item.context_domain}.${item.context_service}`
|
||||
: item.context_entity_id === item.entity_id
|
||||
? // HomeKit or something that self references
|
||||
html` by
|
||||
${item.context_name
|
||||
? item.context_name
|
||||
: item.context_event_type}`
|
||||
: // Another entity such as an automation or script
|
||||
html` by
|
||||
<a
|
||||
href="#"
|
||||
@click=${this._entityClicked}
|
||||
.entityId=${item.context_entity_id}
|
||||
class="name"
|
||||
>${item.context_entity_id_name}</a
|
||||
>`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
213
src/panels/lovelace/cards/hui-calendar-card.ts
Normal file
213
src/panels/lovelace/cards/hui-calendar-card.ts
Normal file
@ -0,0 +1,213 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
internalProperty,
|
||||
} from "lit-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../calendar/ha-full-calendar";
|
||||
|
||||
import type {
|
||||
HomeAssistant,
|
||||
CalendarEvent,
|
||||
Calendar,
|
||||
CalendarViewChanged,
|
||||
FullCalendarView,
|
||||
} from "../../../types";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { CalendarCardConfig } from "./types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import "../components/hui-warning";
|
||||
import { fetchCalendarEvents } from "../../../data/calendar";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { HA_COLOR_PALETTE } from "../../../common/const";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
|
||||
@customElement("hui-calendar-card")
|
||||
export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(
|
||||
/* webpackChunkName: "hui-calendar-card-editor" */ "../editor/config-elements/hui-calendar-card-editor"
|
||||
);
|
||||
return document.createElement("hui-calendar-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFill: string[]
|
||||
) {
|
||||
const includeDomains = ["calendar"];
|
||||
const maxEntities = 2;
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
entitiesFill,
|
||||
includeDomains
|
||||
);
|
||||
|
||||
return {
|
||||
entities: foundEntities,
|
||||
};
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public _events: CalendarEvent[] = [];
|
||||
|
||||
@internalProperty() private _config?: CalendarCardConfig;
|
||||
|
||||
@internalProperty() private _calendars: Calendar[] = [];
|
||||
|
||||
@internalProperty() private _narrow = false;
|
||||
|
||||
@internalProperty() private _veryNarrow = false;
|
||||
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
|
||||
public setConfig(config: CalendarCardConfig): void {
|
||||
if (!config.entities) {
|
||||
throw new Error("Entities must be defined");
|
||||
}
|
||||
|
||||
if (config.entities && !Array.isArray(config.entities)) {
|
||||
throw new Error("Entities need to be an array");
|
||||
}
|
||||
|
||||
this._calendars = config!.entities.map((entity, idx) => {
|
||||
return {
|
||||
entity_id: entity,
|
||||
backgroundColor: `#${HA_COLOR_PALETTE[idx % HA_COLOR_PALETTE.length]}`,
|
||||
};
|
||||
});
|
||||
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
return 4;
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.updateComplete.then(() => this._attachObserver());
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config || !this.hass || !this._calendars.length) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const views: FullCalendarView[] = this._veryNarrow
|
||||
? ["listWeek"]
|
||||
: ["listWeek", "dayGridMonth", "dayGridDay"];
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<div class="header">${this._config.title}</div>
|
||||
<ha-full-calendar
|
||||
.narrow=${this._narrow}
|
||||
.events=${this._events}
|
||||
.hass=${this.hass}
|
||||
.views=${views}
|
||||
@view-changed=${this._handleViewChanged}
|
||||
></ha-full-calendar>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
const oldConfig = changedProps.get("_config") as
|
||||
| CalendarCardConfig
|
||||
| undefined;
|
||||
|
||||
if (
|
||||
!oldHass ||
|
||||
!oldConfig ||
|
||||
(changedProps.has("hass") && oldHass.themes !== this.hass.themes) ||
|
||||
(changedProps.has("_config") && oldConfig.theme !== this._config.theme)
|
||||
) {
|
||||
applyThemesOnElement(this, this.hass.themes, this._config!.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleViewChanged(
|
||||
ev: HASSDomEvent<CalendarViewChanged>
|
||||
): Promise<void> {
|
||||
this._events = await fetchCalendarEvents(
|
||||
this.hass!,
|
||||
ev.detail.start,
|
||||
ev.detail.end,
|
||||
this._calendars
|
||||
);
|
||||
}
|
||||
|
||||
private _measureCard() {
|
||||
const card = this.shadowRoot!.querySelector("ha-card");
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
this._narrow = card.offsetWidth < 870;
|
||||
this._veryNarrow = card.offsetWidth < 350;
|
||||
}
|
||||
|
||||
private async _attachObserver(): Promise<void> {
|
||||
if (!this._resizeObserver) {
|
||||
await installResizeObserver();
|
||||
this._resizeObserver = new ResizeObserver(
|
||||
debounce(() => this._measureCard(), 250, false)
|
||||
);
|
||||
}
|
||||
const card = this.shadowRoot!.querySelector("ha-card");
|
||||
// If we show an error or warning there is no ha-card
|
||||
if (!card) {
|
||||
return;
|
||||
}
|
||||
this._resizeObserver.observe(card);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-card {
|
||||
position: relative;
|
||||
padding: 0 8px 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
font-size: var(--ha-card-header-font-size, 24px);
|
||||
line-height: 1.2;
|
||||
padding-top: 16px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-calendar-card": HuiCalendarCard;
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ import { processConfigEntities } from "../common/process-config-entities";
|
||||
import { EntityConfig } from "../entity-rows/types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { HistoryGraphCardConfig } from "./types";
|
||||
import { HistoryResult } from "../../../data/history";
|
||||
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
||||
|
||||
@customElement("hui-history-graph-card")
|
||||
export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
@ -49,7 +51,7 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@internalProperty() private _stateHistory?: any;
|
||||
@internalProperty() private _stateHistory?: HistoryResult;
|
||||
|
||||
@internalProperty() private _config?: HistoryGraphCardConfig;
|
||||
|
||||
@ -59,10 +61,10 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
|
||||
private _cacheConfig?: CacheConfig;
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
private _fetching = false;
|
||||
|
||||
private _date?: Date;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 4;
|
||||
}
|
||||
@ -97,9 +99,8 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
};
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._clearInterval();
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntitiesChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
@ -108,21 +109,19 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!changedProps.has("_config")) {
|
||||
if (!changedProps.has("_config") && !changedProps.has("hass")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldConfig = changedProps.get("_config") as HistoryGraphCardConfig;
|
||||
|
||||
if (oldConfig !== this._config) {
|
||||
if (changedProps.has("_config") && oldConfig !== this._config) {
|
||||
this._getStateHistory();
|
||||
} else if (
|
||||
this._cacheConfig.refresh &&
|
||||
Date.now() - this._date!.getTime() >= this._cacheConfig.refresh * 100
|
||||
) {
|
||||
this._getStateHistory();
|
||||
this._clearInterval();
|
||||
|
||||
if (!this._interval && this._cacheConfig.refresh) {
|
||||
this._interval = window.setInterval(() => {
|
||||
this._getStateHistory();
|
||||
}, this._cacheConfig.refresh * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,27 +154,23 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
if (this._fetching) {
|
||||
return;
|
||||
}
|
||||
this._date = new Date();
|
||||
this._fetching = true;
|
||||
try {
|
||||
this._stateHistory = await getRecentWithCache(
|
||||
this.hass!,
|
||||
this._cacheConfig!.cacheKey,
|
||||
this._cacheConfig!,
|
||||
this.hass!.localize,
|
||||
this.hass!.language
|
||||
);
|
||||
this._stateHistory = {
|
||||
...(await getRecentWithCache(
|
||||
this.hass!,
|
||||
this._cacheConfig!.cacheKey,
|
||||
this._cacheConfig!,
|
||||
this.hass!.localize,
|
||||
this.hass!.language
|
||||
)),
|
||||
};
|
||||
} finally {
|
||||
this._fetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _clearInterval(): void {
|
||||
if (this._interval) {
|
||||
window.clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "../../../components/ha-icon-button";
|
||||
import "@polymer/paper-progress/paper-progress";
|
||||
import type { PaperProgressElement } from "@polymer/paper-progress/paper-progress";
|
||||
import {
|
||||
@ -6,9 +5,9 @@ import {
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
internalProperty,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
@ -25,12 +24,17 @@ import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import {
|
||||
computeMediaDescription,
|
||||
CONTRAST_RATIO,
|
||||
ControlButton,
|
||||
getCurrentProgress,
|
||||
MediaPickedEvent,
|
||||
SUPPORTS_PLAY,
|
||||
SUPPORT_BROWSE_MEDIA,
|
||||
SUPPORT_NEXT_TRACK,
|
||||
SUPPORT_PAUSE,
|
||||
SUPPORT_PREVIOUS_TRACK,
|
||||
@ -38,17 +42,16 @@ import {
|
||||
SUPPORT_STOP,
|
||||
SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON,
|
||||
ControlButton,
|
||||
} from "../../../data/media-player";
|
||||
import type { HomeAssistant, MediaEntity } from "../../../types";
|
||||
import { contrast } from "../common/color/contrast";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-marquee";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { MediaControlCardConfig } from "./types";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
import "../components/hui-marquee";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { MediaControlCardConfig } from "./types";
|
||||
|
||||
function getContrastRatio(
|
||||
rgb1: [number, number, number],
|
||||
@ -185,7 +188,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
return { type: "media-control", entity: foundEntities[0] || "" };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@internalProperty() private _config?: MediaControlCardConfig;
|
||||
|
||||
@ -389,12 +392,27 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
${controls!.map(
|
||||
(control) => html`
|
||||
<ha-icon-button
|
||||
.title=${this.hass.localize(
|
||||
`ui.card.media_player.${control.action}`
|
||||
)}
|
||||
.icon=${control.icon}
|
||||
action=${control.action}
|
||||
@click=${this._handleClick}
|
||||
></ha-icon-button>
|
||||
`
|
||||
)}
|
||||
${supportsFeature(stateObj, SUPPORT_BROWSE_MEDIA)
|
||||
? html`
|
||||
<ha-icon-button
|
||||
class="browse-media"
|
||||
icon="hass:folder-multiple"
|
||||
.title=${this.hass.localize(
|
||||
"ui.card.media_player.browse_media"
|
||||
)}
|
||||
@click=${this._handleBrowseMedia}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
@ -643,14 +661,31 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
});
|
||||
}
|
||||
|
||||
private _handleBrowseMedia(): void {
|
||||
showMediaBrowserDialog(this, {
|
||||
action: "play",
|
||||
entityId: this._config!.entity,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) =>
|
||||
this._playMedia(
|
||||
pickedMedia.media_content_id,
|
||||
pickedMedia.media_content_type
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
private _playMedia(media_content_id: string, media_content_type: string) {
|
||||
this.hass!.callService("media_player", "play_media", {
|
||||
entity_id: this._config!.entity,
|
||||
media_content_id,
|
||||
media_content_type,
|
||||
});
|
||||
}
|
||||
|
||||
private _handleClick(e: MouseEvent): void {
|
||||
this.hass!.callService(
|
||||
"media_player",
|
||||
(e.currentTarget! as HTMLElement).getAttribute("action")!,
|
||||
{
|
||||
entity_id: this._config!.entity,
|
||||
}
|
||||
);
|
||||
const action = (e.currentTarget! as HTMLElement).getAttribute("action")!;
|
||||
this.hass!.callService("media_player", action, {
|
||||
entity_id: this._config!.entity,
|
||||
});
|
||||
}
|
||||
|
||||
private _updateProgressBar(): void {
|
||||
@ -831,6 +866,12 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
--mdc-icon-size: 40px;
|
||||
}
|
||||
|
||||
ha-icon-button.browse-media {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
|
||||
.top-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -900,6 +941,10 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
--mdc-icon-size: 36px;
|
||||
}
|
||||
|
||||
.narrow ha-icon-button.browse-media {
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
|
||||
.no-progress.player:not(.no-controls) {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
@ -12,6 +12,12 @@ export interface AlarmPanelCardConfig extends LovelaceCardConfig {
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
export interface CalendarCardConfig extends LovelaceCardConfig {
|
||||
entities: string[];
|
||||
title?: string;
|
||||
theme?: string;
|
||||
}
|
||||
|
||||
export interface ConditionalCardConfig extends LovelaceCardConfig {
|
||||
card: LovelaceCardConfig;
|
||||
conditions: Condition[];
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { PropertyValues } from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { processConfigEntities } from "./process-config-entities";
|
||||
|
||||
// Check if config or Entity changed
|
||||
export function hasConfigOrEntityChanged(
|
||||
element: any,
|
||||
changedProps: PropertyValues
|
||||
): boolean {
|
||||
function hasConfigChanged(element: any, changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("_config")) {
|
||||
return true;
|
||||
}
|
||||
@ -23,9 +20,41 @@ export function hasConfigOrEntityChanged(
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if config or Entity changed
|
||||
export function hasConfigOrEntityChanged(
|
||||
element: any,
|
||||
changedProps: PropertyValues
|
||||
): boolean {
|
||||
if (hasConfigChanged(element, changedProps)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant;
|
||||
|
||||
return (
|
||||
oldHass.states[element._config!.entity] !==
|
||||
element.hass!.states[element._config!.entity]
|
||||
);
|
||||
}
|
||||
|
||||
// Check if config or Entities changed
|
||||
export function hasConfigOrEntitiesChanged(
|
||||
element: any,
|
||||
changedProps: PropertyValues
|
||||
): boolean {
|
||||
if (hasConfigChanged(element, changedProps)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant;
|
||||
|
||||
const entities = processConfigEntities(element._config!.entities);
|
||||
|
||||
return entities.some(
|
||||
(entity) =>
|
||||
oldHass.states[entity.entity] !== element.hass!.states[entity.entity]
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import "../cards/hui-button-card";
|
||||
import "../cards/hui-calendar-card";
|
||||
import "../cards/hui-entities-card";
|
||||
import "../cards/hui-entity-button-card";
|
||||
import "../cards/hui-entity-card";
|
||||
@ -52,6 +53,7 @@ const LAZY_LOAD_TYPES = {
|
||||
map: () => import("../cards/hui-map-card"),
|
||||
markdown: () => import("../cards/hui-markdown-card"),
|
||||
picture: () => import("../cards/hui-picture-card"),
|
||||
calendar: () => import("../cards/hui-calendar-card"),
|
||||
};
|
||||
|
||||
// This will not return an error card but will throw the error
|
||||
|
@ -0,0 +1,133 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
internalProperty,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { CalendarCardConfig } from "../../cards/types";
|
||||
import "../../components/hui-entity-editor";
|
||||
import "../../../../components/entity/ha-entities-picker";
|
||||
import "../../components/hui-theme-select-editor";
|
||||
import type { LovelaceCardEditor } from "../../types";
|
||||
import type { EditorTarget, EntitiesEditorEvent } from "../types";
|
||||
import { configElementStyle } from "./config-elements-style";
|
||||
import {
|
||||
string,
|
||||
optional,
|
||||
object,
|
||||
boolean,
|
||||
array,
|
||||
union,
|
||||
assert,
|
||||
} from "superstruct";
|
||||
|
||||
const cardConfigStruct = object({
|
||||
type: string(),
|
||||
title: optional(union([string(), boolean()])),
|
||||
theme: optional(string()),
|
||||
entities: array(string()),
|
||||
});
|
||||
|
||||
@customElement("hui-calendar-card-editor")
|
||||
export class HuiCalendarCardEditor extends LitElement
|
||||
implements LovelaceCardEditor {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) private _config?: CalendarCardConfig;
|
||||
|
||||
@internalProperty() private _configEntities?: string[];
|
||||
|
||||
public setConfig(config: CalendarCardConfig): void {
|
||||
assert(config, cardConfigStruct);
|
||||
this._config = config;
|
||||
this._configEntities = config.entities;
|
||||
}
|
||||
|
||||
get _title(): string {
|
||||
return this._config!.title || "";
|
||||
}
|
||||
|
||||
get _theme(): string {
|
||||
return this._config!.theme || "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${configElementStyle}
|
||||
<div class="card-config">
|
||||
<div class="side-by-side">
|
||||
<paper-input
|
||||
.label="${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.generic.title"
|
||||
)} (${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._title}
|
||||
.configValue=${"title"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
<hui-theme-select-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._theme}
|
||||
.configValue=${"theme"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></hui-theme-select-editor>
|
||||
</div>
|
||||
</div>
|
||||
<h3>
|
||||
${"Calendar Entities" +
|
||||
" (" +
|
||||
this.hass!.localize("ui.panel.lovelace.editor.card.config.required") +
|
||||
")"}
|
||||
</h3>
|
||||
<ha-entities-picker
|
||||
.hass=${this.hass!}
|
||||
.value=${this._configEntities}
|
||||
.includeDomains=${["calendar"]}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</ha-entities-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: EntitiesEditorEvent | CustomEvent): void {
|
||||
if (!this._config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = ev.target! as EditorTarget;
|
||||
|
||||
if (this[`_${target.configValue}`] === target.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.detail && ev.detail.value && Array.isArray(ev.detail.value)) {
|
||||
this._config = { ...this._config, entities: ev.detail.value };
|
||||
} else if (target.configValue) {
|
||||
if (target.value === "") {
|
||||
delete this._config[target.configValue!];
|
||||
} else {
|
||||
this._config = {
|
||||
...this._config,
|
||||
[target.configValue]: target.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fireEvent(this, "config-changed", { config: this._config });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-calendar-card-editor": HuiCalendarCardEditor;
|
||||
}
|
||||
}
|
@ -9,6 +9,10 @@ export const coreCards: Card[] = [
|
||||
type: "button",
|
||||
showElement: true,
|
||||
},
|
||||
{
|
||||
type: "calendar",
|
||||
showElement: true,
|
||||
},
|
||||
{
|
||||
type: "entities",
|
||||
showElement: true,
|
||||
|
@ -179,6 +179,13 @@
|
||||
"media_player": {
|
||||
"source": "Source",
|
||||
"sound_mode": "Sound mode",
|
||||
"browse_media": "Browse media",
|
||||
"turn_on": "Turn on",
|
||||
"turn_off": "Turn off",
|
||||
"media_play": "Play",
|
||||
"media_play_pause": "Play/pause",
|
||||
"media_next_track": "Next",
|
||||
"media_previous_track": "Previous",
|
||||
"text_to_speak": "Text to speak"
|
||||
},
|
||||
"persistent_notification": {
|
||||
@ -312,6 +319,7 @@
|
||||
"past": "{time} ago",
|
||||
"future": "In {time}",
|
||||
"never": "Never",
|
||||
"just_now": "Just now",
|
||||
"duration": {
|
||||
"second": "{count} {count, plural,\n one {second}\n other {seconds}\n}",
|
||||
"minute": "{count} {count, plural,\n one {minute}\n other {minutes}\n}",
|
||||
@ -341,6 +349,22 @@
|
||||
"data-table": {
|
||||
"search": "Search",
|
||||
"no-data": "No data"
|
||||
},
|
||||
"media-browser": {
|
||||
"pick": "Pick",
|
||||
"play": "Play",
|
||||
"play-media": "Play Media",
|
||||
"pick-media": "Pick Media",
|
||||
"no_items": "No items",
|
||||
"choose-source": "Choose Source",
|
||||
"media-player-browser": "Media Player Browser",
|
||||
"content-type": {
|
||||
"server": "Server",
|
||||
"library": "Library",
|
||||
"artist": "Artist",
|
||||
"album": "Album",
|
||||
"playlist": "Playlist"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dialogs": {
|
||||
@ -608,6 +632,7 @@
|
||||
"add_tag": "Add tag",
|
||||
"write": "Write",
|
||||
"edit": "Edit",
|
||||
"never_scanned": "Never scanned",
|
||||
"create_automation": "Create automation with tag",
|
||||
"automation_title": "Tag {name} is scanned",
|
||||
"headers": {
|
||||
@ -812,7 +837,8 @@
|
||||
"input_text": "Reload input texts",
|
||||
"input_number": "Reload input numbers",
|
||||
"input_datetime": "Reload input date times",
|
||||
"input_select": "Reload input selects"
|
||||
"input_select": "Reload input selects",
|
||||
"template": "Reload template entities"
|
||||
},
|
||||
"server_management": {
|
||||
"heading": "Server management",
|
||||
@ -1376,6 +1402,7 @@
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "No {name} have been added using this device yet. You can add one by clicking the + button above.",
|
||||
"caption": "Devices",
|
||||
"description": "Manage connected devices",
|
||||
"device_info": "Device info",
|
||||
@ -1421,6 +1448,7 @@
|
||||
"scripts": "Scripts",
|
||||
"scenes": "Scenes",
|
||||
"confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?",
|
||||
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, Lovelace) that is currently using these entities, you will have to update them yourself.",
|
||||
"data_table": {
|
||||
"device": "Device",
|
||||
"manufacturer": "Manufacturer",
|
||||
@ -1656,10 +1684,14 @@
|
||||
"message_received": "Message {id} received on {topic} at {time}:"
|
||||
},
|
||||
"ozw": {
|
||||
"button": "Configure",
|
||||
"common": {
|
||||
"zwave": "Z-Wave",
|
||||
"node_id": "Node ID",
|
||||
"ozw_instance": "OpenZWave Instance"
|
||||
"ozw_instance": "OpenZWave Instance",
|
||||
"instance": "Instance",
|
||||
"controller": "Controller",
|
||||
"network": "Network"
|
||||
},
|
||||
"device_info": {
|
||||
"zwave_info": "Z-Wave Info",
|
||||
@ -1696,6 +1728,44 @@
|
||||
"refreshing_description": "Refreshing node information...",
|
||||
"node_status": "Node Status",
|
||||
"step": "Step"
|
||||
},
|
||||
"network_status": {
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"starting": "Starting",
|
||||
"unknown": "Unknown",
|
||||
"details": {
|
||||
"driverallnodesqueried": "All nodes have been queried",
|
||||
"driverallnodesqueriedsomedead": "All nodes have been queried. Some nodes were found dead",
|
||||
"driverawakenodesqueries": "All awake nodes have been queried",
|
||||
"driverremoved": "The driver has been removed",
|
||||
"driverreset": "The driver has been reset",
|
||||
"driverfailed": "Failed to connect to Z-Wave controller",
|
||||
"driverready": "Initializing the Z-Wave controller",
|
||||
"ready": "Ready to connect",
|
||||
"stopped": "OpenZWave stopped",
|
||||
"started": "Connected to MQTT",
|
||||
"starting": "Connecting to MQTT",
|
||||
"offline": "OZWDaemon offline"
|
||||
}
|
||||
},
|
||||
"navigation": {
|
||||
"select_instance": "Select Instance",
|
||||
"network": "Network",
|
||||
"nodes": "Nodes"
|
||||
},
|
||||
"select_instance": {
|
||||
"header": "Select an OpenZWave Instance",
|
||||
"introduction": "You have more than one OpenZWave instance running. Which instance would you like to manage?"
|
||||
},
|
||||
"network": {
|
||||
"header": "Network Management",
|
||||
"introduction": "Manage network-wide functions.",
|
||||
"node_count": "{count} nodes"
|
||||
},
|
||||
"services": {
|
||||
"add_node": "Add Node",
|
||||
"remove_node": "Remove Node"
|
||||
}
|
||||
},
|
||||
"zha": {
|
||||
@ -2064,6 +2134,10 @@
|
||||
"available_states": "Available States",
|
||||
"description": "The Alarm Panel card allows you to Arm and Disarm your alarm control panel integrations."
|
||||
},
|
||||
"calendar": {
|
||||
"name": "Calendar",
|
||||
"description": "The Calendar card displays a calendar including day, week and list views"
|
||||
},
|
||||
"conditional": {
|
||||
"name": "Conditional",
|
||||
"description": "The Conditional card displays another card based on entity states.",
|
||||
@ -2564,6 +2638,7 @@
|
||||
"title": "Template",
|
||||
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
||||
"editor": "Template editor",
|
||||
"reset": "Reset to demo template",
|
||||
"jinja_documentation": "Jinja2 template documentation",
|
||||
"template_extensions": "Home Assistant template extensions",
|
||||
"unknown_error_template": "Unknown error rendering template"
|
||||
@ -2591,6 +2666,7 @@
|
||||
"intro": "Hello {name}, welcome to Home Assistant. How would you like to name your home?",
|
||||
"intro_location": "We would like to know where you live. This information will help with displaying information and setting up sun-based automations. This data is never shared outside of your network.",
|
||||
"intro_location_detect": "We can help you fill in this information by making a one-time request to an external service.",
|
||||
"location_name": "Name of your Home Assistant installation",
|
||||
"location_name_default": "Home",
|
||||
"button_detect": "Detect",
|
||||
"finish": "Next"
|
||||
|
14
src/types.ts
14
src/types.ts
@ -118,8 +118,8 @@ export interface Panels {
|
||||
|
||||
export interface Calendar {
|
||||
entity_id: string;
|
||||
name: string;
|
||||
backgroundColor: string;
|
||||
name?: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export interface SelectedCalendar {
|
||||
@ -144,9 +144,15 @@ export interface CalendarViewChanged {
|
||||
view: string;
|
||||
}
|
||||
|
||||
export type FullCalendarView =
|
||||
| "dayGridMonth"
|
||||
| "dayGridWeek"
|
||||
| "dayGridDay"
|
||||
| "listWeek";
|
||||
|
||||
export interface ToggleButton {
|
||||
label?: string;
|
||||
icon: string;
|
||||
label: string;
|
||||
iconPath: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
|
57
test-mocha/hassio/create_session.spec.ts
Normal file
57
test-mocha/hassio/create_session.spec.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import * as assert from "assert";
|
||||
import { createHassioSession } from "../../src/data/hassio/supervisor";
|
||||
|
||||
const sessionID = "fhdsu73rh3io4h8f3irhjel8ousafehf8f3yh";
|
||||
|
||||
describe("Create hassio session", function () {
|
||||
it("Test create session without HTTPS", async function () {
|
||||
// @ts-ignore
|
||||
global.document = {};
|
||||
// @ts-ignore
|
||||
global.location = {};
|
||||
await createHassioSession({
|
||||
// @ts-ignore
|
||||
callApi: async function () {
|
||||
return { data: { session: sessionID } };
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
// @ts-ignore
|
||||
global.document.cookie,
|
||||
"ingress_session=fhdsu73rh3io4h8f3irhjel8ousafehf8f3yh;path=/api/hassio_ingress/;SameSite=Strict"
|
||||
);
|
||||
});
|
||||
it("Test create session with HTTPS", async function () {
|
||||
// @ts-ignore
|
||||
global.document = {};
|
||||
// @ts-ignore
|
||||
global.location = { protocol: "https:" };
|
||||
await createHassioSession({
|
||||
// @ts-ignore
|
||||
callApi: async function () {
|
||||
return { data: { session: sessionID } };
|
||||
},
|
||||
});
|
||||
assert.equal(
|
||||
// @ts-ignore
|
||||
global.document.cookie,
|
||||
"ingress_session=fhdsu73rh3io4h8f3irhjel8ousafehf8f3yh;path=/api/hassio_ingress/;SameSite=Strict;Secure"
|
||||
);
|
||||
|
||||
// Clean up in case they will be used in other tests
|
||||
// @ts-ignore
|
||||
global.document = {};
|
||||
// @ts-ignore
|
||||
global.location = {};
|
||||
});
|
||||
it("Test fail to create", async function () {
|
||||
const createSessionPromise = createHassioSession({
|
||||
// @ts-ignore
|
||||
callApi: async function () {},
|
||||
}).then(
|
||||
() => true,
|
||||
() => false
|
||||
);
|
||||
assert.equal(await createSessionPromise, false);
|
||||
});
|
||||
});
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Historie stavu se načítá...",
|
||||
"no_history_found": "Historie stavu chybí."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Obrázek",
|
||||
"unsupported_format": "Nepodporovaný formát, prosím vyberte obrázek typu JPEG, PNG nebo GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Oblast",
|
||||
"automation": "Část následujících automatizací",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "Toto pole je povinné",
|
||||
"yaml_not_editable": "Nastavení této entity nelze upravovat z uživatelského rozhraní. Pouze entity nastavené z uživatelského rozhraní lze konfigurovat v uživatelském rozhraní."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Oříznout"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Zavřít dialog",
|
||||
"edit": "Upravit entitu",
|
||||
@ -837,7 +844,7 @@
|
||||
"add_option": "Přidat volbu",
|
||||
"conditions": "Podmínky",
|
||||
"default": "Výchozí akce",
|
||||
"label": "Vybrat",
|
||||
"label": "Volby",
|
||||
"option": "Volba {number}",
|
||||
"remove_option": "Odebrat volbu",
|
||||
"sequence": "Akce"
|
||||
@ -862,12 +869,20 @@
|
||||
"service_data": "Data služby"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Opakovat",
|
||||
"label": "Opakovaní",
|
||||
"sequence": "Akce",
|
||||
"type_select": "Typ opakování",
|
||||
"type": {
|
||||
"count": {
|
||||
"label": "Počet"
|
||||
},
|
||||
"until": {
|
||||
"conditions": "Podmínky \"Dokud nenastane\"",
|
||||
"label": "Dokud nenastane"
|
||||
},
|
||||
"while": {
|
||||
"conditions": "Podmínky \"Pokud platí\"",
|
||||
"label": "Pokud platí"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1042,6 +1057,9 @@
|
||||
"sunrise": "Východ slunce",
|
||||
"sunset": "Západ slunce"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Značka"
|
||||
},
|
||||
"template": {
|
||||
"label": "Šablona",
|
||||
"value_template": "Šablona hodnoty"
|
||||
@ -1319,6 +1337,7 @@
|
||||
"caption": "Zařízení",
|
||||
"confirm_delete": "Opravdu chcete toto zařízení odstranit?",
|
||||
"confirm_rename_entity_ids": "Chcete také přejmenovat ID entit vašich entit?",
|
||||
"confirm_rename_entity_ids_warning": "Žádná konfigurace (např. automatizace, skripty, scény, Lovelace), která tyto entity aktuálně používá, nebude změněna. Budete je muset aktualizovat sami.",
|
||||
"data_table": {
|
||||
"area": "Oblast",
|
||||
"battery": "Baterie",
|
||||
@ -1641,6 +1660,9 @@
|
||||
"ozw": {
|
||||
"common": {
|
||||
"zwave": "Z-Wave"
|
||||
},
|
||||
"refresh_node": {
|
||||
"step": "Krok"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1797,6 +1819,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Přidat značku",
|
||||
"automation_title": "Značka {name} je naskenována",
|
||||
"caption": "Značky",
|
||||
"create_automation": "Vytvořit automatizaci se značkou",
|
||||
"description": "Správa značek",
|
||||
"detail": {
|
||||
"create": "Vytvořit",
|
||||
"create_and_write": "Vytvořit a zapsat",
|
||||
"delete": "Smazat",
|
||||
"description": "Popis",
|
||||
"name": "Název",
|
||||
"new_tag": "Nová značka",
|
||||
"tag_id": "ID značky",
|
||||
"tag_id_placeholder": "Automaticky vygenerováno, pokud zůstane prázdné",
|
||||
"update": "Aktualizovat"
|
||||
},
|
||||
"edit": "Upravit",
|
||||
"headers": {
|
||||
"last_scanned": "Naposledy naskenováno",
|
||||
"name": "Název"
|
||||
},
|
||||
"no_tags": "Žádné značky",
|
||||
"write": "Zapsat"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Přidat uživatele",
|
||||
|
@ -57,7 +57,7 @@
|
||||
"boost": "Boost",
|
||||
"comfort": "Komfort",
|
||||
"eco": "Eco",
|
||||
"home": "Zu Hause",
|
||||
"home": "Zuhause",
|
||||
"normal": "Normal",
|
||||
"sleep": "Schlafen"
|
||||
}
|
||||
@ -96,7 +96,7 @@
|
||||
"armed": "Aktiv",
|
||||
"armed_away": "Aktiv, abwesend",
|
||||
"armed_custom_bypass": "Aktiv, benutzerdefiniert",
|
||||
"armed_home": "Aktiv, zu Hause",
|
||||
"armed_home": "Aktiv, Zuhause",
|
||||
"armed_night": "Aktiv, Nacht",
|
||||
"arming": "Aktiviere",
|
||||
"disarmed": "Inaktiv",
|
||||
@ -267,7 +267,7 @@
|
||||
"standby": "Standby"
|
||||
},
|
||||
"person": {
|
||||
"home": "Zu Hause"
|
||||
"home": "Zuhause"
|
||||
},
|
||||
"plant": {
|
||||
"ok": "OK",
|
||||
@ -2137,7 +2137,7 @@
|
||||
"empty_state": {
|
||||
"go_to_integrations_page": "Gehe zur Integrationsseite.",
|
||||
"no_devices": "Auf dieser Seite können Sie Ihre Geräte steuern, es sieht jedoch so aus, als hätten Sie noch keine eingerichtet. Gehen Sie zur Integrationsseite, um damit zu beginnen.",
|
||||
"title": "Willkommen zu Hause"
|
||||
"title": "Willkommen Zuhause"
|
||||
},
|
||||
"entities": {
|
||||
"never_triggered": "Nie ausgelöst"
|
||||
|
@ -899,7 +899,7 @@
|
||||
"wait_template": "Wait Template"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Unsupported action: {action}"
|
||||
"unsupported_action": "No UI support for action: {action}"
|
||||
},
|
||||
"alias": "Name",
|
||||
"conditions": {
|
||||
@ -965,7 +965,7 @@
|
||||
"zone": "Zone"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Unsupported condition: {condition}"
|
||||
"unsupported_condition": "No UI support for condition: {condition}"
|
||||
},
|
||||
"default_name": "New Automation",
|
||||
"description": {
|
||||
@ -1057,6 +1057,9 @@
|
||||
"sunrise": "Sunrise",
|
||||
"sunset": "Sunset"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Tag"
|
||||
},
|
||||
"template": {
|
||||
"label": "Template",
|
||||
"value_template": "Value template"
|
||||
@ -1084,7 +1087,7 @@
|
||||
"zone": "Zone"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Unsupported platform: {platform}"
|
||||
"unsupported_platform": "No UI support for platform: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "You have unsaved changes. Are you sure you want to leave?"
|
||||
},
|
||||
@ -1334,6 +1337,7 @@
|
||||
"caption": "Devices",
|
||||
"confirm_delete": "Are you sure you want to delete this device?",
|
||||
"confirm_rename_entity_ids": "Do you also want to rename the entity id's of your entities?",
|
||||
"confirm_rename_entity_ids_warning": "This will not change any configuration (like automations, scripts, scenes, Lovelace) that is currently using these entities, you will have to update them yourself.",
|
||||
"data_table": {
|
||||
"area": "Area",
|
||||
"battery": "Battery",
|
||||
@ -1850,6 +1854,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Add tag",
|
||||
"automation_title": "Tag {name} is scanned",
|
||||
"caption": "Tags",
|
||||
"create_automation": "Create automation with tag",
|
||||
"description": "Manage tags",
|
||||
"detail": {
|
||||
"create": "Create",
|
||||
"create_and_write": "Create and Write",
|
||||
"delete": "Delete",
|
||||
"description": "Description",
|
||||
"name": "Name",
|
||||
"new_tag": "New tag",
|
||||
"tag_id": "Tag id",
|
||||
"tag_id_placeholder": "Autogenerated when left empty",
|
||||
"update": "Update"
|
||||
},
|
||||
"edit": "Edit",
|
||||
"headers": {
|
||||
"last_scanned": "Last scanned",
|
||||
"name": "Name"
|
||||
},
|
||||
"no_tags": "No tags",
|
||||
"write": "Write"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Add user",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Cargando historial de estado...",
|
||||
"no_history_found": "No se encontró historial de estado."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Imagen",
|
||||
"unsupported_format": "Formato no soportado, por favor, selecciona una imagen JPEG, PNG o GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Área",
|
||||
"automation": "Parte de las siguientes automatizaciones",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "Este campo es obligatorio",
|
||||
"yaml_not_editable": "La configuración de esta entidad no se puede editar desde la IU. Solo las entidades configuradas desde la IU se pueden configurar desde la IU"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Recortar"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Descartar diálogo",
|
||||
"edit": "Editar entidad",
|
||||
@ -892,7 +899,7 @@
|
||||
"wait_template": "Plantilla de espera"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Acción no admitida: {action}"
|
||||
"unsupported_action": "No hay soporte en la IU para la acción: {action}"
|
||||
},
|
||||
"alias": "Nombre",
|
||||
"conditions": {
|
||||
@ -958,7 +965,7 @@
|
||||
"zone": "Zona"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Condición no admitida: {condition}"
|
||||
"unsupported_condition": "No hay soporte en la IU para la condición: {condition}"
|
||||
},
|
||||
"default_name": "Nueva automatización",
|
||||
"description": {
|
||||
@ -1050,6 +1057,9 @@
|
||||
"sunrise": "Amanecer",
|
||||
"sunset": "Puesta de sol"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Etiqueta"
|
||||
},
|
||||
"template": {
|
||||
"label": "Plantilla",
|
||||
"value_template": "Valor de la plantilla"
|
||||
@ -1077,7 +1087,7 @@
|
||||
"zone": "Zona"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Plataforma no admitida: {platform}"
|
||||
"unsupported_platform": "No hay soporte en la IU para la plataforma: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "Tienes cambios sin guardar. ¿Estás seguro de que quieres salir?"
|
||||
},
|
||||
@ -1327,6 +1337,7 @@
|
||||
"caption": "Dispositivos",
|
||||
"confirm_delete": "¿Estás seguro de que quieres eliminar este dispositivo?",
|
||||
"confirm_rename_entity_ids": "¿También quieres cambiar el nombre de los identificadores de entidad de tus entidades?",
|
||||
"confirm_rename_entity_ids_warning": "Esto no cambiará ninguna configuración (como automatizaciones, scripts, escenas, Lovelace) que estén utilizando actualmente estas entidades, tendrá que actualizarlas usted mismo.",
|
||||
"data_table": {
|
||||
"area": "Área",
|
||||
"battery": "Batería",
|
||||
@ -1843,6 +1854,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Añadir etiqueta",
|
||||
"automation_title": "Se escanea la etiqueta {name}",
|
||||
"caption": "Etiquetas",
|
||||
"create_automation": "Crear automatización con etiqueta",
|
||||
"description": "Administrar etiquetas",
|
||||
"detail": {
|
||||
"create": "Crear",
|
||||
"create_and_write": "Crear y escribir",
|
||||
"delete": "Eliminar",
|
||||
"description": "Descripción",
|
||||
"name": "Nombre",
|
||||
"new_tag": "Nueva etiqueta",
|
||||
"tag_id": "ID de etiqueta",
|
||||
"tag_id_placeholder": "Autogenerado cuando se deja vacío",
|
||||
"update": "Actualizar"
|
||||
},
|
||||
"edit": "Editar",
|
||||
"headers": {
|
||||
"last_scanned": "Última vez escaneada",
|
||||
"name": "Nombre"
|
||||
},
|
||||
"no_tags": "Sin etiquetas",
|
||||
"write": "Escribir"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Añadir usuario",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Chargement de l'historique des valeurs ...",
|
||||
"no_history_found": "Aucun historique des valeurs trouvé."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Image",
|
||||
"unsupported_format": "Format non pris en charge, veuillez choisir une image JPEG, PNG ou GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Pièce",
|
||||
"automation": "Partie des automatisations suivantes",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "Ce champ est requis",
|
||||
"yaml_not_editable": "Les paramètres de cette entité ne peuvent pas être modifiés à partir de l'interface utilisateur. Seules les entités configurées à partir de l'interface utilisateur sont configurables à partir de l'interface utilisateur."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Recadrer"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Fermer la fenêtre de dialogue",
|
||||
"edit": "Modifier l'entité",
|
||||
@ -892,7 +899,7 @@
|
||||
"wait_template": "Template d'attente"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Action non supportée : {action}"
|
||||
"unsupported_action": "Pas d'interface utilisateur pour l'action : {action}"
|
||||
},
|
||||
"alias": "Nom",
|
||||
"conditions": {
|
||||
@ -958,7 +965,7 @@
|
||||
"zone": "Zone"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Condition non supportée: {condition}"
|
||||
"unsupported_condition": "Pas d'interface utilisateur pour la condition : {condition}"
|
||||
},
|
||||
"default_name": "Nouvelle automatisation",
|
||||
"description": {
|
||||
@ -1050,6 +1057,9 @@
|
||||
"sunrise": "Lever du soleil",
|
||||
"sunset": "Coucher du soleil"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Balise"
|
||||
},
|
||||
"template": {
|
||||
"label": "Template",
|
||||
"value_template": "Contenu du template"
|
||||
@ -1077,7 +1087,7 @@
|
||||
"zone": "Zone"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Plateforme non supportée : {platform}"
|
||||
"unsupported_platform": "Pas d'interface utilisateur pour la plateforme : {platform}"
|
||||
},
|
||||
"unsaved_confirm": "Vous avez des changements non enregistrés. Êtes-vous sûr de vouloir quitter?"
|
||||
},
|
||||
@ -1327,6 +1337,7 @@
|
||||
"caption": "Appareils",
|
||||
"confirm_delete": "Voulez-vous vraiment supprimer cet appareil ?",
|
||||
"confirm_rename_entity_ids": "Voulez-vous aussi renommer les ID de vos entités?",
|
||||
"confirm_rename_entity_ids_warning": "Cela ne changera aucune configuration (comme les automatismes, les scripts, les scènes, Lovelace) qui utilise actuellement ces entités, vous devrez les mettre à jour vous-même.",
|
||||
"data_table": {
|
||||
"area": "Zone",
|
||||
"battery": "Batterie",
|
||||
@ -1663,9 +1674,18 @@
|
||||
"complete": "Le processus d'interrogation est terminé",
|
||||
"configuration": "Obtention la configuration à partir du nœud",
|
||||
"dynamic": "Obtention de valeurs fréquemment modifiées du nœud",
|
||||
"instances": "Obtention des détails sur les instances ou les canaux pris en charge par un appareil",
|
||||
"manufacturerspecific1": "Obtention des codes d'identification du fabricant et du produit auprès du nœud",
|
||||
"manufacturerspecific2": "Obtention de codes d’identification supplémentaires du fabricant et du produit à partir du nœud",
|
||||
"neighbors": "Obtenir une liste des nœuds voisins",
|
||||
"nodeinfo": "Obtention des classes de commande prises en charge à partir du nœud",
|
||||
"nodeplusinfo": "Obtention d'informations Z-Wave + à partir du nœud",
|
||||
"probe": "Vérification si le nœud est éveillé/vivant",
|
||||
"protocolinfo": "Obtention des capacités Z-Wave de base de ce nœud à partir du contrôleur",
|
||||
"session": "Obtention de valeurs rarement modifiées du nœud",
|
||||
"static": "Obtention des valeurs statiques de l'appareil"
|
||||
"static": "Obtention des valeurs statiques de l'appareil",
|
||||
"versions": "Obtention d'informations sur les versions des microprogrammes et des classes de commande",
|
||||
"wakeup": "Configuration de la prise en charge des files d'attente et des messages de réveil"
|
||||
},
|
||||
"refresh_node": {
|
||||
"battery_note": "Si le nœud est alimenté par batterie, assurez-vous de l'activer avant de continuer",
|
||||
@ -1834,6 +1854,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Ajouter une balise",
|
||||
"automation_title": "La balise {name} est analysée",
|
||||
"caption": "Balises",
|
||||
"create_automation": "Créer une automatisation avec une balise",
|
||||
"description": "Gérer les balises",
|
||||
"detail": {
|
||||
"create": "Créer",
|
||||
"create_and_write": "Créer et Ecrire",
|
||||
"delete": "Supprimer",
|
||||
"description": "Description",
|
||||
"name": "Nom",
|
||||
"new_tag": "Nouvelle balise",
|
||||
"tag_id": "Id de balise",
|
||||
"tag_id_placeholder": "Généré automatiquement lorsque laissé vide",
|
||||
"update": "Mise à jour"
|
||||
},
|
||||
"edit": "Modifier",
|
||||
"headers": {
|
||||
"last_scanned": "Dernière analyse",
|
||||
"name": "Nom"
|
||||
},
|
||||
"no_tags": "Aucunes balises",
|
||||
"write": "Écrire"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Ajouter un utilisateur",
|
||||
@ -2497,7 +2542,7 @@
|
||||
},
|
||||
"menu": {
|
||||
"close": "Fermer",
|
||||
"configure_ui": "Configurer l'interface utilisateur",
|
||||
"configure_ui": "Modifier le tableau de bord",
|
||||
"exit_edit_mode": "Quitter le mode d'édition de l'interface utilisateur",
|
||||
"help": "Aide",
|
||||
"refresh": "Actualiser",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "טוען היסטוריה...",
|
||||
"no_history_found": "לא נמצאה היסטוריה"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "תמונה",
|
||||
"unsupported_format": "פורמט לא נתמך, בחר תמונת JPEG, PNG או GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "אֵזוֹר",
|
||||
"automation": "חלק מהאוטומציות הבאות",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "שדה זה הוא חובה",
|
||||
"yaml_not_editable": "אין אפשרות לערוך את ההגדרות של ישות זו מממשק המשתמש. רק ישויות שהוגדרו מממשק המשתמש ניתנות להגדרה מתוך ממשק המשתמש."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "חתוך"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "סגור",
|
||||
"edit": "ערוך יישות",
|
||||
@ -1050,6 +1057,9 @@
|
||||
"sunrise": "זריחה",
|
||||
"sunset": "שקיעה"
|
||||
},
|
||||
"tag": {
|
||||
"label": "תג "
|
||||
},
|
||||
"template": {
|
||||
"label": "תבנית",
|
||||
"value_template": "תבנית ערך"
|
||||
@ -1327,6 +1337,7 @@
|
||||
"caption": "התקנים",
|
||||
"confirm_delete": "האם אתה בטוח שברצונך למחוק מכשיר זה?",
|
||||
"confirm_rename_entity_ids": "האם אתה רוצה גם לשנות את המזהים של הישויות שלך?",
|
||||
"confirm_rename_entity_ids_warning": "שינוי זה לא ישפיע על התצורה (כמו אוטומציות, קבצי Script, סצנות, Lovelace) המשתמשת כעת בישויות אלה, יהיה עליך לעדכן אותן בעצמך.",
|
||||
"data_table": {
|
||||
"area": "אזור",
|
||||
"battery": "סוללה",
|
||||
@ -1656,6 +1667,37 @@
|
||||
"node_failed": "הרכיב נכשל",
|
||||
"stage": "שלב",
|
||||
"zwave_info": "מידע Z-Wave"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "מרענן קבוצות וחברויות",
|
||||
"cacheload": "טוען מידע מקובץ המטמון של OpenZWave. רכיבי סוללה יישארו בשלב זה עד שהרכיב יתעורר.",
|
||||
"complete": "תהליך הראיון הושלם",
|
||||
"configuration": "מקבל ערכי תצורה מהרכיב",
|
||||
"dynamic": "מקבל ערכים המשתנים לעתים קרובות מהרכיב",
|
||||
"instances": "מקבל פרטים אודות המופעים או הערוצים שבהם הרכיב תומך",
|
||||
"manufacturerspecific1": "קבלת קודי יצרן ומזהי מוצר מהרכיב",
|
||||
"manufacturerspecific2": "מקבל קודי יצרן ומזהי מוצר נוספים מהרכיב",
|
||||
"neighbors": "קבלת רשימת שכנים מהרכיב",
|
||||
"nodeinfo": "קבלת מחלקות פקודות נתמכות מהרכיב",
|
||||
"nodeplusinfo": "קבלת מידע Z-Wave+ מהרכיב",
|
||||
"probe": "בדיקה האם הרכיב ער/חי",
|
||||
"protocolinfo": "קבלת יכולות Z-Wave בסיסיות של רכיב זה מהבקר",
|
||||
"session": "מקבל ערכים המשתנים לעתים רחוקות מהרכיב",
|
||||
"static": "מקבל ערכים סטטיים מהרכיב",
|
||||
"versions": "מקבל מידע אודות גירסאות קושחה ומחלקות פקודות",
|
||||
"wakeup": "הגדרת תמיכה עבור תורי התעוררות והודעות"
|
||||
},
|
||||
"refresh_node": {
|
||||
"battery_note": "אם הרכיב מופעל באמצעות סוללה, הקפד להעיר אותו לפני שתמשיך",
|
||||
"complete": "רענון הרכיב הושלם",
|
||||
"description": "זה יגיד ל- OpenZWave לראיין מחדש את הרכיב ולעדכן את הפקודות, היכולות והערכים של הרכיב.",
|
||||
"node_status": "מצה הרכיב",
|
||||
"refreshing_description": "מרענן מידע על הרכיב",
|
||||
"start_refresh_button": "התחל לרענן",
|
||||
"step": "שלב",
|
||||
"title": "רענן מידע הרכיב",
|
||||
"wakeup_header": "הוראות התעוררות עבור",
|
||||
"wakeup_instructions_source": "הוראות ההתעוררות מקורן במסד הנתונים של קהילת ה OpenZWave."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -1812,6 +1854,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "הוסף תג",
|
||||
"automation_title": "התג {name} נסרק",
|
||||
"caption": "תגים",
|
||||
"create_automation": "צור אוטומציה עם תג",
|
||||
"description": "ניהול תגים",
|
||||
"detail": {
|
||||
"create": "צור",
|
||||
"create_and_write": "צור וכתוב",
|
||||
"delete": "מחק",
|
||||
"description": "תיאור",
|
||||
"name": "שם",
|
||||
"new_tag": "תג חדש",
|
||||
"tag_id": "מזהה תג",
|
||||
"tag_id_placeholder": "מחולל אוטומטית אם ריק",
|
||||
"update": "עדכן"
|
||||
},
|
||||
"edit": "ערוך",
|
||||
"headers": {
|
||||
"last_scanned": "נסרק לאחרונה",
|
||||
"name": "שם"
|
||||
},
|
||||
"no_tags": "אין תגים",
|
||||
"write": "כתוב"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "הוסף משתמש",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "상태 기록 내용 읽는 중...",
|
||||
"no_history_found": "상태 기록 내용이 없습니다."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "그림",
|
||||
"unsupported_format": "지원되지 않는 형식입니다. JPEG, PNG 또는 GIF 이미지를 선택해주세요."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "영역",
|
||||
"automation": "관련된 자동화",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "이 입력란은 필수 요소입니다",
|
||||
"yaml_not_editable": "이 구성요소의 설정은 UI 에서 편집할 수 없습니다. UI 에서 설정한 구성요소만 UI 에서 구성할 수 있습니다."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "자르기"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "대화창 닫기",
|
||||
"edit": "구성요소 편집",
|
||||
@ -1656,6 +1663,37 @@
|
||||
"node_failed": "노드 실패",
|
||||
"stage": "단계",
|
||||
"zwave_info": "Z-Wave 정보"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "연결 그룹 및 구성원 자격 새로고침",
|
||||
"cacheload": "OpenZWave 캐시 파일에서 정보를 읽어오기. 배터리 노드는 노드가 절전 해제 상태가 될 때까지 이 단계를 유지합니다.",
|
||||
"complete": "인터뷰 과정이 완료되었습니다",
|
||||
"configuration": "노드에서 구성 값 가져오기",
|
||||
"dynamic": "노드에서 자주 변경되는 값 가져오기",
|
||||
"instances": "기기가 지원하는 인스턴스 또는 채널에 대한 세부 정보 가져오기",
|
||||
"manufacturerspecific1": "노드에서 제조업체 및 제품 ID 코드 가져오기",
|
||||
"manufacturerspecific2": "노드에서 추가 제조업체 및 제품 ID 코드 가져오기",
|
||||
"neighbors": "노드의 연관 항목 목록 가져오기",
|
||||
"nodeinfo": "노드에서 지원되는 명령 클래스 가져오기",
|
||||
"nodeplusinfo": "노드에서 Z-Wave+ 정보 가져오기",
|
||||
"probe": "노드가 절전 해제 상태 또는 활성 상태인지 확인",
|
||||
"protocolinfo": "컨트롤러에서 이 노드의 기본 Z-Wave 기능 가져오기",
|
||||
"session": "노드에서 간헐적으로 변경되는 값 가져오기",
|
||||
"static": "기기에서 정적 값 가져오기",
|
||||
"versions": "펌웨어 및 명령 클래스 버전에 대한 정보 가져오기",
|
||||
"wakeup": "절전 해제 대기열 및 메시지에 대한 지원 설정"
|
||||
},
|
||||
"refresh_node": {
|
||||
"battery_note": "노드가 배터리 전원을 사용하는 경우 계속하기 전에 절전 모드가 해제되어 있는지 확인해주세요",
|
||||
"complete": "노드 새로고침 완료",
|
||||
"description": "OpenZWave 가 노드를 다시 인터뷰하고 노드의 명령 클래스, 기능 및 값을 업데이트하도록 지시합니다.",
|
||||
"node_status": "노드 상태",
|
||||
"refreshing_description": "노드 정보를 새로고침 하는 중...",
|
||||
"start_refresh_button": "새로고침 시작",
|
||||
"step": "단계",
|
||||
"title": "노드 정보 새로고침",
|
||||
"wakeup_header": "절전 해제 지침",
|
||||
"wakeup_instructions_source": "절전 해제 지침은 OpenZWave 커뮤니티 기기 데이터베이스에서 제공됩니다."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
@ -2272,7 +2310,7 @@
|
||||
"minimum": "최소",
|
||||
"name": "이름",
|
||||
"no_theme": "테마 없음",
|
||||
"refresh_interval": "새로 고침 간격",
|
||||
"refresh_interval": "새로고침 간격",
|
||||
"search": "검색",
|
||||
"secondary_info_attribute": "보조 정보 속성",
|
||||
"show_icon": "아이콘 표시",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Lued Status Verlaaf",
|
||||
"no_history_found": "Keen Status Verlaaf fonnt"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Bild",
|
||||
"unsupported_format": "Net ënnerstëtzte Format, wiel e JPEG, PNG oder GIF Bild."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Beräich",
|
||||
"automation": "Deel vun de folgenden Automatismen",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "Dëst Feld ass erfuerderlech",
|
||||
"yaml_not_editable": "D'Astellunge vun dëser Entitéit kënnen net vun vum Benotzer Interface as geännert ginn. Nëmmen Entitéiten déi iwwer den Benotzer Interface aus konfiguréiert sinn kënnen vun do aus geännert ginn."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Kierzen"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Dialog ofbriechen",
|
||||
"edit": "Entitéit änneren",
|
||||
@ -1659,6 +1666,7 @@
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Associatiounsgruppen a Memberen aktualiséieren",
|
||||
"cacheload": "Lued Informatioune vun der OpenZWave Cache Datei. Batterie Nodes bleiwen op dëser Etapp bis de Node erwächt.",
|
||||
"complete": "Interview Prozess ass komplett",
|
||||
"configuration": "Konfiguratiounswerter vum Node kréien",
|
||||
"instances": "Detailer kréien iwwert wéieng Instanzen oder Kanäl en Apparat ënnerstëtzt",
|
||||
|
@ -6,6 +6,7 @@
|
||||
},
|
||||
"panel": {
|
||||
"config": "Konfigūracija",
|
||||
"developer_tools": "Kūrėjo įrankiai",
|
||||
"history": "Istorija",
|
||||
"logbook": "Veiksmų žurnalas",
|
||||
"mailbox": "Pašto dėžutė",
|
||||
@ -17,6 +18,7 @@
|
||||
"state_attributes": {
|
||||
"climate": {
|
||||
"fan_mode": {
|
||||
"auto": "Auto",
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
}
|
||||
@ -40,9 +42,11 @@
|
||||
"unknown": "Nžn"
|
||||
},
|
||||
"device_tracker": {
|
||||
"home": "Namai",
|
||||
"not_home": "Išvykęs"
|
||||
},
|
||||
"person": {
|
||||
"home": "Namai",
|
||||
"not_home": "Išvykęs"
|
||||
}
|
||||
},
|
||||
@ -61,6 +65,13 @@
|
||||
"on": "Įjungta"
|
||||
},
|
||||
"binary_sensor": {
|
||||
"battery": {
|
||||
"off": "Normalus",
|
||||
"on": "Žemas"
|
||||
},
|
||||
"cold": {
|
||||
"on": "Šalta"
|
||||
},
|
||||
"connectivity": {
|
||||
"off": "Atsijungęs",
|
||||
"on": "Prisijungęs"
|
||||
@ -81,6 +92,9 @@
|
||||
"off": "Neaptikta",
|
||||
"on": "Aptikta"
|
||||
},
|
||||
"heat": {
|
||||
"on": "Karšta"
|
||||
},
|
||||
"moisture": {
|
||||
"off": "Sausa",
|
||||
"on": "Šlapia"
|
||||
@ -98,7 +112,12 @@
|
||||
"on": "Atidaryta"
|
||||
},
|
||||
"presence": {
|
||||
"off": "Išvykęs"
|
||||
"off": "Išvykęs",
|
||||
"on": "Namai"
|
||||
},
|
||||
"problem": {
|
||||
"off": "Ok",
|
||||
"on": "Problema"
|
||||
},
|
||||
"safety": {
|
||||
"off": "Saugu",
|
||||
@ -131,21 +150,44 @@
|
||||
"streaming": "Transliuojama"
|
||||
},
|
||||
"climate": {
|
||||
"cool": "Šaltas",
|
||||
"dry": "Sausa",
|
||||
"fan_only": "Tik ventiliatorius",
|
||||
"heat": "Šiluma",
|
||||
"off": "Išjungta"
|
||||
},
|
||||
"configurator": {
|
||||
"configure": "Konfigūruoti",
|
||||
"configured": "Sukonfigūruotas"
|
||||
},
|
||||
"cover": {
|
||||
"closed": "Uždarytas",
|
||||
"closing": "Uždarymas",
|
||||
"open": "Atidarytas",
|
||||
"opening": "Atidarymas",
|
||||
"stopped": "Sustabdytas"
|
||||
},
|
||||
"default": {
|
||||
"unavailable": "(nepasiekiamas)",
|
||||
"unknown": "Nežinoma"
|
||||
},
|
||||
"device_tracker": {
|
||||
"not_home": "Išvykęs"
|
||||
},
|
||||
"fan": {
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
},
|
||||
"group": {
|
||||
"closing": "Uždarymas",
|
||||
"home": "Namai",
|
||||
"not_home": "Išvykęs",
|
||||
"off": "Išjungta",
|
||||
"ok": "Ok",
|
||||
"on": "Įjungta"
|
||||
"on": "Įjungta",
|
||||
"open": "Atidarytas",
|
||||
"stopped": "Sustabdytas",
|
||||
"unlocked": "Atrakinta"
|
||||
},
|
||||
"input_boolean": {
|
||||
"on": "Įjungta"
|
||||
@ -154,9 +196,17 @@
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
},
|
||||
"lock": {
|
||||
"locked": "Užrakintas",
|
||||
"unlocked": "Atrakinta"
|
||||
},
|
||||
"media_player": {
|
||||
"idle": "Laukimo režimas",
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
"on": "Įjungta",
|
||||
"paused": "pristabdytas",
|
||||
"playing": "Groja",
|
||||
"standby": "Laukimo"
|
||||
},
|
||||
"person": {
|
||||
"home": "Namuose"
|
||||
@ -173,6 +223,9 @@
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
},
|
||||
"sun": {
|
||||
"above_horizon": "Virš horizonto"
|
||||
},
|
||||
"switch": {
|
||||
"off": "Išjungta",
|
||||
"on": "Įjungta"
|
||||
@ -200,6 +253,12 @@
|
||||
"alarm_control_panel": {
|
||||
"arm_custom_bypass": "Individualizuotas apėjimas"
|
||||
},
|
||||
"camera": {
|
||||
"not_available": "Vaizdas negalimas"
|
||||
},
|
||||
"persistent_notification": {
|
||||
"dismiss": "Atmesti"
|
||||
},
|
||||
"weather": {
|
||||
"attributes": {
|
||||
"air_pressure": "Atmosferos slėgis",
|
||||
@ -221,6 +280,8 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Atšaukti",
|
||||
"loading": "Pakrovimas",
|
||||
"save": "Išsaugoti"
|
||||
},
|
||||
"components": {
|
||||
@ -250,6 +311,11 @@
|
||||
"second": "{count} {count, plural,\n one {sekundė}\n other {sekundės}\n}",
|
||||
"week": "{count} {count, plural,\n one {savaitė}\n other {savaitės}\n}"
|
||||
},
|
||||
"login-form": {
|
||||
"log_in": "Prisijungti",
|
||||
"password": "Slaptažodis",
|
||||
"remember": "Prisiminti"
|
||||
},
|
||||
"notification_drawer": {
|
||||
"close": "Uždaryti"
|
||||
},
|
||||
@ -328,10 +394,20 @@
|
||||
"edit_ui": "Redaguoti naudojant vartotojo sąsają",
|
||||
"edit_yaml": "Redaguoti kaip YAML",
|
||||
"triggers": {
|
||||
"add": "Pridėti trigerį",
|
||||
"delete": "Ištrinti",
|
||||
"delete_confirm": "Ar tikrai norite tai ištrinti?",
|
||||
"duplicate": "Pasikartojantys",
|
||||
"introduction": "Trigeriai yra tai, kas pradeda automatizavimo taisyklės apdorojimą. Tai pačiai taisyklei galima nurodyti kelis aktyviklius. Kai tik įsijungs trigeris, Home Assistant patikrins sąlygas, jei tokių yra, ir iškviečia veiksmą.",
|
||||
"learn_more": "Sužinokite daugiau apie trigerius",
|
||||
"name": "Trigeris",
|
||||
"type_select": "Trigerio tipas",
|
||||
"type": {
|
||||
"event": {
|
||||
"event_data": "Įvykio duomenys",
|
||||
"event_type": "Įvykio tipas",
|
||||
"label": "Įvykis:"
|
||||
},
|
||||
"geo_location": {
|
||||
"enter": "Įveskite",
|
||||
"event": "Įvykis:",
|
||||
@ -340,11 +416,32 @@
|
||||
"source": "Šaltinis",
|
||||
"zone": "Zona"
|
||||
},
|
||||
"homeassistant": {
|
||||
"event": "Įvykis:",
|
||||
"label": "Home Assistant",
|
||||
"shutdown": "Išjungti",
|
||||
"start": "Pradėti"
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "MQTT"
|
||||
"label": "MQTT",
|
||||
"topic": "Tema"
|
||||
},
|
||||
"numeric_state": {
|
||||
"above": "Aukščiau",
|
||||
"below": "Žemiau",
|
||||
"label": "Skaitinė būsena",
|
||||
"value_template": "Vertės šablonas (pasirinktinai)"
|
||||
},
|
||||
"state": {
|
||||
"from": "Iš",
|
||||
"to": "Kam"
|
||||
},
|
||||
"sun": {
|
||||
"event": "Ivykis"
|
||||
"event": "Ivykis",
|
||||
"label": "Saulė",
|
||||
"offset": "Poslinkis (pasirinktinai)",
|
||||
"sunrise": "Saulėtekis",
|
||||
"sunset": "Saulėlydis"
|
||||
},
|
||||
"time_pattern": {
|
||||
"hours": "Valandos",
|
||||
@ -360,7 +457,8 @@
|
||||
"event": "Įvykis",
|
||||
"zone": "Vieta"
|
||||
}
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Nėra UI palaikymo platformai: {platform}"
|
||||
}
|
||||
},
|
||||
"picker": {
|
||||
@ -374,6 +472,9 @@
|
||||
},
|
||||
"caption": "Home Assistant Cloud"
|
||||
},
|
||||
"core": {
|
||||
"caption": "Bendra"
|
||||
},
|
||||
"customize": {
|
||||
"picker": {
|
||||
"header": "Pritaikymas"
|
||||
@ -395,6 +496,7 @@
|
||||
"introduction2": "Naudokite subjekto registrą, kad perrašytumėte pavadinimą, pakeiskite subjekto ID arba pašalintumėte įrašą iš namų asistento. Atminkite, kad pašalindami registro įrašą tai nepanaikins pačio subjekto. Norėdami tai padaryti, sekite toliau pateiktą nuorodą ir pašalinkite ją iš integracijos puslapio."
|
||||
}
|
||||
},
|
||||
"header": "Konfigūruoti Home Assistant",
|
||||
"integrations": {
|
||||
"config_entry": {
|
||||
"hub": "Prijungtas per",
|
||||
@ -408,6 +510,7 @@
|
||||
},
|
||||
"details": "Integravimo informacija"
|
||||
},
|
||||
"introduction": "Šiame vaizde galima sukonfigūruoti savo komponentus ir Home Assistant. Dar ne viską galima sukonfigūruoti iš vartotojo sąsajos (UI ), tačiau mes prie to dirbame.",
|
||||
"mqtt": {
|
||||
"title": "MQTT"
|
||||
},
|
||||
@ -454,11 +557,29 @@
|
||||
"question_trust": "Ar pasitikite išoriniu skydeliu {name} adresu {link}?"
|
||||
}
|
||||
},
|
||||
"developer-tools": {
|
||||
"tabs": {
|
||||
"events": {
|
||||
"title": "Įvykiai"
|
||||
},
|
||||
"services": {
|
||||
"title": "Paslaugos"
|
||||
},
|
||||
"states": {
|
||||
"title": "Būsenos"
|
||||
},
|
||||
"templates": {
|
||||
"title": "Šablonas"
|
||||
}
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
"period": "Laikotarpis",
|
||||
"ranges": {
|
||||
"today": "Šiandien",
|
||||
"yesterday": "Vakar"
|
||||
}
|
||||
},
|
||||
"showing_entries": "Rodomi įrašai, skirti"
|
||||
},
|
||||
"logbook": {
|
||||
"period": "Laikotarpis",
|
||||
@ -535,6 +656,12 @@
|
||||
"entity_not_found": "Subjektas nepasiekiamas: {entity}"
|
||||
}
|
||||
},
|
||||
"mailbox": {
|
||||
"delete_button": "Ištrinti",
|
||||
"delete_prompt": "Ištrinti šį pranešimą?",
|
||||
"empty": "Neturite jokių pranešimų",
|
||||
"playback_title": "Pranešimų atkūrimas"
|
||||
},
|
||||
"page-authorize": {
|
||||
"form": {
|
||||
"providers": {
|
||||
@ -587,6 +714,9 @@
|
||||
"push_notifications": {
|
||||
"description": "Siųsti pranešimus į šį įrenginį."
|
||||
}
|
||||
},
|
||||
"shopping-list": {
|
||||
"microphone_tip": "Bakstelėkite mikrofoną viršutinėje dešinėje ir pasakykite arba įveskite „Pridėti saldainių prie mano pirkinių sąrašo“"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Laster statushistorikk...",
|
||||
"no_history_found": "Ingen statushistorikk funnet."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Bilde",
|
||||
"unsupported_format": "Formatet støttes ikke, vennligst velge et JPEG-, PNG- eller GIF-bilde."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Område",
|
||||
"automation": "Del av følgende automasjoner",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "Dette feltet er påkrevd",
|
||||
"yaml_not_editable": "Innstillingene for denne entiteten kan ikke redigeres fra brukergrensesnittet. Bare entiteter som er satt opp fra brukergrensesnittet, kan konfigureres fra brukergrensesnittet."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Beskjære"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Avvis dialogboksen",
|
||||
"edit": "Redigér entitet",
|
||||
@ -892,7 +899,7 @@
|
||||
"wait_template": "Ventemal"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Ikke-støttet handling: {action}"
|
||||
"unsupported_action": "Ingen brukergrensesnitt for handling: {action}"
|
||||
},
|
||||
"alias": "Navn",
|
||||
"conditions": {
|
||||
@ -958,7 +965,7 @@
|
||||
"zone": "Sone"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Ikke-støttet tilstand: {condition}"
|
||||
"unsupported_condition": "Ingen brukergrensesnitt for bruk: {condition}"
|
||||
},
|
||||
"default_name": "Ny automasjon",
|
||||
"description": {
|
||||
@ -1050,6 +1057,9 @@
|
||||
"sunrise": "Soloppgang",
|
||||
"sunset": "Solnedgang"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Tag"
|
||||
},
|
||||
"template": {
|
||||
"label": "Mal",
|
||||
"value_template": "Verdi mal"
|
||||
@ -1077,7 +1087,7 @@
|
||||
"zone": "Sone"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Ikke-støttet plattform: {platform}"
|
||||
"unsupported_platform": "Ingen UI-støtte for plattform: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "Du har ulagrede endringer. Er du sikker på at du vil lukke?"
|
||||
},
|
||||
@ -1843,6 +1853,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Legg til tag",
|
||||
"automation_title": "Tag {name} skannes",
|
||||
"caption": "Tags",
|
||||
"create_automation": "Opprett automatisering med tag",
|
||||
"description": "Administrer tagger",
|
||||
"detail": {
|
||||
"create": "Opprett",
|
||||
"create_and_write": "Opprette og skrive",
|
||||
"delete": "Slett",
|
||||
"description": "Beskrivelse",
|
||||
"name": "Navn",
|
||||
"new_tag": "Ny tag",
|
||||
"tag_id": "Tag id",
|
||||
"tag_id_placeholder": "Automatisk generert når den er tom",
|
||||
"update": "Oppdater"
|
||||
},
|
||||
"edit": "Redigere",
|
||||
"headers": {
|
||||
"last_scanned": "Sist skannet",
|
||||
"name": "Navn"
|
||||
},
|
||||
"no_tags": "Ingen tagger",
|
||||
"write": "Skrive"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Legg til bruker",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Ładowanie historii...",
|
||||
"no_history_found": "Nie znaleziono historii."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Obraz",
|
||||
"unsupported_format": "Nieobsługiwany format, wybierz obraz JPEG, PNG lub GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Obszar",
|
||||
"automation": "Element następujących automatyzacji",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "To pole jest wymagane",
|
||||
"yaml_not_editable": "Ustawienia tej encji nie mogą być edytowane z interfejsu użytkownika. Tylko encje dodane z interfejsu użytkownika można konfigurować z poziomu interfejsu użytkownika."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Przytnij"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Zamknij okno dialogowe",
|
||||
"edit": "Edytuj encję",
|
||||
@ -1656,6 +1663,37 @@
|
||||
"node_failed": "Węzeł uszkodzony",
|
||||
"stage": "Etap",
|
||||
"zwave_info": "Informacje Z-Wave"
|
||||
},
|
||||
"node_query_stages": {
|
||||
"associations": "Odświeżanie grup skojarzeń i członkostwa",
|
||||
"cacheload": "Ładowanie informacji z pliku pamięci podręcznej OpenZWave. Węzły baterii pozostaną na tym etapie, dopóki węzeł się nie wybudzi.",
|
||||
"complete": "Proces wywiadu jest zakończony",
|
||||
"configuration": "Pobieranie wartości konfiguracyjnych z węzła",
|
||||
"dynamic": "Pobieranie często zmieniających się wartości z węzła",
|
||||
"instances": "Pobieranie szczegółowych informacji o instancjach lub kanałach obsługiwanych przez urządzenie",
|
||||
"manufacturerspecific1": "Pobieranie kodów producenta i produktu z węzła",
|
||||
"manufacturerspecific2": "Pobieranie dodatkowych kodów producenta i produktu z węzła",
|
||||
"neighbors": "Pobieranie listy sąsiadów węzła",
|
||||
"nodeinfo": "Pobieranie obsługiwanych klas poleceń z węzła",
|
||||
"nodeplusinfo": "Pobieranie informacji Z-Wave+ z węzła",
|
||||
"probe": "Sprawdzanie, czy węzeł jest wybudzony/żywy",
|
||||
"protocolinfo": "Pobieranie z kontrolera informacji o podstawowych możliwościach Z-Wave tego węzła",
|
||||
"session": "Pobieranie rzadko zmieniających się wartości z węzła",
|
||||
"static": "Pobieranie wartości statycznych z urządzenia",
|
||||
"versions": "Pobieranie informacji o wersjach oprogramowania i klas poleceń",
|
||||
"wakeup": "Konfigurowanie obsługi kolejek wybudzania i wiadomości"
|
||||
},
|
||||
"refresh_node": {
|
||||
"battery_note": "Jeśli węzeł jest zasilany bateryjnie, przed kontynuowaniem należy go wybudzić",
|
||||
"complete": "Odświeżanie węzła zakończone",
|
||||
"description": "Poinformuje to OpenZWave o konieczności ponownego odpytania węzła i zaktualizowaniu jego klas poleceń, możliwości i wartości.",
|
||||
"node_status": "Stan węzła",
|
||||
"refreshing_description": "Odświeżanie informacji o węźle...",
|
||||
"start_refresh_button": "Rozpocznij odświeżanie",
|
||||
"step": "Krok",
|
||||
"title": "Odświeżanie informacji o węźle",
|
||||
"wakeup_header": "Instrukcje budzenia dla",
|
||||
"wakeup_instructions_source": "Instrukcje budzenia pochodzą z bazy danych urządzeń społeczności OpenZWave."
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
|
@ -804,6 +804,14 @@
|
||||
"name": "Acțiune",
|
||||
"type_select": "Tip acțiune",
|
||||
"type": {
|
||||
"choose": {
|
||||
"add_option": "Adauga optiune",
|
||||
"conditions": "Condiții",
|
||||
"default": "Acţiuni implicite",
|
||||
"label": "Alege",
|
||||
"remove_option": "Elimina optiune",
|
||||
"sequence": "Acţiuni"
|
||||
},
|
||||
"condition": {
|
||||
"label": "Condiție"
|
||||
},
|
||||
@ -822,6 +830,22 @@
|
||||
"label": "Eveniment declansare",
|
||||
"service_data": "Date servicii"
|
||||
},
|
||||
"repeat": {
|
||||
"label": "Repetaţi",
|
||||
"sequence": "Acţiuni",
|
||||
"type": {
|
||||
"count": {
|
||||
"label": "Numara"
|
||||
},
|
||||
"until": {
|
||||
"label": "Pana cand"
|
||||
},
|
||||
"while": {
|
||||
"conditions": "Condiții in timp ce",
|
||||
"label": "In timp ce"
|
||||
}
|
||||
}
|
||||
},
|
||||
"service": {
|
||||
"label": "Cheama serviciu",
|
||||
"service_data": "Date serviciu"
|
||||
@ -1398,6 +1422,18 @@
|
||||
"title": "MQTT",
|
||||
"topic": "subiect"
|
||||
},
|
||||
"ozw": {
|
||||
"common": {
|
||||
"node_id": "ID Nod",
|
||||
"ozw_instance": "Instanta OpenZWave",
|
||||
"zwave": "Z-Wave"
|
||||
},
|
||||
"device_info": {
|
||||
"node_failed": "Nod nereușit",
|
||||
"stage": "Etapă",
|
||||
"zwave_info": "Z-Wave Info"
|
||||
}
|
||||
},
|
||||
"person": {
|
||||
"caption": "Persoane",
|
||||
"description": "Gestionează persoanele pe care Home Assistant le urmărește.",
|
||||
@ -2040,6 +2076,9 @@
|
||||
"para_migrate": "Home Assistant poate adăuga ID-ul la toate cărțile și vizualizările în mod automat pentru tine apăsând butonul \"Migrează configurația\".",
|
||||
"para_no_id": "Acest element nu are un ID. Adăugați un ID la acest element în \"ui-lovelace.yaml\"."
|
||||
},
|
||||
"move_card": {
|
||||
"header": "Alegeți o vedere pentru a muta cardul în"
|
||||
},
|
||||
"raw_editor": {
|
||||
"confirm_remove_config_text": "Vom genera automat vizualizările UI Lovelace cu zonele și dispozitivele dvs., dacă eliminați configurația UI Lovelace.",
|
||||
"confirm_remove_config_title": "Sigur eliminați configurația Ui Lovelace? Vom genera automat vizualizările UI Lovelace cu zonele și dispozitivele dvs.",
|
||||
@ -2067,6 +2106,10 @@
|
||||
"yaml_control": "Pentru a prelua controlul în modul YAML, creați un fișier YAML cu numele pe care l-ați specificat în configurația dvs. pentru acest tablou de bord sau implicit „ui-lovelace.yaml”.",
|
||||
"yaml_mode": "Utilizați modul YAML, ceea ce înseamnă că nu puteți modifica configurația Lovelace din UI. Dacă doriți să schimbați Lovelace din interfața de utilizator, eliminați „modul: yaml” din configurația dvs. Lovelace din „configuration.yaml”."
|
||||
},
|
||||
"select_view": {
|
||||
"dashboard_label": "Tablou de bord",
|
||||
"header": "Alegeți o vizualizare"
|
||||
},
|
||||
"suggest_card": {
|
||||
"add": "Adăugați la Lovelace UI",
|
||||
"create_own": "Alege alt card",
|
||||
@ -2379,10 +2422,18 @@
|
||||
"header": "Închideți automat conexiunea"
|
||||
},
|
||||
"themes": {
|
||||
"accent_color": "Culoare de accent",
|
||||
"dark_mode": {
|
||||
"auto": "Auto",
|
||||
"dark": "Întuneric",
|
||||
"light": "Lumina"
|
||||
},
|
||||
"dropdown_label": "Temă",
|
||||
"error_no_theme": "Nu există teme disponibile.",
|
||||
"header": "Temă",
|
||||
"link_promo": "Aflați mai multe despre teme"
|
||||
"link_promo": "Aflați mai multe despre teme",
|
||||
"primary_color": "Culoare primară",
|
||||
"reset": "Resetați"
|
||||
}
|
||||
},
|
||||
"shopping-list": {
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "Загрузка истории...",
|
||||
"no_history_found": "История не найдена."
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "Изображение",
|
||||
"unsupported_format": "Неподдерживаемый формат. Выберите изображение в формате JPEG, PNG или GIF."
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Помещение",
|
||||
"automation": "Используется в автоматизациях",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "Обязательное поле",
|
||||
"yaml_not_editable": "Настройки этого объекта нельзя изменить из пользовательского интерфейса. Настраиваться из пользовательского интерфейса могут только те объекты, которые были созданы в нём."
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "Обрезать"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "Закрыть диалог",
|
||||
"edit": "Изменить объект",
|
||||
@ -892,7 +899,7 @@
|
||||
"wait_template": "Шаблон ожидания"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "Неподдерживаемое действие: {action}"
|
||||
"unsupported_action": "Отсутствует форма ввода для этого действия: {action}"
|
||||
},
|
||||
"alias": "Название",
|
||||
"conditions": {
|
||||
@ -958,7 +965,7 @@
|
||||
"zone": "Зона"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "Неподдерживаемое условие: {condition}"
|
||||
"unsupported_condition": "Отсутствует форма ввода для этого условия: {condition}"
|
||||
},
|
||||
"default_name": "Новая автоматизация",
|
||||
"description": {
|
||||
@ -1050,6 +1057,9 @@
|
||||
"sunrise": "Восход",
|
||||
"sunset": "Закат"
|
||||
},
|
||||
"tag": {
|
||||
"label": "Метка"
|
||||
},
|
||||
"template": {
|
||||
"label": "Шаблон",
|
||||
"value_template": "Значение шаблона"
|
||||
@ -1077,7 +1087,7 @@
|
||||
"zone": "Зона"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "Неподдерживаемая платформа: {platform}"
|
||||
"unsupported_platform": "Отсутствует форма ввода для этой платформы: {platform}"
|
||||
},
|
||||
"unsaved_confirm": "У вас есть несохраненные изменения. Вы уверены, что хотите выйти?"
|
||||
},
|
||||
@ -1327,6 +1337,7 @@
|
||||
"caption": "Устройства",
|
||||
"confirm_delete": "Вы уверены, что хотите удалить это устройство?",
|
||||
"confirm_rename_entity_ids": "Хотите ли Вы также переименовать идентификаторы объектов?",
|
||||
"confirm_rename_entity_ids_warning": "Переименование повлечёт за собой необходимость вручную обновлять изменённые данные в правилах автоматизации, сценариях, сценах и пользовательском интерфейсе",
|
||||
"data_table": {
|
||||
"area": "Помещение",
|
||||
"battery": "Аккумулятор",
|
||||
@ -1843,6 +1854,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "Добавить метку",
|
||||
"automation_title": "Считана метка {name}",
|
||||
"caption": "Метки",
|
||||
"create_automation": "Создать автоматизацию с меткой",
|
||||
"description": "Управление метками",
|
||||
"detail": {
|
||||
"create": "Добавить",
|
||||
"create_and_write": "Добавить и записать",
|
||||
"delete": "Удалить",
|
||||
"description": "Описание",
|
||||
"name": "Название",
|
||||
"new_tag": "Новая метка",
|
||||
"tag_id": "ID метки",
|
||||
"tag_id_placeholder": "Если не указан, создается автоматически",
|
||||
"update": "Обновить"
|
||||
},
|
||||
"edit": "Изменить",
|
||||
"headers": {
|
||||
"last_scanned": "Последнее считывание",
|
||||
"name": "Название"
|
||||
},
|
||||
"no_tags": "Нет меток",
|
||||
"write": "Записать"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "Добавить пользователя",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "正在加载历史状态...",
|
||||
"no_history_found": "没有找到历史状态。"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "图片",
|
||||
"unsupported_format": "格式不受支持,请选择 JPEG、PNG 或 GIF 图像。"
|
||||
},
|
||||
"related-items": {
|
||||
"area": "区域",
|
||||
"automation": "以下自动化的一部分",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "此字段为必填字段",
|
||||
"yaml_not_editable": "无法从 UI 编辑此实体的设置。只有通过 UI 设置的实体可以从 UI 配置。"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "剪裁"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "关闭对话框",
|
||||
"edit": "编辑实体",
|
||||
|
@ -554,6 +554,10 @@
|
||||
"loading_history": "正在載入狀態歷史...",
|
||||
"no_history_found": "找不到狀態歷史。"
|
||||
},
|
||||
"picture-upload": {
|
||||
"label": "照片",
|
||||
"unsupported_format": "不支援的格式,請選擇 JPEG、PNG 或 GIF 圖檔。"
|
||||
},
|
||||
"related-items": {
|
||||
"area": "分區",
|
||||
"automation": "以下自動化部分",
|
||||
@ -656,6 +660,9 @@
|
||||
"required_error_msg": "必填欄位",
|
||||
"yaml_not_editable": "此物件的設定無法藉由 UI 編輯、僅有透過 UI 設定的物件可於 UI 進行設定。"
|
||||
},
|
||||
"image_cropper": {
|
||||
"crop": "裁切"
|
||||
},
|
||||
"more_info_control": {
|
||||
"dismiss": "忽略對話",
|
||||
"edit": "編輯物件",
|
||||
@ -892,7 +899,7 @@
|
||||
"wait_template": "等待模板"
|
||||
}
|
||||
},
|
||||
"unsupported_action": "不支援的觸發動作: {action}"
|
||||
"unsupported_action": "UI 介面不支援觸發動作:{action}"
|
||||
},
|
||||
"alias": "名稱",
|
||||
"conditions": {
|
||||
@ -958,7 +965,7 @@
|
||||
"zone": "區域"
|
||||
}
|
||||
},
|
||||
"unsupported_condition": "不支援的觸發判斷: {condition}"
|
||||
"unsupported_condition": "UI 介面不支援觸發判斷:{condition}"
|
||||
},
|
||||
"default_name": "建立新的自動化",
|
||||
"description": {
|
||||
@ -1050,6 +1057,9 @@
|
||||
"sunrise": "日出",
|
||||
"sunset": "日落"
|
||||
},
|
||||
"tag": {
|
||||
"label": "標籤"
|
||||
},
|
||||
"template": {
|
||||
"label": "模板",
|
||||
"value_template": "數值模板"
|
||||
@ -1077,7 +1087,7 @@
|
||||
"zone": "區域"
|
||||
}
|
||||
},
|
||||
"unsupported_platform": "不支持的平台: {platform}"
|
||||
"unsupported_platform": "UI 介面不支援平台:{platform}"
|
||||
},
|
||||
"unsaved_confirm": "設定尚未儲存,確定要放棄嗎?"
|
||||
},
|
||||
@ -1327,6 +1337,7 @@
|
||||
"caption": "設備",
|
||||
"confirm_delete": "確定要刪除此設備?",
|
||||
"confirm_rename_entity_ids": "是否也要變更物件的物件 ID?",
|
||||
"confirm_rename_entity_ids_warning": "將不會變更任何物件正在使用的設定(例如自動化、腳本、場景與 Lovelace),必須自行更新。",
|
||||
"data_table": {
|
||||
"area": "分區",
|
||||
"battery": "電量",
|
||||
@ -1843,6 +1854,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"add_tag": "新增標籤",
|
||||
"automation_title": "標籤 {name} 已掃描",
|
||||
"caption": "標籤",
|
||||
"create_automation": "以標籤新增自動化",
|
||||
"description": "管理標籤",
|
||||
"detail": {
|
||||
"create": "新增",
|
||||
"create_and_write": "新增與編寫",
|
||||
"delete": "刪除",
|
||||
"description": "說明",
|
||||
"name": "名稱",
|
||||
"new_tag": "新標籤",
|
||||
"tag_id": "標籤 ID",
|
||||
"tag_id_placeholder": "空白時將自動產生",
|
||||
"update": "更新"
|
||||
},
|
||||
"edit": "編輯",
|
||||
"headers": {
|
||||
"last_scanned": "最後掃描時間",
|
||||
"name": "名稱"
|
||||
},
|
||||
"no_tags": "無標籤",
|
||||
"write": "編寫"
|
||||
},
|
||||
"users": {
|
||||
"add_user": {
|
||||
"caption": "新增用戶",
|
||||
|
Loading…
x
Reference in New Issue
Block a user