mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Merge branch 'dev' of https://github.com/home-assistant/frontend into waitfor-ingress
This commit is contained in:
commit
9ced09035f
@ -5,8 +5,6 @@ const paths = require("./paths.js");
|
||||
|
||||
// Files from NPM Packages that should not be imported
|
||||
module.exports.ignorePackages = ({ latestBuild }) => [
|
||||
// Bloats bundle and it's not used.
|
||||
path.resolve(require.resolve("moment"), "../locale"),
|
||||
// Part of yaml.js and only used for !!js functions that we don't use
|
||||
require.resolve("esprima"),
|
||||
];
|
||||
|
@ -19,10 +19,12 @@ const bothBuilds = (createConfigFunc, params) => [
|
||||
createConfigFunc({ ...params, latestBuild: false }),
|
||||
];
|
||||
|
||||
const isWsl = fs
|
||||
.readFileSync("/proc/version", "utf-8")
|
||||
.toLocaleLowerCase()
|
||||
.includes("microsoft");
|
||||
const isWsl =
|
||||
fs.existsSync("/proc/version") &&
|
||||
fs
|
||||
.readFileSync("/proc/version", "utf-8")
|
||||
.toLocaleLowerCase()
|
||||
.includes("microsoft");
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
|
@ -134,7 +134,7 @@ class HassioAddonConfig extends LitElement {
|
||||
></ha-form>`
|
||||
: html` <ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
.schema=${ADDON_YAML_SCHEMA}
|
||||
.yamlSchema=${ADDON_YAML_SCHEMA}
|
||||
></ha-yaml-editor>`}
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${!this._yamlMode ||
|
||||
|
@ -892,10 +892,19 @@ class HassioAddonInfo extends LitElement {
|
||||
|
||||
private async _openChangelog(): Promise<void> {
|
||||
try {
|
||||
const content = await fetchHassioAddonChangelog(
|
||||
this.hass,
|
||||
this.addon.slug
|
||||
);
|
||||
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
|
||||
if (
|
||||
content.includes(`# ${this.addon.version}`) &&
|
||||
content.includes(`# ${this.addon.version_latest}`)
|
||||
) {
|
||||
const newcontent = content.split(`# ${this.addon.version}`)[0];
|
||||
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
|
||||
// Only change the content if the new version still exist
|
||||
// if the changelog does not have the newests version on top
|
||||
// this will not be true, and we don't modify the content
|
||||
content = newcontent;
|
||||
}
|
||||
}
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: this.supervisor.localize("addon.dashboard.changelog"),
|
||||
content,
|
||||
|
@ -158,8 +158,8 @@ class DialogSupervisorUpdate extends LitElement {
|
||||
} catch (err) {
|
||||
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
|
||||
this._error = extractApiErrorMessage(err);
|
||||
this._action = null;
|
||||
}
|
||||
this._action = null;
|
||||
return;
|
||||
}
|
||||
|
||||
|
33
package.json
33
package.json
@ -44,20 +44,21 @@
|
||||
"@fullcalendar/list": "5.1.0",
|
||||
"@lit-labs/virtualizer": "^0.6.0",
|
||||
"@material/chips": "=12.0.0-canary.1a8d06483.0",
|
||||
"@material/mwc-button": "canary",
|
||||
"@material/mwc-checkbox": "canary",
|
||||
"@material/mwc-circular-progress": "canary",
|
||||
"@material/mwc-dialog": "canary",
|
||||
"@material/mwc-fab": "canary",
|
||||
"@material/mwc-formfield": "canary",
|
||||
"@material/mwc-icon-button": "canary",
|
||||
"@material/mwc-list": "canary",
|
||||
"@material/mwc-menu": "canary",
|
||||
"@material/mwc-radio": "canary",
|
||||
"@material/mwc-ripple": "canary",
|
||||
"@material/mwc-switch": "canary",
|
||||
"@material/mwc-tab": "canary",
|
||||
"@material/mwc-tab-bar": "canary",
|
||||
"@material/mwc-button": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-checkbox": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-circular-progress": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-dialog": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-fab": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-formfield": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-icon-button": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-linear-progress": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-list": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-menu": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-radio": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-ripple": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-switch": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-tab": "0.22.0-canary.cc04657a.0",
|
||||
"@material/mwc-tab-bar": "0.22.0-canary.cc04657a.0",
|
||||
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0",
|
||||
"@mdi/js": "5.9.55",
|
||||
"@mdi/svg": "5.9.55",
|
||||
@ -96,11 +97,11 @@
|
||||
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "^2.9.4",
|
||||
"chartjs-chart-timeline": "^0.4.0",
|
||||
"chart.js": "^3.3.2",
|
||||
"comlink": "^4.3.1",
|
||||
"core-js": "^3.6.5",
|
||||
"cropperjs": "^1.5.11",
|
||||
"date-fns": "^2.22.1",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"fecha": "^4.2.0",
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import "./ha-password-manager-polyfill";
|
||||
import { property, state } from "lit/decorators";
|
||||
import "../components/ha-form/ha-form";
|
||||
import "../components/ha-markdown";
|
||||
@ -20,7 +21,7 @@ import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
@property() public authProvider?: AuthProvider;
|
||||
@property({ attribute: false }) public authProvider?: AuthProvider;
|
||||
|
||||
@property() public clientId?: string;
|
||||
|
||||
@ -37,7 +38,15 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
@state() private _errorMessage?: string;
|
||||
|
||||
protected render() {
|
||||
return html` <form>${this._renderForm()}</form> `;
|
||||
return html`
|
||||
<form>${this._renderForm()}</form>
|
||||
<ha-password-manager-polyfill
|
||||
.step=${this._step}
|
||||
.stepData=${this._stepData}
|
||||
@form-submitted=${this._handleSubmit}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
></ha-password-manager-polyfill>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
@ -231,11 +240,17 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
await this.updateComplete;
|
||||
// 100ms to give all the form elements time to initialize.
|
||||
setTimeout(() => {
|
||||
const form = this.shadowRoot!.querySelector("ha-form");
|
||||
const form = this.renderRoot.querySelector("ha-form");
|
||||
if (form) {
|
||||
(form as any).focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
this.renderRoot.querySelector(
|
||||
"ha-password-manager-polyfill"
|
||||
)!.boundingRect = this.getBoundingClientRect();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _stepDataChanged(ev: CustomEvent) {
|
||||
@ -329,3 +344,9 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
customElements.define("ha-auth-flow", HaAuthFlow);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-flow": HaAuthFlow;
|
||||
}
|
||||
}
|
||||
|
110
src/auth/ha-password-manager-polyfill.ts
Normal file
110
src/auth/ha-password-manager-polyfill.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { HaFormSchema } from "../components/ha-form/ha-form";
|
||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"form-submitted": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const ENABLED_HANDLERS = [
|
||||
"homeassistant",
|
||||
"legacy_api_password",
|
||||
"command_line",
|
||||
];
|
||||
|
||||
@customElement("ha-password-manager-polyfill")
|
||||
export class HaPasswordManagerPolyfill extends LitElement {
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ attribute: false }) public stepData: any;
|
||||
|
||||
@property({ attribute: false }) public boundingRect?: DOMRect;
|
||||
|
||||
protected createRenderRoot() {
|
||||
// Add under document body so the element isn't placed inside any shadow roots
|
||||
return document.body;
|
||||
}
|
||||
|
||||
private get styles() {
|
||||
return `
|
||||
.password-manager-polyfill {
|
||||
position: absolute;
|
||||
top: ${this.boundingRect?.y || 148}px;
|
||||
left: calc(50% - ${(this.boundingRect?.width || 360) / 2}px);
|
||||
width: ${this.boundingRect?.width || 360}px;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
.password-manager-polyfill input {
|
||||
width: 100%;
|
||||
height: 62px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.password-manager-polyfill input[type="submit"] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
this.step &&
|
||||
this.step.type === "form" &&
|
||||
this.step.step_id === "init" &&
|
||||
ENABLED_HANDLERS.includes(this.step.handler[0])
|
||||
) {
|
||||
return html`
|
||||
<form
|
||||
class="password-manager-polyfill"
|
||||
aria-hidden="true"
|
||||
@submit=${this._handleSubmit}
|
||||
>
|
||||
${this.step.data_schema.map((input) => this.render_input(input))}
|
||||
<input type="submit" />
|
||||
<style>
|
||||
${this.styles}
|
||||
</style>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
return html``;
|
||||
}
|
||||
|
||||
private render_input(schema: HaFormSchema): TemplateResult | string {
|
||||
const inputType = schema.name.includes("password") ? "password" : "text";
|
||||
if (schema.type !== "string") {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<input
|
||||
tabindex="-1"
|
||||
.id=${schema.name}
|
||||
.type=${inputType}
|
||||
.value=${this.stepData[schema.name] || ""}
|
||||
@input=${this._valueChanged}
|
||||
/>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSubmit(ev: Event) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "form-submitted");
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const target = ev.target! as HTMLInputElement;
|
||||
this.stepData = { ...this.stepData, [target.id]: target.value };
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.stepData,
|
||||
});
|
||||
}
|
||||
}
|
63
src/common/color/colors.ts
Normal file
63
src/common/color/colors.ts
Normal file
@ -0,0 +1,63 @@
|
||||
export const COLORS = [
|
||||
"#377eb8",
|
||||
"#984ea3",
|
||||
"#00d2d5",
|
||||
"#ff7f00",
|
||||
"#af8d00",
|
||||
"#7f80cd",
|
||||
"#b3e900",
|
||||
"#c42e60",
|
||||
"#a65628",
|
||||
"#f781bf",
|
||||
"#8dd3c7",
|
||||
"#bebada",
|
||||
"#fb8072",
|
||||
"#80b1d3",
|
||||
"#fdb462",
|
||||
"#fccde5",
|
||||
"#bc80bd",
|
||||
"#ffed6f",
|
||||
"#c4eaff",
|
||||
"#cf8c00",
|
||||
"#1b9e77",
|
||||
"#d95f02",
|
||||
"#e7298a",
|
||||
"#e6ab02",
|
||||
"#a6761d",
|
||||
"#0097ff",
|
||||
"#00d067",
|
||||
"#f43600",
|
||||
"#4ba93b",
|
||||
"#5779bb",
|
||||
"#927acc",
|
||||
"#97ee3f",
|
||||
"#bf3947",
|
||||
"#9f5b00",
|
||||
"#f48758",
|
||||
"#8caed6",
|
||||
"#f2b94f",
|
||||
"#eff26e",
|
||||
"#e43872",
|
||||
"#d9b100",
|
||||
"#9d7a00",
|
||||
"#698cff",
|
||||
"#d9d9d9",
|
||||
"#00d27e",
|
||||
"#d06800",
|
||||
"#009f82",
|
||||
"#c49200",
|
||||
"#cbe8ff",
|
||||
"#fecddf",
|
||||
"#c27eb6",
|
||||
"#8cd2ce",
|
||||
"#c4b8d9",
|
||||
"#f883b0",
|
||||
"#a49100",
|
||||
"#f48800",
|
||||
"#27d0df",
|
||||
"#a04a9b",
|
||||
];
|
||||
|
||||
export function getColorByIndex(index: number) {
|
||||
return COLORS[index % COLORS.length];
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
const luminosity = (rgb: [number, number, number]): number => {
|
||||
export const luminosity = (rgb: [number, number, number]): number => {
|
||||
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
|
||||
const lum: [number, number, number] = [0, 0, 0];
|
||||
for (let i = 0; i < rgb.length; i++) {
|
||||
|
@ -42,6 +42,7 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
remote: "hass:remote",
|
||||
scene: "hass:palette",
|
||||
script: "hass:script-text",
|
||||
select: "hass:format-list-bulleted",
|
||||
sensor: "hass:eye",
|
||||
simple_alarm: "hass:bell",
|
||||
sun: "hass:white-balance-sunny",
|
||||
@ -83,6 +84,7 @@ export const DOMAINS_WITH_CARD = [
|
||||
"number",
|
||||
"scene",
|
||||
"script",
|
||||
"select",
|
||||
"timer",
|
||||
"vacuum",
|
||||
"water_heater",
|
||||
@ -121,6 +123,7 @@ export const DOMAINS_HIDE_MORE_INFO = [
|
||||
"input_text",
|
||||
"number",
|
||||
"scene",
|
||||
"select",
|
||||
];
|
||||
|
||||
/** Domains that should have the history hidden in the more info dialog. */
|
||||
|
@ -17,6 +17,19 @@ export const formatDate = toLocaleDateStringSupportsOptions
|
||||
formatDateMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "longDate");
|
||||
|
||||
const formatDateShortMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
})
|
||||
);
|
||||
|
||||
export const formatDateShort = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateShortMem(locale).format(dateObj)
|
||||
: (dateObj: Date) => format(dateObj, "shortDate");
|
||||
|
||||
const formatDateWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
|
@ -29,37 +29,61 @@ export const computeStateDisplay = (
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
if (domain === "input_datetime") {
|
||||
let date: Date;
|
||||
if (!stateObj.attributes.has_time) {
|
||||
if (state) {
|
||||
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
||||
// Attributes aren't available, we have to use `state`.
|
||||
try {
|
||||
const components = state.split(" ");
|
||||
if (components.length === 2) {
|
||||
// Date and time.
|
||||
return formatDateTime(new Date(components.join("T")), locale);
|
||||
}
|
||||
if (components.length === 1) {
|
||||
if (state.includes("-")) {
|
||||
// Date only.
|
||||
return formatDate(new Date(`${state}T00:00`), locale);
|
||||
}
|
||||
if (state.includes(":")) {
|
||||
// Time only.
|
||||
const now = new Date();
|
||||
return formatTime(
|
||||
new Date(`${now.toISOString().split("T")[0]}T${state}`),
|
||||
locale
|
||||
);
|
||||
}
|
||||
}
|
||||
return state;
|
||||
} catch {
|
||||
// Formatting methods may throw error if date parsing doesn't go well,
|
||||
// just return the state string in that case.
|
||||
return state;
|
||||
}
|
||||
} else {
|
||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||
let date: Date;
|
||||
if (!stateObj.attributes.has_time) {
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day
|
||||
);
|
||||
return formatDate(date, locale);
|
||||
}
|
||||
if (!stateObj.attributes.has_date) {
|
||||
date = new Date();
|
||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
||||
return formatTime(date, locale);
|
||||
}
|
||||
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day
|
||||
);
|
||||
return formatDate(date, locale);
|
||||
}
|
||||
if (!stateObj.attributes.has_date) {
|
||||
const now = new Date();
|
||||
date = new Date(
|
||||
// Due to bugs.chromium.org/p/chromium/issues/detail?id=797548
|
||||
// don't use artificial 1970 year.
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDay(),
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
return formatTime(date, locale);
|
||||
return formatDateTime(date, locale);
|
||||
}
|
||||
|
||||
date = new Date(
|
||||
stateObj.attributes.year,
|
||||
stateObj.attributes.month - 1,
|
||||
stateObj.attributes.day,
|
||||
stateObj.attributes.hour,
|
||||
stateObj.attributes.minute
|
||||
);
|
||||
return formatDateTime(date, locale);
|
||||
}
|
||||
|
||||
if (domain === "humidifier") {
|
||||
|
2
src/common/number/clamp.ts
Normal file
2
src/common/number/clamp.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const clamp = (value: number, min: number, max: number) =>
|
||||
Math.min(Math.max(value, min), max);
|
@ -29,31 +29,28 @@ export const iconColorCSS = css`
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="cooling"] {
|
||||
color: var(--cool-color, #2b9af9);
|
||||
color: var(--cool-color, var(--state-climate-cool-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="heating"] {
|
||||
color: var(--heat-color, #ff8100);
|
||||
color: var(--heat-color, var(--state-climate-heat-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
||||
color: var(--dry-color, #efbd07);
|
||||
color: var(--dry-color, var(--state-climate-dry-color));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"] {
|
||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||
color: var(--alarm-color-triggered, var(--label-badge-red));
|
||||
animation: pulse 1s infinite;
|
||||
@ -73,11 +70,11 @@ export const iconColorCSS = css`
|
||||
|
||||
ha-icon[data-domain="plant"][data-state="problem"],
|
||||
ha-icon[data-domain="zwave"][data-state="dead"] {
|
||||
color: var(--error-state-color, #db4437);
|
||||
color: var(--state-icon-error-color);
|
||||
}
|
||||
|
||||
/* Color the icon if unavailable */
|
||||
ha-icon[data-state="unavailable"] {
|
||||
color: var(--state-icon-unavailable-color);
|
||||
color: var(--state-unavailable-color);
|
||||
}
|
||||
`;
|
||||
|
@ -5,32 +5,20 @@
|
||||
// as much as it can, without ever going more than once per `wait` duration;
|
||||
// but if you'd like to disable the execution on the leading edge, pass
|
||||
// `false for leading`. To disable execution on the trailing edge, ditto.
|
||||
export const throttle = <T extends (...args) => unknown>(
|
||||
func: T,
|
||||
export const throttle = <T extends any[]>(
|
||||
func: (...args: T) => void,
|
||||
wait: number,
|
||||
leading = true,
|
||||
trailing = true
|
||||
): T => {
|
||||
) => {
|
||||
let timeout: number | undefined;
|
||||
let previous = 0;
|
||||
let context: any;
|
||||
let args: any;
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
func.apply(context, args);
|
||||
if (!timeout) {
|
||||
context = null;
|
||||
args = null;
|
||||
}
|
||||
};
|
||||
// @ts-ignore
|
||||
return function (...argmnts) {
|
||||
// @ts-ignore
|
||||
// @typescript-eslint/no-this-alias
|
||||
context = this;
|
||||
args = argmnts;
|
||||
|
||||
return (...args: T): void => {
|
||||
const later = () => {
|
||||
previous = leading === false ? 0 : Date.now();
|
||||
timeout = undefined;
|
||||
func(...args);
|
||||
};
|
||||
const now = Date.now();
|
||||
if (!previous && leading === false) {
|
||||
previous = now;
|
||||
@ -42,7 +30,7 @@ export const throttle = <T extends (...args) => unknown>(
|
||||
timeout = undefined;
|
||||
}
|
||||
previous = now;
|
||||
func.apply(context, args);
|
||||
func(...args);
|
||||
} else if (!timeout && trailing !== false) {
|
||||
timeout = window.setTimeout(later, remaining);
|
||||
}
|
||||
|
197
src/components/chart/chart-date-adapter.ts
Normal file
197
src/components/chart/chart-date-adapter.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import { _adapters } from "chart.js";
|
||||
import {
|
||||
startOfSecond,
|
||||
startOfMinute,
|
||||
startOfHour,
|
||||
startOfDay,
|
||||
startOfWeek,
|
||||
startOfMonth,
|
||||
startOfQuarter,
|
||||
startOfYear,
|
||||
addMilliseconds,
|
||||
addSeconds,
|
||||
addMinutes,
|
||||
addHours,
|
||||
addDays,
|
||||
addWeeks,
|
||||
addMonths,
|
||||
addQuarters,
|
||||
addYears,
|
||||
differenceInMilliseconds,
|
||||
differenceInSeconds,
|
||||
differenceInMinutes,
|
||||
differenceInHours,
|
||||
differenceInDays,
|
||||
differenceInWeeks,
|
||||
differenceInMonths,
|
||||
differenceInQuarters,
|
||||
differenceInYears,
|
||||
endOfSecond,
|
||||
endOfMinute,
|
||||
endOfHour,
|
||||
endOfDay,
|
||||
endOfWeek,
|
||||
endOfMonth,
|
||||
endOfQuarter,
|
||||
endOfYear,
|
||||
} from "date-fns";
|
||||
import { formatDate, formatDateShort } from "../../common/datetime/format_date";
|
||||
import {
|
||||
formatDateTime,
|
||||
formatDateTimeWithSeconds,
|
||||
} from "../../common/datetime/format_date_time";
|
||||
import {
|
||||
formatTime,
|
||||
formatTimeWithSeconds,
|
||||
} from "../../common/datetime/format_time";
|
||||
|
||||
const FORMATS = {
|
||||
datetime: "datetime",
|
||||
datetimeseconds: "datetimeseconds",
|
||||
millisecond: "millisecond",
|
||||
second: "second",
|
||||
minute: "minute",
|
||||
hour: "hour",
|
||||
day: "day",
|
||||
week: "week",
|
||||
month: "month",
|
||||
quarter: "quarter",
|
||||
year: "year",
|
||||
};
|
||||
|
||||
_adapters._date.override({
|
||||
formats: () => FORMATS,
|
||||
parse: (value: Date | number) => {
|
||||
if (!(value instanceof Date)) {
|
||||
return value;
|
||||
}
|
||||
return value.getTime();
|
||||
},
|
||||
format: function (time, fmt: keyof typeof FORMATS) {
|
||||
switch (fmt) {
|
||||
case "datetime":
|
||||
return formatDateTime(new Date(time), this.options.locale);
|
||||
case "datetimeseconds":
|
||||
return formatDateTimeWithSeconds(new Date(time), this.options.locale);
|
||||
case "millisecond":
|
||||
return formatTimeWithSeconds(new Date(time), this.options.locale);
|
||||
case "second":
|
||||
return formatTimeWithSeconds(new Date(time), this.options.locale);
|
||||
case "minute":
|
||||
return formatTime(new Date(time), this.options.locale);
|
||||
case "hour":
|
||||
return formatTime(new Date(time), this.options.locale);
|
||||
case "day":
|
||||
return formatDateShort(new Date(time), this.options.locale);
|
||||
case "week":
|
||||
return formatDate(new Date(time), this.options.locale);
|
||||
case "month":
|
||||
return formatDate(new Date(time), this.options.locale);
|
||||
case "quarter":
|
||||
return formatDate(new Date(time), this.options.locale);
|
||||
case "year":
|
||||
return formatDate(new Date(time), this.options.locale);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
add: (time, amount, unit) => {
|
||||
switch (unit) {
|
||||
case "millisecond":
|
||||
return addMilliseconds(time, amount);
|
||||
case "second":
|
||||
return addSeconds(time, amount);
|
||||
case "minute":
|
||||
return addMinutes(time, amount);
|
||||
case "hour":
|
||||
return addHours(time, amount);
|
||||
case "day":
|
||||
return addDays(time, amount);
|
||||
case "week":
|
||||
return addWeeks(time, amount);
|
||||
case "month":
|
||||
return addMonths(time, amount);
|
||||
case "quarter":
|
||||
return addQuarters(time, amount);
|
||||
case "year":
|
||||
return addYears(time, amount);
|
||||
default:
|
||||
return time;
|
||||
}
|
||||
},
|
||||
diff: (max, min, unit) => {
|
||||
switch (unit) {
|
||||
case "millisecond":
|
||||
return differenceInMilliseconds(max, min);
|
||||
case "second":
|
||||
return differenceInSeconds(max, min);
|
||||
case "minute":
|
||||
return differenceInMinutes(max, min);
|
||||
case "hour":
|
||||
return differenceInHours(max, min);
|
||||
case "day":
|
||||
return differenceInDays(max, min);
|
||||
case "week":
|
||||
return differenceInWeeks(max, min);
|
||||
case "month":
|
||||
return differenceInMonths(max, min);
|
||||
case "quarter":
|
||||
return differenceInQuarters(max, min);
|
||||
case "year":
|
||||
return differenceInYears(max, min);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
startOf: (time, unit, weekday) => {
|
||||
switch (unit) {
|
||||
case "second":
|
||||
return startOfSecond(time);
|
||||
case "minute":
|
||||
return startOfMinute(time);
|
||||
case "hour":
|
||||
return startOfHour(time);
|
||||
case "day":
|
||||
return startOfDay(time);
|
||||
case "week":
|
||||
return startOfWeek(time);
|
||||
case "isoWeek":
|
||||
return startOfWeek(time, {
|
||||
weekStartsOn: +weekday! as 0 | 1 | 2 | 3 | 4 | 5 | 6,
|
||||
});
|
||||
case "month":
|
||||
return startOfMonth(time);
|
||||
case "quarter":
|
||||
return startOfQuarter(time);
|
||||
case "year":
|
||||
return startOfYear(time);
|
||||
default:
|
||||
return time;
|
||||
}
|
||||
},
|
||||
// @ts-ignore
|
||||
endOf: (time, unit) => {
|
||||
switch (unit) {
|
||||
case "second":
|
||||
return endOfSecond(time);
|
||||
case "minute":
|
||||
return endOfMinute(time);
|
||||
case "hour":
|
||||
return endOfHour(time);
|
||||
case "day":
|
||||
return endOfDay(time);
|
||||
case "week":
|
||||
return endOfWeek(time);
|
||||
case "month":
|
||||
return endOfMonth(time);
|
||||
case "quarter":
|
||||
return endOfQuarter(time);
|
||||
case "year":
|
||||
return endOfYear(time);
|
||||
default:
|
||||
return time;
|
||||
}
|
||||
},
|
||||
});
|
315
src/components/chart/ha-chart-base.ts
Normal file
315
src/components/chart/ha-chart-base.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import type {
|
||||
Chart,
|
||||
ChartType,
|
||||
ChartData,
|
||||
ChartOptions,
|
||||
TooltipModel,
|
||||
} from "chart.js";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { clamp } from "../../common/number/clamp";
|
||||
|
||||
interface Tooltip extends TooltipModel<any> {
|
||||
top: string;
|
||||
left: string;
|
||||
}
|
||||
|
||||
@customElement("ha-chart-base")
|
||||
export default class HaChartBase extends LitElement {
|
||||
public chart?: Chart;
|
||||
|
||||
@property()
|
||||
public chartType: ChartType = "line";
|
||||
|
||||
@property({ attribute: false })
|
||||
public data: ChartData = { datasets: [] };
|
||||
|
||||
@property({ attribute: false })
|
||||
public options?: ChartOptions;
|
||||
|
||||
@state() private _tooltip?: Tooltip;
|
||||
|
||||
@state() private _height?: string;
|
||||
|
||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
||||
|
||||
protected firstUpdated() {
|
||||
this._setupChart();
|
||||
this.data.datasets.forEach((dataset, index) => {
|
||||
if (dataset.hidden) {
|
||||
this._hiddenDatasets.add(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues): void {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this.hasUpdated || !this.chart) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("type")) {
|
||||
this.chart.config.type = this.chartType;
|
||||
}
|
||||
|
||||
if (changedProps.has("data")) {
|
||||
this.chart.data = this.data;
|
||||
}
|
||||
if (changedProps.has("options")) {
|
||||
this.chart.options = this._createOptions();
|
||||
}
|
||||
this.chart.update("none");
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.options?.plugins?.legend?.display === true
|
||||
? html` <div class="chartLegend">
|
||||
<ul>
|
||||
${this.data.datasets.map(
|
||||
(dataset, index) => html`<li
|
||||
.datasetIndex=${index}
|
||||
@click=${this._legendClick}
|
||||
class=${classMap({
|
||||
hidden: this._hiddenDatasets.has(index),
|
||||
})}
|
||||
>
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
backgroundColor: dataset.backgroundColor as string,
|
||||
borderColor: dataset.borderColor as string,
|
||||
})}
|
||||
></div>
|
||||
${dataset.label}
|
||||
</li>`
|
||||
)}
|
||||
</ul>
|
||||
</div>`
|
||||
: ""}
|
||||
<div
|
||||
class="chartContainer"
|
||||
style=${styleMap({
|
||||
height:
|
||||
this.chartType === "timeline"
|
||||
? `${this.data.datasets.length * 30 + 30}px`
|
||||
: this._height,
|
||||
overflow: this._height ? "initial" : "hidden",
|
||||
})}
|
||||
>
|
||||
<canvas></canvas>
|
||||
${this._tooltip
|
||||
? html`<div
|
||||
class="chartTooltip ${classMap({ [this._tooltip.yAlign]: true })}"
|
||||
style=${styleMap({
|
||||
top: this._tooltip.top,
|
||||
left: this._tooltip.left,
|
||||
})}
|
||||
>
|
||||
<div class="title">${this._tooltip.title}</div>
|
||||
${this._tooltip.beforeBody
|
||||
? html`<div class="beforeBody">
|
||||
${this._tooltip.beforeBody}
|
||||
</div>`
|
||||
: ""}
|
||||
<div>
|
||||
<ul>
|
||||
${this._tooltip.body.map(
|
||||
(item, i) => html`<li>
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
backgroundColor: this._tooltip!.labelColors[i]
|
||||
.backgroundColor as string,
|
||||
borderColor: this._tooltip!.labelColors[i]
|
||||
.borderColor as string,
|
||||
})}
|
||||
></div>
|
||||
${item.lines.join("\n")}
|
||||
</li>`
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _setupChart() {
|
||||
const ctx: CanvasRenderingContext2D = this.renderRoot
|
||||
.querySelector("canvas")!
|
||||
.getContext("2d")!;
|
||||
|
||||
this.chart = new (await import("../../resources/chartjs")).Chart(ctx, {
|
||||
type: this.chartType,
|
||||
data: this.data,
|
||||
options: this._createOptions(),
|
||||
plugins: [
|
||||
{
|
||||
id: "afterRenderHook",
|
||||
afterRender: (chart) => {
|
||||
this._height = `${chart.height}px`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private _createOptions() {
|
||||
return {
|
||||
...this.options,
|
||||
plugins: {
|
||||
...this.options?.plugins,
|
||||
tooltip: {
|
||||
...this.options?.plugins?.tooltip,
|
||||
enabled: false,
|
||||
external: (context) => this._handleTooltip(context),
|
||||
},
|
||||
legend: {
|
||||
...this.options?.plugins?.legend,
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private _legendClick(ev) {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
const index = ev.currentTarget.datasetIndex;
|
||||
if (this.chart.isDatasetVisible(index)) {
|
||||
this.chart.setDatasetVisibility(index, false);
|
||||
this._hiddenDatasets.add(index);
|
||||
} else {
|
||||
this.chart.setDatasetVisibility(index, true);
|
||||
this._hiddenDatasets.delete(index);
|
||||
}
|
||||
this.chart.update("none");
|
||||
this.requestUpdate("_hiddenDatasets");
|
||||
}
|
||||
|
||||
private _handleTooltip(context: {
|
||||
chart: Chart;
|
||||
tooltip: TooltipModel<any>;
|
||||
}) {
|
||||
if (context.tooltip.opacity === 0) {
|
||||
this._tooltip = undefined;
|
||||
return;
|
||||
}
|
||||
this._tooltip = {
|
||||
...context.tooltip,
|
||||
top: this.chart!.canvas.offsetTop + context.tooltip.caretY + 12 + "px",
|
||||
left:
|
||||
this.chart!.canvas.offsetLeft +
|
||||
clamp(context.tooltip.caretX, 100, this.clientWidth - 100) -
|
||||
100 +
|
||||
"px",
|
||||
};
|
||||
}
|
||||
|
||||
public updateChart = (): void => {
|
||||
if (this.chart) {
|
||||
this.chart.update();
|
||||
}
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.chartContainer {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
.chartLegend {
|
||||
text-align: center;
|
||||
}
|
||||
.chartLegend li {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
padding: 0 8px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
align-items: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.chartLegend .hidden {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.chartLegend .bullet,
|
||||
.chartTooltip .bullet {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
width: 16px;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.chartTooltip .bullet {
|
||||
align-self: baseline;
|
||||
}
|
||||
:host([rtl]) .chartTooltip .bullet {
|
||||
margin-right: inherit;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.chartTooltip {
|
||||
padding: 8px;
|
||||
font-size: 90%;
|
||||
position: absolute;
|
||||
background: rgba(80, 80, 80, 0.9);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
width: 200px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
:host([rtl]) .chartTooltip {
|
||||
direction: rtl;
|
||||
}
|
||||
.chartLegend ul,
|
||||
.chartTooltip ul {
|
||||
display: inline-block;
|
||||
padding: 0 0px;
|
||||
margin: 8px 0 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
.chartTooltip ul {
|
||||
margin: 0 4px;
|
||||
}
|
||||
.chartTooltip li {
|
||||
display: flex;
|
||||
white-space: pre-line;
|
||||
align-items: center;
|
||||
line-height: 16px;
|
||||
}
|
||||
.chartTooltip .title {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
.chartTooltip .beforeBody {
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
word-break: break-all;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-chart-base": HaChartBase;
|
||||
}
|
||||
}
|
392
src/components/chart/state-history-chart-line.ts
Normal file
392
src/components/chart/state-history-chart-line.ts
Normal file
@ -0,0 +1,392 @@
|
||||
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { getColorByIndex } from "../../common/color/colors";
|
||||
import { LineChartEntity, LineChartState } from "../../data/history";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-chart-base";
|
||||
|
||||
class StateHistoryChartLine extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public data: LineChartEntity[] = [];
|
||||
|
||||
@property({ type: Boolean }) public names = false;
|
||||
|
||||
@property() public unit?: string;
|
||||
|
||||
@property() public identifier?: string;
|
||||
|
||||
@property({ type: Boolean }) public isSingleDevice = false;
|
||||
|
||||
@property({ attribute: false }) public endTime?: Date;
|
||||
|
||||
@state() private _chartData?: ChartData<"line">;
|
||||
|
||||
@state() private _chartOptions?: ChartOptions<"line">;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
chartType="line"
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (!this.hasUpdated) {
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
adapters: {
|
||||
date: {
|
||||
locale: this.hass.locale,
|
||||
},
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
major: {
|
||||
enabled: true,
|
||||
},
|
||||
font: (context) =>
|
||||
context.tick && context.tick.major
|
||||
? ({ weight: "bold" } as any)
|
||||
: {},
|
||||
},
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: this.unit,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
label: (context) =>
|
||||
`${context.dataset.label}: ${context.parsed.y} ${this.unit}`,
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
propagate: true,
|
||||
},
|
||||
legend: {
|
||||
display: !this.isSingleDevice,
|
||||
labels: {
|
||||
usePointStyle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.1,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
this._generateData();
|
||||
}
|
||||
}
|
||||
|
||||
private _generateData() {
|
||||
let colorIndex = 0;
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const deviceStates = this.data;
|
||||
const datasets: ChartDataset<"line">[] = [];
|
||||
let endTime: Date;
|
||||
|
||||
if (deviceStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
function safeParseFloat(value) {
|
||||
const parsed = parseFloat(value);
|
||||
return isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
endTime =
|
||||
this.endTime ||
|
||||
// Get the highest date from the last date of each device
|
||||
new Date(
|
||||
Math.max.apply(
|
||||
null,
|
||||
deviceStates.map((devSts) =>
|
||||
new Date(
|
||||
devSts.states[devSts.states.length - 1].last_changed
|
||||
).getMilliseconds()
|
||||
)
|
||||
)
|
||||
);
|
||||
if (endTime > new Date()) {
|
||||
endTime = new Date();
|
||||
}
|
||||
|
||||
const names = this.names || {};
|
||||
deviceStates.forEach((states) => {
|
||||
const domain = states.domain;
|
||||
const name = names[states.entity_id] || states.name;
|
||||
// array containing [value1, value2, etc]
|
||||
let prevValues: any[] | null = null;
|
||||
|
||||
const data: ChartDataset<"line">[] = [];
|
||||
|
||||
const pushData = (timestamp: Date, datavalues: any[] | null) => {
|
||||
if (!datavalues) return;
|
||||
if (timestamp > endTime) {
|
||||
// Drop datapoints that are after the requested endTime. This could happen if
|
||||
// endTime is "now" and client time is not in sync with server time.
|
||||
return;
|
||||
}
|
||||
data.forEach((d, i) => {
|
||||
if (datavalues[i] === null && prevValues && prevValues[i] !== null) {
|
||||
// null data values show up as gaps in the chart.
|
||||
// If the current value for the dataset is null and the previous
|
||||
// value of the data set is not null, then add an 'end' point
|
||||
// to the chart for the previous value. Otherwise the gap will
|
||||
// be too big. It will go from the start of the previous data
|
||||
// value until the start of the next data value.
|
||||
d.data.push({ x: timestamp.getTime(), y: prevValues[i] });
|
||||
}
|
||||
d.data.push({ x: timestamp.getTime(), y: datavalues[i] });
|
||||
});
|
||||
prevValues = datavalues;
|
||||
};
|
||||
|
||||
const addDataSet = (
|
||||
nameY: string,
|
||||
step = false,
|
||||
fill = false,
|
||||
color?: string
|
||||
) => {
|
||||
if (!color) {
|
||||
color = getColorByIndex(colorIndex);
|
||||
colorIndex++;
|
||||
}
|
||||
data.push({
|
||||
label: nameY,
|
||||
fill: fill ? "origin" : false,
|
||||
borderColor: color,
|
||||
backgroundColor: color + "7F",
|
||||
stepped: step ? "before" : false,
|
||||
pointRadius: 0,
|
||||
data: [],
|
||||
});
|
||||
};
|
||||
|
||||
if (
|
||||
domain === "thermostat" ||
|
||||
domain === "climate" ||
|
||||
domain === "water_heater"
|
||||
) {
|
||||
const hasHvacAction = states.states.some(
|
||||
(entityState) => entityState.attributes?.hvac_action
|
||||
);
|
||||
|
||||
const isHeating =
|
||||
domain === "climate" && hasHvacAction
|
||||
? (entityState: LineChartState) =>
|
||||
entityState.attributes?.hvac_action === "heating"
|
||||
: (entityState: LineChartState) => entityState.state === "heat";
|
||||
const isCooling =
|
||||
domain === "climate" && hasHvacAction
|
||||
? (entityState: LineChartState) =>
|
||||
entityState.attributes?.hvac_action === "cooling"
|
||||
: (entityState: LineChartState) => entityState.state === "cool";
|
||||
|
||||
const hasHeat = states.states.some(isHeating);
|
||||
const hasCool = states.states.some(isCooling);
|
||||
// We differentiate between thermostats that have a target temperature
|
||||
// range versus ones that have just a target temperature
|
||||
|
||||
// Using step chart by step-before so manually interpolation not needed.
|
||||
const hasTargetRange = states.states.some(
|
||||
(entityState) =>
|
||||
entityState.attributes &&
|
||||
entityState.attributes.target_temp_high !==
|
||||
entityState.attributes.target_temp_low
|
||||
);
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.current_temperature", {
|
||||
name: name,
|
||||
})}`,
|
||||
true
|
||||
);
|
||||
if (hasHeat) {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.heating", { name: name })}`,
|
||||
true,
|
||||
true,
|
||||
computedStyles.getPropertyValue("--state-climate-heat-color")
|
||||
);
|
||||
// The "heating" series uses steppedArea to shade the area below the current
|
||||
// temperature when the thermostat is calling for heat.
|
||||
}
|
||||
if (hasCool) {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.cooling", { name: name })}`,
|
||||
true,
|
||||
true,
|
||||
computedStyles.getPropertyValue("--state-climate-cool-color")
|
||||
);
|
||||
// The "cooling" series uses steppedArea to shade the area below the current
|
||||
// temperature when the thermostat is calling for heat.
|
||||
}
|
||||
|
||||
if (hasTargetRange) {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.target_temperature_mode", {
|
||||
name: name,
|
||||
mode: this.hass.localize("ui.card.climate.high"),
|
||||
})}`,
|
||||
true
|
||||
);
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.target_temperature_mode", {
|
||||
name: name,
|
||||
mode: this.hass.localize("ui.card.climate.low"),
|
||||
})}`,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.climate.target_temperature_entity", {
|
||||
name: name,
|
||||
})}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
states.states.forEach((entityState) => {
|
||||
if (!entityState.attributes) return;
|
||||
const curTemp = safeParseFloat(
|
||||
entityState.attributes.current_temperature
|
||||
);
|
||||
const series = [curTemp];
|
||||
if (hasHeat) {
|
||||
series.push(isHeating(entityState) ? curTemp : null);
|
||||
}
|
||||
if (hasCool) {
|
||||
series.push(isCooling(entityState) ? curTemp : null);
|
||||
}
|
||||
if (hasTargetRange) {
|
||||
const targetHigh = safeParseFloat(
|
||||
entityState.attributes.target_temp_high
|
||||
);
|
||||
const targetLow = safeParseFloat(
|
||||
entityState.attributes.target_temp_low
|
||||
);
|
||||
series.push(targetHigh, targetLow);
|
||||
pushData(new Date(entityState.last_changed), series);
|
||||
} else {
|
||||
const target = safeParseFloat(entityState.attributes.temperature);
|
||||
series.push(target);
|
||||
pushData(new Date(entityState.last_changed), series);
|
||||
}
|
||||
});
|
||||
} else if (domain === "humidifier") {
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.humidifier.target_humidity_entity", {
|
||||
name: name,
|
||||
})}`,
|
||||
true
|
||||
);
|
||||
addDataSet(
|
||||
`${this.hass.localize("ui.card.humidifier.on_entity", {
|
||||
name: name,
|
||||
})}`,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
states.states.forEach((entityState) => {
|
||||
if (!entityState.attributes) return;
|
||||
const target = safeParseFloat(entityState.attributes.humidity);
|
||||
const series = [target];
|
||||
series.push(entityState.state === "on" ? target : null);
|
||||
pushData(new Date(entityState.last_changed), series);
|
||||
});
|
||||
} else {
|
||||
// Only disable interpolation for sensors
|
||||
const isStep = domain === "sensor";
|
||||
addDataSet(name, isStep);
|
||||
|
||||
let lastValue: number;
|
||||
let lastDate: Date;
|
||||
let lastNullDate: Date | null = null;
|
||||
|
||||
// Process chart data.
|
||||
// When state is `unknown`, calculate the value and break the line.
|
||||
states.states.forEach((entityState) => {
|
||||
const value = safeParseFloat(entityState.state);
|
||||
const date = new Date(entityState.last_changed);
|
||||
if (value !== null && lastNullDate) {
|
||||
const dateTime = date.getTime();
|
||||
const lastNullDateTime = lastNullDate.getTime();
|
||||
const lastDateTime = lastDate?.getTime();
|
||||
const tmpValue =
|
||||
(value - lastValue) *
|
||||
((lastNullDateTime - lastDateTime) /
|
||||
(dateTime - lastDateTime)) +
|
||||
lastValue;
|
||||
pushData(lastNullDate, [tmpValue]);
|
||||
pushData(new Date(lastNullDateTime + 1), [null]);
|
||||
pushData(date, [value]);
|
||||
lastDate = date;
|
||||
lastValue = value;
|
||||
lastNullDate = null;
|
||||
} else if (value !== null && lastNullDate === null) {
|
||||
pushData(date, [value]);
|
||||
lastDate = date;
|
||||
lastValue = value;
|
||||
} else if (
|
||||
value === null &&
|
||||
lastNullDate === null &&
|
||||
lastValue !== undefined
|
||||
) {
|
||||
lastNullDate = date;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add an entry for final values
|
||||
pushData(endTime, prevValues);
|
||||
|
||||
// Concat two arrays
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
});
|
||||
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
}
|
||||
}
|
||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"state-history-chart-line": StateHistoryChartLine;
|
||||
}
|
||||
}
|
310
src/components/chart/state-history-chart-timeline.ts
Normal file
310
src/components/chart/state-history-chart-timeline.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { getColorByIndex } from "../../common/color/colors";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { TimelineEntity } from "../../data/history";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "./ha-chart-base";
|
||||
import type { TimeLineData } from "./timeline-chart/const";
|
||||
|
||||
/** Binary sensor device classes for which the static colors for on/off need to be inverted.
|
||||
* List the ones were "off" = good or normal state = should be rendered "green".
|
||||
*/
|
||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
||||
"battery",
|
||||
"door",
|
||||
"garage_door",
|
||||
"gas",
|
||||
"lock",
|
||||
"opening",
|
||||
"problem",
|
||||
"safety",
|
||||
"smoke",
|
||||
"window",
|
||||
]);
|
||||
|
||||
const STATIC_STATE_COLORS = new Set([
|
||||
"on",
|
||||
"off",
|
||||
"home",
|
||||
"not_home",
|
||||
"unavailable",
|
||||
"unknown",
|
||||
"idle",
|
||||
]);
|
||||
|
||||
const stateColorMap: Map<string, string> = new Map();
|
||||
|
||||
let colorIndex = 0;
|
||||
|
||||
const invertOnOff = (entityState?: HassEntity) =>
|
||||
entityState &&
|
||||
computeDomain(entityState.entity_id) === "binary_sensor" &&
|
||||
"device_class" in entityState.attributes &&
|
||||
BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED.has(
|
||||
entityState.attributes.device_class!
|
||||
);
|
||||
|
||||
const getColor = (
|
||||
stateString: string,
|
||||
entityState: HassEntity,
|
||||
computedStyles: CSSStyleDeclaration
|
||||
) => {
|
||||
if (invertOnOff(entityState)) {
|
||||
stateString = stateString === "on" ? "off" : "on";
|
||||
}
|
||||
if (stateColorMap.has(stateString)) {
|
||||
return stateColorMap.get(stateString);
|
||||
}
|
||||
if (STATIC_STATE_COLORS.has(stateString)) {
|
||||
const color = computedStyles.getPropertyValue(
|
||||
`--state-${stateString}-color`
|
||||
);
|
||||
stateColorMap.set(stateString, color);
|
||||
return color;
|
||||
}
|
||||
const color = getColorByIndex(colorIndex);
|
||||
colorIndex++;
|
||||
stateColorMap.set(stateString, color);
|
||||
return color;
|
||||
};
|
||||
|
||||
@customElement("state-history-chart-timeline")
|
||||
export class StateHistoryChartTimeline extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public data: TimelineEntity[] = [];
|
||||
|
||||
@property({ type: Boolean }) public names = false;
|
||||
|
||||
@property() public unit?: string;
|
||||
|
||||
@property() public identifier?: string;
|
||||
|
||||
@property({ type: Boolean }) public isSingleDevice = false;
|
||||
|
||||
@property({ attribute: false }) public endTime?: Date;
|
||||
|
||||
@state() private _chartData?: ChartData<"timeline">;
|
||||
|
||||
@state() private _chartOptions?: ChartOptions<"timeline">;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
chartType="timeline"
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps: PropertyValues) {
|
||||
if (!this.hasUpdated) {
|
||||
this._chartOptions = {
|
||||
maintainAspectRatio: false,
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "timeline",
|
||||
position: "bottom",
|
||||
adapters: {
|
||||
date: {
|
||||
locale: this.hass.locale,
|
||||
},
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
maxRotation: 0,
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
major: {
|
||||
enabled: true,
|
||||
},
|
||||
font: (context) =>
|
||||
context.tick && context.tick.major
|
||||
? ({ weight: "bold" } as any)
|
||||
: {},
|
||||
},
|
||||
grid: {
|
||||
offset: false,
|
||||
},
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "category",
|
||||
barThickness: 20,
|
||||
offset: true,
|
||||
grid: {
|
||||
display: false,
|
||||
drawBorder: false,
|
||||
drawTicks: false,
|
||||
},
|
||||
ticks: {
|
||||
display: this.data.length !== 1,
|
||||
},
|
||||
afterSetDimensions: (y) => {
|
||||
y.maxWidth = y.chart.width * 0.18;
|
||||
},
|
||||
position: computeRTL(this.hass) ? "right" : "left",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "nearest",
|
||||
callbacks: {
|
||||
title: (context) =>
|
||||
context![0].chart!.data!.labels![
|
||||
context[0].datasetIndex
|
||||
] as string,
|
||||
beforeBody: (context) => context[0].dataset.label || "",
|
||||
label: (item) => {
|
||||
const d = item.dataset.data[item.dataIndex] as TimeLineData;
|
||||
return [
|
||||
d.label || "",
|
||||
formatDateTimeWithSeconds(d.start, this.hass.locale),
|
||||
formatDateTimeWithSeconds(d.end, this.hass.locale),
|
||||
];
|
||||
},
|
||||
labelColor: (item) => ({
|
||||
borderColor: (item.dataset.data[item.dataIndex] as TimeLineData)
|
||||
.color!,
|
||||
backgroundColor: (item.dataset.data[
|
||||
item.dataIndex
|
||||
] as TimeLineData).color!,
|
||||
}),
|
||||
},
|
||||
},
|
||||
filler: {
|
||||
propagate: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
if (changedProps.has("data")) {
|
||||
this._generateData();
|
||||
}
|
||||
}
|
||||
|
||||
private _generateData() {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
let stateHistory = this.data;
|
||||
|
||||
if (!stateHistory) {
|
||||
stateHistory = [];
|
||||
}
|
||||
|
||||
const startTime = new Date(
|
||||
stateHistory.reduce(
|
||||
(minTime, stateInfo) =>
|
||||
Math.min(minTime, new Date(stateInfo.data[0].last_changed).getTime()),
|
||||
new Date().getTime()
|
||||
)
|
||||
);
|
||||
|
||||
// end time is Math.max(startTime, last_event)
|
||||
let endTime =
|
||||
this.endTime ||
|
||||
new Date(
|
||||
stateHistory.reduce(
|
||||
(maxTime, stateInfo) =>
|
||||
Math.max(
|
||||
maxTime,
|
||||
new Date(
|
||||
stateInfo.data[stateInfo.data.length - 1].last_changed
|
||||
).getTime()
|
||||
),
|
||||
startTime.getTime()
|
||||
)
|
||||
);
|
||||
|
||||
if (endTime > new Date()) {
|
||||
endTime = new Date();
|
||||
}
|
||||
|
||||
const labels: string[] = [];
|
||||
const datasets: ChartDataset<"timeline">[] = [];
|
||||
const names = this.names || {};
|
||||
// stateHistory is a list of lists of sorted state objects
|
||||
stateHistory.forEach((stateInfo) => {
|
||||
let newLastChanged: Date;
|
||||
let prevState: string | null = null;
|
||||
let locState: string | null = null;
|
||||
let prevLastChanged = startTime;
|
||||
const entityDisplay: string =
|
||||
names[stateInfo.entity_id] || stateInfo.name;
|
||||
|
||||
const dataRow: TimeLineData[] = [];
|
||||
stateInfo.data.forEach((entityState) => {
|
||||
let newState: string | null = entityState.state;
|
||||
const timeStamp = new Date(entityState.last_changed);
|
||||
if (!newState) {
|
||||
newState = null;
|
||||
}
|
||||
if (timeStamp > endTime) {
|
||||
// Drop datapoints that are after the requested endTime. This could happen if
|
||||
// endTime is 'now' and client time is not in sync with server time.
|
||||
return;
|
||||
}
|
||||
if (prevState === null) {
|
||||
prevState = newState;
|
||||
locState = entityState.state_localize;
|
||||
prevLastChanged = new Date(entityState.last_changed);
|
||||
} else if (newState !== prevState) {
|
||||
newLastChanged = new Date(entityState.last_changed);
|
||||
|
||||
dataRow.push({
|
||||
start: prevLastChanged,
|
||||
end: newLastChanged,
|
||||
label: locState,
|
||||
color: getColor(
|
||||
prevState,
|
||||
this.hass.states[stateInfo.entity_id],
|
||||
computedStyles
|
||||
),
|
||||
});
|
||||
|
||||
prevState = newState;
|
||||
locState = entityState.state_localize;
|
||||
prevLastChanged = newLastChanged;
|
||||
}
|
||||
});
|
||||
|
||||
if (prevState !== null) {
|
||||
dataRow.push({
|
||||
start: prevLastChanged,
|
||||
end: endTime,
|
||||
label: locState,
|
||||
color: getColor(
|
||||
prevState,
|
||||
this.hass.states[stateInfo.entity_id],
|
||||
computedStyles
|
||||
),
|
||||
});
|
||||
}
|
||||
datasets.push({
|
||||
data: dataRow,
|
||||
label: stateInfo.entity_id,
|
||||
});
|
||||
labels.push(entityDisplay);
|
||||
});
|
||||
|
||||
this._chartData = {
|
||||
labels: labels,
|
||||
datasets: datasets,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"state-history-chart-timeline": StateHistoryChartTimeline;
|
||||
}
|
||||
}
|
@ -7,10 +7,10 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import { HistoryResult } from "../data/history";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-circular-progress";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { HistoryResult } from "../../data/history";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-circular-progress";
|
||||
import "./state-history-chart-line";
|
||||
import "./state-history-chart-timeline";
|
||||
|
||||
@ -24,7 +24,7 @@ class StateHistoryCharts extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public endTime?: Date;
|
||||
|
||||
@property({ type: Boolean }) public upToNow = false;
|
||||
@property({ type: Boolean, attribute: "up-to-now" }) public upToNow = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-single" }) public noSingle = false;
|
||||
|
||||
@ -101,12 +101,12 @@ class StateHistoryCharts extends LitElement {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
/* height of single timeline chart = 58px */
|
||||
min-height: 58px;
|
||||
/* height of single timeline chart = 60px */
|
||||
min-height: 60px;
|
||||
}
|
||||
.info {
|
||||
text-align: center;
|
||||
line-height: 58px;
|
||||
line-height: 60px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
18
src/components/chart/timeline-chart/const.ts
Normal file
18
src/components/chart/timeline-chart/const.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface TimeLineData {
|
||||
start: Date;
|
||||
end: Date;
|
||||
label?: string | null;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
declare module "chart.js" {
|
||||
interface ChartTypeRegistry {
|
||||
timeline: {
|
||||
chartOptions: BarControllerChartOptions;
|
||||
datasetOptions: BarControllerDatasetOptions;
|
||||
defaultDataPoint: TimeLineData;
|
||||
parsedDataType: any;
|
||||
scales: "timeline";
|
||||
};
|
||||
}
|
||||
}
|
60
src/components/chart/timeline-chart/textbar-element.ts
Normal file
60
src/components/chart/timeline-chart/textbar-element.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { BarElement, BarOptions, BarProps } from "chart.js";
|
||||
import { hex2rgb } from "../../../common/color/convert-color";
|
||||
import { luminosity } from "../../../common/color/rgb";
|
||||
|
||||
export interface TextBarProps extends BarProps {
|
||||
text?: string | null;
|
||||
options?: Partial<TextBaroptions>;
|
||||
}
|
||||
|
||||
export interface TextBaroptions extends BarOptions {
|
||||
textPad?: number;
|
||||
textColor?: string;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export class TextBarElement extends BarElement {
|
||||
static id = "textbar";
|
||||
|
||||
draw(ctx) {
|
||||
super.draw(ctx);
|
||||
const options = this.options as TextBaroptions;
|
||||
const { x, y, base, width, text } = (this as BarElement<
|
||||
TextBarProps,
|
||||
TextBaroptions
|
||||
>).getProps(["x", "y", "base", "width", "text"]);
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
const textRect = ctx.measureText(text);
|
||||
if (
|
||||
textRect.width === 0 ||
|
||||
textRect.width + (options.textPad || 4) + 2 > width
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const textColor =
|
||||
options.textColor ||
|
||||
(options.backgroundColor &&
|
||||
(luminosity(hex2rgb(options.backgroundColor)) > 0.5 ? "#000" : "#fff"));
|
||||
|
||||
// ctx.font = "12px arial";
|
||||
ctx.fillStyle = textColor;
|
||||
ctx.lineWidth = 0;
|
||||
ctx.strokeStyle = textColor;
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(
|
||||
text,
|
||||
x - width / 2 + (options.textPad || 4),
|
||||
y + (base - y) / 2
|
||||
);
|
||||
}
|
||||
|
||||
tooltipPosition(useFinalPosition: boolean) {
|
||||
const { x, y, base } = this.getProps(["x", "y", "base"], useFinalPosition);
|
||||
return { x, y: y + (base - y) / 2 };
|
||||
}
|
||||
}
|
160
src/components/chart/timeline-chart/timeline-controller.ts
Normal file
160
src/components/chart/timeline-chart/timeline-controller.ts
Normal file
@ -0,0 +1,160 @@
|
||||
import { BarController, BarElement } from "chart.js";
|
||||
import { TimeLineData } from "./const";
|
||||
import { TextBarProps } from "./textbar-element";
|
||||
|
||||
function parseValue(entry, item, vScale, i) {
|
||||
const startValue = vScale.parse(entry.start, i);
|
||||
const endValue = vScale.parse(entry.end, i);
|
||||
const min = Math.min(startValue, endValue);
|
||||
const max = Math.max(startValue, endValue);
|
||||
let barStart = min;
|
||||
let barEnd = max;
|
||||
|
||||
if (Math.abs(min) > Math.abs(max)) {
|
||||
barStart = max;
|
||||
barEnd = min;
|
||||
}
|
||||
|
||||
// Store `barEnd` (furthest away from origin) as parsed value,
|
||||
// to make stacking straight forward
|
||||
item[vScale.axis] = barEnd;
|
||||
|
||||
item._custom = {
|
||||
barStart,
|
||||
barEnd,
|
||||
start: startValue,
|
||||
end: endValue,
|
||||
min,
|
||||
max,
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
export class TimelineController extends BarController {
|
||||
static id = "timeline";
|
||||
|
||||
static defaults = {
|
||||
dataElementType: "textbar",
|
||||
dataElementOptions: ["text", "textColor", "textPadding"],
|
||||
elements: {
|
||||
showText: true,
|
||||
textPadding: 4,
|
||||
minBarWidth: 1,
|
||||
},
|
||||
|
||||
layout: {
|
||||
padding: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static overrides = {
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
parseObjectData(meta, data, start, count) {
|
||||
const iScale = meta.iScale;
|
||||
const vScale = meta.vScale;
|
||||
const labels = iScale.getLabels();
|
||||
const singleScale = iScale === vScale;
|
||||
const parsed: any[] = [];
|
||||
let i;
|
||||
let ilen;
|
||||
let item;
|
||||
let entry;
|
||||
|
||||
for (i = start, ilen = start + count; i < ilen; ++i) {
|
||||
entry = data[i];
|
||||
item = {};
|
||||
item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
|
||||
parsed.push(parseValue(entry, item, vScale, i));
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
getLabelAndValue(index) {
|
||||
const meta = this._cachedMeta;
|
||||
const { vScale } = meta;
|
||||
const data = this.getDataset().data[index] as TimeLineData;
|
||||
|
||||
return {
|
||||
label: vScale!.getLabelForValue(this.index) || "",
|
||||
value: data.label || "",
|
||||
};
|
||||
}
|
||||
|
||||
updateElements(
|
||||
bars: BarElement[],
|
||||
start: number,
|
||||
count: number,
|
||||
mode: "reset" | "resize" | "none" | "hide" | "show" | "normal" | "active"
|
||||
) {
|
||||
const vScale = this._cachedMeta.vScale!;
|
||||
const iScale = this._cachedMeta.iScale!;
|
||||
const dataset = this.getDataset();
|
||||
|
||||
const firstOpts = this.resolveDataElementOptions(start, mode);
|
||||
const sharedOptions = this.getSharedOptions(firstOpts);
|
||||
const includeOptions = this.includeOptions(mode, sharedOptions!);
|
||||
|
||||
const horizontal = vScale.isHorizontal();
|
||||
|
||||
this.updateSharedOptions(sharedOptions!, mode, firstOpts);
|
||||
|
||||
for (let index = start; index < start + count; index++) {
|
||||
const data = dataset.data[index] as TimeLineData;
|
||||
|
||||
// @ts-ignore
|
||||
const y = vScale.getPixelForValue(this.index);
|
||||
|
||||
// @ts-ignore
|
||||
const xStart = iScale.getPixelForValue(data.start.getTime());
|
||||
// @ts-ignore
|
||||
const xEnd = iScale.getPixelForValue(data.end.getTime());
|
||||
const width = xEnd - xStart;
|
||||
|
||||
const height = 10;
|
||||
|
||||
const properties: TextBarProps = {
|
||||
horizontal,
|
||||
x: xStart + width / 2, // Center of the bar
|
||||
y: y - height, // Top of bar
|
||||
width,
|
||||
height: 0,
|
||||
base: y + height, // Bottom of bar,
|
||||
// Text
|
||||
text: data.label,
|
||||
};
|
||||
|
||||
if (includeOptions) {
|
||||
properties.options =
|
||||
sharedOptions || this.resolveDataElementOptions(index, mode);
|
||||
|
||||
properties.options = {
|
||||
...properties.options,
|
||||
backgroundColor: data.color,
|
||||
};
|
||||
}
|
||||
|
||||
this.updateElement(bars[index], index, properties as any, mode);
|
||||
}
|
||||
}
|
||||
|
||||
removeHoverStyle(_element, _datasetIndex, _index) {
|
||||
// this._setStyle(element, index, 'active', false);
|
||||
}
|
||||
|
||||
setHoverStyle(_element, _datasetIndex, _index) {
|
||||
// this._setStyle(element, index, 'active', true);
|
||||
}
|
||||
}
|
55
src/components/chart/timeline-chart/timeline-scale.ts
Normal file
55
src/components/chart/timeline-chart/timeline-scale.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { TimeScale } from "chart.js";
|
||||
import { TimeLineData } from "./const";
|
||||
|
||||
export class TimeLineScale extends TimeScale {
|
||||
static id = "timeline";
|
||||
|
||||
static defaults = {
|
||||
position: "bottom",
|
||||
tooltips: {
|
||||
mode: "nearest",
|
||||
},
|
||||
ticks: {
|
||||
autoSkip: true,
|
||||
},
|
||||
};
|
||||
|
||||
determineDataLimits() {
|
||||
const options = this.options;
|
||||
// @ts-ignore
|
||||
const adapter = this._adapter;
|
||||
const unit = options.time.unit || "day";
|
||||
let { min, max } = this.getUserBounds();
|
||||
|
||||
const chart = this.chart;
|
||||
|
||||
// Convert data to timestamps
|
||||
chart.data.datasets.forEach((dataset, index) => {
|
||||
if (!chart.isDatasetVisible(index)) {
|
||||
return;
|
||||
}
|
||||
for (const data of dataset.data as TimeLineData[]) {
|
||||
let timestamp0 = adapter.parse(data.start, this);
|
||||
let timestamp1 = adapter.parse(data.end, this);
|
||||
if (timestamp0 > timestamp1) {
|
||||
[timestamp0, timestamp1] = [timestamp1, timestamp0];
|
||||
}
|
||||
if (min > timestamp0 && timestamp0) {
|
||||
min = timestamp0;
|
||||
}
|
||||
if (max < timestamp1 && timestamp1) {
|
||||
max = timestamp1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// In case there is no valid min/max, var's use today limits
|
||||
min =
|
||||
isFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
|
||||
max = isFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit);
|
||||
|
||||
// Make sure that max is strictly higher than min (required by the lookup table)
|
||||
this.min = Math.min(min, max - 1);
|
||||
this.max = Math.max(min + 1, max);
|
||||
}
|
||||
}
|
@ -1,661 +0,0 @@
|
||||
/* eslint-plugin-disable lit */
|
||||
import { IronResizableBehavior } from "@polymer/iron-resizable-behavior/iron-resizable-behavior";
|
||||
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 */
|
||||
|
||||
let scriptsLoaded = null;
|
||||
|
||||
class HaChartBase extends mixinBehaviors(
|
||||
[IronResizableBehavior],
|
||||
PolymerElement
|
||||
) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.chartHeader {
|
||||
padding: 6px 0 0 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.chartHeader > div {
|
||||
vertical-align: top;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.chartHeader > div.chartTitle {
|
||||
padding-top: 8px;
|
||||
flex: 0 0 0;
|
||||
max-width: 30%;
|
||||
}
|
||||
.chartHeader > div.chartLegend {
|
||||
flex: 1 1;
|
||||
min-width: 70%;
|
||||
}
|
||||
:root {
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
.chartTooltip {
|
||||
font-size: 90%;
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
background: rgba(80, 80, 80, 0.9);
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, 12px);
|
||||
z-index: 1000;
|
||||
width: 200px;
|
||||
transition: opacity 0.15s ease-in-out;
|
||||
}
|
||||
:host([rtl]) .chartTooltip {
|
||||
direction: rtl;
|
||||
}
|
||||
.chartLegend ul,
|
||||
.chartTooltip ul {
|
||||
display: inline-block;
|
||||
padding: 0 0px;
|
||||
margin: 5px 0 0 0;
|
||||
width: 100%;
|
||||
}
|
||||
.chartTooltip ul {
|
||||
margin: 0 3px;
|
||||
}
|
||||
.chartTooltip li {
|
||||
display: block;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.chartTooltip li::first-line {
|
||||
line-height: 0;
|
||||
}
|
||||
.chartTooltip .title {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
.chartTooltip .beforeBody {
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
word-break: break-all;
|
||||
}
|
||||
.chartLegend li {
|
||||
display: inline-block;
|
||||
padding: 0 6px;
|
||||
max-width: 49%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.chartLegend li:nth-child(odd):last-of-type {
|
||||
/* Make last item take full width if it is odd-numbered. */
|
||||
max-width: 100%;
|
||||
}
|
||||
.chartLegend li[data-hidden] {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.chartLegend em,
|
||||
.chartTooltip em {
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
height: 10px;
|
||||
margin-right: 4px;
|
||||
width: 10px;
|
||||
}
|
||||
:host([rtl]) .chartTooltip em {
|
||||
margin-right: inherit;
|
||||
margin-left: 4px;
|
||||
}
|
||||
ha-icon-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[unit]]">
|
||||
<div class="chartHeader">
|
||||
<div class="chartTitle">[[unit]]</div>
|
||||
<div class="chartLegend">
|
||||
<ul>
|
||||
<template is="dom-repeat" items="[[metas]]">
|
||||
<li on-click="_legendClick" data-hidden$="[[item.hidden]]">
|
||||
<em style$="background-color:[[item.bgColor]]"></em>
|
||||
[[item.label]]
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div id="chartTarget" style="height:40px; width:100%">
|
||||
<canvas id="chartCanvas"></canvas>
|
||||
<div
|
||||
class$="chartTooltip [[tooltip.yAlign]]"
|
||||
style$="opacity:[[tooltip.opacity]]; top:[[tooltip.top]]; left:[[tooltip.left]]; padding:[[tooltip.yPadding]]px [[tooltip.xPadding]]px"
|
||||
>
|
||||
<div class="title">[[tooltip.title]]</div>
|
||||
<template is="dom-if" if="[[tooltip.beforeBody]]">
|
||||
<div class="beforeBody">[[tooltip.beforeBody]]</div>
|
||||
</template>
|
||||
<div>
|
||||
<ul>
|
||||
<template is="dom-repeat" items="[[tooltip.lines]]">
|
||||
<li>
|
||||
<em style$="background-color:[[item.bgColor]]"></em
|
||||
>[[item.text]]
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
get chart() {
|
||||
return this._chart;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
data: Object,
|
||||
identifier: String,
|
||||
rendered: {
|
||||
type: Boolean,
|
||||
notify: true,
|
||||
value: false,
|
||||
readOnly: true,
|
||||
},
|
||||
metas: {
|
||||
type: Array,
|
||||
value: () => [],
|
||||
},
|
||||
tooltip: {
|
||||
type: Object,
|
||||
value: () => ({
|
||||
opacity: "0",
|
||||
left: "0",
|
||||
top: "0",
|
||||
xPadding: "5",
|
||||
yPadding: "3",
|
||||
}),
|
||||
},
|
||||
unit: Object,
|
||||
rtl: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return ["onPropsChange(data)"];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._isAttached = true;
|
||||
this.onPropsChange();
|
||||
this._resizeListener = () => {
|
||||
this._debouncer = Debouncer.debounce(
|
||||
this._debouncer,
|
||||
timeOut.after(10),
|
||||
() => {
|
||||
if (this._isAttached) {
|
||||
this.resizeChart();
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (typeof ResizeObserver === "function") {
|
||||
this.resizeObserver = new ResizeObserver((entries) => {
|
||||
entries.forEach(() => {
|
||||
this._resizeListener();
|
||||
});
|
||||
});
|
||||
this.resizeObserver.observe(this.$.chartTarget);
|
||||
} else {
|
||||
this.addEventListener("iron-resize", this._resizeListener);
|
||||
}
|
||||
|
||||
if (scriptsLoaded === null) {
|
||||
scriptsLoaded = import("../../resources/ha-chart-scripts.js");
|
||||
}
|
||||
scriptsLoaded.then((ChartModule) => {
|
||||
this.ChartClass = ChartModule.default;
|
||||
this.onPropsChange();
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._isAttached = false;
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.unobserve(this.$.chartTarget);
|
||||
}
|
||||
|
||||
this.removeEventListener("iron-resize", this._resizeListener);
|
||||
|
||||
if (this._resizeTimer !== undefined) {
|
||||
clearInterval(this._resizeTimer);
|
||||
this._resizeTimer = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
onPropsChange() {
|
||||
if (!this._isAttached || !this.ChartClass || !this.data) {
|
||||
return;
|
||||
}
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
_customTooltips(tooltip) {
|
||||
// Hide if no tooltip
|
||||
if (tooltip.opacity === 0) {
|
||||
this.set(["tooltip", "opacity"], 0);
|
||||
return;
|
||||
}
|
||||
// Set caret Position
|
||||
if (tooltip.yAlign) {
|
||||
this.set(["tooltip", "yAlign"], tooltip.yAlign);
|
||||
} else {
|
||||
this.set(["tooltip", "yAlign"], "no-transform");
|
||||
}
|
||||
|
||||
const title = tooltip.title ? tooltip.title[0] || "" : "";
|
||||
this.set(["tooltip", "title"], title);
|
||||
|
||||
if (tooltip.beforeBody) {
|
||||
this.set(["tooltip", "beforeBody"], tooltip.beforeBody.join("\n"));
|
||||
}
|
||||
|
||||
const bodyLines = tooltip.body.map((n) => n.lines);
|
||||
|
||||
// Set Text
|
||||
if (tooltip.body) {
|
||||
this.set(
|
||||
["tooltip", "lines"],
|
||||
bodyLines.map((body, i) => {
|
||||
const colors = tooltip.labelColors[i];
|
||||
return {
|
||||
color: colors.borderColor,
|
||||
bgColor: colors.backgroundColor,
|
||||
text: body.join("\n"),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
const parentWidth = this.$.chartTarget.clientWidth;
|
||||
let positionX = tooltip.caretX;
|
||||
const positionY = this._chart.canvas.offsetTop + tooltip.caretY;
|
||||
if (tooltip.caretX + 100 > parentWidth) {
|
||||
positionX = parentWidth - 100;
|
||||
} else if (tooltip.caretX < 100) {
|
||||
positionX = 100;
|
||||
}
|
||||
positionX += this._chart.canvas.offsetLeft;
|
||||
// Display, position, and set styles for font
|
||||
this.tooltip = {
|
||||
...this.tooltip,
|
||||
opacity: 1,
|
||||
left: `${positionX}px`,
|
||||
top: `${positionY}px`,
|
||||
};
|
||||
}
|
||||
|
||||
_legendClick(event) {
|
||||
event = event || window.event;
|
||||
event.stopPropagation();
|
||||
let target = event.target || event.srcElement;
|
||||
while (target.nodeName !== "LI") {
|
||||
// user clicked child, find parent LI
|
||||
target = target.parentElement;
|
||||
}
|
||||
const index = event.model.itemsIndex;
|
||||
|
||||
const meta = this._chart.getDatasetMeta(index);
|
||||
meta.hidden =
|
||||
meta.hidden === null ? !this._chart.data.datasets[index].hidden : null;
|
||||
this.set(
|
||||
["metas", index, "hidden"],
|
||||
this._chart.isDatasetVisible(index) ? null : "hidden"
|
||||
);
|
||||
this._chart.update();
|
||||
}
|
||||
|
||||
_drawLegend() {
|
||||
const chart = this._chart;
|
||||
// New data for old graph. Keep metadata.
|
||||
const preserveVisibility =
|
||||
this._oldIdentifier && this.identifier === this._oldIdentifier;
|
||||
this._oldIdentifier = this.identifier;
|
||||
this.set(
|
||||
"metas",
|
||||
this._chart.data.datasets.map((x, i) => ({
|
||||
label: x.label,
|
||||
color: x.color,
|
||||
bgColor: x.backgroundColor,
|
||||
hidden:
|
||||
preserveVisibility && i < this.metas.length
|
||||
? this.metas[i].hidden
|
||||
: !chart.isDatasetVisible(i),
|
||||
}))
|
||||
);
|
||||
let updateNeeded = false;
|
||||
if (preserveVisibility) {
|
||||
for (let i = 0; i < this.metas.length; i++) {
|
||||
const meta = chart.getDatasetMeta(i);
|
||||
if (!!meta.hidden !== !!this.metas[i].hidden) updateNeeded = true;
|
||||
meta.hidden = this.metas[i].hidden ? true : null;
|
||||
}
|
||||
}
|
||||
if (updateNeeded) {
|
||||
chart.update();
|
||||
}
|
||||
this.unit = this.data.unit;
|
||||
}
|
||||
|
||||
_formatTickValue(value, index, values) {
|
||||
if (values.length === 0) {
|
||||
return value;
|
||||
}
|
||||
const date = new Date(values[index].value);
|
||||
return formatTime(date, this.hass.locale);
|
||||
}
|
||||
|
||||
drawChart() {
|
||||
const data = this.data.data;
|
||||
const ctx = this.$.chartCanvas;
|
||||
|
||||
if ((!data.datasets || !data.datasets.length) && !this._chart) {
|
||||
return;
|
||||
}
|
||||
if (this.data.type !== "timeline" && data.datasets.length > 0) {
|
||||
const cnt = data.datasets.length;
|
||||
const colors = this.constructor.getColorList(cnt);
|
||||
for (let loopI = 0; loopI < cnt; loopI++) {
|
||||
data.datasets[loopI].borderColor = colors[loopI].rgbString();
|
||||
data.datasets[loopI].backgroundColor = colors[loopI]
|
||||
.alpha(0.6)
|
||||
.rgbaString();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._chart) {
|
||||
this._customTooltips({ opacity: 0 });
|
||||
this._chart.data = data;
|
||||
this._chart.update({ duration: 0 });
|
||||
if (this.isTimeline) {
|
||||
this._chart.options.scales.yAxes[0].gridLines.display = data.length > 1;
|
||||
} else if (this.data.legend === true) {
|
||||
this._drawLegend();
|
||||
}
|
||||
this.resizeChart();
|
||||
} else {
|
||||
if (!data.datasets) {
|
||||
return;
|
||||
}
|
||||
this._customTooltips({ opacity: 0 });
|
||||
const plugins = [{ afterRender: () => this._setRendered(true) }];
|
||||
let options = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
hover: {
|
||||
animationDuration: 0,
|
||||
},
|
||||
responsiveAnimationDuration: 0,
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
custom: this._customTooltips.bind(this),
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
line: {
|
||||
spanGaps: true,
|
||||
},
|
||||
elements: {
|
||||
font: "12px 'Roboto', 'sans-serif'",
|
||||
},
|
||||
ticks: {
|
||||
fontFamily: "'Roboto', 'sans-serif'",
|
||||
},
|
||||
};
|
||||
options = Chart.helpers.merge(options, this.data.options);
|
||||
options.scales.xAxes[0].ticks.callback = this._formatTickValue.bind(this);
|
||||
if (this.data.type === "timeline") {
|
||||
this.set("isTimeline", true);
|
||||
if (this.data.colors !== undefined) {
|
||||
this._colorFunc = this.constructor.getColorGenerator(
|
||||
this.data.colors.staticColors,
|
||||
this.data.colors.staticColorIndex
|
||||
);
|
||||
}
|
||||
if (this._colorFunc !== undefined) {
|
||||
options.elements.colorFunction = this._colorFunc;
|
||||
}
|
||||
if (data.datasets.length === 1) {
|
||||
if (options.scales.yAxes[0].ticks) {
|
||||
options.scales.yAxes[0].ticks.display = false;
|
||||
} else {
|
||||
options.scales.yAxes[0].ticks = { display: false };
|
||||
}
|
||||
if (options.scales.yAxes[0].gridLines) {
|
||||
options.scales.yAxes[0].gridLines.display = false;
|
||||
} else {
|
||||
options.scales.yAxes[0].gridLines = { display: false };
|
||||
}
|
||||
}
|
||||
this.$.chartTarget.style.height = "50px";
|
||||
} else {
|
||||
this.$.chartTarget.style.height = "160px";
|
||||
}
|
||||
const chartData = {
|
||||
type: this.data.type,
|
||||
data: this.data.data,
|
||||
options: options,
|
||||
plugins: plugins,
|
||||
};
|
||||
// Async resize after dom update
|
||||
this._chart = new this.ChartClass(ctx, chartData);
|
||||
if (this.isTimeline !== true && this.data.legend === true) {
|
||||
this._drawLegend();
|
||||
}
|
||||
this.resizeChart();
|
||||
}
|
||||
}
|
||||
|
||||
resizeChart() {
|
||||
if (!this._chart) return;
|
||||
// Chart not ready
|
||||
if (this._resizeTimer === undefined) {
|
||||
this._resizeTimer = setInterval(this.resizeChart.bind(this), 10);
|
||||
return;
|
||||
}
|
||||
|
||||
clearInterval(this._resizeTimer);
|
||||
this._resizeTimer = undefined;
|
||||
|
||||
this._resizeChart();
|
||||
}
|
||||
|
||||
_resizeChart() {
|
||||
const chartTarget = this.$.chartTarget;
|
||||
|
||||
const options = this.data;
|
||||
const data = options.data;
|
||||
|
||||
if (data.datasets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.isTimeline) {
|
||||
this._chart.resize();
|
||||
return;
|
||||
}
|
||||
|
||||
// Recalculate chart height for Timeline chart
|
||||
const areaTop = this._chart.chartArea.top;
|
||||
const areaBot = this._chart.chartArea.bottom;
|
||||
const height1 = this._chart.canvas.clientHeight;
|
||||
if (areaBot > 0) {
|
||||
this._axisHeight = height1 - areaBot + areaTop;
|
||||
}
|
||||
|
||||
if (!this._axisHeight) {
|
||||
chartTarget.style.height = "50px";
|
||||
this._chart.resize();
|
||||
this.resizeChart();
|
||||
return;
|
||||
}
|
||||
if (this._axisHeight) {
|
||||
const cnt = data.datasets.length;
|
||||
const targetHeight = 30 * cnt + this._axisHeight + "px";
|
||||
if (chartTarget.style.height !== targetHeight) {
|
||||
chartTarget.style.height = targetHeight;
|
||||
}
|
||||
this._chart.resize();
|
||||
}
|
||||
}
|
||||
|
||||
// Get HSL distributed color list
|
||||
static getColorList(count) {
|
||||
let processL = false;
|
||||
if (count > 10) {
|
||||
processL = true;
|
||||
count = Math.ceil(count / 2);
|
||||
}
|
||||
const h1 = 360 / count;
|
||||
const result = [];
|
||||
for (let loopI = 0; loopI < count; loopI++) {
|
||||
result[loopI] = Color().hsl(h1 * loopI, 80, 38);
|
||||
if (processL) {
|
||||
result[loopI + count] = Color().hsl(h1 * loopI, 80, 62);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static getColorGenerator(staticColors, startIndex) {
|
||||
// Known colors for static data,
|
||||
// should add for very common state string manually.
|
||||
// Palette modified from http://google.github.io/palette.js/ mpn65, Apache 2.0
|
||||
const palette = [
|
||||
"ff0029",
|
||||
"66a61e",
|
||||
"377eb8",
|
||||
"984ea3",
|
||||
"00d2d5",
|
||||
"ff7f00",
|
||||
"af8d00",
|
||||
"7f80cd",
|
||||
"b3e900",
|
||||
"c42e60",
|
||||
"a65628",
|
||||
"f781bf",
|
||||
"8dd3c7",
|
||||
"bebada",
|
||||
"fb8072",
|
||||
"80b1d3",
|
||||
"fdb462",
|
||||
"fccde5",
|
||||
"bc80bd",
|
||||
"ffed6f",
|
||||
"c4eaff",
|
||||
"cf8c00",
|
||||
"1b9e77",
|
||||
"d95f02",
|
||||
"e7298a",
|
||||
"e6ab02",
|
||||
"a6761d",
|
||||
"0097ff",
|
||||
"00d067",
|
||||
"f43600",
|
||||
"4ba93b",
|
||||
"5779bb",
|
||||
"927acc",
|
||||
"97ee3f",
|
||||
"bf3947",
|
||||
"9f5b00",
|
||||
"f48758",
|
||||
"8caed6",
|
||||
"f2b94f",
|
||||
"eff26e",
|
||||
"e43872",
|
||||
"d9b100",
|
||||
"9d7a00",
|
||||
"698cff",
|
||||
"d9d9d9",
|
||||
"00d27e",
|
||||
"d06800",
|
||||
"009f82",
|
||||
"c49200",
|
||||
"cbe8ff",
|
||||
"fecddf",
|
||||
"c27eb6",
|
||||
"8cd2ce",
|
||||
"c4b8d9",
|
||||
"f883b0",
|
||||
"a49100",
|
||||
"f48800",
|
||||
"27d0df",
|
||||
"a04a9b",
|
||||
];
|
||||
function getColorIndex(idx) {
|
||||
// Reuse the color if index too large.
|
||||
return Color("#" + palette[idx % palette.length]);
|
||||
}
|
||||
const colorDict = {};
|
||||
let colorIndex = 0;
|
||||
if (startIndex > 0) colorIndex = startIndex;
|
||||
if (staticColors) {
|
||||
Object.keys(staticColors).forEach((c) => {
|
||||
const c1 = staticColors[c];
|
||||
if (isFinite(c1)) {
|
||||
colorDict[c.toLowerCase()] = getColorIndex(c1);
|
||||
} else {
|
||||
colorDict[c.toLowerCase()] = Color(staticColors[c]);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Custom color assign
|
||||
function getColor(__, data) {
|
||||
let ret;
|
||||
const name = data[3];
|
||||
if (name === null) return Color().hsl(0, 40, 38);
|
||||
if (name === undefined) return Color().hsl(120, 40, 38);
|
||||
let name1 = name.toLowerCase();
|
||||
if (ret === undefined) {
|
||||
if (data[4]) {
|
||||
// Invert on/off if data[4] is true. Required for some binary_sensor device classes
|
||||
// (BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED) where "off" is the good (= green color) value.
|
||||
name1 = name1 === "on" ? "off" : name1 === "off" ? "on" : name1;
|
||||
}
|
||||
|
||||
ret = colorDict[name1];
|
||||
}
|
||||
if (ret === undefined) {
|
||||
ret = getColorIndex(colorIndex);
|
||||
colorIndex++;
|
||||
colorDict[name1] = ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return getColor;
|
||||
}
|
||||
}
|
||||
customElements.define("ha-chart-base", HaChartBase);
|
@ -1,14 +1,19 @@
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiClose, mdiMenuDown } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-menu-button/paper-menu-button";
|
||||
import "@polymer/paper-ripple/paper-ripple";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-paper-dropdown-menu";
|
||||
import "../ha-svg-icon";
|
||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form";
|
||||
|
||||
@customElement("ha-form-select")
|
||||
export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormSelectSchema;
|
||||
@property({ attribute: false }) public schema!: HaFormSelectSchema;
|
||||
|
||||
@property() public data!: HaFormSelectData;
|
||||
|
||||
@ -26,7 +31,33 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-paper-dropdown-menu .label=${this.label}>
|
||||
<paper-menu-button horizontal-align="right" vertical-offset="8">
|
||||
<div class="dropdown-trigger" slot="dropdown-trigger">
|
||||
<paper-ripple></paper-ripple>
|
||||
<paper-input
|
||||
id="input"
|
||||
type="text"
|
||||
readonly
|
||||
value=${this.data}
|
||||
label=${this.label}
|
||||
input-role="button"
|
||||
input-aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
>
|
||||
${this.data && this.schema.optional
|
||||
? html`<mwc-icon-button
|
||||
slot="suffix"
|
||||
class="clear-button"
|
||||
@click=${this._clearValue}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>`
|
||||
: ""}
|
||||
<mwc-icon-button slot="suffix">
|
||||
<ha-svg-icon .path=${mdiMenuDown}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-input>
|
||||
</div>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-value"
|
||||
@ -45,7 +76,7 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
)
|
||||
}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
</paper-menu-button>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -57,6 +88,11 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
return Array.isArray(item) ? item[1] || item[0] : item;
|
||||
}
|
||||
|
||||
private _clearValue(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", { value: undefined });
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
@ -68,8 +104,16 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-paper-dropdown-menu {
|
||||
paper-menu-button {
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
paper-input > mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
padding: 2px;
|
||||
}
|
||||
.clear-button {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import { getExternalConfig } from "../external_app/external_config";
|
||||
@ -42,27 +42,23 @@ class HaHLSPlayer extends LitElement {
|
||||
// don't cache this, as we remove it on disconnects
|
||||
@query("video") private _videoEl!: HTMLVideoElement;
|
||||
|
||||
@state() private _attached = false;
|
||||
|
||||
private _hlsPolyfillInstance?: HlsLite;
|
||||
|
||||
private _useExoPlayer = false;
|
||||
private _exoPlayer = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._attached = true;
|
||||
if (this.hasUpdated) {
|
||||
this._startHls();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._attached = false;
|
||||
this._cleanUp();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._attached) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<video
|
||||
?autoplay=${this.autoPlay}
|
||||
@ -77,21 +73,13 @@ class HaHLSPlayer extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
const attachedChanged = changedProps.has("_attached");
|
||||
const urlChanged = changedProps.has("url");
|
||||
|
||||
if (!urlChanged && !attachedChanged) {
|
||||
if (!urlChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we are no longer attached, destroy polyfill
|
||||
if (attachedChanged && !this._attached) {
|
||||
// Tear down existing polyfill, if available
|
||||
this._destroyPolyfill();
|
||||
return;
|
||||
}
|
||||
|
||||
this._destroyPolyfill();
|
||||
this._cleanUp();
|
||||
this._startHls();
|
||||
}
|
||||
|
||||
@ -118,13 +106,13 @@ class HaHLSPlayer extends LitElement {
|
||||
}
|
||||
|
||||
if (!hlsSupported) {
|
||||
this._videoEl.innerHTML = this.hass.localize(
|
||||
videoEl.innerHTML = this.hass.localize(
|
||||
"ui.components.media-browser.video_not_supported"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._useExoPlayer = await useExoPlayerPromise;
|
||||
const useExoPlayer = await useExoPlayerPromise;
|
||||
const masterPlaylist = await (await masterPlaylistPromise).text();
|
||||
|
||||
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
|
||||
@ -144,7 +132,7 @@ class HaHLSPlayer extends LitElement {
|
||||
}
|
||||
|
||||
// If codec is HEVC and ExoPlayer is supported, use ExoPlayer.
|
||||
if (this._useExoPlayer && match !== null && match[1] !== undefined) {
|
||||
if (useExoPlayer && match !== null && match[1] !== undefined) {
|
||||
this._renderHLSExoPlayer(playlist_url);
|
||||
} else if (Hls.isSupported()) {
|
||||
this._renderHLSPolyfill(videoEl, Hls, playlist_url);
|
||||
@ -154,6 +142,7 @@ class HaHLSPlayer extends LitElement {
|
||||
}
|
||||
|
||||
private async _renderHLSExoPlayer(url: string) {
|
||||
this._exoPlayer = true;
|
||||
window.addEventListener("resize", this._resizeExoPlayer);
|
||||
this.updateComplete.then(() => nextRender()).then(this._resizeExoPlayer);
|
||||
this._videoEl.style.visibility = "hidden";
|
||||
@ -202,25 +191,28 @@ class HaHLSPlayer extends LitElement {
|
||||
|
||||
private async _renderHLSNative(videoEl: HTMLVideoElement, url: string) {
|
||||
videoEl.src = url;
|
||||
await new Promise((resolve) =>
|
||||
videoEl.addEventListener("loadedmetadata", resolve)
|
||||
);
|
||||
videoEl.play();
|
||||
videoEl.addEventListener("loadedmetadata", () => {
|
||||
videoEl.play();
|
||||
});
|
||||
}
|
||||
|
||||
private _elementResized() {
|
||||
fireEvent(this, "iron-resize");
|
||||
}
|
||||
|
||||
private _destroyPolyfill() {
|
||||
private _cleanUp() {
|
||||
if (this._hlsPolyfillInstance) {
|
||||
this._hlsPolyfillInstance.destroy();
|
||||
this._hlsPolyfillInstance = undefined;
|
||||
}
|
||||
if (this._useExoPlayer) {
|
||||
if (this._exoPlayer) {
|
||||
window.removeEventListener("resize", this._resizeExoPlayer);
|
||||
this.hass!.auth.external!.fireMessage({ type: "exoplayer/stop" });
|
||||
this._exoPlayer = false;
|
||||
}
|
||||
const videoEl = this._videoEl;
|
||||
videoEl.removeAttribute("src");
|
||||
videoEl.load();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@ -1,433 +0,0 @@
|
||||
import "@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 { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import "./entity/ha-chart-base";
|
||||
|
||||
class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height 0.3s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
<ha-chart-base
|
||||
id="chart"
|
||||
hass="[[hass]]"
|
||||
data="[[chartData]]"
|
||||
identifier="[[identifier]]"
|
||||
rendered="{{rendered}}"
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
chartData: Object,
|
||||
data: Object,
|
||||
names: Object,
|
||||
unit: String,
|
||||
identifier: String,
|
||||
|
||||
isSingleDevice: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
endTime: Object,
|
||||
rendered: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: "_onRenderedChanged",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return ["dataChanged(data, endTime, isSingleDevice)"];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._isAttached = true;
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
// safari doesn't always render the canvas when we animate it, so we remove overflow hidden when the animation is complete
|
||||
this.addEventListener("transitionend", () => {
|
||||
this.style.overflow = "auto";
|
||||
});
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
_onRenderedChanged(rendered) {
|
||||
if (rendered) {
|
||||
this.animateHeight();
|
||||
}
|
||||
}
|
||||
|
||||
animateHeight() {
|
||||
requestAnimationFrame(() =>
|
||||
requestAnimationFrame(() => {
|
||||
this.style.height = this.$.chart.scrollHeight + "px";
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
drawChart() {
|
||||
if (!this._isAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unit = this.unit;
|
||||
const deviceStates = this.data;
|
||||
const datasets = [];
|
||||
let endTime;
|
||||
|
||||
if (deviceStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
function safeParseFloat(value) {
|
||||
const parsed = parseFloat(value);
|
||||
return isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
endTime =
|
||||
this.endTime ||
|
||||
// Get the highest date from the last date of each device
|
||||
new Date(
|
||||
Math.max.apply(
|
||||
null,
|
||||
deviceStates.map(
|
||||
(devSts) =>
|
||||
new Date(devSts.states[devSts.states.length - 1].last_changed)
|
||||
)
|
||||
)
|
||||
);
|
||||
if (endTime > new Date()) {
|
||||
endTime = new Date();
|
||||
}
|
||||
|
||||
const names = this.names || {};
|
||||
deviceStates.forEach((states) => {
|
||||
const domain = states.domain;
|
||||
const name = names[states.entity_id] || states.name;
|
||||
// array containing [value1, value2, etc]
|
||||
let prevValues;
|
||||
const data = [];
|
||||
|
||||
function pushData(timestamp, datavalues) {
|
||||
if (!datavalues) return;
|
||||
if (timestamp > endTime) {
|
||||
// Drop datapoints that are after the requested endTime. This could happen if
|
||||
// endTime is "now" and client time is not in sync with server time.
|
||||
return;
|
||||
}
|
||||
data.forEach((d, i) => {
|
||||
if (datavalues[i] === null && prevValues && prevValues[i] !== null) {
|
||||
// null data values show up as gaps in the chart.
|
||||
// If the current value for the dataset is null and the previous
|
||||
// value of the data set is not null, then add an 'end' point
|
||||
// to the chart for the previous value. Otherwise the gap will
|
||||
// be too big. It will go from the start of the previous data
|
||||
// value until the start of the next data value.
|
||||
d.data.push({ x: timestamp, y: prevValues[i] });
|
||||
}
|
||||
d.data.push({ x: timestamp, y: datavalues[i] });
|
||||
});
|
||||
prevValues = datavalues;
|
||||
}
|
||||
|
||||
function addColumn(nameY, step, fill) {
|
||||
let dataFill = false;
|
||||
let dataStep = false;
|
||||
if (fill) {
|
||||
dataFill = "origin";
|
||||
}
|
||||
if (step) {
|
||||
dataStep = "before";
|
||||
}
|
||||
data.push({
|
||||
label: nameY,
|
||||
fill: dataFill,
|
||||
steppedLine: dataStep,
|
||||
pointRadius: 0,
|
||||
data: [],
|
||||
unitText: unit,
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
domain === "thermostat" ||
|
||||
domain === "climate" ||
|
||||
domain === "water_heater"
|
||||
) {
|
||||
const hasHvacAction = states.states.some(
|
||||
(state) => state.attributes && state.attributes.hvac_action
|
||||
);
|
||||
|
||||
const isHeating =
|
||||
domain === "climate" && hasHvacAction
|
||||
? (state) => state.attributes.hvac_action === "heating"
|
||||
: (state) => state.state === "heat";
|
||||
const isCooling =
|
||||
domain === "climate" && hasHvacAction
|
||||
? (state) => state.attributes.hvac_action === "cooling"
|
||||
: (state) => state.state === "cool";
|
||||
|
||||
const hasHeat = states.states.some(isHeating);
|
||||
const hasCool = states.states.some(isCooling);
|
||||
// We differentiate between thermostats that have a target temperature
|
||||
// range versus ones that have just a target temperature
|
||||
|
||||
// Using step chart by step-before so manually interpolation not needed.
|
||||
const hasTargetRange = states.states.some(
|
||||
(state) =>
|
||||
state.attributes &&
|
||||
state.attributes.target_temp_high !==
|
||||
state.attributes.target_temp_low
|
||||
);
|
||||
|
||||
addColumn(
|
||||
`${this.hass.localize(
|
||||
"ui.card.climate.current_temperature",
|
||||
"name",
|
||||
name
|
||||
)}`,
|
||||
true
|
||||
);
|
||||
if (hasHeat) {
|
||||
addColumn(
|
||||
`${this.hass.localize("ui.card.climate.heating", "name", name)}`,
|
||||
true,
|
||||
true
|
||||
);
|
||||
// The "heating" series uses steppedArea to shade the area below the current
|
||||
// temperature when the thermostat is calling for heat.
|
||||
}
|
||||
if (hasCool) {
|
||||
addColumn(
|
||||
`${this.hass.localize("ui.card.climate.cooling", "name", name)}`,
|
||||
true,
|
||||
true
|
||||
);
|
||||
// The "cooling" series uses steppedArea to shade the area below the current
|
||||
// temperature when the thermostat is calling for heat.
|
||||
}
|
||||
|
||||
if (hasTargetRange) {
|
||||
addColumn(
|
||||
`${this.hass.localize(
|
||||
"ui.card.climate.target_temperature_mode",
|
||||
"name",
|
||||
name,
|
||||
"mode",
|
||||
this.hass.localize("ui.card.climate.high")
|
||||
)}`,
|
||||
true
|
||||
);
|
||||
addColumn(
|
||||
`${this.hass.localize(
|
||||
"ui.card.climate.target_temperature_mode",
|
||||
"name",
|
||||
name,
|
||||
"mode",
|
||||
this.hass.localize("ui.card.climate.low")
|
||||
)}`,
|
||||
true
|
||||
);
|
||||
} else {
|
||||
addColumn(
|
||||
`${this.hass.localize(
|
||||
"ui.card.climate.target_temperature_entity",
|
||||
"name",
|
||||
name
|
||||
)}`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
states.states.forEach((state) => {
|
||||
if (!state.attributes) return;
|
||||
const curTemp = safeParseFloat(state.attributes.current_temperature);
|
||||
const series = [curTemp];
|
||||
if (hasHeat) {
|
||||
series.push(isHeating(state) ? curTemp : null);
|
||||
}
|
||||
if (hasCool) {
|
||||
series.push(isCooling(state) ? curTemp : null);
|
||||
}
|
||||
if (hasTargetRange) {
|
||||
const targetHigh = safeParseFloat(
|
||||
state.attributes.target_temp_high
|
||||
);
|
||||
const targetLow = safeParseFloat(state.attributes.target_temp_low);
|
||||
series.push(targetHigh, targetLow);
|
||||
pushData(new Date(state.last_changed), series);
|
||||
} else {
|
||||
const target = safeParseFloat(state.attributes.temperature);
|
||||
series.push(target);
|
||||
pushData(new Date(state.last_changed), series);
|
||||
}
|
||||
});
|
||||
} else if (domain === "humidifier") {
|
||||
addColumn(
|
||||
`${this.hass.localize(
|
||||
"ui.card.humidifier.target_humidity_entity",
|
||||
"name",
|
||||
name
|
||||
)}`,
|
||||
true
|
||||
);
|
||||
addColumn(
|
||||
`${this.hass.localize("ui.card.humidifier.on_entity", "name", name)}`,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
states.states.forEach((state) => {
|
||||
if (!state.attributes) return;
|
||||
const target = safeParseFloat(state.attributes.humidity);
|
||||
const series = [target];
|
||||
series.push(state.state === "on" ? target : null);
|
||||
pushData(new Date(state.last_changed), series);
|
||||
});
|
||||
} else {
|
||||
// Only disable interpolation for sensors
|
||||
const isStep = domain === "sensor";
|
||||
addColumn(name, isStep);
|
||||
|
||||
let lastValue = null;
|
||||
let lastDate = null;
|
||||
let lastNullDate = null;
|
||||
|
||||
// Process chart data.
|
||||
// When state is `unknown`, calculate the value and break the line.
|
||||
states.states.forEach((state) => {
|
||||
const value = safeParseFloat(state.state);
|
||||
const date = new Date(state.last_changed);
|
||||
if (value !== null && lastNullDate !== null) {
|
||||
const dateTime = date.getTime();
|
||||
const lastNullDateTime = lastNullDate.getTime();
|
||||
const lastDateTime = lastDate.getTime();
|
||||
const tmpValue =
|
||||
(value - lastValue) *
|
||||
((lastNullDateTime - lastDateTime) /
|
||||
(dateTime - lastDateTime)) +
|
||||
lastValue;
|
||||
pushData(lastNullDate, [tmpValue]);
|
||||
pushData(new Date(lastNullDateTime + 1), [null]);
|
||||
pushData(date, [value]);
|
||||
lastDate = date;
|
||||
lastValue = value;
|
||||
lastNullDate = null;
|
||||
} else if (value !== null && lastNullDate === null) {
|
||||
pushData(date, [value]);
|
||||
lastDate = date;
|
||||
lastValue = value;
|
||||
} else if (
|
||||
value === null &&
|
||||
lastNullDate === null &&
|
||||
lastValue !== null
|
||||
) {
|
||||
lastNullDate = date;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add an entry for final values
|
||||
pushData(endTime, prevValues, false);
|
||||
|
||||
// Concat two arrays
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
});
|
||||
|
||||
const formatTooltipTitle = (items, data) => {
|
||||
const item = items[0];
|
||||
const date = data.datasets[item.datasetIndex].data[item.index].x;
|
||||
|
||||
return formatDateTimeWithSeconds(date, this.hass.locale);
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
type: "line",
|
||||
unit: unit,
|
||||
legend: !this.isSingleDevice,
|
||||
options: {
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
ticks: {
|
||||
major: {
|
||||
fontStyle: "bold",
|
||||
},
|
||||
source: "auto",
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 20,
|
||||
maxRotation: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
ticks: {
|
||||
maxTicksLimit: 7,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
tooltips: {
|
||||
mode: "neareach",
|
||||
callbacks: {
|
||||
title: formatTooltipTitle,
|
||||
},
|
||||
},
|
||||
hover: {
|
||||
mode: "neareach",
|
||||
},
|
||||
layout: {
|
||||
padding: {
|
||||
top: 5,
|
||||
},
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.1,
|
||||
pointRadius: 0,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
filler: {
|
||||
propagate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: datasets,
|
||||
},
|
||||
};
|
||||
this.chartData = chartOptions;
|
||||
}
|
||||
}
|
||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
@ -1,286 +0,0 @@
|
||||
import "@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 { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import "./entity/ha-chart-base";
|
||||
|
||||
/** Binary sensor device classes for which the static colors for on/off need to be inverted.
|
||||
* List the ones were "off" = good or normal state = should be rendered "green".
|
||||
*/
|
||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
||||
"battery",
|
||||
"door",
|
||||
"garage_door",
|
||||
"gas",
|
||||
"lock",
|
||||
"opening",
|
||||
"problem",
|
||||
"safety",
|
||||
"smoke",
|
||||
"window",
|
||||
]);
|
||||
|
||||
class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
:host([rendered]) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ha-chart-base {
|
||||
direction: ltr;
|
||||
}
|
||||
</style>
|
||||
<ha-chart-base
|
||||
hass="[[hass]]"
|
||||
data="[[chartData]]"
|
||||
rendered="{{rendered}}"
|
||||
rtl="{{rtl}}"
|
||||
></ha-chart-base>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
chartData: Object,
|
||||
data: {
|
||||
type: Object,
|
||||
observer: "dataChanged",
|
||||
},
|
||||
names: Object,
|
||||
noSingle: Boolean,
|
||||
endTime: Date,
|
||||
rendered: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
rtl: {
|
||||
reflectToAttribute: true,
|
||||
computed: "_computeRTL(hass)",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return ["dataChanged(data, endTime, localize, language)"];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._isAttached = true;
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
dataChanged() {
|
||||
this.drawChart();
|
||||
}
|
||||
|
||||
drawChart() {
|
||||
const staticColors = {
|
||||
on: 1,
|
||||
off: 0,
|
||||
home: 1,
|
||||
not_home: 0,
|
||||
unavailable: "#a0a0a0",
|
||||
unknown: "#606060",
|
||||
idle: 2,
|
||||
};
|
||||
let stateHistory = this.data;
|
||||
|
||||
if (!this._isAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!stateHistory) {
|
||||
stateHistory = [];
|
||||
}
|
||||
|
||||
const startTime = new Date(
|
||||
stateHistory.reduce(
|
||||
(minTime, stateInfo) =>
|
||||
Math.min(minTime, new Date(stateInfo.data[0].last_changed)),
|
||||
new Date()
|
||||
)
|
||||
);
|
||||
|
||||
// end time is Math.max(startTime, last_event)
|
||||
let endTime =
|
||||
this.endTime ||
|
||||
new Date(
|
||||
stateHistory.reduce(
|
||||
(maxTime, stateInfo) =>
|
||||
Math.max(
|
||||
maxTime,
|
||||
new Date(stateInfo.data[stateInfo.data.length - 1].last_changed)
|
||||
),
|
||||
startTime
|
||||
)
|
||||
);
|
||||
|
||||
if (endTime > new Date()) {
|
||||
endTime = new Date();
|
||||
}
|
||||
|
||||
const labels = [];
|
||||
const datasets = [];
|
||||
// stateHistory is a list of lists of sorted state objects
|
||||
const names = this.names || {};
|
||||
stateHistory.forEach((stateInfo) => {
|
||||
let newLastChanged;
|
||||
let prevState = null;
|
||||
let locState = null;
|
||||
let prevLastChanged = startTime;
|
||||
const entityDisplay = names[stateInfo.entity_id] || stateInfo.name;
|
||||
|
||||
const invertOnOff =
|
||||
computeDomain(stateInfo.entity_id) === "binary_sensor" &&
|
||||
BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED.has(
|
||||
this.hass.states[stateInfo.entity_id].attributes.device_class
|
||||
);
|
||||
|
||||
const dataRow = [];
|
||||
stateInfo.data.forEach((state) => {
|
||||
let newState = state.state;
|
||||
const timeStamp = new Date(state.last_changed);
|
||||
if (newState === undefined || newState === "") {
|
||||
newState = null;
|
||||
}
|
||||
if (timeStamp > endTime) {
|
||||
// Drop datapoints that are after the requested endTime. This could happen if
|
||||
// endTime is 'now' and client time is not in sync with server time.
|
||||
return;
|
||||
}
|
||||
if (prevState !== null && newState !== prevState) {
|
||||
newLastChanged = new Date(state.last_changed);
|
||||
|
||||
dataRow.push([
|
||||
prevLastChanged,
|
||||
newLastChanged,
|
||||
locState,
|
||||
prevState,
|
||||
invertOnOff,
|
||||
]);
|
||||
|
||||
prevState = newState;
|
||||
locState = state.state_localize;
|
||||
prevLastChanged = newLastChanged;
|
||||
} else if (prevState === null) {
|
||||
prevState = newState;
|
||||
locState = state.state_localize;
|
||||
prevLastChanged = new Date(state.last_changed);
|
||||
}
|
||||
});
|
||||
|
||||
if (prevState !== null) {
|
||||
dataRow.push([
|
||||
prevLastChanged,
|
||||
endTime,
|
||||
locState,
|
||||
prevState,
|
||||
invertOnOff,
|
||||
]);
|
||||
}
|
||||
datasets.push({
|
||||
data: dataRow,
|
||||
entity_id: stateInfo.entity_id,
|
||||
});
|
||||
labels.push(entityDisplay);
|
||||
});
|
||||
|
||||
const formatTooltipLabel = (item, data) => {
|
||||
const values = data.datasets[item.datasetIndex].data[item.index];
|
||||
|
||||
const start = formatDateTimeWithSeconds(values[0], this.hass.locale);
|
||||
const end = formatDateTimeWithSeconds(values[1], this.hass.locale);
|
||||
const state = values[2];
|
||||
|
||||
return [state, start, end];
|
||||
};
|
||||
|
||||
const formatTooltipBeforeBody = (item, data) => {
|
||||
if (!this.hass.userData || !this.hass.userData.showAdvanced || !item[0]) {
|
||||
return "";
|
||||
}
|
||||
// Extract the entity ID from the dataset.
|
||||
const values = data.datasets[item[0].datasetIndex];
|
||||
return values.entity_id || "";
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
type: "timeline",
|
||||
options: {
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
label: formatTooltipLabel,
|
||||
beforeBody: formatTooltipBeforeBody,
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
ticks: {
|
||||
major: {
|
||||
fontStyle: "bold",
|
||||
},
|
||||
sampleSize: 5,
|
||||
autoSkipPadding: 50,
|
||||
maxRotation: 0,
|
||||
},
|
||||
categoryPercentage: undefined,
|
||||
barPercentage: undefined,
|
||||
time: {
|
||||
format: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
afterSetDimensions: (yaxe) => {
|
||||
yaxe.maxWidth = yaxe.chart.width * 0.18;
|
||||
},
|
||||
position: this._computeRTL ? "right" : "left",
|
||||
categoryPercentage: undefined,
|
||||
barPercentage: undefined,
|
||||
time: { format: undefined },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
datasets: {
|
||||
categoryPercentage: 0.8,
|
||||
barPercentage: 0.9,
|
||||
},
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: datasets,
|
||||
},
|
||||
colors: {
|
||||
staticColors: staticColors,
|
||||
staticColorIndex: 3,
|
||||
},
|
||||
};
|
||||
this.chartData = chartOptions;
|
||||
}
|
||||
|
||||
_computeRTL(hass) {
|
||||
return computeRTL(hass);
|
||||
}
|
||||
}
|
||||
customElements.define(
|
||||
"state-history-chart-timeline",
|
||||
StateHistoryChartTimeline
|
||||
);
|
@ -27,6 +27,12 @@ export type ConfigEntryMutableParams = Partial<
|
||||
>
|
||||
>;
|
||||
|
||||
export const ERROR_STATES: ConfigEntry["state"][] = [
|
||||
"migration_error",
|
||||
"setup_error",
|
||||
"setup_retry",
|
||||
];
|
||||
|
||||
export const getConfigEntries = (hass: HomeAssistant) =>
|
||||
hass.callApi<ConfigEntry[]>("GET", "config/config_entries/entry");
|
||||
|
||||
|
@ -8,6 +8,7 @@ export enum LightColorModes {
|
||||
ONOFF = "onoff",
|
||||
BRIGHTNESS = "brightness",
|
||||
COLOR_TEMP = "color_temp",
|
||||
WHITE = "white",
|
||||
HS = "hs",
|
||||
XY = "xy",
|
||||
RGB = "rgb",
|
||||
|
25
src/data/select.ts
Normal file
25
src/data/select.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
interface SelectEntityAttributes extends HassEntityAttributeBase {
|
||||
options: string[];
|
||||
}
|
||||
|
||||
export interface SelectEntity extends HassEntityBase {
|
||||
attributes: SelectEntityAttributes;
|
||||
}
|
||||
|
||||
export const setSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService(
|
||||
"select",
|
||||
"select_option",
|
||||
{ option },
|
||||
{ entity_id: entity }
|
||||
);
|
@ -21,6 +21,7 @@ export interface ZWaveJSClient {
|
||||
export interface ZWaveJSController {
|
||||
home_id: string;
|
||||
nodes: number[];
|
||||
is_heal_network_active: boolean;
|
||||
}
|
||||
|
||||
export interface ZWaveJSNode {
|
||||
@ -77,6 +78,11 @@ export interface ZWaveJSRefreshNodeStatusMessage {
|
||||
stage?: string;
|
||||
}
|
||||
|
||||
export interface ZWaveJSHealNetworkStatusMessage {
|
||||
event: string;
|
||||
heal_node_status: { [key: number]: string };
|
||||
}
|
||||
|
||||
export enum NodeStatus {
|
||||
Unknown,
|
||||
Asleep,
|
||||
@ -172,6 +178,37 @@ export const reinterviewNode = (
|
||||
}
|
||||
);
|
||||
|
||||
export const healNetwork = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/begin_healing_network",
|
||||
entry_id: entry_id,
|
||||
});
|
||||
|
||||
export const stopHealNetwork = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/stop_healing_network",
|
||||
entry_id: entry_id,
|
||||
});
|
||||
|
||||
export const subscribeHealNetworkProgress = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
callbackFunction: (message: ZWaveJSHealNetworkStatusMessage) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_heal_network_progress",
|
||||
entry_id: entry_id,
|
||||
}
|
||||
);
|
||||
|
||||
export const getIdentifiersFromDevice = (
|
||||
device: DeviceRegistryEntry
|
||||
): ZWaveJSNodeIdentifiers | undefined => {
|
||||
@ -193,6 +230,18 @@ export const getIdentifiersFromDevice = (
|
||||
};
|
||||
};
|
||||
|
||||
export type ZWaveJSLogUpdate = ZWaveJSLogMessageUpdate | ZWaveJSLogConfigUpdate;
|
||||
|
||||
interface ZWaveJSLogMessageUpdate {
|
||||
type: "log_message";
|
||||
log_message: ZWaveJSLogMessage;
|
||||
}
|
||||
|
||||
interface ZWaveJSLogConfigUpdate {
|
||||
type: "log_config";
|
||||
log_config: ZWaveJSLogConfig;
|
||||
}
|
||||
|
||||
export interface ZWaveJSLogMessage {
|
||||
timestamp: string;
|
||||
level: string;
|
||||
@ -203,10 +252,10 @@ export interface ZWaveJSLogMessage {
|
||||
export const subscribeZWaveJSLogs = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
callback: (message: ZWaveJSLogMessage) => void
|
||||
callback: (update: ZWaveJSLogUpdate) => void
|
||||
) =>
|
||||
hass.connection.subscribeMessage<ZWaveJSLogMessage>(callback, {
|
||||
type: "zwave_js/subscribe_logs",
|
||||
hass.connection.subscribeMessage<ZWaveJSLogUpdate>(callback, {
|
||||
type: "zwave_js/subscribe_log_updates",
|
||||
entry_id,
|
||||
});
|
||||
|
||||
|
@ -232,7 +232,6 @@ class DataEntryFlowDialog extends LitElement {
|
||||
<step-flow-pick-handler
|
||||
.hass=${this.hass}
|
||||
.handlers=${this._handlers}
|
||||
.showAdvanced=${this._params.showAdvanced}
|
||||
@handler-picked=${this._handlerPicked}
|
||||
></step-flow-pick-handler>
|
||||
`
|
||||
|
@ -90,7 +90,10 @@ export const showConfigFlowDialog = (
|
||||
},
|
||||
|
||||
renderShowFormStepFieldError(hass, step, error) {
|
||||
return hass.localize(`component.${step.handler}.config.error.${error}`);
|
||||
return hass.localize(
|
||||
`component.${step.handler}.config.error.${error}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
},
|
||||
|
||||
renderExternalStepHeader(hass, step) {
|
||||
|
@ -88,9 +88,10 @@ export const showOptionsFlowDialog = (
|
||||
);
|
||||
},
|
||||
|
||||
renderShowFormStepFieldError(hass, _step, error) {
|
||||
renderShowFormStepFieldError(hass, step, error) {
|
||||
return hass.localize(
|
||||
`component.${configEntry.domain}.options.error.${error}`
|
||||
`component.${configEntry.domain}.options.error.${error}`,
|
||||
step.description_placeholders
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -3,7 +3,6 @@ import "@polymer/paper-item/paper-item-body";
|
||||
import Fuse from "fuse.js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@ -34,9 +33,7 @@ declare global {
|
||||
class StepFlowPickHandler extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public handlers!: string[];
|
||||
|
||||
@property() public showAdvanced?: boolean;
|
||||
@property({ attribute: false }) public handlers!: string[];
|
||||
|
||||
@state() private _filter?: string;
|
||||
|
||||
@ -87,47 +84,50 @@ class StepFlowPickHandler extends LitElement {
|
||||
width: `${this._width}px`,
|
||||
height: `${this._height}px`,
|
||||
})}
|
||||
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
|
||||
>
|
||||
${handlers.map(
|
||||
(handler: HandlerObj) =>
|
||||
html`
|
||||
<paper-icon-item
|
||||
@click=${this._handlerPicked}
|
||||
.handler=${handler}
|
||||
>
|
||||
<img
|
||||
slot="item-icon"
|
||||
loading="lazy"
|
||||
src=${brandsUrl(handler.slug, "icon", true)}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
${handlers.length
|
||||
? handlers.map(
|
||||
(handler: HandlerObj) =>
|
||||
html`
|
||||
<paper-icon-item
|
||||
@click=${this._handlerPicked}
|
||||
.handler=${handler}
|
||||
>
|
||||
<img
|
||||
slot="item-icon"
|
||||
loading="lazy"
|
||||
src=${brandsUrl(handler.slug, "icon", true)}
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
||||
<paper-item-body> ${handler.name} </paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-icon-item>
|
||||
`
|
||||
)}
|
||||
<paper-item-body> ${handler.name} </paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-icon-item>
|
||||
`
|
||||
)
|
||||
: html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.note_about_integrations"
|
||||
)}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.note_about_website_reference"
|
||||
)}<a
|
||||
href="${documentationUrl(
|
||||
this.hass,
|
||||
`/integrations/${
|
||||
this._filter ? `#search/${this._filter}` : ""
|
||||
}`
|
||||
)}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.home_assistant_website"
|
||||
)}</a
|
||||
>.
|
||||
</p>
|
||||
`}
|
||||
</div>
|
||||
${this.showAdvanced
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.note_about_integrations"
|
||||
)}<br />
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.note_about_website_reference"
|
||||
)}<a
|
||||
href="${documentationUrl(this.hass, "/integrations/")}"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.home_assistant_website"
|
||||
)}</a
|
||||
>.
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -193,9 +193,6 @@ class StepFlowPickHandler extends LitElement {
|
||||
div {
|
||||
max-height: calc(100vh - 134px);
|
||||
}
|
||||
div.advanced {
|
||||
max-height: calc(100vh - 250px);
|
||||
}
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-button-toggle-group";
|
||||
@ -28,11 +29,6 @@ import {
|
||||
} from "../../../data/light";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
|
||||
const toggleButtons = [
|
||||
{ label: "Color", value: "color" },
|
||||
{ label: "Temperature", value: LightColorModes.COLOR_TEMP },
|
||||
];
|
||||
|
||||
@customElement("more-info-light")
|
||||
class MoreInfoLight extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -59,7 +55,7 @@ class MoreInfoLight extends LitElement {
|
||||
|
||||
@state() private _colorPickerColor?: [number, number, number];
|
||||
|
||||
@state() private _mode?: "color" | LightColorModes.COLOR_TEMP;
|
||||
@state() private _mode?: "color" | LightColorModes;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
@ -71,6 +67,11 @@ class MoreInfoLight extends LitElement {
|
||||
LightColorModes.COLOR_TEMP
|
||||
);
|
||||
|
||||
const supportsWhite = lightSupportsColorMode(
|
||||
this.stateObj,
|
||||
LightColorModes.WHITE
|
||||
);
|
||||
|
||||
const supportsRgbww = lightSupportsColorMode(
|
||||
this.stateObj,
|
||||
LightColorModes.RGBWW
|
||||
@ -101,16 +102,17 @@ class MoreInfoLight extends LitElement {
|
||||
${this.stateObj.state === "on"
|
||||
? html`
|
||||
${supportsTemp || supportsColor ? html`<hr />` : ""}
|
||||
${supportsTemp && supportsColor
|
||||
${supportsColor && (supportsTemp || supportsWhite)
|
||||
? html`<ha-button-toggle-group
|
||||
fullWidth
|
||||
.buttons=${toggleButtons}
|
||||
.buttons=${this._toggleButtons(supportsTemp, supportsWhite)}
|
||||
.active=${this._mode}
|
||||
@value-changed=${this._modeChanged}
|
||||
></ha-button-toggle-group>`
|
||||
: ""}
|
||||
${supportsTemp &&
|
||||
(!supportsColor || this._mode === LightColorModes.COLOR_TEMP)
|
||||
((!supportsColor && !supportsWhite) ||
|
||||
this._mode === LightColorModes.COLOR_TEMP)
|
||||
? html`
|
||||
<ha-labeled-slider
|
||||
class="color_temp"
|
||||
@ -126,7 +128,8 @@ class MoreInfoLight extends LitElement {
|
||||
></ha-labeled-slider>
|
||||
`
|
||||
: ""}
|
||||
${supportsColor && (!supportsTemp || this._mode === "color")
|
||||
${supportsColor &&
|
||||
((!supportsTemp && !supportsWhite) || this._mode === "color")
|
||||
? html`
|
||||
<div class="segmentationContainer">
|
||||
<ha-color-picker
|
||||
@ -251,7 +254,7 @@ class MoreInfoLight extends LitElement {
|
||||
) {
|
||||
this._mode = lightIsInColorMode(this.stateObj!)
|
||||
? "color"
|
||||
: LightColorModes.COLOR_TEMP;
|
||||
: this.stateObj!.attributes.color_mode;
|
||||
}
|
||||
|
||||
let brightnessAdjust = 100;
|
||||
@ -300,6 +303,19 @@ class MoreInfoLight extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleButtons = memoizeOne(
|
||||
(supportsTemp: boolean, supportsWhite: boolean) => {
|
||||
const modes = [{ label: "Color", value: "color" }];
|
||||
if (supportsTemp) {
|
||||
modes.push({ label: "Temperature", value: LightColorModes.COLOR_TEMP });
|
||||
}
|
||||
if (supportsWhite) {
|
||||
modes.push({ label: "White", value: LightColorModes.WHITE });
|
||||
}
|
||||
return modes;
|
||||
}
|
||||
);
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
this._mode = ev.detail.value;
|
||||
}
|
||||
@ -326,6 +342,14 @@ class MoreInfoLight extends LitElement {
|
||||
|
||||
this._brightnessSliderValue = bri;
|
||||
|
||||
if (this._mode === LightColorModes.WHITE) {
|
||||
this.hass.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
white: Math.min(255, Math.round((bri * 255) / 100)),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._brightnessAdjusted) {
|
||||
const rgb =
|
||||
this.stateObj!.attributes.rgb_color ||
|
||||
|
@ -2,7 +2,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/state-history-charts";
|
||||
import "../../components/chart/state-history-charts";
|
||||
import { getRecentWithCache } from "../../data/cached-history";
|
||||
import { HistoryResult } from "../../data/history";
|
||||
import { HomeAssistant } from "../../types";
|
||||
|
@ -4,7 +4,6 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/state-history-charts";
|
||||
import { fetchUsers } from "../../data/user";
|
||||
import { getLogbookData, LogbookEntry } from "../../data/logbook";
|
||||
import { loadTraceContexts, TraceContexts } from "../../data/trace";
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
enableConfigEntry,
|
||||
reloadConfigEntry,
|
||||
updateConfigEntry,
|
||||
ERROR_STATES,
|
||||
} from "../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
@ -38,12 +39,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import type { ConfigEntryExtended } from "./ha-config-integrations";
|
||||
import "./ha-integration-header";
|
||||
|
||||
const ERROR_STATES: ConfigEntry["state"][] = [
|
||||
"migration_error",
|
||||
"setup_error",
|
||||
"setup_retry",
|
||||
];
|
||||
|
||||
const integrationsWithPanel = {
|
||||
hassio: "/hassio/dashboard",
|
||||
mqtt: "/config/mqtt",
|
||||
@ -303,7 +298,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item @request-selected="${this._editEntryName}">
|
||||
<mwc-list-item @request-selected="${this._handleRename}">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}
|
||||
@ -420,6 +415,15 @@ export class HaIntegrationCard extends LitElement {
|
||||
showOptionsFlowDialog(this, ev.target.closest("ha-card").configEntry);
|
||||
}
|
||||
|
||||
private _handleRename(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
this._editEntryName(
|
||||
((ev.target as HTMLElement).closest("ha-card") as any).configEntry
|
||||
);
|
||||
}
|
||||
|
||||
private _handleReload(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
@ -578,8 +582,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _editEntryName(ev) {
|
||||
const configEntry = ev.target.closest("ha-card").configEntry;
|
||||
private async _editEntryName(configEntry: ConfigEntry) {
|
||||
const newName = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.integrations.rename_dialog"),
|
||||
defaultValue: configEntry.title,
|
||||
|
@ -0,0 +1,312 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-linear-progress/mwc-linear-progress";
|
||||
import { mdiStethoscope, mdiCheckCircle, mdiCloseCircle } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../../../components/ha-dialog";
|
||||
import {
|
||||
fetchNetworkStatus,
|
||||
healNetwork,
|
||||
stopHealNetwork,
|
||||
subscribeHealNetworkProgress,
|
||||
ZWaveJSHealNetworkStatusMessage,
|
||||
ZWaveJSNetwork,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import { haStyleDialog } from "../../../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import { ZWaveJSHealNetworkDialogParams } from "./show-dialog-zwave_js-heal-network";
|
||||
|
||||
@customElement("dialog-zwave_js-heal-network")
|
||||
class DialogZWaveJSHealNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private entry_id?: string;
|
||||
|
||||
@state() private _status?: string;
|
||||
|
||||
@state() private _progress_total = 0;
|
||||
|
||||
@state() private _progress_finished = 0;
|
||||
|
||||
@state() private _progress_in_progress = 0;
|
||||
|
||||
private _subscribed?: Promise<UnsubscribeFunc>;
|
||||
|
||||
public showDialog(params: ZWaveJSHealNetworkDialogParams): void {
|
||||
this._progress_total = 0;
|
||||
this.entry_id = params.entry_id;
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.entry_id = undefined;
|
||||
this._status = undefined;
|
||||
this._progress_total = 0;
|
||||
|
||||
this._unsubscribe();
|
||||
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entry_id) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this.hass.localize("ui.panel.config.zwave_js.heal_network.title")
|
||||
)}
|
||||
>
|
||||
${!this._status
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiStethoscope}
|
||||
class="introduction"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.introduction"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
<em>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.traffic_warning"
|
||||
)}
|
||||
</em>
|
||||
</p>
|
||||
<mwc-button slot="primaryAction" @click=${this._startHeal}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.start_heal"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: ``}
|
||||
${this._status === "started"
|
||||
? html`
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.in_progress"
|
||||
)}
|
||||
</b>
|
||||
</p>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.run_in_background"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
${!this._progress_total
|
||||
? html`
|
||||
<mwc-linear-progress indeterminate> </mwc-linear-progress>
|
||||
`
|
||||
: ""}
|
||||
<mwc-button slot="secondaryAction" @click=${this._stopHeal}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.stop_heal"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ``}
|
||||
${this._status === "failed"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.healing_failed"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ``}
|
||||
${this._status === "finished"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCheckCircle}
|
||||
class="success"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.healing_complete"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ``}
|
||||
${this._status === "cancelled"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-svg-icon
|
||||
.path=${mdiCloseCircle}
|
||||
class="failed"
|
||||
></ha-svg-icon>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.heal_network.healing_cancelled"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
${this.hass.localize("ui.panel.config.zwave_js.common.close")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ``}
|
||||
${this._progress_total && this._status !== "finished"
|
||||
? html`
|
||||
<mwc-linear-progress
|
||||
determinate
|
||||
.progress=${this._progress_finished}
|
||||
.buffer=${this._progress_in_progress}
|
||||
>
|
||||
</mwc-linear-progress>
|
||||
`
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
const network: ZWaveJSNetwork = await fetchNetworkStatus(
|
||||
this.hass!,
|
||||
this.entry_id!
|
||||
);
|
||||
if (network.controller.is_heal_network_active) {
|
||||
this._status = "started";
|
||||
this._subscribed = subscribeHealNetworkProgress(
|
||||
this.hass,
|
||||
this.entry_id!,
|
||||
this._handleMessage.bind(this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _startHeal(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
healNetwork(this.hass, this.entry_id!);
|
||||
this._status = "started";
|
||||
this._subscribed = subscribeHealNetworkProgress(
|
||||
this.hass,
|
||||
this.entry_id!,
|
||||
this._handleMessage.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
private _stopHeal(): void {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
stopHealNetwork(this.hass, this.entry_id!);
|
||||
this._unsubscribe();
|
||||
this._status = "cancelled";
|
||||
}
|
||||
|
||||
private _handleMessage(message: ZWaveJSHealNetworkStatusMessage): void {
|
||||
if (message.event === "heal network progress") {
|
||||
let finished = 0;
|
||||
let in_progress = 0;
|
||||
for (const status of Object.values(message.heal_node_status)) {
|
||||
if (status === "pending") {
|
||||
in_progress++;
|
||||
}
|
||||
if (["skipped", "failed", "done"].includes(status)) {
|
||||
finished++;
|
||||
}
|
||||
}
|
||||
this._progress_total = Object.keys(message.heal_node_status).length;
|
||||
this._progress_finished = finished / this._progress_total;
|
||||
this._progress_in_progress = in_progress / this._progress_total;
|
||||
}
|
||||
if (message.event === "heal network done") {
|
||||
this._unsubscribe();
|
||||
this._status = "finished";
|
||||
}
|
||||
}
|
||||
|
||||
private _unsubscribe(): void {
|
||||
if (this._subscribed) {
|
||||
this._subscribed.then((unsub) => unsub());
|
||||
this._subscribed = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.success {
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.failed {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
width: 68px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
ha-svg-icon.introduction {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.flex-container ha-svg-icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
mwc-linear-progress {
|
||||
margin-top: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-zwave_js-heal-network": DialogZWaveJSHealNetwork;
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
|
||||
export interface ZWaveJSHealNetworkDialogParams {
|
||||
entry_id: string;
|
||||
}
|
||||
|
||||
export const loadHealNetworkDialog = () =>
|
||||
import("./dialog-zwave_js-heal-network");
|
||||
|
||||
export const showZWaveJSHealNetworkDialog = (
|
||||
element: HTMLElement,
|
||||
healNetworkDialogParams: ZWaveJSHealNetworkDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-zwave_js-heal-network",
|
||||
dialogImport: loadHealNetworkDialog,
|
||||
dialogParams: healNetworkDialogParams,
|
||||
});
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import { mdiCheckCircle, mdiCircle, mdiRefresh } from "@mdi/js";
|
||||
import { mdiAlertCircle, mdiCheckCircle, mdiCircle, mdiRefresh } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@ -17,6 +17,11 @@ import {
|
||||
ZWaveJSNetwork,
|
||||
ZWaveJSNode,
|
||||
} from "../../../../../data/zwave_js";
|
||||
import {
|
||||
ConfigEntry,
|
||||
getConfigEntries,
|
||||
ERROR_STATES,
|
||||
} from "../../../../../data/config_entries";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@ -27,8 +32,10 @@ import type { HomeAssistant, Route } from "../../../../../types";
|
||||
import { fileDownload } from "../../../../../util/file_download";
|
||||
import "../../../ha-config-section";
|
||||
import { showZWaveJSAddNodeDialog } from "./show-dialog-zwave_js-add-node";
|
||||
import { showZWaveJSHealNetworkDialog } from "./show-dialog-zwave_js-heal-network";
|
||||
import { showZWaveJSRemoveNodeDialog } from "./show-dialog-zwave_js-remove-node";
|
||||
import { configTabs } from "./zwave_js-config-router";
|
||||
import { showOptionsFlowDialog } from "../../../../../dialogs/config-flow/show-dialog-options-flow";
|
||||
|
||||
@customElement("zwave_js-config-dashboard")
|
||||
class ZWaveJSConfigDashboard extends LitElement {
|
||||
@ -42,6 +49,8 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
|
||||
@property() public configEntryId?: string;
|
||||
|
||||
@state() private _configEntry?: ConfigEntry;
|
||||
|
||||
@state() private _network?: ZWaveJSNetwork;
|
||||
|
||||
@state() private _nodes?: ZWaveJSNode[];
|
||||
@ -59,6 +68,14 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._configEntry) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (ERROR_STATES.includes(this._configEntry.state)) {
|
||||
return this._renderErrorScreen();
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
@ -162,6 +179,16 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
"ui.panel.config.zwave_js.common.remove_node"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._healNetworkClicked}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.heal_network"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._openOptionFlow}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.common.reconfigure_server"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
@ -209,10 +236,83 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderErrorScreen() {
|
||||
const item = this._configEntry!;
|
||||
let stateText: [string, ...unknown[]] | undefined;
|
||||
let stateTextExtra: TemplateResult | string | undefined;
|
||||
|
||||
if (item.disabled_by) {
|
||||
stateText = [
|
||||
"ui.panel.config.integrations.config_entry.disable.disabled_cause",
|
||||
{
|
||||
cause:
|
||||
this.hass.localize(
|
||||
`ui.panel.config.integrations.config_entry.disable.disabled_by.${item.disabled_by}`
|
||||
) || item.disabled_by,
|
||||
},
|
||||
];
|
||||
if (item.state === "failed_unload") {
|
||||
stateTextExtra = html`.
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.disable_restart_confirm"
|
||||
)}.`;
|
||||
}
|
||||
} else if (item.state === "not_loaded") {
|
||||
stateText = ["ui.panel.config.integrations.config_entry.not_loaded"];
|
||||
} else if (ERROR_STATES.includes(item.state)) {
|
||||
stateText = [
|
||||
`ui.panel.config.integrations.config_entry.state.${item.state}`,
|
||||
];
|
||||
if (item.reason) {
|
||||
this.hass.loadBackendTranslation("config", item.domain);
|
||||
stateTextExtra = html` ${this.hass.localize(
|
||||
`component.${item.domain}.config.error.${item.reason}`
|
||||
) || item.reason}`;
|
||||
} else {
|
||||
stateTextExtra = html`
|
||||
<br />
|
||||
<a href="/config/logs"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.check_the_logs"
|
||||
)}</a
|
||||
>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
return html` ${stateText
|
||||
? html`
|
||||
<div class="error-message">
|
||||
<ha-svg-icon .path=${mdiAlertCircle}></ha-svg-icon>
|
||||
<h3>
|
||||
${this._configEntry!.title}: ${this.hass.localize(...stateText)}
|
||||
</h3>
|
||||
<p>${stateTextExtra}</p>
|
||||
<mwc-button @click=${this._handleBack}>
|
||||
${this.hass?.localize("ui.panel.error.go_back") || "go back"}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}`;
|
||||
}
|
||||
|
||||
private _handleBack(): void {
|
||||
history.back();
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
if (!this.configEntryId) {
|
||||
return;
|
||||
}
|
||||
const configEntries = await getConfigEntries(this.hass);
|
||||
this._configEntry = configEntries.find(
|
||||
(entry) => entry.entry_id === this.configEntryId!
|
||||
);
|
||||
|
||||
if (ERROR_STATES.includes(this._configEntry!.state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [network, dataCollectionStatus] = await Promise.all([
|
||||
fetchNetworkStatus(this.hass!, this.configEntryId),
|
||||
fetchDataCollectionStatus(this.hass!, this.configEntryId),
|
||||
@ -254,6 +354,12 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _healNetworkClicked() {
|
||||
showZWaveJSHealNetworkDialog(this, {
|
||||
entry_id: this.configEntryId!,
|
||||
});
|
||||
}
|
||||
|
||||
private _dataCollectionToggled(ev) {
|
||||
setDataCollectionPreference(
|
||||
this.hass!,
|
||||
@ -262,6 +368,17 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
private async _openOptionFlow() {
|
||||
if (!this.configEntryId) {
|
||||
return;
|
||||
}
|
||||
const configEntries = await getConfigEntries(this.hass);
|
||||
const configEntry = configEntries.find(
|
||||
(entry) => entry.entry_id === this.configEntryId
|
||||
);
|
||||
showOptionsFlowDialog(this, configEntry!);
|
||||
}
|
||||
|
||||
private async _dumpDebugClicked() {
|
||||
await this._fetchNodeStatus();
|
||||
|
||||
@ -333,6 +450,27 @@ class ZWaveJSConfigDashboard extends LitElement {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: flex;
|
||||
color: var(--primary-text-color);
|
||||
height: calc(100% - var(--header-height));
|
||||
padding: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.error-message h3 {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.error-message ha-svg-icon {
|
||||
color: var(--error-color);
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
@ -33,16 +33,20 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
||||
|
||||
public hassSubscribe(): Array<UnsubscribeFunc | Promise<UnsubscribeFunc>> {
|
||||
return [
|
||||
subscribeZWaveJSLogs(this.hass, this.configEntryId, (log) => {
|
||||
subscribeZWaveJSLogs(this.hass, this.configEntryId, (update) => {
|
||||
if (!this.hasUpdated) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(log.message)) {
|
||||
for (const line of log.message) {
|
||||
this._textarea!.value += `${line}\n`;
|
||||
if (update.type === "log_message") {
|
||||
if (Array.isArray(update.log_message.message)) {
|
||||
for (const line of update.log_message.message) {
|
||||
this._textarea!.value += `${line}\n`;
|
||||
}
|
||||
} else {
|
||||
this._textarea!.value += `${update.log_message.message}\n`;
|
||||
}
|
||||
} else {
|
||||
this._textarea!.value += `${log.message}\n`;
|
||||
this._logConfig = update.log_config;
|
||||
}
|
||||
}).then((unsub) => {
|
||||
this._textarea!.value += `${this.hass.localize(
|
||||
@ -141,7 +145,6 @@ class ZWaveJSLogs extends SubscribeMixin(LitElement) {
|
||||
return;
|
||||
}
|
||||
setZWaveJSLogLevel(this.hass!, this.configEntryId, selected);
|
||||
this._logConfig.level = selected;
|
||||
this._textarea!.value += `${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.logs.log_level_changed",
|
||||
{ level: selected.charAt(0).toUpperCase() + selected.slice(1) }
|
||||
|
@ -8,7 +8,7 @@ import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-date-range-picker";
|
||||
import type { DateRangePickerRanges } from "../../components/ha-date-range-picker";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/state-history-charts";
|
||||
import "../../components/chart/state-history-charts";
|
||||
import { computeHistory, fetchDate } from "../../data/history";
|
||||
import "../../layouts/ha-app-layout";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
|
@ -10,7 +10,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { throttle } from "../../../common/util/throttle";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/state-history-charts";
|
||||
import "../../../components/chart/state-history-charts";
|
||||
import { CacheConfig, getRecentWithCache } from "../../../data/cached-history";
|
||||
import { HistoryResult } from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -139,8 +139,8 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard {
|
||||
.isLoadingData=${!this._stateHistory}
|
||||
.historyData=${this._stateHistory}
|
||||
.names=${this._names}
|
||||
.upToNow=${true}
|
||||
.noSingle=${true}
|
||||
up-to-now
|
||||
no-single
|
||||
></state-history-charts>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@ -244,7 +244,7 @@ export class HuiLogbookCard extends LitElement implements LovelaceCard {
|
||||
let newEntries;
|
||||
|
||||
try {
|
||||
newEntries = await Promise.all([
|
||||
[newEntries] = await Promise.all([
|
||||
getLogbookData(
|
||||
this.hass,
|
||||
lastDate.toISOString(),
|
||||
|
@ -25,23 +25,10 @@ import "../../../components/map/ha-map";
|
||||
import { mdiImageFilterCenterFocus } from "@mdi/js";
|
||||
import type { HaMap, HaMapPaths } from "../../../components/map/ha-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { getColorByIndex } from "../../../common/color/colors";
|
||||
|
||||
const MINUTE = 60000;
|
||||
|
||||
const COLORS = [
|
||||
"#0288D1",
|
||||
"#00AA00",
|
||||
"#984ea3",
|
||||
"#00d2d5",
|
||||
"#ff7f00",
|
||||
"#af8d00",
|
||||
"#7f80cd",
|
||||
"#b3e900",
|
||||
"#c42e60",
|
||||
"#a65628",
|
||||
"#f781bf",
|
||||
"#8dd3c7",
|
||||
];
|
||||
@customElement("hui-map-card")
|
||||
class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -225,7 +212,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
if (color) {
|
||||
return color;
|
||||
}
|
||||
color = COLORS[this._colorIndex % COLORS.length];
|
||||
color = getColorByIndex(this._colorIndex);
|
||||
this._colorIndex++;
|
||||
this._colorDict[entityId] = color;
|
||||
return color;
|
||||
|
@ -447,47 +447,37 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
--name-font-size: 1.2rem;
|
||||
--brightness-font-size: 1.2rem;
|
||||
--rail-border-color: transparent;
|
||||
--auto-color: green;
|
||||
--eco-color: springgreen;
|
||||
--cool-color: #2b9af9;
|
||||
--heat-color: #ff8100;
|
||||
--manual-color: #44739e;
|
||||
--off-color: #8a8a8a;
|
||||
--fan_only-color: #8a8a8a;
|
||||
--dry-color: #efbd07;
|
||||
--idle-color: #8a8a8a;
|
||||
--unknown-color: #bac;
|
||||
}
|
||||
.auto,
|
||||
.heat_cool {
|
||||
--mode-color: var(--auto-color);
|
||||
--mode-color: var(--state-climate-auto-color);
|
||||
}
|
||||
.cool {
|
||||
--mode-color: var(--cool-color);
|
||||
--mode-color: var(--state-climate-cool-color);
|
||||
}
|
||||
.heat {
|
||||
--mode-color: var(--heat-color);
|
||||
--mode-color: var(--state-climate-heat-color);
|
||||
}
|
||||
.manual {
|
||||
--mode-color: var(--manual-color);
|
||||
--mode-color: var(--state-climate-manual-color);
|
||||
}
|
||||
.off {
|
||||
--mode-color: var(--off-color);
|
||||
--mode-color: var(--state-climate-off-color);
|
||||
}
|
||||
.fan_only {
|
||||
--mode-color: var(--fan_only-color);
|
||||
--mode-color: var(--state-climate-fan_only-color);
|
||||
}
|
||||
.eco {
|
||||
--mode-color: var(--eco-color);
|
||||
--mode-color: var(--state-climate-eco-color);
|
||||
}
|
||||
.dry {
|
||||
--mode-color: var(--dry-color);
|
||||
--mode-color: var(--state-climate-dry-color);
|
||||
}
|
||||
.idle {
|
||||
--mode-color: var(--idle-color);
|
||||
--mode-color: var(--state-climate-idle-color);
|
||||
}
|
||||
.unknown-mode {
|
||||
--mode-color: var(--unknown-color);
|
||||
--mode-color: var(--state-unknown-color);
|
||||
}
|
||||
|
||||
.more-info {
|
||||
|
@ -37,6 +37,7 @@ const LAZY_LOAD_TYPES = {
|
||||
"input-text-entity": () => import("../entity-rows/hui-input-text-entity-row"),
|
||||
"lock-entity": () => import("../entity-rows/hui-lock-entity-row"),
|
||||
"number-entity": () => import("../entity-rows/hui-number-entity-row"),
|
||||
"select-entity": () => import("../entity-rows/hui-select-entity-row"),
|
||||
"timer-entity": () => import("../entity-rows/hui-timer-entity-row"),
|
||||
conditional: () => import("../special-rows/hui-conditional-row"),
|
||||
"weather-entity": () => import("../entity-rows/hui-weather-entity-row"),
|
||||
@ -68,6 +69,7 @@ const DOMAIN_TO_ELEMENT_TYPE = {
|
||||
remote: "toggle",
|
||||
scene: "scene",
|
||||
script: "script",
|
||||
select: "select",
|
||||
sensor: "sensor",
|
||||
timer: "timer",
|
||||
switch: "toggle",
|
||||
|
@ -81,6 +81,7 @@ export class HuiGraphFooterEditor
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._hours_to_show}
|
||||
min="1"
|
||||
.configValue=${"hours_to_show"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
|
@ -79,6 +79,7 @@ export class HuiHistoryGraphCardEditor
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value="${this._hours_to_show}"
|
||||
min="1"
|
||||
.configValue=${"hours_to_show"}
|
||||
@value-changed="${this._valueChanged}"
|
||||
></paper-input>
|
||||
|
@ -85,6 +85,7 @@ export class HuiLogbookCardEditor
|
||||
"ui.panel.lovelace.editor.card.config.optional"
|
||||
)})"
|
||||
.value=${this._hours_to_show}
|
||||
min="1"
|
||||
.configValue=${"hours_to_show"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
|
@ -177,6 +177,7 @@ export class HuiSensorCardEditor
|
||||
)})"
|
||||
type="number"
|
||||
.value=${this._hours_to_show}
|
||||
min="1"
|
||||
.configValue=${"hours_to_show"}
|
||||
@value-changed=${this._valueChanged}
|
||||
></paper-input>
|
||||
|
@ -11,7 +11,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-slider";
|
||||
import { UNAVAILABLE_STATES } from "../../../data/entity";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { setValue } from "../../../data/input_text";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
@ -75,7 +75,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
|
||||
? html`
|
||||
<div class="flex">
|
||||
<ha-slider
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
.dir=${computeRTLDirection(this.hass)}
|
||||
.step="${Number(stateObj.attributes.step)}"
|
||||
.min="${Number(stateObj.attributes.min)}"
|
||||
@ -101,7 +101,7 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
|
||||
<paper-input
|
||||
no-label-float
|
||||
auto-validate
|
||||
.disabled=${UNAVAILABLE_STATES.includes(stateObj.state)}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
.step="${Number(stateObj.attributes.step)}"
|
||||
.min="${Number(stateObj.attributes.min)}"
|
||||
|
186
src/panels/lovelace/entity-rows/hui-select-entity-row.ts
Normal file
186
src/panels/lovelace/entity-rows/hui-select-entity-row.ts
Normal file
@ -0,0 +1,186 @@
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { DOMAINS_HIDE_MORE_INFO } from "../../../common/const";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/entity/state-badge";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { SelectEntity, setSelectOption } from "../../../data/select";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { EntitiesCardEntityConfig } from "../cards/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import { LovelaceRow } from "./types";
|
||||
|
||||
@customElement("hui-select-entity-row")
|
||||
class HuiSelectEntityRow extends LitElement implements LovelaceRow {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: EntitiesCardEntityConfig;
|
||||
|
||||
public setConfig(config: EntitiesCardEntityConfig): void {
|
||||
if (!config || !config.entity) {
|
||||
throw new Error("Entity must be specified");
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
return hasConfigOrEntityChanged(this, changedProps);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity] as
|
||||
| SelectEntity
|
||||
| undefined;
|
||||
|
||||
if (!stateObj) {
|
||||
return html`
|
||||
<hui-warning>
|
||||
${createEntityNotFoundWarning(this.hass, this._config.entity)}
|
||||
</hui-warning>
|
||||
`;
|
||||
}
|
||||
|
||||
const pointer =
|
||||
(this._config.tap_action && this._config.tap_action.action !== "none") ||
|
||||
(this._config.entity &&
|
||||
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this._config.entity)));
|
||||
|
||||
return html`
|
||||
<state-badge
|
||||
.stateObj=${stateObj}
|
||||
.overrideIcon=${this._config.icon}
|
||||
.overrideImage=${this._config.image}
|
||||
class=${classMap({
|
||||
pointer,
|
||||
})}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
tabindex=${ifDefined(pointer ? "0" : undefined)}
|
||||
></state-badge>
|
||||
<ha-paper-dropdown-menu
|
||||
.label=${this._config.name || computeStateName(stateObj)}
|
||||
.disabled=${stateObj.state === UNAVAILABLE}
|
||||
@iron-select=${this._selectedChanged}
|
||||
@click=${stopPropagation}
|
||||
>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${stateObj.attributes.options
|
||||
? stateObj.attributes.options.map(
|
||||
(option) =>
|
||||
html`
|
||||
<paper-item .option=${option}
|
||||
>${(stateObj.attributes.device_class &&
|
||||
this.hass!.localize(
|
||||
`component.select.state.${stateObj.attributes.device_class}.${option}`
|
||||
)) ||
|
||||
this.hass!.localize(
|
||||
`component.select.state._.${option}`
|
||||
) ||
|
||||
option}</paper-item
|
||||
>
|
||||
`
|
||||
)
|
||||
: ""}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!this.hass || !this._config) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity] as
|
||||
| SelectEntity
|
||||
| undefined;
|
||||
|
||||
if (!stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update selected after rendering the items or else it won't work in Firefox
|
||||
if (stateObj.attributes.options) {
|
||||
this.shadowRoot!.querySelector(
|
||||
"paper-listbox"
|
||||
)!.selected = stateObj.attributes.options.indexOf(stateObj.state);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
margin-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
state-badge:focus {
|
||||
outline: none;
|
||||
background: var(--divider-color);
|
||||
border-radius: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectedChanged(ev): void {
|
||||
const stateObj = this.hass!.states[this._config!.entity];
|
||||
const option = ev.target.selectedItem.option;
|
||||
if (option === stateObj.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
forwardHaptic("light");
|
||||
|
||||
setSelectOption(this.hass!, stateObj.entity_id, option);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-select-entity-row": HuiSelectEntityRow;
|
||||
}
|
||||
}
|
35
src/resources/chartjs.ts
Normal file
35
src/resources/chartjs.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import {
|
||||
LineController,
|
||||
TimeScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Filler,
|
||||
Legend,
|
||||
Title,
|
||||
Tooltip,
|
||||
CategoryScale,
|
||||
Chart,
|
||||
} from "chart.js";
|
||||
import { TextBarElement } from "../components/chart/timeline-chart/textbar-element";
|
||||
import { TimelineController } from "../components/chart/timeline-chart/timeline-controller";
|
||||
import { TimeLineScale } from "../components/chart/timeline-chart/timeline-scale";
|
||||
import "../components/chart/chart-date-adapter";
|
||||
|
||||
export { Chart } from "chart.js";
|
||||
|
||||
Chart.register(
|
||||
Tooltip,
|
||||
Title,
|
||||
Legend,
|
||||
Filler,
|
||||
TimeScale,
|
||||
LinearScale,
|
||||
LineController,
|
||||
PointElement,
|
||||
LineElement,
|
||||
TextBarElement,
|
||||
TimeLineScale,
|
||||
TimelineController,
|
||||
CategoryScale
|
||||
);
|
@ -1,60 +0,0 @@
|
||||
import Chart from "chart.js";
|
||||
import "chartjs-chart-timeline";
|
||||
|
||||
// This function add a new interaction mode to Chart.js that
|
||||
// returns one point for every dataset.
|
||||
Chart.Interaction.modes.neareach = function (chart, e, options) {
|
||||
const getRange = {
|
||||
x: (a, b) => Math.abs(a.x - b.x),
|
||||
y: (a, b) => Math.abs(a.y - b.y),
|
||||
// eslint-disable-next-line no-restricted-properties
|
||||
xy: (a, b) => Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2),
|
||||
};
|
||||
const getRangeMax = {
|
||||
x: (r) => r,
|
||||
y: (r) => r,
|
||||
xy: (r) => r * r,
|
||||
};
|
||||
let position;
|
||||
if (e.native) {
|
||||
position = {
|
||||
x: e.x,
|
||||
y: e.y,
|
||||
};
|
||||
} else {
|
||||
position = Chart.helpers.getRelativePosition(e, chart);
|
||||
}
|
||||
const elements = [];
|
||||
const elementsRange = [];
|
||||
const datasets = chart.data.datasets;
|
||||
let meta;
|
||||
options.axis = options.axis || "xy";
|
||||
const rangeFunc = getRange[options.axis];
|
||||
const rangeMaxFunc = getRangeMax[options.axis];
|
||||
|
||||
for (let i = 0, ilen = datasets.length; i < ilen; ++i) {
|
||||
if (!chart.isDatasetVisible(i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
meta = chart.getDatasetMeta(i);
|
||||
for (let j = 0, jlen = meta.data.length; j < jlen; ++j) {
|
||||
const element = meta.data[j];
|
||||
if (!element._view.skip) {
|
||||
const vm = element._view;
|
||||
const range = rangeFunc(vm, position);
|
||||
const oldRange = elementsRange[i];
|
||||
if (range < rangeMaxFunc(vm.radius + vm.hitRadius)) {
|
||||
if (oldRange === undefined || oldRange > range) {
|
||||
elementsRange[i] = range;
|
||||
elements[i] = element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const ret = elements.filter((n) => n !== undefined);
|
||||
return ret;
|
||||
};
|
||||
|
||||
export default Chart;
|
@ -42,10 +42,6 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--success-color: #0f9d58;
|
||||
--info-color: #4285f4;
|
||||
|
||||
/* states and badges */
|
||||
--state-icon-color: #44739e;
|
||||
--state-icon-active-color: #FDD835;
|
||||
|
||||
/* background and sidebar */
|
||||
--card-background-color: #ffffff;
|
||||
--primary-background-color: #fafafa;
|
||||
@ -60,6 +56,32 @@ documentContainer.innerHTML = `<custom-style>
|
||||
--label-badge-green: #0DA035;
|
||||
--label-badge-yellow: #f4b400;
|
||||
|
||||
/* states and badges */
|
||||
--state-icon-color: #44739e;
|
||||
/* an active state is anything that would require attention */
|
||||
--state-icon-active-color: #FDD835;
|
||||
/* an error state is anything that would be considered an error */
|
||||
/* --state-icon-error-color: #db4437; derived from error-color */
|
||||
|
||||
--state-on-color: #66a61e;
|
||||
--state-off-color: #ff0029;
|
||||
--state-home-color: #66a61e;
|
||||
--state-not_home-color: #ff0029;
|
||||
/* --state-unavailable-color: #a0a0a0; derived from disabled-text-color */
|
||||
--state-unknown-color: #606060;
|
||||
--state-idle-color: #377eb8;
|
||||
|
||||
/* climate state colors */
|
||||
--state-climate-auto-color: #008000;
|
||||
--state-climate-eco-color: #00ff7f;
|
||||
--state-climate-cool-color: #2b9af9;
|
||||
--state-climate-heat-color: #ff8100;
|
||||
--state-climate-manual-color: #44739e;
|
||||
--state-climate-off-color: #8a8a8a;
|
||||
--state-climate-fan_only-color: #8a8a8a;
|
||||
--state-climate-dry-color: #efbd07;
|
||||
--state-climate-idle-color: #8a8a8a;
|
||||
|
||||
/*
|
||||
Paper-styles color.html dependency is stripped on build.
|
||||
When a default paper-style color is used, it needs to be copied
|
||||
|
@ -34,8 +34,9 @@ export const darkStyles = {
|
||||
};
|
||||
|
||||
export const derivedStyles = {
|
||||
"error-state-color": "var(--error-color)",
|
||||
"state-icon-unavailable-color": "var(--disabled-text-color)",
|
||||
"state-icon-error-color": "var(--error-state-color, var(--error-color))",
|
||||
"state-unavailable-color":
|
||||
"var(--state-icon-unavailable-color, var(--disabled-text-color))",
|
||||
"sidebar-text-color": "var(--primary-text-color)",
|
||||
"sidebar-background-color": "var(--card-background-color)",
|
||||
"sidebar-selected-text-color": "var(--primary-color)",
|
||||
|
@ -14,6 +14,7 @@ import "./state-card-media_player";
|
||||
import "./state-card-number";
|
||||
import "./state-card-scene";
|
||||
import "./state-card-script";
|
||||
import "./state-card-select";
|
||||
import "./state-card-timer";
|
||||
import "./state-card-toggle";
|
||||
import "./state-card-vacuum";
|
||||
|
99
src/state-summary/state-card-select.ts
Normal file
99
src/state-summary/state-card-select.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import "../components/entity/state-badge";
|
||||
import { SelectEntity, setSelectOption } from "../data/select";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("state-card-select")
|
||||
class StateCardSelect extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj!: SelectEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<state-badge .stateObj=${this.stateObj}></state-badge>
|
||||
<paper-dropdown-menu-light
|
||||
.label=${computeStateName(this.stateObj)}
|
||||
@iron-select=${this._selectedOptionChanged}
|
||||
@click=${stopPropagation}
|
||||
>
|
||||
<paper-listbox slot="dropdown-content">
|
||||
${this.stateObj.attributes.options.map(
|
||||
(option) =>
|
||||
html`
|
||||
<paper-item .option=${option}
|
||||
>${(this.stateObj.attributes.device_class &&
|
||||
this.hass.localize(
|
||||
`component.select.state.${this.stateObj.attributes.device_class}.${option}`
|
||||
)) ||
|
||||
this.hass.localize(`component.select.state._.${option}`) ||
|
||||
option}</paper-item
|
||||
>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!changedProps.has("stateObj")) {
|
||||
return;
|
||||
}
|
||||
// Update selected after rendering the items or else it won't work in Firefox
|
||||
this.shadowRoot!.querySelector(
|
||||
"paper-listbox"
|
||||
)!.selected = this.stateObj.attributes.options.indexOf(this.stateObj.state);
|
||||
}
|
||||
|
||||
private _selectedOptionChanged(ev) {
|
||||
const option = ev.target.selectedItem.option;
|
||||
if (option === this.stateObj.state) {
|
||||
return;
|
||||
}
|
||||
setSelectOption(this.hass, this.stateObj.entity_id, option);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
state-badge {
|
||||
float: left;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
paper-dropdown-menu-light {
|
||||
display: block;
|
||||
margin-left: 53px;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
min-width: 200px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"state-card-select": StateCardSelect;
|
||||
}
|
||||
}
|
@ -2121,7 +2121,7 @@
|
||||
"confirm_new": "Do you want to set up {integration}?",
|
||||
"add_integration": "Add integration",
|
||||
"no_integrations": "Seems like you don't have any integrations configured yet. Click on the button below to add your first integration!",
|
||||
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
||||
"note_about_integrations": "No integrations matched your search, the integration you want to set up might not be available to set up via the UI yet.",
|
||||
"note_about_website_reference": "More are available on the ",
|
||||
"home_assistant_website": "Home Assistant website",
|
||||
"configure": "Configure",
|
||||
@ -2587,7 +2587,9 @@
|
||||
"home_id": "Home ID",
|
||||
"close": "Close",
|
||||
"add_node": "Add Node",
|
||||
"remove_node": "Remove Node"
|
||||
"remove_node": "Remove Node",
|
||||
"reconfigure_server": "Re-configure Server",
|
||||
"heal_network": "Heal Network"
|
||||
},
|
||||
"dashboard": {
|
||||
"header": "Manage your Z-Wave Network",
|
||||
@ -2669,6 +2671,18 @@
|
||||
"interview_failed": "The device interview failed. Additional information may be available in the logs.",
|
||||
"interview_complete": "Device interview complete."
|
||||
},
|
||||
"heal_network": {
|
||||
"title": "Heal your Z-Wave Network",
|
||||
"introduction": "Start a network heal on your Z-Wave network. A network heal will cause all devices to re-calculate their routes back to the controller and is recommended if you have recently moved devices or your controller.",
|
||||
"traffic_warning": "The healing process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the heal is in progress.",
|
||||
"start_heal": "Start Healing",
|
||||
"in_progress": "Network healing is in progress. This will take some time.",
|
||||
"run_in_background": "You can close this dialog and the network healing will continue in the background.",
|
||||
"stop_heal": "Stop Healing",
|
||||
"healing_complete": "Network healing is complete.",
|
||||
"healing_failed": "Healing failed. Additional information may be available in the logs.",
|
||||
"healing_cancelled": "Network healing has been cancelled."
|
||||
},
|
||||
"logs": {
|
||||
"title": "Z-Wave JS Logs",
|
||||
"log_level": "Log Level",
|
||||
|
@ -236,6 +236,98 @@ describe("computeStateDisplay", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Localizes input_datetime state parameter with full date time", () => {
|
||||
const stateObj: any = {
|
||||
entity_id: "input_datetime.test",
|
||||
state: "123",
|
||||
attributes: {
|
||||
has_date: true,
|
||||
has_time: true,
|
||||
year: 2021,
|
||||
month: 6,
|
||||
day: 13,
|
||||
hour: 15,
|
||||
minute: 26,
|
||||
second: 36,
|
||||
},
|
||||
};
|
||||
it("Uses am/pm time format", () => {
|
||||
assert.strictEqual(
|
||||
computeStateDisplay(
|
||||
localize,
|
||||
stateObj,
|
||||
localeData,
|
||||
"2021-07-04 15:40:03"
|
||||
),
|
||||
"July 4, 2021, 3:40 PM"
|
||||
);
|
||||
});
|
||||
it("Uses 24h time format", () => {
|
||||
localeData.time_format = TimeFormat.twenty_four;
|
||||
assert.strictEqual(
|
||||
computeStateDisplay(
|
||||
localize,
|
||||
stateObj,
|
||||
localeData,
|
||||
"2021-07-04 15:40:03"
|
||||
),
|
||||
"July 4, 2021, 15:40"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("Localizes input_datetime state parameter with date", () => {
|
||||
const stateObj: any = {
|
||||
entity_id: "input_datetime.test",
|
||||
state: "123",
|
||||
attributes: {
|
||||
has_date: true,
|
||||
has_time: false,
|
||||
year: 2021,
|
||||
month: 6,
|
||||
day: 13,
|
||||
hour: 15,
|
||||
minute: 26,
|
||||
second: 36,
|
||||
},
|
||||
};
|
||||
assert.strictEqual(
|
||||
computeStateDisplay(localize, stateObj, localeData, "2021-07-04"),
|
||||
"July 4, 2021"
|
||||
);
|
||||
});
|
||||
|
||||
describe("Localizes input_datetime state parameter with time", () => {
|
||||
const stateObj: any = {
|
||||
entity_id: "input_datetime.test",
|
||||
state: "123",
|
||||
attributes: {
|
||||
has_date: false,
|
||||
has_time: true,
|
||||
year: 2021,
|
||||
month: 6,
|
||||
day: 13,
|
||||
hour: 15,
|
||||
minute: 26,
|
||||
second: 36,
|
||||
},
|
||||
};
|
||||
it("Uses am/pm time format", () => {
|
||||
localeData.time_format = TimeFormat.am_pm;
|
||||
assert.strictEqual(
|
||||
computeStateDisplay(localize, stateObj, localeData, "17:05:07"),
|
||||
"5:05 PM"
|
||||
);
|
||||
});
|
||||
it("Uses 24h time format", () => {
|
||||
localeData.time_format = TimeFormat.twenty_four;
|
||||
assert.strictEqual(
|
||||
computeStateDisplay(localize, stateObj, localeData, "17:05:07"),
|
||||
"17:05"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("Localizes unavailable", () => {
|
||||
const altLocalize = (message, ...args) => {
|
||||
if (message === "state.sensor.unavailable") {
|
||||
|
@ -344,7 +344,7 @@
|
||||
"error": "Възникна неизвестна грешка",
|
||||
"error_addon_not_found": "Не е намерена добавка",
|
||||
"error_addon_not_installed": "Исканата добавка не е инсталирана. Моля, първо я инсталирайте",
|
||||
"error_addon_not_started": "Заявените добавки не е стартирана. Моля, първо я стартирайте",
|
||||
"error_addon_not_started": "Заявената добавка не е стартирана. Моля, първо я стартирайте",
|
||||
"faq_link": "My Home Assistant ЧЗВ",
|
||||
"not_supported": "Тази препратка не се поддържа от вашата Home Assistant инсталация. Последвайте {link} за поддържани препратки както и версиите при тяхното пускане."
|
||||
},
|
||||
@ -1052,6 +1052,7 @@
|
||||
"areas": "Области",
|
||||
"automation": "Автоматизации",
|
||||
"blueprint": "Планове",
|
||||
"core": "Общи",
|
||||
"customize": "Персонализации",
|
||||
"devices": "Устройства",
|
||||
"entities": "Обекти",
|
||||
@ -1084,6 +1085,7 @@
|
||||
"rpi_gpio": "Raspberry Pi GPIO обекти",
|
||||
"scene": "Презареждане на сцените",
|
||||
"script": "Презареждане на скриптовете",
|
||||
"smtp": "SMTP услуги за уведомяване",
|
||||
"telegram": "Презареждане на Telegram услугите за нотификация",
|
||||
"zone": "Презареждане на зоните"
|
||||
},
|
||||
@ -1111,7 +1113,7 @@
|
||||
"buttons": {
|
||||
"add": "Добавете устройства чрез това устройство",
|
||||
"clusters": "Управление на клъстери",
|
||||
"device_children": "Преглед на дъщерни",
|
||||
"device_children": "Преглед на дъщерните устройства",
|
||||
"reconfigure": "Преконфигуриране на устройството",
|
||||
"remove": "Премахване на устройството",
|
||||
"zigbee_information": "Подпис на Zigbee устройството"
|
||||
@ -1215,6 +1217,7 @@
|
||||
},
|
||||
"description": "Групирайте устройствата и обектите в области",
|
||||
"editor": {
|
||||
"area_id": "ID на област",
|
||||
"create": "Създаване",
|
||||
"default_name": "Нова област",
|
||||
"delete": "Изтриване",
|
||||
@ -2239,6 +2242,7 @@
|
||||
"common": {
|
||||
"controller": "Контролер",
|
||||
"network": "Мрежа",
|
||||
"node_id": "ID на възел",
|
||||
"query_stage": "Етап на заявка",
|
||||
"wakeup_instructions": "Инструкции за събуждане",
|
||||
"zwave": "Z-Wave"
|
||||
@ -2246,6 +2250,7 @@
|
||||
"navigation": {
|
||||
"network": "Мрежа",
|
||||
"node": {
|
||||
"config": "Конфигуриране",
|
||||
"dashboard": "Табло"
|
||||
},
|
||||
"nodes": "Възли"
|
||||
@ -2266,6 +2271,9 @@
|
||||
"network": {
|
||||
"node_count": "{count} възли"
|
||||
},
|
||||
"node_config": {
|
||||
"header": "Конфигурация на възел"
|
||||
},
|
||||
"node_metadata": {
|
||||
"product_manual": "Ръководство за продукта"
|
||||
},
|
||||
@ -2290,7 +2298,8 @@
|
||||
"description": "Това ще накара OpenZWave да разпита дадено устройство и да актуализира командните му класове, възможностите и стойностите му.",
|
||||
"node_status": "Състояние на възела",
|
||||
"refreshing_description": "Опресняване на информацията за възела...",
|
||||
"step": "Стъпка"
|
||||
"step": "Стъпка",
|
||||
"wakeup_header": "Инструкции за събуждане за"
|
||||
},
|
||||
"select_instance": {
|
||||
"none_found": "Не можахме да намерим OpenZWave инстанция. Ако смятате че не е вярно, проверете Вашите OpenZWare и MQTT и се уверете. че Home Assistant може да комуникира със MQTT брокера."
|
||||
@ -2326,6 +2335,7 @@
|
||||
"learn_more": "Научете повече за хората",
|
||||
"no_persons_created_yet": "Изглежда все още не сте добавили хора.",
|
||||
"note_about_persons_configured_in_yaml": "Забележка: хората, конфигурирани чрез configuration.yaml, не могат да бъдат редактирани чрез потребителския интерфейс.",
|
||||
"person_not_found": "Не успяхме да открием лицето, което се опитвате да редактирате.",
|
||||
"person_not_found_title": "Не е намерено лице"
|
||||
},
|
||||
"scene": {
|
||||
@ -2621,6 +2631,7 @@
|
||||
"zwave_js": {
|
||||
"add_node": {
|
||||
"inclusion_failed": "Възелът не може да бъде добавен. Моля, проверете журналите за повече информация.",
|
||||
"inclusion_finished": "Възелът е добавен.",
|
||||
"interview_failed": "Разпита на устройството не бе успешен. Допълнителна информация може да е налична в логовете.",
|
||||
"interview_started": "Устройството се разпитва. Това може да отнеме известно време.",
|
||||
"title": "Добавяне на Z-Wave възел"
|
||||
@ -2945,6 +2956,7 @@
|
||||
"tilt-position": "Наклон"
|
||||
},
|
||||
"show_header_toggle": "Показване на превключване на заглавка?",
|
||||
"special_row": "специален ред",
|
||||
"toggle": "Превключване на обекти."
|
||||
},
|
||||
"entity-filter": {
|
||||
@ -3060,6 +3072,7 @@
|
||||
"cardpicker": {
|
||||
"by_card": "По карта",
|
||||
"by_entity": "По обект",
|
||||
"custom_card": "Персонализирана",
|
||||
"domain": "Домейн",
|
||||
"entity": "Обект",
|
||||
"no_description": "Няма налично описание."
|
||||
|
@ -558,9 +558,9 @@
|
||||
},
|
||||
"counter": {
|
||||
"actions": {
|
||||
"decrement": "decreixement",
|
||||
"increment": "increment",
|
||||
"reset": "restablir"
|
||||
"decrement": "decrementa",
|
||||
"increment": "incrementa",
|
||||
"reset": "restableix"
|
||||
}
|
||||
},
|
||||
"cover": {
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "s'ha desendollat",
|
||||
"was_unsafe": "és insegur"
|
||||
},
|
||||
"retrieval_error": "Error durant l'obtenció de l'entrada del diari de registre",
|
||||
"show_trace": "Mostra la traça"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "ID principal",
|
||||
"network": "Xarxa",
|
||||
"node_id": "ID del node",
|
||||
"reconfigure_server": "Torna a configurar el servidor",
|
||||
"remove_node": "Elimina node"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "bylo odpojeno",
|
||||
"was_unsafe": "bylo v nebezpečí"
|
||||
},
|
||||
"retrieval_error": "Chyba při načítání záznamu",
|
||||
"show_trace": "Zobrazit trasu"
|
||||
},
|
||||
"media-browser": {
|
||||
|
@ -200,7 +200,7 @@
|
||||
"manager": "Manager"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Add-ons können sich in einer von drei Phasen befinden:\n\n{icon_stable} **Stable**: Dies sind Add-ons, die bereits vollständig sind und Updates erhalten.\n\n{icon_experimental} **Experimentell**: Diese können Fehler enthalten und unvollendet sein.\n\n{icon_deprecated} **Veraltet**: Diese Add-ons erhalten keine Updates mehr.",
|
||||
"description": "Add-ons können sich in einer von drei Phasen befinden:\n\n{icon_stable} **Stabil**: Dies sind Add-ons, die bereits vollständig sind und Updates erhalten.\n\n{icon_experimental} **Experimentell**: Diese können Fehler enthalten und unvollendet sein.\n\n{icon_deprecated} **Veraltet**: Diese Add-ons erhalten keine Updates mehr.",
|
||||
"title": "Add-on Phase"
|
||||
}
|
||||
},
|
||||
@ -235,7 +235,7 @@
|
||||
}
|
||||
},
|
||||
"protection_mode": {
|
||||
"content": "Der Schutzmodus für dieses Add-on ist deaktiviert! Dadurch erhält das Add-on vollen Zugriff auf das gesamte System, was Sicherheitsrisiken mit sich bringt und dein System bei unsachgemäßer Verwendung beschädigen kann. Deaktiviere den Schutzmodus nur, wenn du die Quelle dieses Add-ons kennst, dieses benötigst UND vertraust.",
|
||||
"content": "Der Schutzmodus für dieses Add-on ist deaktiviert! Dadurch erhält das Add-on vollen Zugriff auf das gesamte System, was Sicherheitsrisiken mit sich bringt und dein System bei unsachgemäßer Verwendung beschädigen kann. Deaktiviere den Schutzmodus nur, wenn du dieses Add-on benötigst und die Quelle dieses Add-ons kennst UND ihr vertraust.",
|
||||
"enable": "Gesicherten Modus einschalten",
|
||||
"title": "Warnung: Der gesicherte Modus ist deaktiviert!"
|
||||
},
|
||||
@ -477,7 +477,7 @@
|
||||
"join_beta_description": "Erhalte Beta-Updates für Home Assistant (RCs), Supervisor und Host",
|
||||
"leave_beta_action": "Beta-Kanal verlassen",
|
||||
"leave_beta_description": "Erhalte stabile Updates für Home Assistant, Supervisor und Host",
|
||||
"ram_usage": "Arbeitsspeicherbedarf von Supervisor",
|
||||
"ram_usage": "Arbeitsspeicherbedarf des Supervisors",
|
||||
"reload_supervisor": "Supervisor neu laden",
|
||||
"search": "Suche",
|
||||
"share_diagnostics": "Diagnose teilen",
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "wurde ausgesteckt",
|
||||
"was_unsafe": "war unsicher"
|
||||
},
|
||||
"retrieval_error": "Fehler beim Abrufen von Logbucheinträgen",
|
||||
"show_trace": "Trace anzeigen"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -941,7 +942,7 @@
|
||||
},
|
||||
"dialogs": {
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Wenn deaktiviert werden neu erkannte Entitäten für {integration} nicht automatisch zu Home Assistant hinzugefügt.",
|
||||
"enable_new_entities_description": "Wenn aktiviert werden neu erkannte Entitäten für {integration} automatisch zu Home Assistant hinzugefügt.",
|
||||
"enable_new_entities_label": "Neu hinzugefügte Entitäten aktivieren.",
|
||||
"enable_polling_description": "Ob Home Assistant automatisch {integration} Entitäten nach Updates abfragen soll.",
|
||||
"enable_polling_label": "Aktiviere Polling für Updates.",
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "Heim-ID",
|
||||
"network": "Netzwerk",
|
||||
"node_id": "Knoten-ID",
|
||||
"reconfigure_server": "Server neu konfigurieren",
|
||||
"remove_node": "Knoten entfernen"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -377,7 +377,7 @@
|
||||
"error_addon_no_ingress": "The requested add-on does not support ingress",
|
||||
"error_addon_not_found": "Add-on not found",
|
||||
"error_addon_not_installed": "The requested add-on is not installed. Please install it first",
|
||||
"error_addon_not_started": "The requested add-on are not running. Please start it first",
|
||||
"error_addon_not_started": "The requested add-on is not running. Please start it first",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"not_supported": "This redirect is not supported by your Home Assistant instance. Check the {link} for the supported redirects and the version they where introduced."
|
||||
},
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "was unplugged",
|
||||
"was_unsafe": "was unsafe"
|
||||
},
|
||||
"retrieval_error": "Error during logbook entry retrieval",
|
||||
"show_trace": "Show trace"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2318,7 +2319,7 @@
|
||||
"none": "Nothing configured yet",
|
||||
"none_found": "No integrations found",
|
||||
"none_found_detail": "Adjust your search criteria.",
|
||||
"note_about_integrations": "Not all integrations can be configured via the UI yet.",
|
||||
"note_about_integrations": "No integrations matched your search, the integration you want to set up might not be available to set up via the UI yet.",
|
||||
"note_about_website_reference": "More are available on the ",
|
||||
"reconfigure": "Reconfigure",
|
||||
"rename_dialog": "Edit the name of this config entry",
|
||||
@ -2961,9 +2962,11 @@
|
||||
"common": {
|
||||
"add_node": "Add Node",
|
||||
"close": "Close",
|
||||
"heal_network": "Heal Network",
|
||||
"home_id": "Home ID",
|
||||
"network": "Network",
|
||||
"node_id": "Node ID",
|
||||
"reconfigure_server": "Re-configure Server",
|
||||
"remove_node": "Remove Node"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -2986,12 +2989,23 @@
|
||||
"node_status": "Node Status",
|
||||
"zwave_info": "Z-Wave Info"
|
||||
},
|
||||
"heal_network": {
|
||||
"healing_cancelled": "Network healing has been cancelled.",
|
||||
"healing_complete": "Network healing is complete.",
|
||||
"healing_failed": "Healing failed. Additional information may be available in the logs.",
|
||||
"in_progress": "Network healing is in progress. This will take some time.",
|
||||
"introduction": "Start a network heal on your Z-Wave network. A network heal will cause all devices to re-calculate their routes back to the controller and is recommended if you have recently moved devices or your controller.",
|
||||
"run_in_background": "You can close this dialog and the network healing will continue in the background.",
|
||||
"start_heal": "Start Healing",
|
||||
"stop_heal": "Stop Healing",
|
||||
"title": "Heal your Z-Wave Network",
|
||||
"traffic_warning": "The healing process generates a large amount of traffic on the Z-Wave network. This may cause devices to respond slowly (or not at all) while the heal is in progress."
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Log Level",
|
||||
"log_level_changed": "Log Level changed to: {level}",
|
||||
"subscribed_to_logs": "Subscribed to Z-Wave JS Log Messages...",
|
||||
"title": "Z-Wave JS Logs",
|
||||
"download_logs": "Download Logs"
|
||||
"title": "Z-Wave JS Logs"
|
||||
},
|
||||
"navigation": {
|
||||
"logs": "Logs",
|
||||
|
@ -197,7 +197,7 @@
|
||||
"backup": "copia de seguridad",
|
||||
"default": "predeterminado",
|
||||
"homeassistant": "homeassistant",
|
||||
"manager": "gerente"
|
||||
"manager": "administrador"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Los complementos pueden tener una de tres etapas: \n\n {icon_stable} ** Estable **: estos son complementos listos para usarse en producción. \n\n {icon_experimental} ** Experimental **: estos pueden contener errores y pueden estar inacabados. \n\n {icon_deprecated} ** Obsoleto **: estos complementos ya no recibirán actualizaciones.",
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "se desenchufó",
|
||||
"was_unsafe": "era inseguro"
|
||||
},
|
||||
"retrieval_error": "Error durante la recuperación de la entrada del libro de registro",
|
||||
"show_trace": "Mostrar traza"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2961,9 +2962,11 @@
|
||||
"common": {
|
||||
"add_node": "Añadir Nodo",
|
||||
"close": "Cerrar",
|
||||
"heal_network": "Sanar red",
|
||||
"home_id": "ID de casa",
|
||||
"network": "Red",
|
||||
"node_id": "ID del Nodo",
|
||||
"reconfigure_server": "Reconfigurar el servidor",
|
||||
"remove_node": "Eliminar Nodo"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -2986,6 +2989,18 @@
|
||||
"node_status": "Estado del Nodo",
|
||||
"zwave_info": "Información de Z-Wave"
|
||||
},
|
||||
"heal_network": {
|
||||
"healing_cancelled": "Se ha cancelado la curación en red.",
|
||||
"healing_complete": "La curación de la red se ha completado.",
|
||||
"healing_failed": "La curación falló. Puede haber información adicional disponible en los registros.",
|
||||
"in_progress": "La curación de la red está en curso. Esto llevará algún tiempo.",
|
||||
"introduction": "Inicia una cura de red en tu red Z-Wave. Una curación de la red hará que todos los dispositivos vuelvan a calcular sus rutas de regreso al controlador y se recomienda si has movido dispositivos o tu controlador recientemente.",
|
||||
"run_in_background": "Puedes cerrar este cuadro de diálogo y la curación de la red continuará en segundo plano.",
|
||||
"start_heal": "Iniciar la curación",
|
||||
"stop_heal": "Detener la curación",
|
||||
"title": "Sana tu red Z-Wave",
|
||||
"traffic_warning": "El proceso de curación genera una gran cantidad de tráfico en la red Z-Wave. Esto puede hacer que los dispositivos respondan lentamente (o no respondan en absoluto) mientras la curación está en curso."
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Nivel de registro",
|
||||
"log_level_changed": "Nivel de registro cambiado a: {level}",
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "eemaldati",
|
||||
"was_unsafe": "oli turvamata"
|
||||
},
|
||||
"retrieval_error": "Viga logiraamatu sissekande otsimisel",
|
||||
"show_trace": "Kuva täitmise samme"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2961,9 +2962,11 @@
|
||||
"common": {
|
||||
"add_node": "Lisa sõlm",
|
||||
"close": "Sulge",
|
||||
"heal_network": "Paranda võrgustikku",
|
||||
"home_id": "Kodu ID",
|
||||
"network": "Võrk",
|
||||
"node_id": "Sõlme ID",
|
||||
"reconfigure_server": "Taasseadista server",
|
||||
"remove_node": "Eemalda sõlm"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -2986,6 +2989,18 @@
|
||||
"node_status": "Sõlme olek",
|
||||
"zwave_info": "Z-Wave teave"
|
||||
},
|
||||
"heal_network": {
|
||||
"healing_cancelled": "Võrgu parandamine on tühistatud.",
|
||||
"healing_complete": "Võrgu parandamine on lõpule viidud.",
|
||||
"healing_failed": "Parandamine nurjus. Lisateave võib olla saadaval logides.",
|
||||
"in_progress": "Võrgu parandamine on pooleli. See võtab aega.",
|
||||
"introduction": "Alusta oma Z-Wave võrgu parandamist. Võrgu parandamine põhjustab kõigi seadmete marsruudi kontrollerisse tagasi arvutamise ja on soovitatav, kui oled hiljuti seadmeid või oma kontrollerit teisaldanud.",
|
||||
"run_in_background": "Saad selle dialoogi sulgeda ja võrgu parandamine jätkub taustal.",
|
||||
"start_heal": "Alusta parandamist",
|
||||
"stop_heal": "Peata parandamine",
|
||||
"title": "Paranda oma Z-Wave võrku",
|
||||
"traffic_warning": "Parandamissprotsess tekitab Z-Wave võrgus palju liiklust. See võib põhjustada seadmete aeglast reageerimise (või üldse mitte) kuni parandamine on pooleli."
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Logimise tase",
|
||||
"log_level_changed": "Silumisteave on muudetud: {level}",
|
||||
|
@ -318,6 +318,14 @@
|
||||
"no_addons": "Vous n'avez pas encore installé de modules complémentaires. Rendez-vous dans la boutique des modules complémentaires pour commencer!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attributs",
|
||||
"device_path": "Chemin du dispositif",
|
||||
"id": "Identifiant",
|
||||
"search": "Recherche de matériel",
|
||||
"subsystem": "Sous-système",
|
||||
"title": "Matériel informatique"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Connecté à {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@ -368,6 +376,8 @@
|
||||
"error": "Une erreur inconnue s'est produite",
|
||||
"error_addon_no_ingress": "L’add-on demandé ne prend pas en charge l’entrée",
|
||||
"error_addon_not_found": "Module complémentaire introuvable",
|
||||
"error_addon_not_installed": "Le module complémentaire demandé n'est pas installé. Veuillez l'installer d'abord",
|
||||
"error_addon_not_started": "Le module complémentaire demandé n'est pas en cours d'exécution. Veuillez le lancer d'abord",
|
||||
"faq_link": "FAQ de My Home Assistant",
|
||||
"not_supported": "Cette redirection n'est pas prise en charge par votre instance Home Assistant. Vérifiez le {link} pour les redirections prises en charge et la version dans laquelle elles ont été introduites."
|
||||
},
|
||||
@ -380,13 +390,19 @@
|
||||
"snapshot": {
|
||||
"addons": "Modules complémentaires",
|
||||
"available_snapshots": "Instantanés disponibles",
|
||||
"confirm_password": "Confirmez le mot de passe de l'instantané",
|
||||
"could_not_create": "Impossible de créer un instantané",
|
||||
"create": "Créer",
|
||||
"create_blocked_not_running": "La création d’un instantané n’est pas possible en ce moment car le système est en état {state}.",
|
||||
"create_snapshot": "Créer un instantané",
|
||||
"created": "créé",
|
||||
"delete_selected": "Supprimer les instantanés sélectionnés",
|
||||
"delete_snapshot_confirm": "Supprimer",
|
||||
"delete_snapshot_text": "Voulez-vous supprimer {number} {number, plural,\n one {entité}\n other {entités}\n} ?",
|
||||
"delete_snapshot_title": "Supprimer l'instantané",
|
||||
"description": "Les instantanés vous permettent de sauvegarder et de restaurer facilement toutes les données de votre instance Home Assistant.",
|
||||
"enter_password": "Veuillez entrer un mot de passe.",
|
||||
"failed_to_delete": "Échec de la suppression du fichier: %@",
|
||||
"folder": {
|
||||
"addons/local": "Modules complémentaires locaux",
|
||||
"homeassistant": "Configuration Home Assistant",
|
||||
@ -402,7 +418,10 @@
|
||||
"password": "Mot de passe",
|
||||
"password_protected": "protégé par mot de passe",
|
||||
"password_protection": "Protection par mot de passe",
|
||||
"passwords_not_matching": "Les mots de passe ne correspondent pas",
|
||||
"security": "Sécurité",
|
||||
"select_type": "Sélectionnez ce qu'il faut restaurer",
|
||||
"selected": "{number} sélectionné",
|
||||
"type": "Type",
|
||||
"upload_snapshot": "Téléverser un instantané"
|
||||
},
|
||||
@ -720,6 +739,9 @@
|
||||
"no_match": "Aucune pièce correspondante trouvée",
|
||||
"show_areas": "Afficher les pièces"
|
||||
},
|
||||
"attributes": {
|
||||
"expansion_header": "Attributs"
|
||||
},
|
||||
"blueprint-picker": {
|
||||
"add_user": "Ajouter un utilisateur",
|
||||
"remove_user": "Supprimer l'utilisateur",
|
||||
@ -798,6 +820,7 @@
|
||||
"was_unplugged": "était débranché",
|
||||
"was_unsafe": "n'était pas sûr"
|
||||
},
|
||||
"retrieval_error": "Erreur lors de la récupération d'une entrée du journal",
|
||||
"show_trace": "Afficher la trace"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -921,6 +944,9 @@
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Si désactivé, les nouvelles entités découvertes pour {integration} ne seront pas ajoutées automatiquement à Home Assistant.",
|
||||
"enable_new_entities_label": "Activer les entités nouvellement ajoutées.",
|
||||
"enable_polling_description": "Si Home Assistant doit interroger automatiquement les entités {integration} pour les mises à jour.",
|
||||
"enable_polling_label": "Activez l'interrogation pour les mises à jour.",
|
||||
"restart_home_assistant": "Vous devez redémarrer Home Assistant pour que vos modifications soient prises en compte.",
|
||||
"title": "Options système pour {integration}",
|
||||
"update": "Mise à jour"
|
||||
},
|
||||
@ -1719,6 +1745,7 @@
|
||||
"title": "Alexa"
|
||||
},
|
||||
"connected": "Connecté",
|
||||
"connecting": "Connexion...",
|
||||
"connection_status": "État de la connexion cloud",
|
||||
"fetching_subscription": "Récupération de l'abonnement...",
|
||||
"google": {
|
||||
@ -2062,7 +2089,8 @@
|
||||
"scripts": "Scripts",
|
||||
"unknown_error": "Erreur inconnue",
|
||||
"unnamed_device": "Appareil sans nom",
|
||||
"update": "Mettre à jour"
|
||||
"update": "Mettre à jour",
|
||||
"update_device_error": "Échec de la mise à jour de l’appareil"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entités",
|
||||
@ -2195,6 +2223,7 @@
|
||||
"depends_on_cloud": "Dépend du cloud",
|
||||
"device_unavailable": "Appareil non disponible",
|
||||
"devices": "{count} {count, plural,\n one {appareil}\n other {appareils}\n}",
|
||||
"disable_error": "Échec de l’activation ou de la désactivation de l’intégration",
|
||||
"disable_restart_confirm": "Redémarrez Home Assistant pour terminer la désactivation de cette intégration",
|
||||
"disable": {
|
||||
"disable_confirm": "Voulez-vous vraiment désactiver cette entrée de configuration? Ses appareils et entités seront désactivés.",
|
||||
@ -2206,6 +2235,7 @@
|
||||
},
|
||||
"disabled_cause": "Désactivé par {cause}"
|
||||
},
|
||||
"disabled_polling": "Francais",
|
||||
"documentation": "Documentation",
|
||||
"enable_restart_confirm": "Redémarrez Home Assistant pour terminer l'activation de cette intégration",
|
||||
"entities": "{count} {count, plural,\n one {entity}\n other {entities}\n}",
|
||||
@ -2588,6 +2618,8 @@
|
||||
"add_scene": "Ajouter une scène",
|
||||
"delete_confirm": "Êtes-vous sûr de vouloir supprimer cette scène?",
|
||||
"delete_scene": "Supprimer la scène",
|
||||
"duplicate": "Dupliquer",
|
||||
"duplicate_scene": "Dupliquée la scène",
|
||||
"edit_scene": "Éditer la scène",
|
||||
"header": "Éditeur de scène",
|
||||
"headers": {
|
||||
@ -2916,6 +2948,8 @@
|
||||
"follow_device_instructions": "Suivez les instructions fournies avec votre appareil pour déclencher l'appairage sur l'appareil.",
|
||||
"inclusion_failed": "Le nœud n'a pas pu être ajouté. Veuillez consulter les journaux pour plus d'informations.",
|
||||
"inclusion_finished": "Le nœud a été ajouté. Quelques minutes peuvent s'écouler avant que toutes les entités n'apparaissent, alors que nous terminons la mise en place du nœud en arrière-plan.",
|
||||
"interview_failed": "L'interrogation de l'appareil a échoué. Des informations additionnelles peuvent être disponibles dans les journaux.",
|
||||
"interview_started": "L'appareil est en cours d'interrogation. Cela peut prendre du temps.",
|
||||
"introduction": "Cet assistant vous guidera dans l'ajout d'un nœud à votre réseau Z-Wave.",
|
||||
"secure_inclusion_warning": "Les dispositifs sécurisés nécessitent une bande passante supplémentaire ; un trop grand nombre de dispositifs sécurisés peut ralentir votre réseau Z-Wave. Nous recommandons de n'utiliser l'inclusion sécurisée que pour les dispositifs qui en ont besoin, comme les serrures ou les ouvre-portes de garage.",
|
||||
"start_inclusion": "Commencer l'inclusion",
|
||||
@ -2931,6 +2965,7 @@
|
||||
"home_id": "ID de la maison",
|
||||
"network": "Réseau",
|
||||
"node_id": "ID du nœud",
|
||||
"reconfigure_server": "Reconfigurer le serveur",
|
||||
"remove_node": "Supprimer le nœud"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -2955,6 +2990,7 @@
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Niveau du journal",
|
||||
"log_level_changed": "Niveau de journal modifié en : {level}",
|
||||
"subscribed_to_logs": "S'abonner aux messages du journal Z-Wave JS",
|
||||
"title": "Journaux Z-Wave JS"
|
||||
},
|
||||
@ -3857,10 +3893,19 @@
|
||||
"intro": "Êtes-vous prêt à réveiller votre maison, à récupérer votre vie privée et à rejoindre une communauté mondiale de bricoleurs?",
|
||||
"next": "Suivant",
|
||||
"restore": {
|
||||
"addons": "Modules complémentaires",
|
||||
"confirm_password": "Confirmez le mot de passe de l'instantané",
|
||||
"description": "Vous pouvez également restaurer à partir d'un instantané précédent.",
|
||||
"folders": "Dossiers",
|
||||
"full_snapshot": "Instantané complet",
|
||||
"hide_log": "Masquer le journal",
|
||||
"in_progress": "Restauration en cours",
|
||||
"show_log": "Afficher le journal"
|
||||
"partial_snapshot": "Instantané partiel",
|
||||
"password": "Mot de passe de l'instantané",
|
||||
"password_protection": "Protection par mot de passe",
|
||||
"select_type": "Sélectionnez les éléments à restaurer",
|
||||
"show_log": "Afficher le journal",
|
||||
"type": "Type d'instantané"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Créer un compte",
|
||||
@ -4010,6 +4055,7 @@
|
||||
"formats": {
|
||||
"12": "12 heures (AM / PM)",
|
||||
"24": "24 heures",
|
||||
"language": "Auto (utiliser le paramètre de langue)",
|
||||
"system": "Utiliser les paramètres régionaux du système"
|
||||
},
|
||||
"header": "Format de l'heure"
|
||||
|
@ -172,8 +172,8 @@
|
||||
"title": "מרחב שמות של תהליכי מארח"
|
||||
},
|
||||
"ingress": {
|
||||
"description": "תוסף זה משתמש ב-Ingress כדי להטמיע את הממשק שלו בצורה מאובטחת ב-Home Assistant.",
|
||||
"title": "חדירה"
|
||||
"description": "תוסף זה משתמש בכניסה כדי להטמיע את הממשק שלו בצורה מאובטחת ב-Home Assistant.",
|
||||
"title": "כניסה"
|
||||
},
|
||||
"label": {
|
||||
"apparmor": "apparmor",
|
||||
@ -377,7 +377,7 @@
|
||||
"error_addon_no_ingress": "ההרחבה המבוקשת אינו תומכת בכניסה",
|
||||
"error_addon_not_found": "התוסף לא נמצא",
|
||||
"error_addon_not_installed": "התוסף המבוקש אינו מותקן. אנא התקן אותו תחילה",
|
||||
"error_addon_not_started": "התוסף המבוקש אינו פועל. אנא הפעל אותו תחילה",
|
||||
"error_addon_not_started": "ההרחבה המבוקשת אינה פועלת. נא הפעל אותו תחילה",
|
||||
"faq_link": "שאלות נפוצות על Home Assistant שלי",
|
||||
"not_supported": "הפניה זו אינה נתמכת על ידי מופע ה-Home Assistant שלך. בדוק ב-{link} את ההפניות הנתמכות ואת הגרסה שהוצגה."
|
||||
},
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "היה מנותק",
|
||||
"was_unsafe": "היה לא בטוח"
|
||||
},
|
||||
"retrieval_error": "שגיאה במהלך אחזור ערך יומן רישום",
|
||||
"show_trace": "הצג מעקב"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2002,7 +2003,7 @@
|
||||
}
|
||||
},
|
||||
"devices": {
|
||||
"add_prompt": "אף {name} לא נוסף באמצעות מכשיר זה. אתה יכול להוסיף אחד על ידי לחיצה על כפתור + למעלה.",
|
||||
"add_prompt": "אף {name} לא נוסף באמצעות התקן זה. ניתן להוסיף אחד על ידי לחיצה על כפתור + למעלה.",
|
||||
"automation": {
|
||||
"actions": {
|
||||
"caption": "כשמשהו מופעל...",
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "מזהה בית",
|
||||
"network": "רשת",
|
||||
"node_id": "מזהה צומת",
|
||||
"reconfigure_server": "קביעת תצורה מחדש של שרת",
|
||||
"remove_node": "הסר צומת"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -126,12 +126,14 @@
|
||||
},
|
||||
"dashboard": {
|
||||
"action_error": {
|
||||
"go_to_config": "Nem sikerült elindítani a bővítményt - a konfiguráció ellenőrzése nem sikerült!",
|
||||
"install": "A bővítmény telepítése sikertelen",
|
||||
"restart": "A bővítmény újraindítása sikertelen",
|
||||
"start": "A bővítmény indítása sikertelen",
|
||||
"start_invalid_config": "Ugrás a konfigurációra",
|
||||
"stop": "A bővítmény leállítása sikertelen",
|
||||
"uninstall": "A bővítmény törlése sikertelen"
|
||||
"uninstall": "A bővítmény törlése sikertelen",
|
||||
"validate_config": "Nem sikerült ellenőrizni a kiegészítő konfigurációját"
|
||||
},
|
||||
"capability": {
|
||||
"apparmor": {
|
||||
@ -141,13 +143,25 @@
|
||||
"title": "Home Assistant Hitelesítés"
|
||||
},
|
||||
"docker_api": {
|
||||
"description": "A kiegészítő szerzője kérte a hozzáférését a rendszeren futó Docker-példányhoz. Ez a mód teljes hozzáférést és vezérlést biztosít a kiegészítőnek az egész Home Assistant rendszerhez, ami biztonsági kockázatokat jelent, és visszaélés esetén károsíthatja a rendszert. Ezért ez a szolgáltatás negatívan befolyásolja a Home assistant biztonságát\n\nEzt a hozzáférési szintet a program nem kapja meg automatikusan, ezt Önnek kell megerősítenie. Ehhez manuálisan le kell tiltania a kiegészítő védelmi módját. Csak akkor tiltsa le a védelmi módot, ha tudja, szükség van ÉS megbízik a kiegészítő forrásában.",
|
||||
"title": "Teljes Docker hozzáférés"
|
||||
},
|
||||
"full_access": {
|
||||
"description": "Ez a kiegészítő teljes hozzáférést biztosít a rendszer hardveréhez, a kiegészítő szerzőének kérésére. A hozzáférés összehasonlítható a Docker privilegizált módjával. Mivel ez lehetséges biztonsági kockázatokat tár fel, ez a szolgáltatás negatívan befolyásolja a Home assistant biztonságát. \n\nEzt a hozzáférési szintet a program nem kapja meg automatikusan, ezt Önnek kell megerősítenie. Ehhez manuálisan le kell tiltania a kiegészítő védelmi módját. Csak akkor tiltsa le a védelmi módot, ha tudja, szükség van ÉS megbízik a kiegészítő forrásában.",
|
||||
"title": "Teljes hozzáférés a hardverhez"
|
||||
},
|
||||
"hassio_api": {
|
||||
"title": "Supervisor API hozzáférés"
|
||||
},
|
||||
"homeassistant_api": {
|
||||
"title": "Home Assistant API-hozzáférés"
|
||||
},
|
||||
"host_network": {
|
||||
"description": "A bővítmények általában a saját elszigetelt hálózati rétegükben futnak, ami megakadályozza, hogy hozzáférjenek a gazdagép operációs rendszer hálózatához. Bizonyos esetekben ez a hálózati elkülönítés korlátozhatja a bővítményeket a szolgáltatásaik nyújtásában, ezért az elkülönítést a bővítmény szerzője feloldhatja, így a bővítmény teljes hozzáférést biztosít a gazdaszámítógép hálózati képességeihez. Ez több hálózati lehetőséget biztosít a bővítménynek, de csökkenti a biztonságot, ezért a bővítmény biztonsági besorolása csökken, ha ezt a beállítást a bővítmény használja."
|
||||
"description": "A bővítmények általában a saját elszigetelt hálózati rétegükben futnak, ami megakadályozza, hogy hozzáférjenek a gazdagép operációs rendszer hálózatához. Bizonyos esetekben ez a hálózati elkülönítés korlátozhatja a bővítményeket a szolgáltatásaik nyújtásában, ezért az elkülönítést a bővítmény szerzője feloldhatja, így a bővítmény teljes hozzáférést biztosít a gazdaszámítógép hálózati képességeihez. Ez több hálózati lehetőséget biztosít a bővítménynek, de csökkenti a biztonságot, ezért a bővítmény biztonsági besorolása csökken, ha ezt a beállítást a bővítmény használja.",
|
||||
"title": "Gazdahálózat"
|
||||
},
|
||||
"host_pid": {
|
||||
"description": "Általában a kiegészítő futtatásának folyamatait elkülönítik az összes többi rendszerfolyamattól. A kiegészítő szerzője azt kérte, hogy a kiegészítő hozzáférjen a gazda rendszerpéldányon futó rendszerfolyamatokhoz, és engedélyezze a kiegészítő számára a folyamatok futtatását a fogadó rendszeren is. Ez a mód teljes hozzáférést és vezérlést biztosít a kiegészítőnek az egész Home Assistant rendszerhez, ami biztonsági kockázatokat jelent, és visszaélés esetén károsíthatja a rendszert. Ezért ez a szolgáltatás negatívan befolyásolja a Home assistant biztonságát.\n\nEzt a hozzáférési szintet a program nem kapja meg automatikusan, ezt Önnek kell megerősítenie. Ehhez manuálisan le kell tiltania a kiegészítő védelmi módját. Csak akkor tiltsa le a védelmi módot, ha tudja, szükség van ÉS megbízik a kiegészítő forrásában."
|
||||
},
|
||||
"label": {
|
||||
"apparmor": "apparmor",
|
||||
@ -161,6 +175,9 @@
|
||||
"rating": "értékelés",
|
||||
"stage": "szakasz"
|
||||
},
|
||||
"rating": {
|
||||
"title": "Kiegészítő biztonsági besorolása"
|
||||
},
|
||||
"role": {
|
||||
"admin": "admin",
|
||||
"backup": "biztonsági mentés",
|
||||
@ -176,6 +193,7 @@
|
||||
"cpu_usage": "Bővítmény CPU-használat",
|
||||
"hostname": "Gazdagép neve",
|
||||
"install": "telepítés",
|
||||
"new_update_available": "{name} {version} elérhető",
|
||||
"not_available_version": "A(z) {core_version_installed} otthoni segédet futtatja, hogy frissítse az Otthoni segéd legalább {core_version_needed} verzióját.",
|
||||
"open_web_ui": "Webes felhasználói felület megnyitása",
|
||||
"option": {
|
||||
@ -198,7 +216,13 @@
|
||||
"title": "Watchdog"
|
||||
}
|
||||
},
|
||||
"protection_mode": {
|
||||
"content": "A kiegészítő védelmi módja le van tiltva! Ez a kiegészítő számára teljes hozzáférést biztosít a teljes rendszerhez, ami növeli a biztonsági kockázatokat, és helytelen használat esetén károsíthatja a rendszert. Csak akkor tiltsa le a védelmi módot, ha tudja, szükség van ÉS megbízik a kiegészítő forrásában.",
|
||||
"enable": "Védelmi mód engedélyezése",
|
||||
"title": "Figyelem: A védelmi mód ki van kapcsolva!"
|
||||
},
|
||||
"ram_usage": "Bővítmény RAM-használat",
|
||||
"rebuild": "Újjraépít",
|
||||
"restart": "újraindítás",
|
||||
"start": "indítás",
|
||||
"stop": "leállítás",
|
||||
@ -272,10 +296,18 @@
|
||||
"addon_new_version": "Új verzió érhető el",
|
||||
"addon_running": "A bővítmény fut",
|
||||
"addon_stopped": "A bővítmény leállt",
|
||||
"addons": "Bővítmények",
|
||||
"addons": "Telepített bővítmények",
|
||||
"no_addons": "Még nincs telepítve egyetlen bővítmény sem. A kezdéshez menjen át a bővítmény boltba!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Attribútumok",
|
||||
"device_path": "Eszköz útvonala",
|
||||
"id": "ID",
|
||||
"search": "Hardver keresése",
|
||||
"subsystem": "Alrendszer",
|
||||
"title": "Hardver"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "Csatlakozva a következőhöz: {ssid}",
|
||||
"dhcp": "DHCP",
|
||||
@ -293,7 +325,11 @@
|
||||
"wpa": "wpa-psk"
|
||||
},
|
||||
"registries": {
|
||||
"add_new_registry": "Hozzáadás új rendszerleíró adatbázishoz",
|
||||
"add_registry": "Hozzáadás a rendszerleíró adatbázishoz",
|
||||
"failed_to_add": "Nem sikerült hozzáadni a rendszerleíró adatbázishoz",
|
||||
"password": "Jelszó",
|
||||
"registry": "Regisztrálás",
|
||||
"remove": "Eltávolítás",
|
||||
"username": "Felhasználónév"
|
||||
},
|
||||
@ -315,6 +351,8 @@
|
||||
"my": {
|
||||
"error": "Ismeretlen hiba történt",
|
||||
"error_addon_not_found": "A bővítmény nem található",
|
||||
"error_addon_not_installed": "A kért kiegészítő nincs telepítve. Kérjük, először telepítse",
|
||||
"error_addon_not_started": "A kért bővítmény nem fut. Kérjük, először indítsa el",
|
||||
"faq_link": "My Home Assistant GYIK"
|
||||
},
|
||||
"panel": {
|
||||
@ -326,12 +364,19 @@
|
||||
"snapshot": {
|
||||
"addons": "Bővítmények",
|
||||
"available_snapshots": "Rendelkezésreálló pillanatképek",
|
||||
"confirm_password": "Pillanatkép jelszó megerősítése",
|
||||
"could_not_create": "Nem lehet pillanatképet létrehozni",
|
||||
"create": "Létrehozás",
|
||||
"create_blocked_not_running": "Jelenleg nem lehet pillanatképet létrehozni, mivel a rendszer {state} állapotban van.",
|
||||
"create_snapshot": "Pillanatkép létrehozása",
|
||||
"created": "Létrehozva",
|
||||
"delete_selected": "A kijelölt pillanatképek törlése",
|
||||
"delete_snapshot_confirm": "törlés",
|
||||
"delete_snapshot_text": "Törölni szeretné a {szám} {szám, többes szám,\n egy {pillanatkép}\n egyéb {pillanatképek}\n}?",
|
||||
"delete_snapshot_title": "Pillanatkép törlése",
|
||||
"description": "A pillanatképekkel könnyen hozhatsz létre vagy tölthetsz vissza mentést a teljes Home Assistant példányodról.",
|
||||
"enter_password": "Add meg a jelszót",
|
||||
"failed_to_delete": "Nem sikerült törölni",
|
||||
"folder": {
|
||||
"addons/local": "Helyi bővítmények",
|
||||
"homeassistant": "Home Assistant konfiguráció",
|
||||
@ -341,14 +386,17 @@
|
||||
},
|
||||
"folders": "Mappák",
|
||||
"full_snapshot": "Teljes pillanatkép",
|
||||
"name": "Név",
|
||||
"name": "Pillanatkép neve",
|
||||
"no_snapshots": "Nincs még egyetlen pillanatképed sem létrehozva.",
|
||||
"partial_snapshot": "Részleges pillanatkép",
|
||||
"password": "Jelszó",
|
||||
"password": "Pillanatkép jelszava",
|
||||
"password_protected": "jelszóval védett",
|
||||
"password_protection": "Jelszóvédelem",
|
||||
"passwords_not_matching": "A jelszavak nem egyeznek",
|
||||
"security": "Biztonság",
|
||||
"type": "Típus",
|
||||
"select_type": "Válassza ki, hogy mit szeretne visszaállítani",
|
||||
"selected": "{number} kiválasztva",
|
||||
"type": "Pillanatkép típusa",
|
||||
"upload_snapshot": "Pillanatkép feltöltése"
|
||||
},
|
||||
"store": {
|
||||
@ -383,7 +431,8 @@
|
||||
"used_space": "Felhasznált terület"
|
||||
},
|
||||
"log": {
|
||||
"get_logs": "Nem sikerült lekérni a(z) {provider} naplókat, {error}"
|
||||
"get_logs": "Nem sikerült lekérni a(z) {provider} naplókat, {error}",
|
||||
"log_provider": "Naplószolgáltató"
|
||||
},
|
||||
"supervisor": {
|
||||
"beta_backup": "A szolgáltatás aktiválása előtt győződj meg arról, hogy rendelkezel biztonsági másolattal az adatokról.",
|
||||
@ -400,6 +449,7 @@
|
||||
"leave_beta_description": "Stabil frissítések a Home Assistanthoz, a Supervisorhoz és a gazdagéphez",
|
||||
"ram_usage": "Supervisor RAM használat",
|
||||
"reload_supervisor": "Supervisor újratöltése",
|
||||
"search": "Keresés",
|
||||
"share_diagnostics": "Diagnosztika megosztása",
|
||||
"share_diagnostics_description": "Összeomlási jelentések és diagnosztikai információk megosztása.",
|
||||
"share_diagonstics_title": "Segíts a Home Assistant fejlesztésében",
|
||||
@ -411,6 +461,7 @@
|
||||
},
|
||||
"unhealthy_title": "A telepítés nem megfelelő",
|
||||
"unsupported_reason": {
|
||||
"apparmor": "Az AppArmor nincs engedélyezve a gazdagépen",
|
||||
"container": "Konténerek, amelyekről ismert, hogy problémákat okoznak",
|
||||
"dbus": "DBUS",
|
||||
"docker_configuration": "Docker konfiguráció",
|
||||
@ -444,7 +495,7 @@
|
||||
},
|
||||
"automation": {
|
||||
"last_triggered": "Utoljára aktiválva",
|
||||
"trigger": "Végrehajt"
|
||||
"trigger": "Műveletek futtatása"
|
||||
},
|
||||
"camera": {
|
||||
"not_available": "Kép nem áll rendelkezésre"
|
||||
@ -495,9 +546,12 @@
|
||||
},
|
||||
"light": {
|
||||
"brightness": "Fényerő",
|
||||
"cold_white_value": "Hideg fehér fény",
|
||||
"color_brightness": "Színes fény",
|
||||
"color_temperature": "Színhőmérséklet",
|
||||
"effect": "Hatás",
|
||||
"white_value": "Fehér érték"
|
||||
"warm_white_value": "Meleg fehér fény",
|
||||
"white_value": "Fehér fényerő"
|
||||
},
|
||||
"lock": {
|
||||
"code": "Kód",
|
||||
@ -623,6 +677,9 @@
|
||||
"addon-picker": {
|
||||
"addon": "Bővítmény",
|
||||
"error": {
|
||||
"fetch_addons": {
|
||||
"title": "Hiba történt a bővítmények beolvasása közben"
|
||||
},
|
||||
"no_supervisor": {
|
||||
"title": "Nincs Supervisor"
|
||||
}
|
||||
@ -643,6 +700,9 @@
|
||||
"no_match": "Nem található egyező terület",
|
||||
"show_areas": "Területek megjelenítése"
|
||||
},
|
||||
"attributes": {
|
||||
"expansion_header": "Attribútumok"
|
||||
},
|
||||
"blueprint-picker": {
|
||||
"add_user": "Felhasználó hozzáadása",
|
||||
"remove_user": "Felhasználó eltávolítása",
|
||||
@ -653,6 +713,9 @@
|
||||
"today": "Ma"
|
||||
},
|
||||
"data-table": {
|
||||
"clear": "Tiszta",
|
||||
"filtering_by": "Szűrés",
|
||||
"hidden": "{number} rejtve",
|
||||
"no-data": "Nincs adat",
|
||||
"search": "Keresés"
|
||||
},
|
||||
@ -716,7 +779,9 @@
|
||||
"was_unlocked": "fel lett oldva",
|
||||
"was_unplugged": "ki lett húzva",
|
||||
"was_unsafe": "nem volt biztonságos"
|
||||
}
|
||||
},
|
||||
"retrieval_error": "Hiba a naplóbejegyzés beolvasása során",
|
||||
"show_trace": "Nyomkövetés megjelenítése"
|
||||
},
|
||||
"media-browser": {
|
||||
"audio_not_supported": "A böngésződ nem támogatja az audio elemet.",
|
||||
@ -765,6 +830,14 @@
|
||||
"label": "Kép",
|
||||
"unsupported_format": "Nem támogatott formátum, válassz JPEG, PNG vagy GIF képet."
|
||||
},
|
||||
"related-filter-menu": {
|
||||
"filter_by_area": "Szűrés terület szerint",
|
||||
"filter_by_device": "Szűrés eszköz szerint",
|
||||
"filter_by_entity": "Szűrés entitás szerint",
|
||||
"filtered_by_area": "terület: {area_name}",
|
||||
"filtered_by_device": "eszköz: {device_name}",
|
||||
"filtered_by_entity": "entitás: {entity_name}"
|
||||
},
|
||||
"related-items": {
|
||||
"area": "Terület",
|
||||
"automation": "A következő automatizálások része",
|
||||
@ -831,6 +904,8 @@
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "Ha le van tiltva, akkor az újonnan felfedezett {integration} entitások nem lesznek automatikusan hozzáadva a Home Assistant-hoz.",
|
||||
"enable_new_entities_label": "Újonnan hozzáadott entitások engedélyezése.",
|
||||
"enable_polling_label": "A frissítések lekérdezésének engedélyezése.",
|
||||
"restart_home_assistant": "A módosítások életbe lépéséhez újra kell indítania a Home Assistant alkalmazást.",
|
||||
"title": "{integration} rendszerbeállításai",
|
||||
"update": "Frissítés"
|
||||
},
|
||||
@ -840,6 +915,7 @@
|
||||
},
|
||||
"entity_registry": {
|
||||
"control": "Ellenőrzés",
|
||||
"customize_link": "entitás testreszabása",
|
||||
"dismiss": "Elvetés",
|
||||
"editor": {
|
||||
"advanced": "Haladó beállítások",
|
||||
@ -951,8 +1027,8 @@
|
||||
"remove_intro": "Ha az entitás már nincs használatban, akkor nyugodtan eltávolíthatod."
|
||||
},
|
||||
"script": {
|
||||
"last_action": "Utolsó Művelet",
|
||||
"last_triggered": "Utoljára aktivált"
|
||||
"last_action": "Utolsó művelet",
|
||||
"last_triggered": "Utolsó aktiválás"
|
||||
},
|
||||
"settings": "Entitás beállítások",
|
||||
"sun": {
|
||||
@ -1020,42 +1096,47 @@
|
||||
"zone": "Zónák"
|
||||
},
|
||||
"reload": {
|
||||
"automation": "Automatizálások újratöltése",
|
||||
"command_line": "Parancssori entitások újratöltése",
|
||||
"core": "Lokáció és testreszabások újratöltése",
|
||||
"filesize": "Fájlméret entitások újratöltése",
|
||||
"filter": "Szűrőentitások újratöltése",
|
||||
"generic": "Általános IP kamera entitások újratöltése",
|
||||
"generic_thermostat": "Általános termosztát entitások újratöltése",
|
||||
"group": "Csoportok, csoport entitások és értesítési szolgáltatások újratöltése",
|
||||
"history_stats": "Előzmény statisztika entitások újratöltése",
|
||||
"homekit": "HomeKit újratöltése",
|
||||
"input_boolean": "Logikai változó bemenetek újratöltése",
|
||||
"input_datetime": "Időpont bemenetek újratöltése",
|
||||
"input_number": "Szám bemenetek újratöltése",
|
||||
"input_select": "Választási bemenetek újratöltése",
|
||||
"input_text": "Szöveg bemenetek újratöltése",
|
||||
"min_max": "Min/max entitások újratöltése",
|
||||
"mqtt": "Manuálisan konfigurált MQTT entitások újratöltése",
|
||||
"person": "Személyek újratöltése",
|
||||
"ping": "Ping bináris érzékelő entitások újratöltése",
|
||||
"reload": "{domain} újratöltése",
|
||||
"rest": "Rest entitások és értesítési szolgáltatások újratöltése",
|
||||
"rpi_gpio": "Raspberry Pi GPIO entitások újratöltése",
|
||||
"scene": "Jelenetek újratöltése",
|
||||
"script": "Szkriptek újratöltése",
|
||||
"smtp": "SMTP értesítési szolgáltatások újratöltése",
|
||||
"statistics": "Statisztikai entitások újratöltése",
|
||||
"telegram": "Telegram értesítési szolgáltatások újratöltése",
|
||||
"template": "Sablon entitások újratöltése",
|
||||
"trend": "Trend entitások újratöltése",
|
||||
"universal": "Univerzális médialejátszó entitások újratöltése",
|
||||
"zone": "Zónák újratöltése"
|
||||
"automation": "Automatizálások",
|
||||
"command_line": "Parancssori entitások",
|
||||
"core": "Lokáció és testreszabások",
|
||||
"filesize": "Fájlméret entitások",
|
||||
"filter": "Szűrőentitások",
|
||||
"generic": "Általános IP kamera entitások",
|
||||
"generic_thermostat": "Általános termosztát entitások",
|
||||
"group": "Csoportok, csoport entitások és értesítési szolgáltatások",
|
||||
"history_stats": "Előzmény statisztika entitások",
|
||||
"homekit": "HomeKit",
|
||||
"input_boolean": "Logikai változó bemenetek",
|
||||
"input_datetime": "Időpont bemenetek",
|
||||
"input_number": "Szám bemenetek",
|
||||
"input_select": "Választási bemenetek",
|
||||
"input_text": "Szöveg bemenetek",
|
||||
"min_max": "Min/max entitások",
|
||||
"mqtt": "Manuálisan konfigurált MQTT entitások",
|
||||
"person": "Személyek",
|
||||
"ping": "Ping bináris érzékelő entitások",
|
||||
"reload": "{domain}",
|
||||
"rest": "Rest entitások és értesítési szolgáltatások",
|
||||
"rpi_gpio": "Raspberry Pi GPIO entitások",
|
||||
"scene": "Jelenetek",
|
||||
"script": "Szkriptek",
|
||||
"smtp": "SMTP értesítési szolgáltatások",
|
||||
"statistics": "Statisztikai entitások",
|
||||
"telegram": "Telegram értesítési szolgáltatások",
|
||||
"template": "Sablon entitások",
|
||||
"trend": "Trend entitások",
|
||||
"universal": "Univerzális médialejátszó entitások",
|
||||
"zone": "Zónák"
|
||||
},
|
||||
"server_control": {
|
||||
"perform_action": "{action} Szerver",
|
||||
"perform_action": "{action} szerver",
|
||||
"restart": "Újraindítás",
|
||||
"stop": "Leállítás"
|
||||
},
|
||||
"types": {
|
||||
"navigation": "Navigál",
|
||||
"reload": "Újratöltés",
|
||||
"server_control": "Szerver"
|
||||
}
|
||||
},
|
||||
"filter_placeholder": "Entitásszűrő"
|
||||
@ -1072,6 +1153,7 @@
|
||||
"buttons": {
|
||||
"add": "Eszközök hozzáadása ezen az eszközön keresztül",
|
||||
"clusters": "Klaszterek Kezelése",
|
||||
"device_children": "Aleszközök megtekintése",
|
||||
"reconfigure": "Eszköz újrakonfigurálása",
|
||||
"remove": "Eszköz eltávolítása",
|
||||
"view_in_visualization": "Megtekintés a Vizualizációban",
|
||||
@ -1080,6 +1162,7 @@
|
||||
"confirmations": {
|
||||
"remove": "Biztosan el szeretnéd távolítani az eszközt?"
|
||||
},
|
||||
"device_children": "Zigbee aleszköz",
|
||||
"device_signature": "Zigbee eszköz aláírása",
|
||||
"last_seen": "Utolsó jelentés",
|
||||
"manuf": "{manufacturer} által",
|
||||
@ -1096,6 +1179,21 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Eszköznév módosítása"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"attribute": "Attribútum",
|
||||
"bind_header": "Kötés",
|
||||
"button_hide": "Részletek elrejtése",
|
||||
"button_show": "Részletek megjelenítése",
|
||||
"cluster_header": "Klaszter",
|
||||
"configuration_complete": "Az eszköz újrakonfigurálása befejeződött.",
|
||||
"configuration_failed": "Az eszköz újrakonfigurálása nem sikerült. További információk elérhetők a naplókban.",
|
||||
"configuring_alt": "Konfigurálás",
|
||||
"heading": "Eszköz újrakonfigurálása",
|
||||
"in_progress": "Az eszköz újrakonfigurálása folyamatban van. Ez eltarthat egy ideig.",
|
||||
"min_max_change": "min/max/változás",
|
||||
"reporting_header": "Jelentés",
|
||||
"start_reconfiguration": "Újrakonfigurálás indítása"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1114,6 +1212,10 @@
|
||||
"key_missing": "A (z) \" {key}\" szükséges kulcs hiányzik.",
|
||||
"key_not_expected": "A(z) \"{key}\" kulcs nem várt, vagy a vizuális szerkesztő nem támogatja.",
|
||||
"no_type_provided": "Nincs megadva típus."
|
||||
},
|
||||
"supervisor": {
|
||||
"reboot": "Próbálja újraindítani a gazdagépet",
|
||||
"system_health": "Rendszerállapot ellenőrzése"
|
||||
}
|
||||
},
|
||||
"login-form": {
|
||||
@ -1131,10 +1233,12 @@
|
||||
"notification_toast": {
|
||||
"connection_lost": "A kapcsolat megszakadt. Újracsatlakozás…",
|
||||
"dismiss": "Elvetés",
|
||||
"integration_starting": "{integration} indítása folyamatban van, nem lesz elérhető, amíg ez be nem fejeződik.",
|
||||
"service_call_failed": "Nem sikerült meghívni a(z) {service} szolgáltatást.",
|
||||
"started": "A Home Assistant indítása befejeződött!",
|
||||
"starting": "A Home Assistant indítása folyamatban van. Nem lesz minden elérhető, amíg ez be nem fejeződik.",
|
||||
"triggered": "Aktiválva {name}"
|
||||
"triggered": "Aktiválva {name}",
|
||||
"wrapping_up_startup": "Az indítás folyamatban. A folyamat befejezéséig nem minden lesz elérhető."
|
||||
},
|
||||
"panel": {
|
||||
"config": {
|
||||
@ -1146,7 +1250,8 @@
|
||||
"caption": "Területek",
|
||||
"data_table": {
|
||||
"area": "Terület",
|
||||
"devices": "Eszközök"
|
||||
"devices": "Eszközök",
|
||||
"entities": "Entitások"
|
||||
},
|
||||
"delete": {
|
||||
"confirmation_text": "Minden ebben a területben lévő eszköz hozzárendelés nélküli lesz.",
|
||||
@ -1223,6 +1328,7 @@
|
||||
"extra_fields": {
|
||||
"brightness_pct": "Fényerő",
|
||||
"code": "Kód",
|
||||
"flash": "Villogás",
|
||||
"humidity": "Páratartalom",
|
||||
"message": "Üzenet",
|
||||
"mode": "Üzemmód",
|
||||
@ -1388,6 +1494,7 @@
|
||||
"move_down": "Le",
|
||||
"move_up": "Fel",
|
||||
"save": "Mentés",
|
||||
"show_trace": "Nyomkövetés megjelenítése",
|
||||
"triggers": {
|
||||
"add": "Eseményindító hozzáadása",
|
||||
"delete": "Törlés",
|
||||
@ -1496,6 +1603,7 @@
|
||||
"add_automation": "Automatizálás hozzáadása",
|
||||
"delete_automation": "Automatizálás törlése",
|
||||
"delete_confirm": "Biztosan törölni szeretnéd ezt az automatizálást?",
|
||||
"dev_automation": "Hibakeresési automatizálás",
|
||||
"duplicate": "Duplikálás",
|
||||
"duplicate_automation": "Automatizálás duplikálása",
|
||||
"edit_automation": "Automatizálás szerkesztése",
|
||||
@ -1576,11 +1684,12 @@
|
||||
"info_state_reporting": "Ha engedélyezed az állapotjelentést, akkor a Home Assistant minden állapotváltozást el fog küldeni a feltárt entitásokról az Amazon-nak. Ez lehetővé teszi, hogy mindig láthasd a legfrissebb állapotokat az Alexa alkalmazásban, és az állapotváltozásokkal rutinokat hozhass létre.",
|
||||
"manage_entities": "Entitások kezelése",
|
||||
"state_reporting_error": "Nem lehet {enable_disable} az állapotjelentést.",
|
||||
"sync_entities": "Entitások szinkronizálása",
|
||||
"sync_entities": "Entitások szinkronizálása az Amazonnal",
|
||||
"sync_entities_error": "Nem sikerült szinkronizálni az entitásokat:",
|
||||
"title": "Alexa"
|
||||
},
|
||||
"connected": "Csatlakoztatva",
|
||||
"connecting": "Csatlakozás...",
|
||||
"connection_status": "Felhő kapcsolat állapota",
|
||||
"fetching_subscription": "Előfizetés lekérése...",
|
||||
"google": {
|
||||
@ -1594,6 +1703,7 @@
|
||||
"info": "A Google Asszisztens integrációval a Home Assistant Felhő lehetőséget nyújt minden Home Assistant eszköz vezérlésére bármely Google Asszisztens képes eszközzel.",
|
||||
"info_state_reporting": "Ha engedélyezed az állapotjelentést, akkor a Home Assistant minden állapotváltozást el fog küldeni a feltárt entitásokról a Google-nak. Ez lehetővé teszi, hogy mindig láthasd a legfrissebb állapotokat a Google alkalmazásban.",
|
||||
"manage_entities": "Entitások kezelése",
|
||||
"not_configured_title": "A Google Assistant nincs aktiválva",
|
||||
"security_devices": "Biztonsági eszközök",
|
||||
"sync_entities": "Entitások szinkronizálása a Google-lal",
|
||||
"sync_entities_404_message": "Nem sikerült szinkronizálni az entitásokat a Google-lal. A szinkronizáláshoz kérd meg a Google-t a következőre: \"Hey Google, sync my devices\"",
|
||||
@ -1609,10 +1719,15 @@
|
||||
"remote": {
|
||||
"access_is_being_prepared": "A távoli hozzáférés előkészítése folyamatban van. Értesíteni fogunk, ha készen áll.",
|
||||
"certificate_info": "Tanúsítvány infó",
|
||||
"connected": "Csatlakoztatva",
|
||||
"info": "A Home Assistant Felhő biztonságos távoli kapcsolatot nyújt a példányodhoz, miközben távol vagy.",
|
||||
"instance_is_available": "A példányod itt elérhető:",
|
||||
"instance_will_be_available": "A példányod itt lesz elérhető:",
|
||||
"link_learn_how_it_works": "Tudd meg, hogyan működik",
|
||||
"not_connected": "Nincs csatlakoztatva",
|
||||
"remote_enabled": {
|
||||
"caption": "Automatikus csatlakozás"
|
||||
},
|
||||
"title": "Távoli vezérlés"
|
||||
},
|
||||
"sign_out": "Kijelentkezés",
|
||||
@ -1626,6 +1741,9 @@
|
||||
"target": "Cél",
|
||||
"target_browser": "Böngésző"
|
||||
},
|
||||
"female": "Nő",
|
||||
"male": "Férfi",
|
||||
"title": "Szövegről beszédre",
|
||||
"try": "Próbálja meg"
|
||||
},
|
||||
"webhooks": {
|
||||
@ -1658,7 +1776,7 @@
|
||||
"description_login": "Bejelentkezve mint {email}",
|
||||
"description_not_login": "Nincs bejelentkezve",
|
||||
"dialog_certificate": {
|
||||
"certificate_expiration_date": "Tanúsítvány lejárati dátuma",
|
||||
"certificate_expiration_date": "Tanúsítvány lejárati dátuma:",
|
||||
"certificate_information": "Tanúsítvány-információ",
|
||||
"close": "Bezárás",
|
||||
"fingerprint": "Tanúsítvány ujjlenyomata:",
|
||||
@ -1752,6 +1870,30 @@
|
||||
"description": "Egységrendszer, hely, időzóna és egyéb általános paraméterek",
|
||||
"section": {
|
||||
"core": {
|
||||
"analytics": {
|
||||
"header": "Analitika",
|
||||
"instance_id": "Példányazonosító: {huuid}",
|
||||
"preference": {
|
||||
"base": {
|
||||
"description": "Példányazonosító, verzió és telepítési típus.",
|
||||
"title": "Alapvető elemzés"
|
||||
},
|
||||
"diagnostics": {
|
||||
"title": "Diagnosztika"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Felhasználási statisztikák"
|
||||
},
|
||||
"usage_supervisor": {
|
||||
"description": "Nevek, verziók és képességek.",
|
||||
"title": "Használt integrációk és kiegészítők"
|
||||
},
|
||||
"usage": {
|
||||
"description": "Nevek és verzióinformációk.",
|
||||
"title": "Használt integrációk"
|
||||
}
|
||||
}
|
||||
},
|
||||
"core_config": {
|
||||
"edit_requires_storage": "A szerkesztő le van tiltva, mert a konfiguráció a configuration.yaml fájlban van tárolva.",
|
||||
"elevation": "Magasság",
|
||||
@ -1881,7 +2023,8 @@
|
||||
"scripts": "Szkriptek",
|
||||
"unknown_error": "Ismeretlen hiba",
|
||||
"unnamed_device": "Névtelen eszköz",
|
||||
"update": "Frissítés"
|
||||
"update": "Frissítés",
|
||||
"update_device_error": "A készülék frissítése sikertelen"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entitások",
|
||||
@ -1938,6 +2081,9 @@
|
||||
"filtering_by": "Szűrés",
|
||||
"show": "Megmutat"
|
||||
},
|
||||
"hassio": {
|
||||
"button": "Konfigurál"
|
||||
},
|
||||
"header": "Home Assistant beállítása",
|
||||
"helpers": {
|
||||
"caption": "Segítők",
|
||||
@ -1985,6 +2131,7 @@
|
||||
"license": "Megjelent az Apache 2.0 licenc alatt",
|
||||
"path_configuration": "A configuration.yaml fájl elérési útja: {path}",
|
||||
"server": "server",
|
||||
"setup_time": "Beállítási idő",
|
||||
"source": "Forrás:",
|
||||
"system_health_error": "A rendszerállapot összetevő nincs betöltve. Add hozzá a 'system_health:' sort a configuration.yaml fájlhoz.",
|
||||
"system_health": {
|
||||
@ -2003,10 +2150,13 @@
|
||||
"caption": "Integrációk",
|
||||
"config_entry": {
|
||||
"area": "Terület: {area}",
|
||||
"check_the_logs": "Ellenőrizze a naplókat",
|
||||
"configure": "Konfigurál",
|
||||
"delete": "Törlés",
|
||||
"delete_confirm": "Biztosan törölni szeretnéd ezt az integrációt?",
|
||||
"device_unavailable": "Eszköz nem érhető el",
|
||||
"devices": "{count} {count, plural,\n one {eszköz}\n other {eszköz}\n}",
|
||||
"disable_error": "Az integráció engedélyezése vagy letiltása sikertelen volt",
|
||||
"disable_restart_confirm": "Indítsa újra a Home Assistant programot az integráció lekapcsolásának befejezéséhez",
|
||||
"disable": {
|
||||
"disable_confirm": "Biztosan letiltja ezt a konfigurációs bejegyzést? Eszközeit és entitásait letiltjuk.",
|
||||
@ -2018,14 +2168,17 @@
|
||||
},
|
||||
"disabled_cause": "Letiltva ez által: {cause}"
|
||||
},
|
||||
"disabled_polling": "A frissített adatok automatikus lekérdezése le van tiltva",
|
||||
"documentation": "Dokumentáció",
|
||||
"enable_restart_confirm": "Indítsa újra a Home Assistant programot az integráció bekapcsolásának befejezéséhez",
|
||||
"entities": "{count} {count, plural,\n one {entitás}\n other {entitás}\n}",
|
||||
"entity_unavailable": "Entitás nem érhető el",
|
||||
"firmware": "Firmware: {version}",
|
||||
"hub": "Kapcsolódva",
|
||||
"logs": "naplók",
|
||||
"manuf": "{manufacturer} által",
|
||||
"no_area": "Nincs Terület",
|
||||
"not_loaded": "Nincs betöltve",
|
||||
"options": "Opciók",
|
||||
"reload": "Újratöltés",
|
||||
"reload_confirm": "Az integráció újra lett töltve",
|
||||
@ -2033,6 +2186,11 @@
|
||||
"rename": "Átnevezés",
|
||||
"restart_confirm": "Indítsd újra a Home Assistant-ot az integráció törlésének befejezéséhez",
|
||||
"services": "{count} {count, plural,\n one {szolgáltatás}\n other {szolgáltatás}\n}",
|
||||
"state": {
|
||||
"loaded": "Betöltve",
|
||||
"not_loaded": "Nincs betöltve",
|
||||
"setup_error": "Nem sikerült beállítani"
|
||||
},
|
||||
"system_options": "Rendszerbeállítások",
|
||||
"unnamed_entry": "Névtelen bejegyzés"
|
||||
},
|
||||
@ -2052,6 +2210,7 @@
|
||||
"loading_first_time": "Kérlek várj, amíg az integráció telepítésre kerül",
|
||||
"next": "Következő",
|
||||
"not_all_required_fields": "Nincs kitöltve minden szükséges mező.",
|
||||
"not_loaded": "Az integrációt nem sikerült betölteni. Próbálja meg újraindítani a Home Assistantot.",
|
||||
"pick_flow_step": {
|
||||
"title": "Felfedeztük ezeket, be akarod állítani őket?"
|
||||
},
|
||||
@ -2065,6 +2224,7 @@
|
||||
"disable": {
|
||||
"disabled_integrations": "{number} letiltva",
|
||||
"hide_disabled": "A letiltott integrációk elrejtése",
|
||||
"show": "Mutat",
|
||||
"show_disabled": "A letiltott integrációk megjelenítése"
|
||||
},
|
||||
"discovered": "Felfedezett",
|
||||
@ -2098,8 +2258,16 @@
|
||||
"logs": {
|
||||
"caption": "Napló",
|
||||
"clear": "Törlés",
|
||||
"custom_integration": "egyedi integráció",
|
||||
"description": "A Home Assistant naplófájlok megtekintése",
|
||||
"details": "Naplóbejegyzés részletei ({level})",
|
||||
"level": {
|
||||
"critical": "KRITIKUS HIBA",
|
||||
"debug": "DEBUG",
|
||||
"error": "HIBA",
|
||||
"info": "INFÓ",
|
||||
"warning": "FIGYELMEZTETÉS"
|
||||
},
|
||||
"load_full_log": "Teljes Home Assistant napló betöltése",
|
||||
"loading_log": "Hibanapló betöltése...",
|
||||
"multiple_messages": "az üzenet először a következő időpontban fordult elő: {time}, majd később {counter} alkalommal ismétlődött",
|
||||
@ -2377,6 +2545,8 @@
|
||||
"add_scene": "Jelenet hozzáadása",
|
||||
"delete_confirm": "Biztosan törölni szeretnéd ezt a jelenetet?",
|
||||
"delete_scene": "Jelenet törlése",
|
||||
"duplicate": "Másolás",
|
||||
"duplicate_scene": "Jelenet duplikálása",
|
||||
"edit_scene": "Jelenet szerkesztése",
|
||||
"header": "Jelenet szerkesztő",
|
||||
"headers": {
|
||||
@ -2444,39 +2614,39 @@
|
||||
"description": "A Home Assistant szerver újraindítása és leállítása",
|
||||
"section": {
|
||||
"reloading": {
|
||||
"automation": "Automatizálások újratöltése",
|
||||
"command_line": "Parancssori entitások újratöltése",
|
||||
"core": "Lokáció és testreszabások újratöltése",
|
||||
"filesize": "Fájlméret entitások újratöltése",
|
||||
"filter": "Szűrőentitások újratöltése",
|
||||
"generic": "Általános IP kamera entitások újratöltése",
|
||||
"generic_thermostat": "Általános termosztát entitások újratöltése",
|
||||
"group": "Csoportok, csoport entitások és értesítési szolgáltatások újratöltése",
|
||||
"automation": "Automatizálások",
|
||||
"command_line": "Parancssori entitások",
|
||||
"core": "Lokáció és testreszabások",
|
||||
"filesize": "Fájlméret entitások",
|
||||
"filter": "Szűrőentitások",
|
||||
"generic": "Általános IP kamera entitások",
|
||||
"generic_thermostat": "Általános termosztát entitások",
|
||||
"group": "Csoportok, csoport entitások és értesítési szolgáltatások",
|
||||
"heading": "YAML konfiguráció újratöltése",
|
||||
"history_stats": "Előzmény statisztika entitások újratöltése",
|
||||
"homekit": "HomeKit újratöltése",
|
||||
"input_boolean": "Logikai változó bemenetek újratöltése",
|
||||
"input_datetime": "Időpont bemenetek újratöltése",
|
||||
"input_number": "Szám bemenetek újratöltése",
|
||||
"input_select": "Választási bemenetek újratöltése",
|
||||
"input_text": "Szöveg bemenetek újratöltése",
|
||||
"history_stats": "Előzmény statisztika entitások",
|
||||
"homekit": "HomeKit",
|
||||
"input_boolean": "Logikai változó bemenetek",
|
||||
"input_datetime": "Időpont bemenetek",
|
||||
"input_number": "Szám bemenetek",
|
||||
"input_select": "Választási bemenetek",
|
||||
"input_text": "Szöveg bemenetek",
|
||||
"introduction": "A Home Assistant bizonyos részei újraindítás nélkül újratölthetőek. Újratöltéskor az aktuálisan betöltött YAML konfiguráció helyére betöltődik az új.",
|
||||
"min_max": "Min/max entitások újratöltése",
|
||||
"mqtt": "Manuálisan konfigurált MQTT entitások újratöltése",
|
||||
"person": "Személyek újratöltése",
|
||||
"ping": "Ping bináris érzékelő entitások újratöltése",
|
||||
"reload": "{domain} újratöltése",
|
||||
"rest": "Rest entitások és értesítési szolgáltatások újratöltése",
|
||||
"rpi_gpio": "Raspberry Pi GPIO entitások újratöltése",
|
||||
"scene": "Jelenetek újratöltése",
|
||||
"script": "Szkriptek újratöltése",
|
||||
"smtp": "SMTP értesítési szolgáltatások újratöltése",
|
||||
"statistics": "Statisztikai entitások újratöltése",
|
||||
"telegram": "Telegram értesítési szolgáltatások újratöltése",
|
||||
"template": "Sablon entitások újratöltése",
|
||||
"trend": "Trend entitások újratöltése",
|
||||
"universal": "Univerzális médialejátszó entitások újratöltése",
|
||||
"zone": "Zónák újratöltése"
|
||||
"min_max": "Min/max entitások",
|
||||
"mqtt": "Manuálisan konfigurált MQTT entitások",
|
||||
"person": "Személyek",
|
||||
"ping": "Ping bináris érzékelő entitások",
|
||||
"reload": "{domain}",
|
||||
"rest": "Rest entitások és értesítési szolgáltatások",
|
||||
"rpi_gpio": "Raspberry Pi GPIO entitások",
|
||||
"scene": "Jelenetek",
|
||||
"script": "Szkriptek",
|
||||
"smtp": "SMTP értesítési szolgáltatások",
|
||||
"statistics": "Statisztikai entitások",
|
||||
"telegram": "Telegram értesítési szolgáltatások",
|
||||
"template": "Sablon entitások",
|
||||
"trend": "Trend entitások",
|
||||
"universal": "Univerzális médialejátszó entitások",
|
||||
"zone": "Zónák"
|
||||
},
|
||||
"server_management": {
|
||||
"confirm_restart": "Biztosan újra szeretnéd indítani a Home Assistant-ot?",
|
||||
@ -2698,24 +2868,41 @@
|
||||
},
|
||||
"zwave_js": {
|
||||
"add_node": {
|
||||
"inclusion_finished": "A csomópont hozzá lett adva.",
|
||||
"title": "Adjon hozzá egy Z-Wave csomópontot",
|
||||
"view_device": "Eszköz megtekintése"
|
||||
},
|
||||
"button": "Konfigurálja",
|
||||
"common": {
|
||||
"add_node": "Node hozzáadása",
|
||||
"close": "Bezárás",
|
||||
"home_id": "Otthon azonosító",
|
||||
"network": "Hálózat",
|
||||
"node_id": "Node azonosító",
|
||||
"reconfigure_server": "Kiszolgáló újrakonfigurálása",
|
||||
"remove_node": "Node eltávolítása"
|
||||
},
|
||||
"dashboard": {
|
||||
"driver_version": "Illesztőprogram verzió",
|
||||
"dump_not_ready_confirm": "Letöltés",
|
||||
"header": "Kezelje Z-Wave hálózatát",
|
||||
"home_id": "Otthon azonosító",
|
||||
"nodes_ready": "Csomópontok készen állnak",
|
||||
"server_version": "Szerver verzió"
|
||||
},
|
||||
"device_info": {
|
||||
"device_config": "Eszköz beállítása"
|
||||
"device_config": "Eszköz beállítása",
|
||||
"node_ready": "Csomópont készen áll",
|
||||
"node_status": "Csomópont állapota",
|
||||
"zwave_info": "Z-Wave infó"
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Naplószint",
|
||||
"log_level_changed": "A naplószint megváltozott erre: {level}",
|
||||
"title": "Z-Wave JS naplók"
|
||||
},
|
||||
"navigation": {
|
||||
"logs": "Naplók",
|
||||
"network": "Hálózat"
|
||||
},
|
||||
"network_status": {
|
||||
@ -2727,10 +2914,21 @@
|
||||
"error_device_not_found": "Az eszköz nem található",
|
||||
"header": "Z-Wave eszköz konfigurálása",
|
||||
"parameter_is_read_only": "Ez a paraméter csak olvasható",
|
||||
"set_param_error": "Hiba történt."
|
||||
"set_param_accepted": "A paraméter frissült.",
|
||||
"set_param_error": "Hiba történt.",
|
||||
"zwave_js_device_database": "Z-Wave JS eszközadatbázis"
|
||||
},
|
||||
"node_status": {
|
||||
"alive": "Él",
|
||||
"asleep": "Alszik",
|
||||
"awake": "Ébren",
|
||||
"dead": "Halott",
|
||||
"unknown": "Ismeretlen"
|
||||
},
|
||||
"remove_node": {
|
||||
"cancel_exclusion": "A kizárás megszakítása",
|
||||
"start_exclusion": "Kizárás indítása",
|
||||
"title": "Z-Wave csomópont eltávolítása"
|
||||
}
|
||||
},
|
||||
"zwave": {
|
||||
@ -2937,7 +3135,11 @@
|
||||
"cards": {
|
||||
"actions": {
|
||||
"action_confirmation": "Biztosan futtatod a(z) \"{action}\" műveletet?",
|
||||
"no_service": "Nincs megadva futtatandó szolgáltatás"
|
||||
"no_entity_more_info": "A további információkhoz nincs megadva entitás",
|
||||
"no_entity_toggle": "A váltáshoz nincs megadva entitás",
|
||||
"no_navigation_path": "Nincs megadva navigációs útvonal",
|
||||
"no_service": "Nincs megadva futtatandó szolgáltatás",
|
||||
"no_url": "Nincs megadva megnyitandó URL"
|
||||
},
|
||||
"confirm_delete": "Biztosan törölni szeretnéd ezt a kártyát?",
|
||||
"empty_state": {
|
||||
@ -3549,6 +3751,9 @@
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"analytics": {
|
||||
"finish": "Következő"
|
||||
},
|
||||
"core-config": {
|
||||
"button_detect": "Észlelés",
|
||||
"finish": "Tovább",
|
||||
@ -3558,17 +3763,28 @@
|
||||
"location_name": "A Home Assistant rendszered neve",
|
||||
"location_name_default": "Otthon"
|
||||
},
|
||||
"finish": "Befejezés",
|
||||
"integration": {
|
||||
"finish": "Befejezés",
|
||||
"intro": "Az eszközöket és szolgáltatásokat a Home Assistant integrációként kezeli. Beállíthatod őket most, vagy később a konfigurációs képernyőn.",
|
||||
"more_integrations": "Több"
|
||||
},
|
||||
"intro": "Készen állsz arra, hogy felébreszd az otthonod, visszaszerezd a magánéleted és csatlakozz egy világhálós közösséghez?",
|
||||
"next": "Következő",
|
||||
"restore": {
|
||||
"addons": "Kiegészítők",
|
||||
"confirm_password": "Erősítse meg a Pillanatkép jelszavát",
|
||||
"description": "Alternatív megoldásként visszaállíthatsz egy korábbi pillanatképet.",
|
||||
"folders": "Mappák",
|
||||
"full_snapshot": "Teljes pillanatkép",
|
||||
"hide_log": "Teljes napló elrejtése",
|
||||
"in_progress": "Visszaállítás folyamatban",
|
||||
"show_log": "Teljes napló megjelenítése"
|
||||
"partial_snapshot": "Részleges pillanatkép",
|
||||
"password": "Snapshot jelszó",
|
||||
"password_protection": "Jelszóvédelem",
|
||||
"select_type": "Válassza ki a visszaállítandó elemeket",
|
||||
"show_log": "Teljes napló megjelenítése",
|
||||
"type": "Pillanatkép típus"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Fiók Létrehozása",
|
||||
@ -3658,6 +3874,18 @@
|
||||
"enable": "Engedélyez",
|
||||
"header": "Többfaktoros Hitelesítési Modulok"
|
||||
},
|
||||
"number_format": {
|
||||
"dropdown_label": "Számformátum",
|
||||
"formats": {
|
||||
"comma_decimal": "1 234 567,89",
|
||||
"decimal_comma": "1.234.567,89",
|
||||
"language": "Automatikus (nyelvi beállítás használata)",
|
||||
"none": "Nincs",
|
||||
"space_comma": "1 234 567,89",
|
||||
"system": "Használja a rendszer területi beállításait"
|
||||
},
|
||||
"header": "Számformátum"
|
||||
},
|
||||
"push_notifications": {
|
||||
"add_device_prompt": {
|
||||
"input_label": "Eszköz neve",
|
||||
@ -3699,6 +3927,17 @@
|
||||
"primary_color": "Elsődleges szín",
|
||||
"reset": "Visszaállítás"
|
||||
},
|
||||
"time_format": {
|
||||
"description": "Válaszd ki az időformátumot.",
|
||||
"dropdown_label": "Időformátum",
|
||||
"formats": {
|
||||
"12": "12 óra (AM/PM)",
|
||||
"24": "24 óra",
|
||||
"language": "Automatikus (nyelvi beállítás használata)",
|
||||
"system": "Rendszer területi beállításainak használata"
|
||||
},
|
||||
"header": "Időformátum"
|
||||
},
|
||||
"vibrate": {
|
||||
"description": "Rezgés engedélyezése vagy tiltása ezen az eszközön az eszközök vezérlésekor.",
|
||||
"header": "Rezgés"
|
||||
|
@ -377,7 +377,7 @@
|
||||
"error_addon_no_ingress": "Il componente aggiuntivo richiesto non supporta l'ingresso",
|
||||
"error_addon_not_found": "Componente aggiuntivo non trovato",
|
||||
"error_addon_not_installed": "Il componente aggiuntivo richiesto non è installato. Si prega di installarlo prima di proseguire",
|
||||
"error_addon_not_started": "Il componente aggiuntivo non è in esecuzione. Si prega di avviarlo prima di proseguire",
|
||||
"error_addon_not_started": "Il componente aggiuntivo richiesto non è in esecuzione. Per favore, avvialo prima",
|
||||
"faq_link": "My Home Assistant FAQ",
|
||||
"not_supported": "Questo reindirizzamento non è supportato dall'istanza di Home Assistant. Controlla il {link} per i reindirizzamenti supportati e la versione in cui sono stati introdotti."
|
||||
},
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "è stato scollegato",
|
||||
"was_unsafe": "non era sicuro"
|
||||
},
|
||||
"retrieval_error": "Errore durante il recupero della voce del registro",
|
||||
"show_trace": "Mostra la traccia"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "ID Home",
|
||||
"network": "Rete",
|
||||
"node_id": "ID Nodo",
|
||||
"reconfigure_server": "Riconfigurare il server",
|
||||
"remove_node": "Rimuovi Nodo"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -312,6 +312,14 @@
|
||||
"no_addons": "まだアドオンがインストールされていません。アドオンストアにアクセスして始めましょう!"
|
||||
},
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "属性",
|
||||
"device_path": "デバイスパス",
|
||||
"id": "ID",
|
||||
"search": "ハードウェアの検索",
|
||||
"subsystem": "サブシステム",
|
||||
"title": "ハードウェア"
|
||||
},
|
||||
"network": {
|
||||
"connected_to": "{ssid}に接続しました",
|
||||
"dhcp": "DHCP",
|
||||
@ -360,7 +368,10 @@
|
||||
},
|
||||
"my": {
|
||||
"error": "不明なエラーが発生しました",
|
||||
"error_addon_no_ingress": "要求されたアドオンは入力をサポートしていません",
|
||||
"error_addon_not_found": "アドオンが見つかりません",
|
||||
"error_addon_not_installed": "要求されたアドオンがインストールされていません。先にインストールしてください。",
|
||||
"error_addon_not_started": "要求されたアドオンは実行されていません。最初に起動してください",
|
||||
"faq_link": "マイホームアシスタントFAQ",
|
||||
"not_supported": "このリダイレクトは、HomeAssistantインスタンスではサポートされていません。 サポートされているリダイレクトとそれらが導入されたバージョンについては、{link}を確認してください。"
|
||||
},
|
||||
@ -373,13 +384,19 @@
|
||||
"snapshot": {
|
||||
"addons": "アドオン",
|
||||
"available_snapshots": "利用可能なスナップショット",
|
||||
"confirm_password": "スナップショットパスワードの確認",
|
||||
"could_not_create": "スナップショットを作成できませんでした。",
|
||||
"create": "作成",
|
||||
"create_blocked_not_running": "システムが {state} 状態であるため、スナップショットの作成は現在できません。",
|
||||
"create_snapshot": "スナップショットの作成",
|
||||
"created": "作成",
|
||||
"delete_selected": "選択したスナップショットの削除",
|
||||
"delete_snapshot_confirm": "削除",
|
||||
"delete_snapshot_text": "{number}を削除しますか?{number, plural,\n one {snapshot}\n other {snapshots}\n}?",
|
||||
"delete_snapshot_title": "スナップショットの削除",
|
||||
"description": "スナップショットでは、Home Assistantインスタンスのすべてのデータを簡単にバックアップして復元することができます。",
|
||||
"enter_password": "パスワードを入力してください。",
|
||||
"failed_to_delete": "削除できませんでした",
|
||||
"folder": {
|
||||
"addons/local": "ローカルアドオン",
|
||||
"homeassistant": "HomeAssistantの設定",
|
||||
@ -395,7 +412,10 @@
|
||||
"password": "パスワード",
|
||||
"password_protected": "パスワードで保護されています",
|
||||
"password_protection": "パスワード保護",
|
||||
"passwords_not_matching": "パスワードが一致しません",
|
||||
"security": "セキュリティ",
|
||||
"select_type": "復元する対象を選択する",
|
||||
"selected": "{number}が選択されました",
|
||||
"type": "タイプ",
|
||||
"upload_snapshot": "スナップショットのアップロード"
|
||||
},
|
||||
@ -555,8 +575,11 @@
|
||||
},
|
||||
"light": {
|
||||
"brightness": "明るさ",
|
||||
"cold_white_value": "冷たい白い明るさ",
|
||||
"color_brightness": "色の明るさ",
|
||||
"color_temperature": "色温度",
|
||||
"effect": "効果",
|
||||
"warm_white_value": "暖かい白い明るさ",
|
||||
"white_value": "白さの値"
|
||||
},
|
||||
"lock": {
|
||||
@ -708,6 +731,9 @@
|
||||
"no_match": "一致するエリアが見つかりません",
|
||||
"show_areas": "エリアを表示"
|
||||
},
|
||||
"attributes": {
|
||||
"expansion_header": "属性"
|
||||
},
|
||||
"blueprint-picker": {
|
||||
"add_user": "ユーザーの追加",
|
||||
"remove_user": "ユーザーの削除",
|
||||
@ -786,6 +812,7 @@
|
||||
"was_unplugged": "プラグが抜かれました",
|
||||
"was_unsafe": "安全ではなかった"
|
||||
},
|
||||
"retrieval_error": "ログ ブックエントリの取得中にエラーが発生しました",
|
||||
"show_trace": "トレースの表示"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -835,6 +862,12 @@
|
||||
"label": "画像",
|
||||
"unsupported_format": "サポートされていない形式です。JPEG、PNG、またはGIF画像を選択してください。"
|
||||
},
|
||||
"related-filter-menu": {
|
||||
"filter_by_area": "エリアで絞り込む",
|
||||
"filter_by_device": "デバイスで絞り込む",
|
||||
"filtered_by_area": "エリア: {area_name}",
|
||||
"filtered_by_device": "デバイス: {device_name}"
|
||||
},
|
||||
"related-items": {
|
||||
"area": "エリア",
|
||||
"automation": "次の自動化の一部",
|
||||
@ -872,6 +905,7 @@
|
||||
}
|
||||
},
|
||||
"service-control": {
|
||||
"integration_doc": "統合ドキュメント",
|
||||
"required": "この項目は必須です",
|
||||
"service_data": "サービスデータ",
|
||||
"target": "ターゲット",
|
||||
@ -900,6 +934,9 @@
|
||||
"config_entry_system_options": {
|
||||
"enable_new_entities_description": "無効にするには、 {integration} から検出された新しいエンティティは Home Assistant に自動的に追加されません。",
|
||||
"enable_new_entities_label": "新しく追加されたエンティティを有効にします。",
|
||||
"enable_polling_description": "ホームアシスタントが{integration}エンティティの更新情報を自動的にポーリングする場合。",
|
||||
"enable_polling_label": "更新のポーリングを有効にします。",
|
||||
"restart_home_assistant": "変更を有効にするには、HomeAssistantを再起動する必要があります。",
|
||||
"title": "{integration} のシステムオプション",
|
||||
"update": "更新"
|
||||
},
|
||||
@ -1133,7 +1170,8 @@
|
||||
},
|
||||
"types": {
|
||||
"navigation": "ナビゲート",
|
||||
"reload": "リロード"
|
||||
"reload": "リロード",
|
||||
"server_control": "サーバー"
|
||||
}
|
||||
},
|
||||
"filter_placeholder": "エンティティフィルター"
|
||||
@ -1178,7 +1216,22 @@
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"heading": "デバイスを再設定"
|
||||
"attribute": "属性",
|
||||
"battery_device_warning": "再構成プロセスを開始する前に、バッテリ駆動デバイスをスリープ解除する必要があります。デバイスのスリープ解除方法については、デバイスのマニュアルを参照してください。",
|
||||
"bind_header": "バインディング",
|
||||
"button_hide": "詳細を非表示",
|
||||
"button_show": "詳細を表示",
|
||||
"cluster_header": "クラスター",
|
||||
"configuration_complete": "デバイスの再構成が完了しました。",
|
||||
"configuration_failed": "デバイスの再構成に失敗しました。追加情報はログで入手できる場合があります。",
|
||||
"configuring_alt": "設定",
|
||||
"heading": "デバイスを再設定",
|
||||
"in_progress": "デバイスを再構成中です。これには時間がかかる場合があります。",
|
||||
"introduction": "Zigbee ネットワーク上のデバイスを再構成します。デバイスが正常に機能していない場合は、この機能を使用します。",
|
||||
"min_max_change": "最小/最大/変更",
|
||||
"reporting_header": "報告",
|
||||
"run_in_background": "このダイアログを閉じても、再設定はバックグラウンドで行われます。",
|
||||
"start_reconfiguration": "再構成を開始"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -1224,12 +1277,12 @@
|
||||
"notification_toast": {
|
||||
"connection_lost": "接続が切れました。再接続中...",
|
||||
"dismiss": "閉じる",
|
||||
"integration_starting": "{integration}を開始すると、完了するまですべてが利用できるわけではありません。",
|
||||
"integration_starting": "{integration}の開始、完了するまですべてが利用できるわけではありません。",
|
||||
"service_call_failed": "サービス{service}の呼び出しに失敗しました。",
|
||||
"started": "ホームアシスタントが開始されました!",
|
||||
"starting": "Home Assistantが起動中です。全て利用可能なるまでもうしばらくお待ち下さい。",
|
||||
"triggered": "トリガーしました {name}",
|
||||
"wrapping_up_startup": "スタートアップの締めくくりとして、終了するまですべてが利用できるわけではありません。"
|
||||
"wrapping_up_startup": "起動開始、起動完了するまですべてが利用できるわけではありません。"
|
||||
},
|
||||
"panel": {
|
||||
"config": {
|
||||
@ -1241,7 +1294,8 @@
|
||||
"caption": "エリア",
|
||||
"data_table": {
|
||||
"area": "エリア",
|
||||
"devices": "デバイス"
|
||||
"devices": "デバイス",
|
||||
"entities": "エンティティ"
|
||||
},
|
||||
"delete": {
|
||||
"confirmation_text": "このエリアのすべてのデバイスは割り当て解除されます。",
|
||||
@ -1253,8 +1307,10 @@
|
||||
"create": "作成",
|
||||
"default_name": "新しいエリア",
|
||||
"delete": "削除",
|
||||
"linked_entities_caption": "エンティティ",
|
||||
"name": "名前",
|
||||
"name_required": "名前は必須です",
|
||||
"no_linked_entities": "このエリアにリンクしているエンティティはありません。",
|
||||
"unknown_error": "不明なエラー",
|
||||
"update": "更新"
|
||||
},
|
||||
@ -1679,6 +1735,7 @@
|
||||
"title": "Alexa"
|
||||
},
|
||||
"connected": "接続",
|
||||
"connecting": "接続しています...",
|
||||
"connection_status": "クラウド接続の状態",
|
||||
"fetching_subscription": "サブスクリプションを取得しています。",
|
||||
"google": {
|
||||
@ -1709,10 +1766,16 @@
|
||||
"remote": {
|
||||
"access_is_being_prepared": "リモート アクセスの準備中です。準備ができたらお知らせします。",
|
||||
"certificate_info": "証明書情報",
|
||||
"connected": "接続",
|
||||
"info": "Home Assistant Cloud は、自宅の外からインスタンスへの安全なリモート接続を提供します。",
|
||||
"instance_is_available": "インスタンスは次の場所にあります",
|
||||
"instance_will_be_available": "インスタンスは次の時点で利用可能になります。",
|
||||
"link_learn_how_it_works": "仕組みを学ぶ",
|
||||
"not_connected": "接続されていません",
|
||||
"remote_enabled": {
|
||||
"caption": "自動的に接続",
|
||||
"description": "このオプションを有効にすると、ホームアシスタントのインスタンスが常にリモートでアクセスできるようになります。"
|
||||
},
|
||||
"title": "リモートコントロール"
|
||||
},
|
||||
"sign_out": "サインアウト",
|
||||
@ -2016,7 +2079,8 @@
|
||||
"scripts": "スクリプト",
|
||||
"unknown_error": "不明なエラー",
|
||||
"unnamed_device": "名前のないデバイス",
|
||||
"update": "更新"
|
||||
"update": "更新",
|
||||
"update_device_error": "デバイスの更新に失敗しました"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "エンティティ",
|
||||
@ -2123,6 +2187,7 @@
|
||||
"license": "Apache 2.0ライセンスの下で公開",
|
||||
"path_configuration": "ディスク上に configuration.yaml へのパス: {path}",
|
||||
"server": "サーバー",
|
||||
"setup_time": "設定時間",
|
||||
"source": "ソース:",
|
||||
"system_health_error": "「システムの正常性」コンポーネントが有効されていません、configuration.yaml に 'system_health:' を追加してください。",
|
||||
"system_health": {
|
||||
@ -2141,10 +2206,14 @@
|
||||
"caption": "インテグレーション",
|
||||
"config_entry": {
|
||||
"area": "{area}",
|
||||
"check_the_logs": "ログの確認",
|
||||
"configure": "設定",
|
||||
"delete": "削除",
|
||||
"delete_confirm": "この統合を削除しますか?",
|
||||
"depends_on_cloud": "クラウドに依存",
|
||||
"device_unavailable": "デバイスを利用できません",
|
||||
"devices": "{count} {count, plural,\n one {デバイス}\n other {デバイス}\n}",
|
||||
"disable_error": "統合の有効化または無効化に失敗しました",
|
||||
"disable_restart_confirm": "Homeassistantを再起動して、このintegrationを無効にします。",
|
||||
"disable": {
|
||||
"disable_confirm": "この設定項目を無効にしてよろしいですか?そのデバイスやエンティティは無効になります。",
|
||||
@ -2156,6 +2225,7 @@
|
||||
},
|
||||
"disabled_cause": "{cause}によって無効化"
|
||||
},
|
||||
"disabled_polling": "更新されたデータの自動ポーリングが無効",
|
||||
"documentation": "ドキュメント",
|
||||
"enable_restart_confirm": "Home Assistantを再起動して、このintegrationを有効にします",
|
||||
"entities": "{count} {count, plural,\n one {エンティティ}\n other {エントリー}\n}",
|
||||
@ -2167,12 +2237,21 @@
|
||||
"no_area": "エリアなし",
|
||||
"not_loaded": "読み込まれていない場合は{logs_link} を確認してください",
|
||||
"options": "オプション",
|
||||
"provided_by_custom_integration": "カスタムインテグレーションによる提供",
|
||||
"reload": "再読込",
|
||||
"reload_confirm": "インテグレーションが再読み込みされました",
|
||||
"reload_restart_confirm": "ホームアシスタントを再起動してこのインテグレーションの再読み込みを完了してください。\n",
|
||||
"rename": "名前を変更",
|
||||
"restart_confirm": "ホーム アシスタントを再起動して、この統合の削除を完了します。",
|
||||
"services": "{count} {count, plural,\n one {サービス}\n other {サービス}\n}",
|
||||
"state": {
|
||||
"failed_unload": "アンロードに失敗しました。",
|
||||
"loaded": "読み込み",
|
||||
"migration_error": "移行エラー",
|
||||
"not_loaded": "読み込まれていません",
|
||||
"setup_error": "セットアップに失敗しました。",
|
||||
"setup_retry": "設定をやり直す"
|
||||
},
|
||||
"system_options": "システムオプション",
|
||||
"unnamed_entry": "名前のないエントリ"
|
||||
},
|
||||
@ -2190,6 +2269,7 @@
|
||||
},
|
||||
"finish": "完了",
|
||||
"loading_first_time": "インテグレーションのインストールを完了するまでお待ちください",
|
||||
"next": "次",
|
||||
"not_all_required_fields": "必須フィールドの一覧に入力するわけではありません。",
|
||||
"not_loaded": "インテグレーションファイルを読み込めませんでした。ホームアシスタントを再起動してください。",
|
||||
"pick_flow_step": {
|
||||
@ -2528,6 +2608,8 @@
|
||||
"add_scene": "シーンを追加",
|
||||
"delete_confirm": "このシーンを削除してもよろしいですか?",
|
||||
"delete_scene": "シーンを削除",
|
||||
"duplicate": "複製",
|
||||
"duplicate_scene": "シーンの複製",
|
||||
"edit_scene": "シーンを編集",
|
||||
"header": "シーンエディター",
|
||||
"headers": {
|
||||
@ -2761,9 +2843,12 @@
|
||||
"value": "バリュー"
|
||||
},
|
||||
"configuration_page": {
|
||||
"shortcuts_title": "ショートカット",
|
||||
"update_button": "構成の更新",
|
||||
"zha_options": {
|
||||
"default_light_transition": "デフォルトのライト遷移時間(秒)",
|
||||
"enable_identify_on_join": "デバイスがネットワークに参加する際に、識別効果を有効にする"
|
||||
"enable_identify_on_join": "デバイスがネットワークに参加する際に、識別効果を有効にする",
|
||||
"title": "グローバルオプション"
|
||||
}
|
||||
},
|
||||
"device_pairing_card": {
|
||||
@ -2853,6 +2938,8 @@
|
||||
"follow_device_instructions": "デバイスに付属の指示に従って、デバイスでペアリングを開始します。",
|
||||
"inclusion_failed": "ノードを追加できませんでした。詳細については、ログを確認してください。",
|
||||
"inclusion_finished": "ノードが追加されました。ノードのバックグラウンドでの設定が完了すると、すべてのエンティティが表示されるまで数分かかる場合があります。",
|
||||
"interview_failed": "デバイスのインタビューに失敗しました。追加情報がログに残っている場合があります。",
|
||||
"interview_started": "デバイスはインタビュー中です。これには時間がかかる場合があります。",
|
||||
"introduction": "このウィザードでは、Z-Wave ネットワークにノードを追加する手順を説明します。",
|
||||
"secure_inclusion_warning": "セキュアなデバイスには、追加の帯域幅が必要です。安全なデバイスが多すぎると、Z-Waveネットワークが遅くなる可能性があります。ロックやガレージドアオープナーなど、必要なデバイスにのみ安全な組み込みを使用することをお勧めします。",
|
||||
"start_inclusion": "インクルージョンを開始",
|
||||
@ -2868,6 +2955,7 @@
|
||||
"home_id": "ホームID",
|
||||
"network": "ネットワーク",
|
||||
"node_id": "ノードID",
|
||||
"reconfigure_server": "サーバーの再設定",
|
||||
"remove_node": "ノードの削除"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -2890,7 +2978,14 @@
|
||||
"node_status": "ノードの状態",
|
||||
"zwave_info": "Z-Wave情報"
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "ログレベル",
|
||||
"log_level_changed": "ログレベルが変更されました。{level}に変更されました。",
|
||||
"subscribed_to_logs": "Z-WaveJSログメッセージを購読しました...",
|
||||
"title": "Z-WaveJSログ"
|
||||
},
|
||||
"navigation": {
|
||||
"logs": "ログ",
|
||||
"network": "ネットワーク"
|
||||
},
|
||||
"network_status": {
|
||||
@ -2905,6 +3000,9 @@
|
||||
"header": "Z-Waveデバイスの設定",
|
||||
"introduction": "選択したデバイスのデバイス(ノード)固有の設定パラメータを管理および調整する",
|
||||
"parameter_is_read_only": "このパラメータは読み取り専用です。",
|
||||
"set_param_accepted": "パラメータが更新されました。",
|
||||
"set_param_error": "エラーが発生しました。",
|
||||
"set_param_queued": "パラメーターの変更はキューに入れられており、デバイスがスリープ解除されると更新されます。",
|
||||
"zwave_js_device_database": "Z-Wave JSデバイスデータベース"
|
||||
},
|
||||
"node_status": {
|
||||
@ -2915,6 +3013,13 @@
|
||||
"unknown": "不明"
|
||||
},
|
||||
"reinterview_node": {
|
||||
"battery_device_warning": "再会する前に、バッテリー駆動のデバイスをスリープ解除する必要があります。デバイスをウェイクアップする方法については、デバイスのマニュアルを参照してください。",
|
||||
"in_progress": "デバイスがインタビュー中です。これには時間がかかる場合があります。",
|
||||
"interview_complete": "デバイスのインタビューが完了しました。",
|
||||
"interview_failed": "デバイスのインタビューに失敗しました。追加情報がログに残っている場合があります。",
|
||||
"introduction": "Z-Wave ネットワーク上のデバイスに再インタビューします。デバイスの機能が不足している場合や、機能が正しくない場合は、この機能を使用します。",
|
||||
"run_in_background": "このダイアログを閉じても、インタビューはバックグラウンドで継続されます。",
|
||||
"start_reinterview": "再インタビューを開始",
|
||||
"title": "Z-Waveデバイスへの再インタビュー"
|
||||
},
|
||||
"remove_node": {
|
||||
@ -3056,6 +3161,7 @@
|
||||
"column_parameter": "パラメータ",
|
||||
"description": "サービス開発ツールを使用すると、ホームアシスタントで利用可能なサービスを呼び出すことができます。",
|
||||
"fill_example_data": "データ記入例",
|
||||
"no_template_ui_support": "UIはテンプレートをサポートしていませんが、YAMLエディタを使用することができます。",
|
||||
"title": "サービス",
|
||||
"ui_mode": "UIモードに移動します",
|
||||
"yaml_mode": "YAMLモードに移動します",
|
||||
@ -3777,10 +3883,19 @@
|
||||
"intro": "あなたはあなたの家を目覚めさせ、あなたのプライバシーを取り戻し、ティンカーの世界的なコミュニティに参加する準備ができていますか?",
|
||||
"next": "次",
|
||||
"restore": {
|
||||
"addons": "アドオン",
|
||||
"confirm_password": "スナップショットパスワードの確認",
|
||||
"description": "以前のスナップショットから復元することもできます。",
|
||||
"folders": "フォルダ",
|
||||
"full_snapshot": "フルスナップショット",
|
||||
"hide_log": "ログ全体を非表示",
|
||||
"in_progress": "復元中",
|
||||
"show_log": "ログ全体を表示"
|
||||
"partial_snapshot": "部分的スナップショット",
|
||||
"password": "スナップショットパスワード",
|
||||
"password_protection": "パスワード保護",
|
||||
"select_type": "何を復元するかを選択します",
|
||||
"show_log": "ログ全体を表示",
|
||||
"type": "スナップショットタイプ"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "アカウントの作成",
|
||||
@ -3924,6 +4039,17 @@
|
||||
"primary_color": "プライマリーの色",
|
||||
"reset": "リセット"
|
||||
},
|
||||
"time_format": {
|
||||
"description": "時刻のフォーマットを選択します。",
|
||||
"dropdown_label": "時間形式",
|
||||
"formats": {
|
||||
"12": "12時間(AM / PM)",
|
||||
"24": "24時間",
|
||||
"language": "自動 (言語設定を使用)",
|
||||
"system": "システムロケールを使用する"
|
||||
},
|
||||
"header": "時間形式"
|
||||
},
|
||||
"vibrate": {
|
||||
"description": "デバイスを制御するときに、このデバイスのバイブレーションを有効または無効にします。",
|
||||
"header": "バイブレーション機能"
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "의 플러그가 뽑혔습니다.",
|
||||
"was_unsafe": "이(가) 안전하지 않은 상태가 되었습니다."
|
||||
},
|
||||
"retrieval_error": "로그북 항목 검색 중 오류",
|
||||
"show_trace": "추적 표시하기"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "집 ID",
|
||||
"network": "네트워크",
|
||||
"node_id": "노드 ID",
|
||||
"reconfigure_server": "서버 재구성",
|
||||
"remove_node": "노드 제거하기"
|
||||
},
|
||||
"dashboard": {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "ble frakoblet",
|
||||
"was_unsafe": "var utrygt"
|
||||
},
|
||||
"retrieval_error": "Feil under henting av loggbokoppføring",
|
||||
"show_trace": "Vis spor"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "Hjem-ID",
|
||||
"network": "Nettverk",
|
||||
"node_id": "Node-ID",
|
||||
"reconfigure_server": "Konfigurer server på nytt",
|
||||
"remove_node": "Fjern node"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "is niet aangesloten",
|
||||
"was_unsafe": "was onveilig"
|
||||
},
|
||||
"retrieval_error": "Fout tijdens het ophalen van de logboekregel",
|
||||
"show_trace": "Tracering weergeven"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2089,7 +2090,7 @@
|
||||
"unknown_error": "Onbekende fout",
|
||||
"unnamed_device": "Naamloos apparaat",
|
||||
"update": "Bijwerken",
|
||||
"update_device_error": "Updaten van het apparaat mislukt"
|
||||
"update_device_error": "Bijwerken van het apparaat mislukt"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entiteiten",
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "Home ID",
|
||||
"network": "Netwerk",
|
||||
"node_id": "Knooppunt-ID",
|
||||
"reconfigure_server": "Herconfigureer server",
|
||||
"remove_node": "Knooppunt verwijderen"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -3147,7 +3149,7 @@
|
||||
"available_events": "Beschikbare gebeurtenissen",
|
||||
"count_listeners": " ({count} luisteraars)",
|
||||
"data": "Gebeurtenis data (YAML, optioneel)",
|
||||
"description": "Start een evenement op de Home Assistant-gebeurtenisbus",
|
||||
"description": "Vermeld een gebeurtenis in de gebeurtenisbus.",
|
||||
"documentation": "Gebeurtenissen documentatie",
|
||||
"event_fired": "Gebeurtenis {name} uitgevoerd",
|
||||
"fire_event": "Gebeurtenis uitvoeren",
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "nastąpiło odłączenie",
|
||||
"was_unsafe": "wykryto zagrożenie"
|
||||
},
|
||||
"retrieval_error": "Błąd podczas pobierania wpisów z dziennika",
|
||||
"show_trace": "Pokaż ślad"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "Identyfikator domu",
|
||||
"network": "Sieć",
|
||||
"node_id": "Identyfikator węzła",
|
||||
"reconfigure_server": "Ponownie skonfiguruj serwer",
|
||||
"remove_node": "Usuń węzeł"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -411,18 +411,18 @@
|
||||
"ssl": "SSL"
|
||||
},
|
||||
"folders": "Папки",
|
||||
"full_snapshot": "Все файлы",
|
||||
"full_snapshot": "Полный снимок",
|
||||
"name": "Название",
|
||||
"no_snapshots": "Не найдено ни одного снимка",
|
||||
"partial_snapshot": "Выбрать из списка",
|
||||
"partial_snapshot": "Частичный снимок",
|
||||
"password": "Пароль",
|
||||
"password_protected": "защищено паролем",
|
||||
"password_protection": "Защита паролем",
|
||||
"passwords_not_matching": "Пароли не совпадают",
|
||||
"security": "Безопасность",
|
||||
"select_type": "Выберите что нужно восстановить из этого снимка файловой системы",
|
||||
"select_type": "Выберите что нужно восстановить:",
|
||||
"selected": "Выбрано: {number}",
|
||||
"type": "Выберите что должен включать в себя снимок файловой системы",
|
||||
"type": "Тип снимка файловой системы:",
|
||||
"upload_snapshot": "Загрузить снимок на сервер"
|
||||
},
|
||||
"store": {
|
||||
@ -540,11 +540,11 @@
|
||||
"climate": {
|
||||
"aux_heat": "Дополнительный нагрев",
|
||||
"away_mode": "Режим \"не дома\"",
|
||||
"cooling": "{name} охлаждение",
|
||||
"current_temperature": "{name} текущая температура",
|
||||
"cooling": "Охлаждение {name}",
|
||||
"current_temperature": "Текущая температура {name}",
|
||||
"currently": "Сейчас",
|
||||
"fan_mode": "Режим вентиляции",
|
||||
"heating": "{name} обогрев",
|
||||
"heating": "Обогрев {name}",
|
||||
"high": "высокий",
|
||||
"low": "низкий",
|
||||
"on_off": "Вкл / Выкл",
|
||||
@ -553,8 +553,8 @@
|
||||
"swing_mode": "Режим качания воздушных шторок",
|
||||
"target_humidity": "Заданная влажность",
|
||||
"target_temperature": "Заданная температура",
|
||||
"target_temperature_entity": "{name} заданная температура",
|
||||
"target_temperature_mode": "{name} заданная температура {mode}"
|
||||
"target_temperature_entity": "Заданная температура {name}",
|
||||
"target_temperature_mode": "Заданная температура {name} в режиме {mode}"
|
||||
},
|
||||
"counter": {
|
||||
"actions": {
|
||||
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "отключается",
|
||||
"was_unsafe": "не регистрирует безопасность"
|
||||
},
|
||||
"retrieval_error": "Ошибка при получении записи в журнале событий",
|
||||
"show_trace": "Показать трассировку"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -1021,9 +1022,9 @@
|
||||
},
|
||||
"input_select": {
|
||||
"add": "Добавить",
|
||||
"add_option": "Добавить опцию",
|
||||
"no_options": "Добавьте доступные для выбора опции.",
|
||||
"options": "Опции"
|
||||
"add_option": "Добавить вариант",
|
||||
"no_options": "Добавьте доступные для выбора варианты.",
|
||||
"options": "Варианты"
|
||||
},
|
||||
"input_text": {
|
||||
"max": "Максимальная длина",
|
||||
@ -1263,7 +1264,7 @@
|
||||
"no_type_provided": "Тип не указан."
|
||||
},
|
||||
"supervisor": {
|
||||
"ask": "Обратиться за помощью",
|
||||
"ask": "Обратитесь за помощью",
|
||||
"observer": "Проверьте Observer",
|
||||
"reboot": "Попробуйте перезагрузить хост",
|
||||
"system_health": "Проверьте статус системы",
|
||||
@ -2961,9 +2962,11 @@
|
||||
"common": {
|
||||
"add_node": "Добавить узел",
|
||||
"close": "Закрыть",
|
||||
"heal_network": "Перенастройка сети",
|
||||
"home_id": "ID дома",
|
||||
"network": "Сеть",
|
||||
"node_id": "ID узла",
|
||||
"reconfigure_server": "Перенастроить сервер",
|
||||
"remove_node": "Удалить узел"
|
||||
},
|
||||
"dashboard": {
|
||||
@ -2986,6 +2989,18 @@
|
||||
"node_status": "Статус узла",
|
||||
"zwave_info": "Информация о Z-Wave"
|
||||
},
|
||||
"heal_network": {
|
||||
"healing_cancelled": "Перенастройка сети была отменена.",
|
||||
"healing_complete": "Перенастройка сети завершена.",
|
||||
"healing_failed": "Не удалось завершить перенастройку сети. Дополнительная информация может быть доступна в журналах.",
|
||||
"in_progress": "Идет перенастройка сети. Это займет некоторое время.",
|
||||
"introduction": "Перенастройка сети заставит все устройства пересчитать свои маршруты к контроллеру. Эта процедура рекомендуется, если Вы переместили в другое место устройства или контроллер.",
|
||||
"run_in_background": "Вы можете закрыть это диалоговое окно, перенастройка сети продолжится в фоновом режиме.",
|
||||
"start_heal": "Начать перенастройку",
|
||||
"stop_heal": "Остановить перенастройку",
|
||||
"title": "Перенастройка сети Z-Wave",
|
||||
"traffic_warning": "Во время перенастройки генерируется большой объем трафика в сети Z-Wave. В связи с этим, устройства могут отвечать с задержками (или вообще не отвечать)."
|
||||
},
|
||||
"logs": {
|
||||
"log_level": "Уровень",
|
||||
"log_level_changed": "Уровень журнала изменен на: {level}",
|
||||
@ -3112,8 +3127,8 @@
|
||||
"add_node": "Добавить узел",
|
||||
"add_node_secure": "Добавить защищенный узел",
|
||||
"cancel_command": "Отменить команду",
|
||||
"heal_network": "Исправить сеть",
|
||||
"heal_node": "Исправить узел",
|
||||
"heal_network": "Перенастройка сети",
|
||||
"heal_node": "Перенастроить узел",
|
||||
"node_info": "Информация об узле",
|
||||
"print_node": "Показать узел",
|
||||
"refresh_entity": "Обновить объект",
|
||||
@ -3216,7 +3231,7 @@
|
||||
"error": {
|
||||
"go_back": "Вернуться",
|
||||
"supervisor": {
|
||||
"ask": "Обратиться за помощью",
|
||||
"ask": "Обратитесь за помощью",
|
||||
"observer": "Проверить Observer",
|
||||
"reboot": "Попробуйте перезагрузить хост",
|
||||
"system_health": "Проверить статус системы",
|
||||
@ -3895,15 +3910,15 @@
|
||||
"confirm_password": "Подтверждение пароля",
|
||||
"description": "Восстановить предыдущее состояние из снимка файловой системы (snapshot)",
|
||||
"folders": "Папки",
|
||||
"full_snapshot": "Все файлы",
|
||||
"full_snapshot": "Полный снимок",
|
||||
"hide_log": "Скрыть журнал",
|
||||
"in_progress": "Восстановление системы",
|
||||
"partial_snapshot": "Выбрать из списка",
|
||||
"partial_snapshot": "Частичный снимок",
|
||||
"password": "Пароль",
|
||||
"password_protection": "Защита паролем",
|
||||
"select_type": "Выберите что нужно восстановить из этого снимка файловой системы",
|
||||
"select_type": "Выберите что нужно восстановить:",
|
||||
"show_log": "Показать журнал",
|
||||
"type": "Выберите что должен включать в себя снимок файловой системы"
|
||||
"type": "Тип снимка файловой системы:"
|
||||
},
|
||||
"user": {
|
||||
"create_account": "Создать учётную запись",
|
||||
|
@ -102,6 +102,25 @@
|
||||
"unknown": "Neznano"
|
||||
}
|
||||
},
|
||||
"supervisor": {
|
||||
"dialog": {
|
||||
"hardware": {
|
||||
"attributes": "Lastnosti",
|
||||
"id": "ID",
|
||||
"search": "Iskanje naprav",
|
||||
"subsystem": "Podsistem",
|
||||
"title": "Strojna oprema"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"created": "Ustvarjeno"
|
||||
},
|
||||
"system": {
|
||||
"supervisor": {
|
||||
"search": "Iskanje"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
"auth_store": {
|
||||
"ask": "Ali želite ostati prijavljeni?",
|
||||
@ -309,6 +328,9 @@
|
||||
"no_match": "Ni ustreznih območji",
|
||||
"show_areas": "Prikaži območja"
|
||||
},
|
||||
"attributes": {
|
||||
"expansion_header": "Lastnosti"
|
||||
},
|
||||
"blueprint-picker": {
|
||||
"add_user": "Dodaj uporabnika",
|
||||
"remove_user": "Odstrani uporabnika",
|
||||
@ -746,6 +768,10 @@
|
||||
"zha_device_card": {
|
||||
"device_name_placeholder": "Spremenite ime naprave"
|
||||
}
|
||||
},
|
||||
"zha_reconfigure_device": {
|
||||
"attribute": "Atribut",
|
||||
"button_show": "Pokaži podrobnosti"
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
@ -791,7 +817,8 @@
|
||||
"caption": "Območja",
|
||||
"data_table": {
|
||||
"area": "Območje",
|
||||
"devices": "Naprave"
|
||||
"devices": "Naprave",
|
||||
"entities": "Entitete"
|
||||
},
|
||||
"delete": {
|
||||
"confirmation_text": "Vse naprave na tem območju bodo postale nedodeljene.",
|
||||
@ -803,6 +830,7 @@
|
||||
"create": "Ustvari",
|
||||
"default_name": "Novo območje",
|
||||
"delete": "Izbriši",
|
||||
"linked_entities_caption": "Entitete",
|
||||
"name": "Ime",
|
||||
"name_required": "Ime je obvezno",
|
||||
"unknown_error": "Neznana napaka",
|
||||
@ -1214,6 +1242,7 @@
|
||||
"title": "Alexa"
|
||||
},
|
||||
"connected": "Povezan",
|
||||
"connecting": "Povezovanje...",
|
||||
"connection_status": "Stanje povezave v oblaku",
|
||||
"fetching_subscription": "Pridobivam naročnino...",
|
||||
"google": {
|
||||
@ -1242,6 +1271,7 @@
|
||||
"remote": {
|
||||
"access_is_being_prepared": "Oddaljeni dostop se pripravlja. Obvestili vas bomo, ko bo pripravljen.",
|
||||
"certificate_info": "Informacije o potrdilu",
|
||||
"connected": "Povezan",
|
||||
"info": "Home Assistant Cloud zagotavlja varno oddaljeno povezavo do vašega primerka, medtem ko ste zunaj doma.",
|
||||
"instance_is_available": "Vaš primerek je na voljo na",
|
||||
"instance_will_be_available": "Vaš primerek bo na voljo na",
|
||||
@ -1512,7 +1542,8 @@
|
||||
"scripts": "Skripte",
|
||||
"unknown_error": "Neznana napaka",
|
||||
"unnamed_device": "Neimenovana naprava",
|
||||
"update": "Posodobite"
|
||||
"update": "Posodobite",
|
||||
"update_device_error": "Posodabljanje naprave ni uspelo"
|
||||
},
|
||||
"entities": {
|
||||
"caption": "Entitete",
|
||||
@ -1983,6 +2014,7 @@
|
||||
"add_scene": "Dodajanje scene",
|
||||
"delete_confirm": "Ali ste prepričani, da želite izbrisati to sceno?",
|
||||
"delete_scene": "Brisanje scene",
|
||||
"duplicate": "Podvoji",
|
||||
"edit_scene": "Urejanje scene",
|
||||
"header": "Urejevalnik scen",
|
||||
"headers": {
|
||||
@ -2316,6 +2348,7 @@
|
||||
"zwave_info": "Z-Wave Informacije"
|
||||
},
|
||||
"navigation": {
|
||||
"logs": "Dnevniki",
|
||||
"network": "Omrežje"
|
||||
},
|
||||
"network_status": {
|
||||
@ -3271,6 +3304,12 @@
|
||||
"primary_color": "Primarna barva",
|
||||
"reset": "Ponastavi"
|
||||
},
|
||||
"time_format": {
|
||||
"formats": {
|
||||
"12": "12 ur (AM / PM)",
|
||||
"24": "24 ur"
|
||||
}
|
||||
},
|
||||
"vibrate": {
|
||||
"description": "V tej napravi omogočite ali onemogočite vibracije pri krmiljenju naprav.",
|
||||
"header": "Vibriraj"
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "已拔出",
|
||||
"was_unsafe": "[%key_id:6884523%]"
|
||||
},
|
||||
"retrieval_error": "获取日志条目时出错",
|
||||
"show_trace": "显示轨迹"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "家庭 ID",
|
||||
"network": "网络",
|
||||
"node_id": "节点 ID",
|
||||
"reconfigure_server": "重新配置服务",
|
||||
"remove_node": "删除节点"
|
||||
},
|
||||
"dashboard": {
|
||||
|
@ -820,6 +820,7 @@
|
||||
"was_unplugged": "狀態為拔下",
|
||||
"was_unsafe": "狀態為不安全"
|
||||
},
|
||||
"retrieval_error": "日誌登錄檢索出現錯誤",
|
||||
"show_trace": "顯示紀錄"
|
||||
},
|
||||
"media-browser": {
|
||||
@ -2964,6 +2965,7 @@
|
||||
"home_id": "家庭 ID",
|
||||
"network": "網路",
|
||||
"node_id": "節點 ID",
|
||||
"reconfigure_server": "重新設定伺服器",
|
||||
"remove_node": "移除節點"
|
||||
},
|
||||
"dashboard": {
|
||||
|
100
yarn.lock
100
yarn.lock
@ -1519,6 +1519,19 @@
|
||||
"@material/theme" "12.0.0-canary.1a8d06483.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@material/linear-progress@=12.0.0-canary.1a8d06483.0":
|
||||
version "12.0.0-canary.1a8d06483.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/linear-progress/-/linear-progress-12.0.0-canary.1a8d06483.0.tgz#85b91ef010a98abff972aab40168a695c78d0e83"
|
||||
integrity sha512-pviLL7V77ruNlMaZARzNjc39whRZLN+CZxoPIW2f5/Do0ub9sxw8SDiXjFjC9RMM5a2hPTmA8jyUphS0FlWKbw==
|
||||
dependencies:
|
||||
"@material/animation" "12.0.0-canary.1a8d06483.0"
|
||||
"@material/base" "12.0.0-canary.1a8d06483.0"
|
||||
"@material/feature-targeting" "12.0.0-canary.1a8d06483.0"
|
||||
"@material/progress-indicator" "12.0.0-canary.1a8d06483.0"
|
||||
"@material/rtl" "12.0.0-canary.1a8d06483.0"
|
||||
"@material/theme" "12.0.0-canary.1a8d06483.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
"@material/list@12.0.0-canary.1a8d06483.0", "@material/list@=12.0.0-canary.1a8d06483.0":
|
||||
version "12.0.0-canary.1a8d06483.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/list/-/list-12.0.0-canary.1a8d06483.0.tgz#6ba66e3e0f23dd0b3146090e3ba58a69f835d917"
|
||||
@ -1575,7 +1588,7 @@
|
||||
lit-element "^2.5.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-button@0.22.0-canary.cc04657a.0", "@material/mwc-button@canary":
|
||||
"@material/mwc-button@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-button/-/mwc-button-0.22.0-canary.cc04657a.0.tgz#9640668b340197325ecc493ddc861bc215c117a3"
|
||||
integrity sha512-xM6VorfSUgYDFFHYr5GfzHFbqcgz0j3TE6xR6dXkJQTqQPFameOYFBKn7TKu2j5674+OY4QHeyhw8/fYaWeP0A==
|
||||
@ -1586,7 +1599,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-checkbox@0.22.0-canary.cc04657a.0", "@material/mwc-checkbox@canary":
|
||||
"@material/mwc-checkbox@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-checkbox/-/mwc-checkbox-0.22.0-canary.cc04657a.0.tgz#ebfd10c06120de3fbdcf05751757ca621ce9035c"
|
||||
integrity sha512-m/BSKMecOmZ8xG2g7P0bHZ5mlMBNkXxcmw68FOv3qZZufEkLMEic07zxbHNePcmDhjildwMWzTLfWHgCdTQ0hQ==
|
||||
@ -1597,7 +1610,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-circular-progress@canary":
|
||||
"@material/mwc-circular-progress@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-circular-progress/-/mwc-circular-progress-0.22.0-canary.cc04657a.0.tgz#d5873e347979b2279f66826cca167ed3ac147f96"
|
||||
integrity sha512-hBD2EZhiolkD3m7xaSK7ml+qomX+FCg78RbV1BArrEnLk21ui46NsmtvBBJ1UiZUGs1FvRUcVNyMjSRTByxeDA==
|
||||
@ -1609,7 +1622,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-dialog@canary":
|
||||
"@material/mwc-dialog@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-dialog/-/mwc-dialog-0.22.0-canary.cc04657a.0.tgz#d817a733149893cb4c0d1d1ea563e7c65d360af1"
|
||||
integrity sha512-qcMVcrJxEzgswiP34WPPtR484d2G7YfViVxVUrmAupsIlKqCLn7jYf6OlE/NmX0d65XIdo+WQEt1Qprui6wQiA==
|
||||
@ -1624,7 +1637,7 @@
|
||||
tslib "^2.0.1"
|
||||
wicg-inert "^3.0.0"
|
||||
|
||||
"@material/mwc-fab@canary":
|
||||
"@material/mwc-fab@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-fab/-/mwc-fab-0.22.0-canary.cc04657a.0.tgz#a3307c19a5c432c20da1029451a4dc97a219aa46"
|
||||
integrity sha512-VR1xGp+VaIcysklG6jXP2rky3iTTbI06AvWa3KYevlpwm/sfnY5N4onUpeckAzIVvKUKM9JO/Yx+snJCUCAY5w==
|
||||
@ -1634,7 +1647,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-formfield@canary":
|
||||
"@material/mwc-formfield@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-formfield/-/mwc-formfield-0.22.0-canary.cc04657a.0.tgz#3e5803793e19c047ac0ec71061069c1f4b593dd0"
|
||||
integrity sha512-eVajWwlwSC0ySDYU3btu41agtDQlotuaa6LYR0ZamNa1CMcu8QFq3ZyLaQqqS66/FsJJ4W+72+MpkUnAthj55A==
|
||||
@ -1645,7 +1658,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-icon-button@canary":
|
||||
"@material/mwc-icon-button@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-icon-button/-/mwc-icon-button-0.22.0-canary.cc04657a.0.tgz#79171babfbfde7fd98e2d9fbe66af40fdeeadf80"
|
||||
integrity sha512-GzWXtjTterfWKAjs+qRyPP0RIWHt34FJkMoMjD0YuoVzbKeTFURwo9QBduVUU36ERe73sssnxtBk+5pQ7GWGjA==
|
||||
@ -1662,7 +1675,18 @@
|
||||
lit-element "^2.5.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-list@0.22.0-canary.cc04657a.0", "@material/mwc-list@canary":
|
||||
"@material/mwc-linear-progress@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-linear-progress/-/mwc-linear-progress-0.22.0-canary.cc04657a.0.tgz#fdbe49f81250c5d6d7536778b07b17e63637378e"
|
||||
integrity sha512-QDuou14Q3xrRr3PDFsgZIngPKNjK5GeMRaorYKJgFK1XC7nrwd0rmVZn0CP1jm3+7eRmbnKaoVzj0AU7oFog8w==
|
||||
dependencies:
|
||||
"@material/linear-progress" "=12.0.0-canary.1a8d06483.0"
|
||||
"@material/theme" "=12.0.0-canary.1a8d06483.0"
|
||||
lit-element "^2.5.0"
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-list@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-list/-/mwc-list-0.22.0-canary.cc04657a.0.tgz#490ac8b1aebf57f937122bd9e55bc406c4ef793f"
|
||||
integrity sha512-yA0b38mTEh75+W3LxeSJe97/Yu9bZAXX9783v8lhbwlAbp/IZPcLr0FkSfUt4BM4TIuEU9Sb6E8L8YpCqM6NSA==
|
||||
@ -1678,7 +1702,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-menu@canary":
|
||||
"@material/mwc-menu@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-menu/-/mwc-menu-0.22.0-canary.cc04657a.0.tgz#61aa5d70af6509958aff9b583c5d86a048d23af2"
|
||||
integrity sha512-unXilkODM05Q4Dhmw54OItlRO1A+k8STuWnDSpM7nti1r5uh25BfJ64qs70OBMn4FM9fZGbOAvX1GfW5o/nYrA==
|
||||
@ -1693,7 +1717,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-radio@0.22.0-canary.cc04657a.0", "@material/mwc-radio@canary":
|
||||
"@material/mwc-radio@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-radio/-/mwc-radio-0.22.0-canary.cc04657a.0.tgz#3cf7ea09b4c6bfb8cb654c39c1fadd288235ca91"
|
||||
integrity sha512-NDCBflXsqbL8vZKce1BPlDJgjcC/XD+ZlmtSY2liZgvZ6fPUGxtii/YGQRrrmZvIKDc+JJG0rhzZOty5XHQy+A==
|
||||
@ -1704,7 +1728,7 @@
|
||||
lit-element "^2.5.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-ripple@0.22.0-canary.cc04657a.0", "@material/mwc-ripple@canary":
|
||||
"@material/mwc-ripple@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-ripple/-/mwc-ripple-0.22.0-canary.cc04657a.0.tgz#bd22e2babe11aeffe7a611eadf7d354597c18eae"
|
||||
integrity sha512-mjXSH7OA7iBe6e6ssenInTav9gTGx0lK0hrJfi92ku3YYoSgw8ULK8TiL/7YmGGH7UN67Emv4fdGPhwfiMfYbg==
|
||||
@ -1716,7 +1740,7 @@
|
||||
lit-html "^1.4.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-switch@canary":
|
||||
"@material/mwc-switch@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-switch/-/mwc-switch-0.22.0-canary.cc04657a.0.tgz#44ccbb37946ec2884692b6fff8ef3beab854d07a"
|
||||
integrity sha512-dP4dqqIYULckm7UDynVS5E62nZt28Ae622AGneiHtRc1ob9IVtGRkpTVNoz5QyKvrLyF4XKAsKoOwAm6x66nkg==
|
||||
@ -1727,7 +1751,7 @@
|
||||
lit-element "^2.5.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-tab-bar@canary":
|
||||
"@material/mwc-tab-bar@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-tab-bar/-/mwc-tab-bar-0.22.0-canary.cc04657a.0.tgz#5590d3a3e820b4a7a4dc134d44790735cd375884"
|
||||
integrity sha512-p0T+65FmD2EdMN4L0MStjd1jXSLjq5/w0wQ08WiO2Q7EWgh2iuABxwLLzhL4hMLWKzDXqmzy1rKI2QyiTRIW8A==
|
||||
@ -1762,7 +1786,7 @@
|
||||
lit-element "^2.5.0"
|
||||
tslib "^2.0.1"
|
||||
|
||||
"@material/mwc-tab@0.22.0-canary.cc04657a.0", "@material/mwc-tab@canary":
|
||||
"@material/mwc-tab@0.22.0-canary.cc04657a.0":
|
||||
version "0.22.0-canary.cc04657a.0"
|
||||
resolved "https://registry.yarnpkg.com/@material/mwc-tab/-/mwc-tab-0.22.0-canary.cc04657a.0.tgz#8d4491f55758f5ac4cbabc18c3f4f0de26fd687b"
|
||||
integrity sha512-G3i5bCIqoz1yivFNdNcQMvbGX4B2X99Ayt+VbOpr0Mo3xGDqxZ1FXIEK6bLBcfLPO92kgaWlSyzMFkVS4Z/xxw==
|
||||
@ -4634,33 +4658,10 @@ chardet@^0.7.0:
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
chart.js@^2.9.4:
|
||||
version "2.9.4"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.9.4.tgz#0827f9563faffb2dc5c06562f8eb10337d5b9684"
|
||||
integrity sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==
|
||||
dependencies:
|
||||
chartjs-color "^2.1.0"
|
||||
moment "^2.10.2"
|
||||
|
||||
chartjs-chart-timeline@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-chart-timeline/-/chartjs-chart-timeline-0.4.0.tgz#cbd25dc5ddb5c2b34289f8dd7a2a627d71e251e8"
|
||||
integrity sha512-a3iOFgMUXgEK9zyDFXlL7cfhO6z4DkeuGqok1xnNVNg12ciSt/k1jDBFk8JKN+sVNZfoqeGAFBT9zvb++iEWnA==
|
||||
|
||||
chartjs-color-string@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz#1df096621c0e70720a64f4135ea171d051402f71"
|
||||
integrity sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==
|
||||
dependencies:
|
||||
color-name "^1.0.0"
|
||||
|
||||
chartjs-color@^2.1.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.3.0.tgz#0e7e1e8dba37eae8415fd3db38bf572007dd958f"
|
||||
integrity sha512-hEvVheqczsoHD+fZ+tfPUE+1+RbV6b+eksp2LwAhwRTVXEjCSEavvk+Hg3H6SZfGlPh/UfmWKGIvZbtobOEm3g==
|
||||
dependencies:
|
||||
chartjs-color-string "^0.6.0"
|
||||
color-convert "^0.5.3"
|
||||
chart.js@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.3.2.tgz#667f3a0b6371b9719d8949c04a5bcbaec0d8c615"
|
||||
integrity sha512-H0hSO7xqTIrwxoACqnSoNromEMfXvfuVnrbuSt2TuXfBDDofbnto4zuZlRtRvC73/b37q3wGAWZyUU41QPvNbA==
|
||||
|
||||
check-error@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -4942,11 +4943,6 @@ collection-visit@^1.0.0:
|
||||
map-visit "^1.0.0"
|
||||
object-visit "^1.0.0"
|
||||
|
||||
color-convert@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
|
||||
integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0=
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
@ -4966,7 +4962,7 @@ color-name@1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
|
||||
|
||||
color-name@^1.0.0, color-name@~1.1.4:
|
||||
color-name@~1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
@ -5360,6 +5356,11 @@ d@1:
|
||||
dependencies:
|
||||
es5-ext "^0.10.9"
|
||||
|
||||
date-fns@^2.22.1:
|
||||
version "2.22.1"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4"
|
||||
integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg==
|
||||
|
||||
dateformat@^1.0.7-1.2.3:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
|
||||
@ -9425,11 +9426,6 @@ mocha@^8.4.0:
|
||||
yargs-parser "20.2.4"
|
||||
yargs-unparser "2.0.0"
|
||||
|
||||
moment@^2.10.2:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
|
||||
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
Loading…
x
Reference in New Issue
Block a user