diff --git a/demo/src/ha-demo.ts b/demo/src/ha-demo.ts
index 0587fde184..0066940028 100644
--- a/demo/src/ha-demo.ts
+++ b/demo/src/ha-demo.ts
@@ -15,6 +15,7 @@ import { mockTemplate } from "./stubs/template";
import { mockEvents } from "./stubs/events";
import { mockMediaPlayer } from "./stubs/media_player";
import { HomeAssistant } from "../../src/types";
+import { mockFrontend } from "./stubs/frontend";
class HaDemo extends HomeAssistantAppEl {
protected async _handleConnProm() {
@@ -35,6 +36,7 @@ class HaDemo extends HomeAssistantAppEl {
mockTemplate(hass);
mockEvents(hass);
mockMediaPlayer(hass);
+ mockFrontend(hass);
selectedDemoConfig.then((conf) => {
hass.addEntities(conf.entities());
if (conf.theme) {
diff --git a/demo/src/stubs/frontend.ts b/demo/src/stubs/frontend.ts
new file mode 100644
index 0000000000..a7254b25f4
--- /dev/null
+++ b/demo/src/stubs/frontend.ts
@@ -0,0 +1,7 @@
+import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
+
+export const mockFrontend = (hass: MockHomeAssistant) => {
+ hass.mockWS("frontend/get_user_data", () => ({
+ value: null,
+ }));
+};
diff --git a/hassio/src/addon-view/hassio-addon-view.js b/hassio/src/addon-view/hassio-addon-view.js
index 3a57512751..b4f1505b4d 100644
--- a/hassio/src/addon-view/hassio-addon-view.js
+++ b/hassio/src/addon-view/hassio-addon-view.js
@@ -60,11 +60,7 @@ class HassioAddonView extends PolymerElement {
-
+
-
+
`;
}
@@ -21,8 +16,6 @@ class HassioApp extends PolymerElement {
static get properties() {
return {
hass: Object,
- narrow: Boolean,
- showMenu: Boolean,
route: Object,
hassioPanel: {
type: Object,
@@ -35,12 +28,9 @@ class HassioApp extends PolymerElement {
super.ready();
window.setProperties = this.setProperties.bind(this);
this.addEventListener("location-changed", () => this._locationChanged());
- this.addEventListener("hass-open-menu", () => this._menuEvent(true));
- this.addEventListener("hass-close-menu", () => this._menuEvent(false));
- }
-
- _menuEvent(shouldOpen) {
- this.hassioPanel.fire(shouldOpen ? "hass-open-menu" : "hass-close-menu");
+ this.addEventListener("hass-toggle-menu", (ev) =>
+ this.hassioPanel.fire("hass-toggle-menu", ev.detail)
+ );
}
_locationChanged() {
diff --git a/hassio/src/hassio-main.js b/hassio/src/hassio-main.js
index 940ff70639..8b207f9a00 100644
--- a/hassio/src/hassio-main.js
+++ b/hassio/src/hassio-main.js
@@ -28,18 +28,13 @@ class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
>
-
+
@@ -61,8 +54,6 @@ class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
static get properties() {
return {
hass: Object,
- narrow: Boolean,
- showMenu: Boolean,
route: {
type: Object,
// Fake route object
diff --git a/hassio/src/hassio-pages-with-tabs.js b/hassio/src/hassio-pages-with-tabs.js
index c817624790..506d1974bd 100644
--- a/hassio/src/hassio-pages-with-tabs.js
+++ b/hassio/src/hassio-pages-with-tabs.js
@@ -37,11 +37,7 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
-
+
Hass.io
[[localize('ui.panel.page-authorize.initializing')]]
+ ${this.localize("ui.panel.page-authorize.initializing")}
`;
}
diff --git a/src/common/dom/media_query.ts b/src/common/dom/media_query.ts
new file mode 100644
index 0000000000..df8ba93668
--- /dev/null
+++ b/src/common/dom/media_query.ts
@@ -0,0 +1,16 @@
+/**
+ * Attach a media query. Listener is called right away and when it matches.
+ * @param mediaQuery media query to match.
+ * @param listener listener to call when media query changes between match/unmatch
+ * @returns function to remove the listener.
+ */
+export const listenMediaQuery = (
+ mediaQuery: string,
+ matchesChanged: (matches: boolean) => void
+) => {
+ const mql = matchMedia(mediaQuery);
+ const listener = (e) => matchesChanged(e.matches);
+ mql.addListener(listener);
+ matchesChanged(mql.matches);
+ return () => mql.removeListener(listener);
+};
diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts
index 433e7882f8..11a5246f01 100644
--- a/src/common/translations/localize.ts
+++ b/src/common/translations/localize.ts
@@ -79,3 +79,25 @@ export const computeLocalize = (
}
};
};
+
+/**
+ * Silly helper function that converts an object of placeholders to array so we
+ * can convert it back to an object again inside the localize func.
+ * @param localize
+ * @param key
+ * @param placeholders
+ */
+export const localizeKey = (
+ localize: LocalizeFunc,
+ key: string,
+ placeholders?: { [key: string]: string }
+) => {
+ const args: [string, ...string[]] = [key];
+ if (placeholders) {
+ Object.keys(placeholders).forEach((placeholderKey) => {
+ args.push(placeholderKey);
+ args.push(placeholders[placeholderKey]);
+ });
+ }
+ return localize(...args);
+};
diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts
index 49df5ef061..0d73d6fcd0 100644
--- a/src/components/entity/state-badge.ts
+++ b/src/components/entity/state-badge.ts
@@ -39,7 +39,7 @@ class StateBadge extends LitElement {
}
protected updated(changedProps: PropertyValues) {
- if (!changedProps.has("stateObj")) {
+ if (!changedProps.has("stateObj") || !this.stateObj) {
return;
}
const stateObj = this.stateObj;
diff --git a/src/components/ha-menu-button.ts b/src/components/ha-menu-button.ts
index 199d5228ac..394c6888cb 100644
--- a/src/components/ha-menu-button.ts
+++ b/src/components/ha-menu-button.ts
@@ -11,9 +11,6 @@ import { fireEvent } from "../common/dom/fire_event";
@customElement("ha-menu-button")
class HaMenuButton extends LitElement {
- @property({ type: Boolean })
- public showMenu = false;
-
@property({ type: Boolean })
public hassio = false;
@@ -33,7 +30,7 @@ class HaMenuButton extends LitElement {
}
private _toggleMenu(): void {
- fireEvent(this, this.showMenu ? "hass-close-menu" : "hass-open-menu");
+ fireEvent(this, "hass-toggle-menu");
}
}
diff --git a/src/data/cloud.ts b/src/data/cloud.ts
index 75348bc94a..b15f8b3066 100644
--- a/src/data/cloud.ts
+++ b/src/data/cloud.ts
@@ -47,6 +47,9 @@ export interface CloudWebhook {
managed?: boolean;
}
+export const fetchCloudStatus = (hass: HomeAssistant) =>
+ hass.callWS({ type: "cloud/status" });
+
export const createCloudhook = (hass: HomeAssistant, webhookId: string) =>
hass.callWS({
type: "cloud/cloudhook/create",
diff --git a/src/data/config_entries.ts b/src/data/config_entries.ts
index 2fb4f019a3..48618bdc80 100644
--- a/src/data/config_entries.ts
+++ b/src/data/config_entries.ts
@@ -22,7 +22,8 @@ export interface ConfigFlowStepCreateEntry {
flow_id: string;
handler: string;
title: string;
- data: any;
+ // Config entry ID
+ result: string;
description: string;
description_placeholders: { [key: string]: string };
}
diff --git a/src/dialogs/config-flow/dialog-config-flow.ts b/src/dialogs/config-flow/dialog-config-flow.ts
index c72abf0949..9a98881ab5 100644
--- a/src/dialogs/config-flow/dialog-config-flow.ts
+++ b/src/dialogs/config-flow/dialog-config-flow.ts
@@ -25,16 +25,32 @@ import {
fetchConfigFlow,
createConfigFlow,
ConfigFlowStep,
- handleConfigFlowStep,
deleteConfigFlow,
- FieldSchema,
- ConfigFlowStepForm,
} from "../../data/config_entries";
-import { PolymerChangedEvent, applyPolymerEvent } from "../../polymer-types";
+import { PolymerChangedEvent } from "../../polymer-types";
import { HaConfigFlowParams } from "./show-dialog-config-flow";
+import "./step-flow-loading";
+import "./step-flow-form";
+import "./step-flow-abort";
+import "./step-flow-create-entry";
+import {
+ DeviceRegistryEntry,
+ fetchDeviceRegistry,
+} from "../../data/device_registry";
+import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
+
let instance = 0;
+declare global {
+ // for fire event
+ interface HASSDomEvents {
+ "flow-update": {
+ step?: ConfigFlowStep;
+ };
+ }
+}
+
@customElement("dialog-config-flow")
class ConfigFlowDialog extends LitElement {
@property()
@@ -49,18 +65,15 @@ class ConfigFlowDialog extends LitElement {
private _step?: ConfigFlowStep;
@property()
- private _stepData?: { [key: string]: any };
+ private _devices?: DeviceRegistryEntry[];
@property()
- private _errorMsg?: string;
+ private _areas?: AreaRegistryEntry[];
public async showDialog(params: HaConfigFlowParams): Promise {
this._params = params;
this._loading = true;
this._instance = instance++;
- this._step = undefined;
- this._stepData = {};
- this._errorMsg = undefined;
const fetchStep = params.continueFlowId
? fetchConfigFlow(params.hass, params.continueFlowId)
@@ -93,201 +106,91 @@ class ConfigFlowDialog extends LitElement {
if (!this._params) {
return html``;
}
- const localize = this._params.hass.localize;
-
- const step = this._step;
- let headerContent: string | undefined;
- let bodyContent: TemplateResult | undefined;
- let buttonContent: TemplateResult | undefined;
- let descriptionKey: string | undefined;
-
- if (!step) {
- bodyContent = html`
-
- `;
- } else if (step.type === "abort") {
- descriptionKey = `component.${step.handler}.config.abort.${step.reason}`;
- headerContent = "Aborted";
- bodyContent = html``;
- buttonContent = html`
- Close
- `;
- } else if (step.type === "create_entry") {
- descriptionKey = `component.${
- step.handler
- }.config.create_entry.${step.description || "default"}`;
- headerContent = "Success!";
- bodyContent = html`
- Created config for ${step.title}
- `;
- buttonContent = html`
- Close
- `;
- } else {
- // form
- descriptionKey = `component.${step.handler}.config.step.${
- step.step_id
- }.description`;
- headerContent = localize(
- `component.${step.handler}.config.step.${step.step_id}.title`
- );
- bodyContent = html`
-
- `;
-
- const allRequiredInfoFilledIn =
- this._stepData &&
- step.data_schema.every(
- (field) =>
- field.optional ||
- !["", undefined].includes(this._stepData![field.name])
- );
-
- buttonContent = this._loading
- ? html`
-
- `
- : html`
-
-
- Submit
-
-
- ${!allRequiredInfoFilledIn
- ? html`
-
- Not all required fields are filled in.
-
- `
- : html``}
-
- `;
- }
-
- let description: string | undefined;
-
- if (step && descriptionKey) {
- const args: [string, ...string[]] = [descriptionKey];
- const placeholders = step.description_placeholders || {};
- Object.keys(placeholders).forEach((key) => {
- args.push(key);
- args.push(placeholders[key]);
- });
- description = localize(...args);
- }
return html`
-
-
- ${headerContent}
-
-
- ${this._errorMsg
- ? html`
- ${this._errorMsg}
- `
- : ""}
- ${description
- ? html`
-
- `
- : ""}
- ${bodyContent}
-
-
- ${buttonContent}
-
+
+ ${this._loading
+ ? html`
+
+ `
+ : this._step === undefined
+ ? // When we are going to next step, we render 1 round of empty
+ // to reset the element.
+ ""
+ : this._step.type === "form"
+ ? html`
+
+ `
+ : this._step.type === "abort"
+ ? html`
+
+ `
+ : this._devices === undefined || this._areas === undefined
+ ? // When it's a create entry result, we will fetch device & area registry
+ html`
+
+ `
+ : html`
+
+ `}
`;
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
- this.addEventListener("keypress", (ev) => {
- if (ev.keyCode === 13) {
- this._submitStep();
- }
+ this.addEventListener("flow-update", (ev) => {
+ this._processStep((ev as any).detail.step);
});
}
+ protected updated(changedProps: PropertyValues) {
+ if (
+ changedProps.has("_step") &&
+ this._step &&
+ this._step.type === "create_entry"
+ ) {
+ this._fetchDevices(this._step.result);
+ this._fetchAreas();
+ }
+ }
+
private get _dialog(): PaperDialogElement {
return this.shadowRoot!.querySelector("paper-dialog")!;
}
- private async _submitStep(): Promise {
- this._loading = true;
- this._errorMsg = undefined;
-
- const curInstance = this._instance;
- const stepData = this._stepData || {};
-
- const toSendData = {};
- Object.keys(stepData).forEach((key) => {
- const value = stepData[key];
- const isEmpty = [undefined, ""].includes(value);
-
- if (!isEmpty) {
- toSendData[key] = value;
- }
- });
-
- try {
- const step = await handleConfigFlowStep(
- this._params!.hass,
- this._step!.flow_id,
- toSendData
- );
-
- if (curInstance !== this._instance) {
- return;
- }
-
- this._processStep(step);
- } catch (err) {
- this._errorMsg =
- (err && err.body && err.body.message) || "Unknown error occurred";
- } finally {
- this._loading = false;
- }
+ private async _fetchDevices(configEntryId) {
+ // Wait 5 seconds to give integrations time to find devices
+ await new Promise((resolve) => setTimeout(resolve, 5000));
+ const devices = await fetchDeviceRegistry(this._params!.hass);
+ this._devices = devices.filter((device) =>
+ device.config_entries.includes(configEntryId)
+ );
}
- private _processStep(step: ConfigFlowStep): void {
- this._step = step;
+ private async _fetchAreas() {
+ this._areas = await fetchAreaRegistry(this._params!.hass);
+ }
- // We got a new form if there are no errors.
- if (step.type === "form") {
- if (!step.errors) {
- step.errors = {};
- }
-
- if (Object.keys(step.errors).length === 0) {
- const data = {};
- step.data_schema.forEach((field) => {
- if ("default" in field) {
- data[field.name] = field.default;
- }
- });
- this._stepData = data;
- }
+ private async _processStep(step: ConfigFlowStep): Promise {
+ if (step === undefined) {
+ this._flowDone();
+ return;
}
+ this._step = undefined;
+ await this.updateComplete;
+ this._step = step;
}
private _flowDone(): void {
@@ -307,10 +210,9 @@ class ConfigFlowDialog extends LitElement {
flowFinished,
});
- this._errorMsg = undefined;
this._step = undefined;
- this._stepData = {};
this._params = undefined;
+ this._devices = undefined;
}
private _openedChanged(ev: PolymerChangedEvent): void {
@@ -320,51 +222,17 @@ class ConfigFlowDialog extends LitElement {
}
}
- private _stepDataChanged(ev: PolymerChangedEvent): void {
- this._stepData = applyPolymerEvent(ev, this._stepData);
- }
-
- private _labelCallback = (schema: FieldSchema): string => {
- const step = this._step as ConfigFlowStepForm;
-
- return this._params!.hass.localize(
- `component.${step.handler}.config.step.${step.step_id}.data.${
- schema.name
- }`
- );
- };
-
- private _errorCallback = (error: string) =>
- this._params!.hass.localize(
- `component.${this._step!.handler}.config.error.${error}`
- );
-
static get styles(): CSSResultArray {
return [
haStyleDialog,
css`
- .error {
- color: red;
- }
paper-dialog {
max-width: 500px;
}
- ha-markdown {
- word-break: break-word;
- }
- ha-markdown a {
- color: var(--primary-color);
- }
- ha-markdown img:first-child:last-child {
+ paper-dialog > * {
+ margin: 0;
display: block;
- margin: 0 auto;
- }
- .init-spinner {
- padding: 10px 100px 34px;
- text-align: center;
- }
- .submit-spinner {
- margin-right: 16px;
+ padding: 0;
}
`,
];
diff --git a/src/dialogs/config-flow/step-flow-abort.ts b/src/dialogs/config-flow/step-flow-abort.ts
new file mode 100644
index 0000000000..ecd05fe2b5
--- /dev/null
+++ b/src/dialogs/config-flow/step-flow-abort.ts
@@ -0,0 +1,66 @@
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ customElement,
+ property,
+ CSSResult,
+} from "lit-element";
+import "@material/mwc-button";
+
+import { ConfigFlowStepAbort } from "../../data/config_entries";
+import { HomeAssistant } from "../../types";
+import { localizeKey } from "../../common/translations/localize";
+import { fireEvent } from "../../common/dom/fire_event";
+import { configFlowContentStyles } from "./styles";
+
+@customElement("step-flow-abort")
+class StepFlowAbort extends LitElement {
+ @property()
+ public hass!: HomeAssistant;
+
+ @property()
+ private step!: ConfigFlowStepAbort;
+
+ protected render(): TemplateResult | void {
+ const localize = this.hass.localize;
+ const step = this.step;
+
+ const description = localizeKey(
+ localize,
+ `component.${step.handler}.config.abort.${step.reason}`,
+ step.description_placeholders
+ );
+
+ return html`
+ Aborted
+
+ ${
+ description
+ ? html`
+
+ `
+ : ""
+ }
+
+
+ Close
+
+
+ `;
+ }
+
+ private _flowDone(): void {
+ fireEvent(this, "flow-update", { step: undefined });
+ }
+
+ static get styles(): CSSResult {
+ return configFlowContentStyles;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "step-flow-abort": StepFlowAbort;
+ }
+}
diff --git a/src/dialogs/config-flow/step-flow-create-entry.ts b/src/dialogs/config-flow/step-flow-create-entry.ts
new file mode 100644
index 0000000000..0935ef5d5f
--- /dev/null
+++ b/src/dialogs/config-flow/step-flow-create-entry.ts
@@ -0,0 +1,191 @@
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ customElement,
+ property,
+ CSSResultArray,
+ css,
+} from "lit-element";
+import "@material/mwc-button";
+import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
+import "@polymer/paper-item/paper-item";
+import "@polymer/paper-listbox/paper-listbox";
+
+import { ConfigFlowStepCreateEntry } from "../../data/config_entries";
+import { HomeAssistant } from "../../types";
+import { localizeKey } from "../../common/translations/localize";
+import { fireEvent } from "../../common/dom/fire_event";
+import { configFlowContentStyles } from "./styles";
+import {
+ DeviceRegistryEntry,
+ updateDeviceRegistryEntry,
+} from "../../data/device_registry";
+import {
+ AreaRegistryEntry,
+ createAreaRegistryEntry,
+} from "../../data/area_registry";
+
+@customElement("step-flow-create-entry")
+class StepFlowCreateEntry extends LitElement {
+ @property()
+ public hass!: HomeAssistant;
+
+ @property()
+ public step!: ConfigFlowStepCreateEntry;
+
+ @property()
+ public devices!: DeviceRegistryEntry[];
+
+ @property()
+ public areas!: AreaRegistryEntry[];
+
+ protected render(): TemplateResult | void {
+ const localize = this.hass.localize;
+ const step = this.step;
+
+ const description = localizeKey(
+ localize,
+ `component.${step.handler}.config.create_entry.${step.description ||
+ "default"}`,
+ step.description_placeholders
+ );
+
+ return html`
+ Success!
+
+ ${
+ description
+ ? html`
+
+ `
+ : ""
+ }
+
Created config for ${step.title}.
+ ${
+ this.devices.length === 0
+ ? ""
+ : html`
+
We found the following devices:
+
+ ${this.devices.map(
+ (device) =>
+ html`
+
+
${device.name}
+ ${device.model} (${device.manufacturer})
+
+
+
+
+ ${localize(
+ "ui.panel.config.integrations.config_entry.no_area"
+ )}
+
+ ${this.areas.map(
+ (area) => html`
+
+ ${area.name}
+
+ `
+ )}
+
+
+
+ `
+ )}
+
+ `
+ }
+
+
+ ${
+ this.devices.length > 0
+ ? html`
+ Add Area
+ `
+ : ""
+ }
+
+ Finish
+
+
+ `;
+ }
+
+ private _flowDone(): void {
+ fireEvent(this, "flow-update", { step: undefined });
+ }
+
+ private async _addArea() {
+ const name = prompt("Name of the new area?");
+ if (!name) {
+ return;
+ }
+ try {
+ const area = await createAreaRegistryEntry(this.hass, {
+ name,
+ });
+ this.areas = [...this.areas, area];
+ } catch (err) {
+ alert("Failed to create area.");
+ }
+ }
+
+ private async _handleAreaChanged(ev: Event) {
+ const dropdown = ev.currentTarget as any;
+ const device = dropdown.device;
+
+ // Item first becomes null, then new item.
+ if (!dropdown.selectedItem) {
+ return;
+ }
+
+ const area = dropdown.selectedItem.area;
+ try {
+ await updateDeviceRegistryEntry(this.hass, device, {
+ area_id: area,
+ });
+ } catch (err) {
+ alert(`Error saving area: ${err.message}`);
+ dropdown.value = null;
+ }
+ }
+
+ static get styles(): CSSResultArray {
+ return [
+ configFlowContentStyles,
+ css`
+ .devices {
+ display: flex;
+ flex-wrap: wrap;
+ margin: -4px;
+ }
+ .device {
+ border: 1px solid var(--divider-color);
+ padding: 5px;
+ border-radius: 4px;
+ margin: 4px;
+ display: inline-block;
+ width: 200px;
+ }
+ .buttons > *:last-child {
+ margin-left: auto;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "step-flow-create-entry": StepFlowCreateEntry;
+ }
+}
diff --git a/src/dialogs/config-flow/step-flow-form.ts b/src/dialogs/config-flow/step-flow-form.ts
new file mode 100644
index 0000000000..0a2df43164
--- /dev/null
+++ b/src/dialogs/config-flow/step-flow-form.ts
@@ -0,0 +1,222 @@
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ CSSResultArray,
+ css,
+ customElement,
+ property,
+ PropertyValues,
+} from "lit-element";
+import "@material/mwc-button";
+import "@polymer/paper-tooltip/paper-tooltip";
+import "@polymer/paper-spinner/paper-spinner";
+
+import "../../components/ha-form";
+import "../../components/ha-markdown";
+import "../../resources/ha-style";
+import {
+ handleConfigFlowStep,
+ FieldSchema,
+ ConfigFlowStepForm,
+} from "../../data/config_entries";
+import { PolymerChangedEvent, applyPolymerEvent } from "../../polymer-types";
+import { HomeAssistant } from "../../types";
+import { fireEvent } from "../../common/dom/fire_event";
+import { localizeKey } from "../../common/translations/localize";
+import { configFlowContentStyles } from "./styles";
+
+@customElement("step-flow-form")
+class StepFlowForm extends LitElement {
+ @property()
+ public step!: ConfigFlowStepForm;
+
+ @property()
+ public hass!: HomeAssistant;
+
+ @property()
+ private _loading = false;
+
+ @property()
+ private _stepData?: { [key: string]: any };
+
+ @property()
+ private _errorMsg?: string;
+
+ protected render(): TemplateResult | void {
+ const localize = this.hass.localize;
+ const step = this.step;
+
+ const allRequiredInfoFilledIn =
+ this._stepData === undefined
+ ? // If no data filled in, just check that any field is required
+ step.data_schema.find((field) => !field.optional) === undefined
+ : // If data is filled in, make sure all required fields are
+ this._stepData &&
+ step.data_schema.every(
+ (field) =>
+ field.optional ||
+ !["", undefined].includes(this._stepData![field.name])
+ );
+
+ const description = localizeKey(
+ localize,
+ `component.${step.handler}.config.step.${step.step_id}.description`,
+ step.description_placeholders
+ );
+
+ return html`
+
+ ${localize(
+ `component.${step.handler}.config.step.${step.step_id}.title`
+ )}
+
+
+ ${this._errorMsg
+ ? html`
+
${this._errorMsg}
+ `
+ : ""}
+ ${description
+ ? html`
+
+ `
+ : ""}
+
+
+
+ `;
+ }
+
+ protected firstUpdated(changedProps: PropertyValues) {
+ super.firstUpdated(changedProps);
+ this.addEventListener("keypress", (ev) => {
+ if (ev.keyCode === 13) {
+ this._submitStep();
+ }
+ });
+ }
+
+ private get _stepDataProcessed() {
+ if (this._stepData !== undefined) {
+ return this._stepData;
+ }
+
+ const data = {};
+ this.step.data_schema.forEach((field) => {
+ if ("default" in field) {
+ data[field.name] = field.default;
+ }
+ });
+ return data;
+ }
+
+ private async _submitStep(): Promise {
+ this._loading = true;
+ this._errorMsg = undefined;
+
+ const flowId = this.step.flow_id;
+ const stepData = this._stepData || {};
+
+ const toSendData = {};
+ Object.keys(stepData).forEach((key) => {
+ const value = stepData[key];
+ const isEmpty = [undefined, ""].includes(value);
+
+ if (!isEmpty) {
+ toSendData[key] = value;
+ }
+ });
+
+ try {
+ const step = await handleConfigFlowStep(
+ this.hass,
+ this.step.flow_id,
+ toSendData
+ );
+
+ if (!this.step || flowId !== this.step.flow_id) {
+ return;
+ }
+
+ fireEvent(this, "flow-update", {
+ step,
+ });
+ } catch (err) {
+ this._errorMsg =
+ (err && err.body && err.body.message) || "Unknown error occurred";
+ } finally {
+ this._loading = false;
+ }
+ }
+
+ private _stepDataChanged(ev: PolymerChangedEvent): void {
+ this._stepData = applyPolymerEvent(ev, this._stepData);
+ }
+
+ private _labelCallback = (schema: FieldSchema): string => {
+ const step = this.step as ConfigFlowStepForm;
+
+ return this.hass.localize(
+ `component.${step.handler}.config.step.${step.step_id}.data.${
+ schema.name
+ }`
+ );
+ };
+
+ private _errorCallback = (error: string) =>
+ this.hass.localize(`component.${this.step.handler}.config.error.${error}`);
+
+ static get styles(): CSSResultArray {
+ return [
+ configFlowContentStyles,
+ css`
+ .error {
+ color: red;
+ }
+
+ .submit-spinner {
+ margin-right: 16px;
+ }
+ `,
+ ];
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "step-flow-form": StepFlowForm;
+ }
+}
diff --git a/src/dialogs/config-flow/step-flow-loading.ts b/src/dialogs/config-flow/step-flow-loading.ts
new file mode 100644
index 0000000000..aebee5db4a
--- /dev/null
+++ b/src/dialogs/config-flow/step-flow-loading.ts
@@ -0,0 +1,35 @@
+import {
+ LitElement,
+ TemplateResult,
+ html,
+ css,
+ customElement,
+ CSSResult,
+} from "lit-element";
+import "@polymer/paper-spinner/paper-spinner-lite";
+
+@customElement("step-flow-loading")
+class StepFlowLoading extends LitElement {
+ protected render(): TemplateResult | void {
+ return html`
+
+ `;
+ }
+
+ static get styles(): CSSResult {
+ return css`
+ .init-spinner {
+ padding: 50px 100px;
+ text-align: center;
+ }
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "step-flow-loading": StepFlowLoading;
+ }
+}
diff --git a/src/dialogs/config-flow/styles.ts b/src/dialogs/config-flow/styles.ts
new file mode 100644
index 0000000000..86bb0872bb
--- /dev/null
+++ b/src/dialogs/config-flow/styles.ts
@@ -0,0 +1,33 @@
+import { css } from "lit-element";
+
+export const configFlowContentStyles = css`
+ h2 {
+ margin-top: 24px;
+ padding: 0 24px;
+ }
+
+ .content {
+ margin-top: 20px;
+ padding: 0 24px;
+ }
+
+ .buttons {
+ position: relative;
+ padding: 8px 8px 8px 24px;
+ margin: 0;
+ color: var(--primary-color);
+ display: flex;
+ justify-content: flex-end;
+ }
+
+ ha-markdown {
+ word-break: break-word;
+ }
+ ha-markdown a {
+ color: var(--primary-color);
+ }
+ ha-markdown img:first-child:last-child {
+ display: block;
+ margin: 0 auto;
+ }
+`;
diff --git a/src/dialogs/more-info/controls/more-info-camera.ts b/src/dialogs/more-info/controls/more-info-camera.ts
index 72ec2629cf..aa2a64688d 100644
--- a/src/dialogs/more-info/controls/more-info-camera.ts
+++ b/src/dialogs/more-info/controls/more-info-camera.ts
@@ -46,6 +46,11 @@ class MoreInfoCamera extends UpdatingElement {
return;
}
+ if (!this.hass!.config.components.includes("stream")) {
+ this._renderMJPEG();
+ return;
+ }
+
const videoEl = document.createElement("video");
videoEl.style.width = "100%";
videoEl.autoplay = true;
diff --git a/src/entrypoints/custom-panel.js b/src/entrypoints/custom-panel.js
index 6cb6495e51..4ce6e012b4 100644
--- a/src/entrypoints/custom-panel.js
+++ b/src/entrypoints/custom-panel.js
@@ -55,8 +55,7 @@ function initialize(panel, properties) {
const forwardEvent = (ev) =>
window.parent.customPanel.fire(ev.type, ev.detail);
- root.addEventListener("hass-open-menu", forwardEvent);
- root.addEventListener("hass-close-menu", forwardEvent);
+ root.addEventListener("hass-toggle-menu", forwardEvent);
root.addEventListener("location-changed", () =>
window.parent.customPanel.navigate(window.location.pathname)
);
diff --git a/src/layouts/hass-loading-screen.ts b/src/layouts/hass-loading-screen.ts
index f67d376e1f..9926a9517c 100644
--- a/src/layouts/hass-loading-screen.ts
+++ b/src/layouts/hass-loading-screen.ts
@@ -4,29 +4,31 @@ import {
LitElement,
TemplateResult,
html,
- property,
CSSResultArray,
css,
customElement,
+ property,
} from "lit-element";
import "../components/ha-menu-button";
+import "../components/ha-paper-icon-button-arrow-prev";
import { haStyle } from "../resources/styles";
@customElement("hass-loading-screen")
class HassLoadingScreen extends LitElement {
- @property({ type: Boolean })
- public narrow?: boolean;
-
- @property({ type: Boolean })
- public showMenu?: boolean;
+ @property() public isRoot? = false;
protected render(): TemplateResult | void {
return html`
-
+ ${this.isRoot
+ ? html`
+
+ `
+ : html`
+
+ `}
@@ -34,6 +36,10 @@ class HassLoadingScreen extends LitElement {
`;
}
+ private _handleBack() {
+ history.back();
+ }
+
static get styles(): CSSResultArray {
return [
haStyle,
diff --git a/src/layouts/hass-router-page.ts b/src/layouts/hass-router-page.ts
new file mode 100644
index 0000000000..b46119c0e3
--- /dev/null
+++ b/src/layouts/hass-router-page.ts
@@ -0,0 +1,192 @@
+import { UpdatingElement, property, PropertyValues } from "lit-element";
+import "./hass-error-screen";
+import { Route } from "../types";
+import { navigate } from "../common/navigate";
+
+const extractPage = (path: string, defaultPage: string) => {
+ if (path === "") {
+ return defaultPage;
+ }
+ const subpathStart = path.indexOf("/", 1);
+ return subpathStart === -1
+ ? path.substr(1)
+ : path.substr(1, subpathStart - 1);
+};
+
+interface RouteOptions {
+ tag: string;
+ load: () => Promise
;
+ cache?: boolean;
+}
+
+export interface RouterOptions {
+ isRoot?: boolean;
+ defaultPage?: string;
+ preloadAll?: boolean;
+ cacheAll?: boolean;
+ showLoading?: boolean;
+ routes: {
+ [route: string]: RouteOptions;
+ };
+}
+
+// Time to wait for code to load before we show loading screen.
+const LOADING_SCREEN_THRESHOLD = 400; // ms
+
+export class HassRouterPage extends UpdatingElement {
+ protected static routerOptions: RouterOptions = { routes: {} };
+
+ protected static finalize() {
+ super.finalize();
+ this._routerOptions = this.routerOptions;
+ }
+
+ private static _routerOptions: RouterOptions;
+
+ @property() public route!: Route;
+ private _currentPage = "";
+ private _cache = {};
+
+ protected update(changedProps: PropertyValues) {
+ super.update(changedProps);
+
+ if (!changedProps.has("route")) {
+ if (this.lastChild) {
+ this._updatePageEl(this.lastChild, changedProps);
+ }
+ return;
+ }
+
+ const route = this.route;
+
+ const routerOptions = (this.constructor as typeof HassRouterPage)
+ ._routerOptions;
+ const defaultPage = routerOptions.defaultPage || "";
+
+ if (route && route.path === "") {
+ navigate(this, `${route.prefix}/${defaultPage}`, true);
+ }
+
+ const newPage = route ? extractPage(route.path, defaultPage) : "not_found";
+
+ if (this._currentPage === newPage) {
+ if (this.lastChild) {
+ this._updatePageEl(this.lastChild, changedProps);
+ }
+ return;
+ }
+
+ this._currentPage = newPage;
+
+ const routeOptions = routerOptions.routes[newPage];
+
+ if (!routeOptions) {
+ if (this.lastChild) {
+ this._updatePageEl(this.lastChild, changedProps);
+ }
+ return;
+ }
+
+ const loadProm = routeOptions.load();
+
+ // Check when loading the page source failed.
+ loadProm.catch(() => {
+ // Verify that we're still trying to show the same page.
+ if (this._currentPage !== newPage) {
+ return;
+ }
+
+ // Removes either loading screen or the panel
+ this.removeChild(this.lastChild!);
+
+ // Show error screen
+ const errorEl = document.createElement("hass-error-screen");
+ errorEl.error = `Error while loading page ${newPage}.`;
+ this.appendChild(errorEl);
+ });
+
+ // If we don't show loading screen, just show the panel.
+ // It will be automatically upgraded when loading done.
+ if (!routerOptions.showLoading) {
+ this._createPanel(routerOptions, newPage, routeOptions);
+ return;
+ }
+
+ // We are only going to show the loading screen after some time.
+ // That way we won't have a double fast flash on fast connections.
+ let created = false;
+
+ setTimeout(() => {
+ if (created || this._currentPage !== newPage) {
+ return;
+ }
+
+ // Show a loading screen.
+ if (this.lastChild) {
+ this.removeChild(this.lastChild);
+ }
+
+ const loadingEl = document.createElement("hass-loading-screen");
+ loadingEl.isRoot = routerOptions.isRoot;
+ this.appendChild(loadingEl);
+ }, LOADING_SCREEN_THRESHOLD);
+
+ loadProm.then(() => {
+ // Check if we're still trying to show the same page.
+ if (this._currentPage !== newPage) {
+ return;
+ }
+
+ created = true;
+ this._createPanel(routerOptions, newPage, routeOptions);
+ });
+ }
+
+ protected firstUpdated(changedProps: PropertyValues) {
+ super.firstUpdated(changedProps);
+
+ const options = (this.constructor as typeof HassRouterPage)._routerOptions;
+
+ if (options.preloadAll) {
+ Object.values(options.routes).forEach((route) => route.load());
+ return;
+ }
+ }
+
+ protected _updatePageEl(_pageEl, _changedProps?: PropertyValues) {
+ // default we do nothing
+ }
+
+ private _createPanel(
+ routerOptions: RouterOptions,
+ page: string,
+ routeOptions: RouteOptions
+ ) {
+ if (this.lastChild) {
+ this.removeChild(this.lastChild);
+ }
+
+ const panelEl =
+ this._cache[page] || document.createElement(routeOptions.tag);
+ this._updatePageEl(panelEl);
+ this.appendChild(panelEl);
+
+ if (routerOptions.cacheAll || routeOptions.cache) {
+ this._cache[page] = panelEl;
+ }
+ }
+
+ protected get routeTail(): Route {
+ const route = this.route!;
+ const dividerPos = route.path.indexOf("/", 1);
+ return dividerPos === -1
+ ? {
+ prefix: route.path,
+ path: "",
+ }
+ : {
+ prefix: route.path.substr(0, dividerPos),
+ path: route.path.substr(dividerPos),
+ };
+ }
+}
diff --git a/src/layouts/home-assistant-main.ts b/src/layouts/home-assistant-main.ts
index 656064920f..fc66c847f6 100644
--- a/src/layouts/home-assistant-main.ts
+++ b/src/layouts/home-assistant-main.ts
@@ -25,8 +25,7 @@ const NON_SWIPABLE_PANELS = ["kiosk", "map"];
declare global {
// for fire event
interface HASSDomEvents {
- "hass-open-menu": undefined;
- "hass-close-menu": undefined;
+ "hass-toggle-menu": undefined;
}
}
@@ -70,7 +69,6 @@ class HomeAssistantMain extends LitElement {
.narrow=${this._narrow}
.hass=${hass}
.route=${this.route}
- .showMenu=${hass.dockedSidebar}
>
`;
@@ -79,17 +77,20 @@ class HomeAssistantMain extends LitElement {
protected firstUpdated() {
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
- this.addEventListener("hass-open-menu", () => {
- if (this._narrow) {
- this.drawer.open();
+ this.addEventListener("hass-toggle-menu", () => {
+ const shouldOpen = !this.drawer.opened;
+
+ if (shouldOpen) {
+ if (this._narrow) {
+ this.drawer.open();
+ } else {
+ fireEvent(this, "hass-dock-sidebar", { dock: true });
+ }
} else {
- fireEvent(this, "hass-dock-sidebar", { dock: true });
- }
- });
- this.addEventListener("hass-close-menu", () => {
- this.drawer.close();
- if (this.hass!.dockedSidebar) {
- fireEvent(this, "hass-dock-sidebar", { dock: false });
+ this.drawer.close();
+ if (this.hass!.dockedSidebar) {
+ fireEvent(this, "hass-dock-sidebar", { dock: false });
+ }
}
});
}
diff --git a/src/layouts/partial-panel-resolver.ts b/src/layouts/partial-panel-resolver.ts
index 3503c79a2c..7d5628fe82 100644
--- a/src/layouts/partial-panel-resolver.ts
+++ b/src/layouts/partial-panel-resolver.ts
@@ -1,252 +1,139 @@
-import { LitElement, html, PropertyValues, property } from "lit-element";
+import { property, customElement } from "lit-element";
+import { PolymerElement } from "@polymer/polymer";
-import "./hass-loading-screen";
-import "./hass-error-screen";
-import { HomeAssistant, Panel, PanelElement, Route } from "../types";
+import { HomeAssistant } from "../types";
+import { HassRouterPage, RouterOptions } from "./hass-router-page";
-// Cache of panel loading promises.
-const LOADED: { [panel: string]: Promise } = {};
-
-// Which panel elements we will cache.
-// Maybe we can cache them all eventually, but not sure yet about
-// unknown side effects (like history taking a lot of memory, reset needed)
-const CACHED_EL = ["lovelace", "states"];
-
-function ensureLoaded(panel): Promise | null {
- if (panel in LOADED) {
- return LOADED[panel];
- }
-
- let imported;
- // Name each panel we support here, that way Webpack knows about it.
- switch (panel) {
- case "config":
- imported = import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config");
- break;
-
- case "custom":
- imported = import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom");
- break;
-
- case "dev-event":
- imported = import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event");
- break;
-
- case "dev-info":
- imported = import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info");
- break;
-
- case "dev-mqtt":
- imported = import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt");
- break;
-
- case "dev-service":
- imported = import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service");
- break;
-
- case "dev-state":
- imported = import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state");
- break;
-
- case "dev-template":
- imported = import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template");
- break;
-
- case "lovelace":
- imported = import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace");
- break;
-
- case "states":
- imported = import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states");
- break;
-
- case "history":
- imported = import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history");
- break;
-
- case "iframe":
- imported = import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe");
- break;
-
- case "kiosk":
- imported = import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk");
- break;
-
- case "logbook":
- imported = import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook");
- break;
-
- case "mailbox":
- imported = import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox");
- break;
-
- case "map":
- imported = import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map");
- break;
-
- case "profile":
- imported = import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile");
- break;
-
- case "shopping-list":
- imported = import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list");
- break;
-
- case "calendar":
- imported = import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar");
- break;
-
- default:
- imported = null;
- }
-
- if (imported != null) {
- LOADED[panel] = imported;
- }
-
- return imported;
-}
-
-class PartialPanelResolver extends LitElement {
+@customElement("partial-panel-resolver")
+class PartialPanelResolver extends HassRouterPage {
+ protected static routerOptions: RouterOptions = {
+ isRoot: true,
+ showLoading: true,
+ routes: {
+ calendar: {
+ tag: "ha-panel-calendar",
+ load: () =>
+ import(/* webpackChunkName: "panel-calendar" */ "../panels/calendar/ha-panel-calendar"),
+ },
+ config: {
+ tag: "ha-panel-config",
+ load: () =>
+ import(/* webpackChunkName: "panel-config" */ "../panels/config/ha-panel-config"),
+ },
+ custom: {
+ tag: "ha-panel-custom",
+ load: () =>
+ import(/* webpackChunkName: "panel-custom" */ "../panels/custom/ha-panel-custom"),
+ },
+ "dev-event": {
+ tag: "ha-panel-dev-event",
+ load: () =>
+ import(/* webpackChunkName: "panel-dev-event" */ "../panels/dev-event/ha-panel-dev-event"),
+ },
+ "dev-info": {
+ tag: "ha-panel-dev-info",
+ load: () =>
+ import(/* webpackChunkName: "panel-dev-info" */ "../panels/dev-info/ha-panel-dev-info"),
+ },
+ "dev-mqtt": {
+ tag: "ha-panel-dev-mqtt",
+ load: () =>
+ import(/* webpackChunkName: "panel-dev-mqtt" */ "../panels/dev-mqtt/ha-panel-dev-mqtt"),
+ },
+ "dev-service": {
+ tag: "ha-panel-dev-service",
+ load: () =>
+ import(/* webpackChunkName: "panel-dev-service" */ "../panels/dev-service/ha-panel-dev-service"),
+ },
+ "dev-state": {
+ tag: "ha-panel-dev-state",
+ load: () =>
+ import(/* webpackChunkName: "panel-dev-state" */ "../panels/dev-state/ha-panel-dev-state"),
+ },
+ "dev-template": {
+ tag: "ha-panel-dev-template",
+ load: () =>
+ import(/* webpackChunkName: "panel-dev-template" */ "../panels/dev-template/ha-panel-dev-template"),
+ },
+ lovelace: {
+ cache: true,
+ tag: "ha-panel-lovelace",
+ load: () =>
+ import(/* webpackChunkName: "panel-lovelace" */ "../panels/lovelace/ha-panel-lovelace"),
+ },
+ states: {
+ cache: true,
+ tag: "ha-panel-states",
+ load: () =>
+ import(/* webpackChunkName: "panel-states" */ "../panels/states/ha-panel-states"),
+ },
+ history: {
+ tag: "ha-panel-history",
+ load: () =>
+ import(/* webpackChunkName: "panel-history" */ "../panels/history/ha-panel-history"),
+ },
+ iframe: {
+ tag: "ha-panel-iframe",
+ load: () =>
+ import(/* webpackChunkName: "panel-iframe" */ "../panels/iframe/ha-panel-iframe"),
+ },
+ kiosk: {
+ tag: "ha-panel-kiosk",
+ load: () =>
+ import(/* webpackChunkName: "panel-kiosk" */ "../panels/kiosk/ha-panel-kiosk"),
+ },
+ logbook: {
+ tag: "ha-panel-logbook",
+ load: () =>
+ import(/* webpackChunkName: "panel-logbook" */ "../panels/logbook/ha-panel-logbook"),
+ },
+ mailbox: {
+ tag: "ha-panel-mailbox",
+ load: () =>
+ import(/* webpackChunkName: "panel-mailbox" */ "../panels/mailbox/ha-panel-mailbox"),
+ },
+ map: {
+ tag: "ha-panel-map",
+ load: () =>
+ import(/* webpackChunkName: "panel-map" */ "../panels/map/ha-panel-map"),
+ },
+ profile: {
+ tag: "ha-panel-profile",
+ load: () =>
+ import(/* webpackChunkName: "panel-profile" */ "../panels/profile/ha-panel-profile"),
+ },
+ "shopping-list": {
+ tag: "ha-panel-shopping-list",
+ load: () =>
+ import(/* webpackChunkName: "panel-shopping-list" */ "../panels/shopping-list/ha-panel-shopping-list"),
+ },
+ },
+ };
@property() public hass?: HomeAssistant;
@property() public narrow?: boolean;
- @property() public showMenu?: boolean;
- @property() public route?: Route | null;
- @property() private _routeTail?: Route | null;
- @property() private _panelEl?: PanelElement;
- @property() private _error?: boolean;
- private _panel?: Panel;
- private _cache: { [name: string]: PanelElement } = {};
-
- protected render() {
- if (this._error) {
- return html`
-
- `;
- }
-
- if (!this._panelEl) {
- return html`
-
- `;
- }
-
- return html`
- ${this._panelEl}
- `;
- }
-
- protected updated(changedProps: PropertyValues) {
- super.updated(changedProps);
- if (!this.hass) {
- return;
- }
-
- if (changedProps.has("route")) {
- // Manual splitting
- const route = this.route!;
- const dividerPos = route.path.indexOf("/", 1);
- this._routeTail =
- dividerPos === -1
- ? {
- prefix: route.path,
- path: "",
- }
- : {
- prefix: route.path.substr(0, dividerPos),
- path: route.path.substr(dividerPos),
- };
-
- // If just route changed, no need to process further.
- if (changedProps.size === 1) {
- return;
- }
- }
-
- if (changedProps.has("hass")) {
- const panel = this.hass.panels[this.hass.panelUrl];
-
- if (panel !== this._panel) {
- this._panel = panel;
- this._panelEl = undefined;
-
- // Found cached one, use that
- if (panel.component_name in this._cache) {
- this._panelEl = this._cache[panel.component_name];
- this._updatePanel();
- return;
- }
-
- const loadingProm = ensureLoaded(panel.component_name);
-
- if (loadingProm === null) {
- this._error = true;
- return;
- }
-
- loadingProm.then(
- () => {
- // If panel changed while loading.
- if (this._panel !== panel) {
- return;
- }
-
- this._panelEl = (this._panelEl = document.createElement(
- `ha-panel-${panel.component_name}`
- )) as PanelElement;
-
- if (CACHED_EL.includes(panel.component_name)) {
- this._cache[panel.component_name] = this._panelEl;
- }
-
- this._error = false;
- this._updatePanel();
- },
- (err) => {
- // tslint:disable-next-line
- console.error("Error loading panel", err);
- this._error = true;
- }
- );
- return;
- }
- }
-
- this._updatePanel();
- }
-
- private _updatePanel() {
- const el = this._panelEl;
-
- if (!el) {
- return;
- }
+ protected _updatePageEl(el) {
+ const hass = this.hass!;
if ("setProperties" in el) {
// As long as we have Polymer panels
- (el as any).setProperties({
+ (el as PolymerElement).setProperties({
hass: this.hass,
narrow: this.narrow,
- showMenu: this.showMenu,
- route: this._routeTail,
- panel: this._panel,
+ route: this.routeTail,
+ panel: hass.panels[hass.panelUrl],
});
} else {
- el.hass = this.hass;
+ el.hass = hass;
el.narrow = this.narrow;
- el.showMenu = this.showMenu;
- el.route = this._routeTail;
- el.panel = this._panel;
+ el.route = this.routeTail;
+ el.panel = hass.panels[hass.panelUrl];
}
}
}
-customElements.define("partial-panel-resolver", PartialPanelResolver);
+declare global {
+ interface HTMLElementTagNameMap {
+ "partial-panel-resolver": PartialPanelResolver;
+ }
+}
diff --git a/src/panels/calendar/ha-panel-calendar.js b/src/panels/calendar/ha-panel-calendar.js
index f7285dd111..244cf8a9df 100644
--- a/src/panels/calendar/ha-panel-calendar.js
+++ b/src/panels/calendar/ha-panel-calendar.js
@@ -67,10 +67,7 @@ class HaPanelCalendar extends LocalizeMixin(PolymerElement) {
-
+
[[localize('panel.calendar')]]
@@ -145,11 +142,6 @@ class HaPanelCalendar extends LocalizeMixin(PolymerElement) {
type: Boolean,
reflectToAttribute: true,
},
-
- showMenu: {
- type: Boolean,
- value: false,
- },
};
}
diff --git a/src/panels/config/automation/ha-automation-picker.js b/src/panels/config/automation/ha-automation-picker.js
index a699599bbf..9d0ecbc92a 100644
--- a/src/panels/config/automation/ha-automation-picker.js
+++ b/src/panels/config/automation/ha-automation-picker.js
@@ -128,15 +128,6 @@ class HaAutomationPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
type: Object,
},
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
automations: {
type: Array,
},
diff --git a/src/panels/config/automation/ha-config-automation.js b/src/panels/config/automation/ha-config-automation.js
index a7f97c2d0f..679820e697 100644
--- a/src/panels/config/automation/ha-config-automation.js
+++ b/src/panels/config/automation/ha-config-automation.js
@@ -31,8 +31,6 @@ class HaConfigAutomation extends PolymerElement {
@@ -52,8 +50,6 @@ class HaConfigAutomation extends PolymerElement {
static get properties() {
return {
hass: Object,
- narrow: Boolean,
- showMenu: Boolean,
route: Object,
isWide: Boolean,
_routeData: Object,
diff --git a/src/panels/config/dashboard/ha-config-dashboard.js b/src/panels/config/dashboard/ha-config-dashboard.js
index e931f4d54e..f690b1c2b4 100644
--- a/src/panels/config/dashboard/ha-config-dashboard.js
+++ b/src/panels/config/dashboard/ha-config-dashboard.js
@@ -40,7 +40,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
-
+
[[localize('panel.config')]]
@@ -111,8 +111,6 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
hass: Object,
isWide: Boolean,
cloudStatus: Object,
- narrow: Boolean,
- showMenu: Boolean,
};
}
diff --git a/src/panels/config/ha-config-section.js b/src/panels/config/ha-config-section.js
index cb217cc872..800eda13c8 100644
--- a/src/panels/config/ha-config-section.js
+++ b/src/panels/config/ha-config-section.js
@@ -75,11 +75,6 @@ class HaConfigSection extends PolymerElement {
type: Boolean,
},
- showMenu: {
- type: Boolean,
- value: false,
- },
-
isWide: {
type: Boolean,
value: false,
diff --git a/src/panels/config/ha-panel-config.js b/src/panels/config/ha-panel-config.js
deleted file mode 100644
index 726e46ae85..0000000000
--- a/src/panels/config/ha-panel-config.js
+++ /dev/null
@@ -1,243 +0,0 @@
-import "@polymer/app-route/app-route";
-import "@polymer/iron-media-query/iron-media-query";
-import { html } from "@polymer/polymer/lib/utils/html-tag";
-import { PolymerElement } from "@polymer/polymer/polymer-element";
-
-import isComponentLoaded from "../../common/config/is_component_loaded";
-import EventsMixin from "../../mixins/events-mixin";
-import NavigateMixin from "../../mixins/navigate-mixin";
-
-/*
- * @appliesMixin EventsMixin
- * @appliesMixin NavigateMixin
- */
-class HaPanelConfig extends EventsMixin(NavigateMixin(PolymerElement)) {
- static get template() {
- return html`
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- `;
- }
-
- static get properties() {
- return {
- hass: Object,
- narrow: Boolean,
- showMenu: Boolean,
- _cloudStatus: {
- type: Object,
- value: null,
- },
-
- route: {
- type: Object,
- observer: "_routeChanged",
- },
-
- _routeData: Object,
-
- wide: Boolean,
- wideSidebar: Boolean,
-
- isWide: {
- type: Boolean,
- computed: "computeIsWide(showMenu, wideSidebar, wide)",
- },
- };
- }
-
- ready() {
- super.ready();
- if (isComponentLoaded(this.hass, "cloud")) {
- this._updateCloudStatus();
- }
- this.addEventListener("ha-refresh-cloud-status", () =>
- this._updateCloudStatus()
- );
- import(/* webpackChunkName: "panel-config-area-registry" */ "./area_registry/ha-config-area-registry");
- import(/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation");
- import(/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud");
- import(/* webpackChunkName: "panel-config-config" */ "./config-entries/ha-config-entries");
- import(/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core");
- import(/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize");
- import(/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard");
- import(/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script");
- import(/* webpackChunkName: "panel-config-entity-registry" */ "./entity_registry/ha-config-entity-registry");
- import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users");
- import(/* webpackChunkName: "panel-config-zha" */ "./zha/ha-config-zha");
- import(/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave");
- import(/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person");
- }
-
- async _updateCloudStatus() {
- this._cloudStatus = await this.hass.callWS({ type: "cloud/status" });
-
- if (this._cloudStatus.cloud === "connecting") {
- setTimeout(() => this._updateCloudStatus(), 5000);
- }
- }
-
- computeIsWide(showMenu, wideSidebar, wide) {
- return showMenu ? wideSidebar : wide;
- }
-
- _routeChanged(route) {
- if (route.path === "" && route.prefix === "/config") {
- this.navigate("/config/dashboard", true);
- }
- this.fire("iron-resize");
- }
-
- _equals(a, b) {
- return a === b;
- }
-}
-
-customElements.define("ha-panel-config", HaPanelConfig);
diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts
new file mode 100644
index 0000000000..aad87ca75a
--- /dev/null
+++ b/src/panels/config/ha-panel-config.ts
@@ -0,0 +1,142 @@
+import { property, PropertyValues, customElement } from "lit-element";
+import "../../layouts/hass-loading-screen";
+import isComponentLoaded from "../../common/config/is_component_loaded";
+import { HomeAssistant } from "../../types";
+import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
+import { listenMediaQuery } from "../../common/dom/media_query";
+import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
+
+@customElement("ha-panel-config")
+class HaPanelConfig extends HassRouterPage {
+ protected static routerOptions: RouterOptions = {
+ defaultPage: "dashboard",
+ cacheAll: true,
+ preloadAll: true,
+ routes: {
+ area_registry: {
+ tag: "ha-config-area-registry",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-area-registry" */ "./area_registry/ha-config-area-registry"),
+ },
+ automation: {
+ tag: "ha-config-automation",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-automation" */ "./automation/ha-config-automation"),
+ },
+ cloud: {
+ tag: "ha-config-cloud",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-cloud" */ "./cloud/ha-config-cloud"),
+ },
+ core: {
+ tag: "ha-config-core",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-core" */ "./core/ha-config-core"),
+ },
+ customize: {
+ tag: "ha-config-customize",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-customize" */ "./customize/ha-config-customize"),
+ },
+ dashboard: {
+ tag: "ha-config-dashboard",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-dashboard" */ "./dashboard/ha-config-dashboard"),
+ },
+ entity_registry: {
+ tag: "ha-config-entity-registry",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-entity-registry" */ "./entity_registry/ha-config-entity-registry"),
+ },
+ integrations: {
+ tag: "ha-config-integrations",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"),
+ },
+ person: {
+ tag: "ha-config-person",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-person" */ "./person/ha-config-person"),
+ },
+ script: {
+ tag: "ha-config-script",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-script" */ "./script/ha-config-script"),
+ },
+ users: {
+ tag: "ha-config-users",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-users" */ "./users/ha-config-users"),
+ },
+ zha: {
+ tag: "ha-config-zha",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-zha" */ "./zha/ha-config-zha"),
+ },
+ zwave: {
+ tag: "ha-config-zwave",
+ load: () =>
+ import(/* webpackChunkName: "panel-config-zwave" */ "./zwave/ha-config-zwave"),
+ },
+ },
+ };
+
+ @property() public hass!: HomeAssistant;
+ @property() public _wideSidebar: boolean = false;
+ @property() public _wide: boolean = false;
+ @property() private _cloudStatus?: CloudStatus;
+
+ private _listeners: Array<() => void> = [];
+
+ public connectedCallback() {
+ super.connectedCallback();
+ this._listeners.push(
+ listenMediaQuery("(min-width: 1040px)", (matches) => {
+ this._wide = matches;
+ })
+ );
+ this._listeners.push(
+ listenMediaQuery("(min-width: 1296px)", (matches) => {
+ this._wideSidebar = matches;
+ })
+ );
+ }
+
+ public disconnectedCallback() {
+ super.disconnectedCallback();
+ while (this._listeners.length) {
+ this._listeners.pop()!();
+ }
+ }
+
+ protected firstUpdated(changedProps: PropertyValues) {
+ super.firstUpdated(changedProps);
+ if (isComponentLoaded(this.hass, "cloud")) {
+ this._updateCloudStatus();
+ }
+ this.addEventListener("ha-refresh-cloud-status", () =>
+ this._updateCloudStatus()
+ );
+ }
+
+ protected _updatePageEl(el) {
+ el.route = this.route;
+ el.hass = this.hass;
+ el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide;
+ el.cloudStatus = this._cloudStatus;
+ }
+
+ private async _updateCloudStatus() {
+ this._cloudStatus = await fetchCloudStatus(this.hass);
+
+ if (this._cloudStatus.cloud === "connecting") {
+ setTimeout(() => this._updateCloudStatus(), 5000);
+ }
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-panel-config": HaPanelConfig;
+ }
+}
diff --git a/src/panels/config/config-entries/ha-ce-entities-card.js b/src/panels/config/integrations/ha-ce-entities-card.js
similarity index 100%
rename from src/panels/config/config-entries/ha-ce-entities-card.js
rename to src/panels/config/integrations/ha-ce-entities-card.js
diff --git a/src/panels/config/config-entries/ha-config-entries-dashboard.js b/src/panels/config/integrations/ha-config-entries-dashboard.js
similarity index 100%
rename from src/panels/config/config-entries/ha-config-entries-dashboard.js
rename to src/panels/config/integrations/ha-config-entries-dashboard.js
diff --git a/src/panels/config/config-entries/ha-config-entry-page.js b/src/panels/config/integrations/ha-config-entry-page.js
similarity index 100%
rename from src/panels/config/config-entries/ha-config-entry-page.js
rename to src/panels/config/integrations/ha-config-entry-page.js
diff --git a/src/panels/config/config-entries/ha-config-entries.js b/src/panels/config/integrations/ha-config-integrations.js
similarity index 96%
rename from src/panels/config/config-entries/ha-config-entries.js
rename to src/panels/config/integrations/ha-config-integrations.js
index ec69a18267..035ff0dbca 100644
--- a/src/panels/config/config-entries/ha-config-entries.js
+++ b/src/panels/config/integrations/ha-config-integrations.js
@@ -10,7 +10,7 @@ import NavigateMixin from "../../../mixins/navigate-mixin";
import compare from "../../../common/string/compare";
import { fetchAreaRegistry } from "../../../data/area_registry";
-class HaConfigEntries extends NavigateMixin(PolymerElement) {
+class HaConfigIntegrations extends NavigateMixin(PolymerElement) {
static get template() {
return html`
@@ -53,8 +51,6 @@ class HaConfigScript extends PolymerElement {
static get properties() {
return {
hass: Object,
- narrow: Boolean,
- showMenu: Boolean,
route: Object,
isWide: Boolean,
_routeData: Object,
diff --git a/src/panels/config/script/ha-script-editor.js b/src/panels/config/script/ha-script-editor.js
index 756de22e45..e0a9794971 100644
--- a/src/panels/config/script/ha-script-editor.js
+++ b/src/panels/config/script/ha-script-editor.js
@@ -130,15 +130,6 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
type: Object,
},
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
errors: {
type: Object,
value: null,
diff --git a/src/panels/config/script/ha-script-picker.js b/src/panels/config/script/ha-script-picker.js
index 9d68400551..6f4b52820d 100644
--- a/src/panels/config/script/ha-script-picker.js
+++ b/src/panels/config/script/ha-script-picker.js
@@ -124,15 +124,6 @@ class HaScriptPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
type: Object,
},
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
scripts: {
type: Array,
},
diff --git a/src/panels/custom/ha-panel-custom.js b/src/panels/custom/ha-panel-custom.js
index d0a60efae8..ad0c59dfc0 100644
--- a/src/panels/custom/ha-panel-custom.js
+++ b/src/panels/custom/ha-panel-custom.js
@@ -16,7 +16,6 @@ class HaPanelCustom extends NavigateMixin(EventsMixin(PolymerElement)) {
return {
hass: Object,
narrow: Boolean,
- showMenu: Boolean,
route: Object,
panel: {
type: Object,
@@ -26,7 +25,7 @@ class HaPanelCustom extends NavigateMixin(EventsMixin(PolymerElement)) {
}
static get observers() {
- return ["_dataChanged(hass, narrow, showMenu, route)"];
+ return ["_dataChanged(hass, narrow, route)"];
}
constructor() {
@@ -74,7 +73,6 @@ It will have access to all data in Home Assistant.
panel,
hass: this.hass,
narrow: this.narrow,
- showMenu: this.showMenu,
route: this.route,
});
this.appendChild(element);
@@ -109,16 +107,15 @@ It will have access to all data in Home Assistant.
delete window.customPanel;
}
- _dataChanged(hass, narrow, showMenu, route) {
+ _dataChanged(hass, narrow, route) {
if (!this._setProperties) return;
- this._setProperties({ hass, narrow, showMenu, route });
+ this._setProperties({ hass, narrow, route });
}
registerIframe(initialize, setProperties) {
initialize(this.panel, {
hass: this.hass,
narrow: this.narrow,
- showMenu: this.showMenu,
route: this.route,
});
this._setProperties = setProperties;
diff --git a/src/panels/dev-event/ha-panel-dev-event.js b/src/panels/dev-event/ha-panel-dev-event.js
index 6ca1a1f9a1..dfaa8cbb76 100644
--- a/src/panels/dev-event/ha-panel-dev-event.js
+++ b/src/panels/dev-event/ha-panel-dev-event.js
@@ -52,10 +52,7 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
-
+
Events
@@ -100,16 +97,6 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
type: Object,
},
- narrow: {
- type: Boolean,
- value: false,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
eventType: {
type: String,
value: "",
diff --git a/src/panels/dev-info/ha-panel-dev-info.ts b/src/panels/dev-info/ha-panel-dev-info.ts
index 200bf3baba..798b72d93b 100644
--- a/src/panels/dev-info/ha-panel-dev-info.ts
+++ b/src/panels/dev-info/ha-panel-dev-info.ts
@@ -23,14 +23,10 @@ const OPT_IN_PANEL = "states";
class HaPanelDevInfo extends LitElement {
public hass?: HomeAssistant;
- public narrow?: boolean;
- public showMenu?: boolean;
static get properties(): PropertyDeclarations {
return {
hass: {},
- narrow: {},
- showMenu: {},
};
}
@@ -60,10 +56,7 @@ class HaPanelDevInfo extends LitElement {
-
+
About
diff --git a/src/panels/dev-mqtt/ha-panel-dev-mqtt.js b/src/panels/dev-mqtt/ha-panel-dev-mqtt.js
index 7d539f1eb4..ae76851474 100644
--- a/src/panels/dev-mqtt/ha-panel-dev-mqtt.js
+++ b/src/panels/dev-mqtt/ha-panel-dev-mqtt.js
@@ -41,10 +41,7 @@ class HaPanelDevMqtt extends PolymerElement {
-
+
MQTT
@@ -80,8 +77,6 @@ class HaPanelDevMqtt extends PolymerElement {
static get properties() {
return {
hass: Object,
- narrow: Boolean,
- showMenu: Boolean,
topic: String,
payload: String,
};
diff --git a/src/panels/dev-service/ha-panel-dev-service.js b/src/panels/dev-service/ha-panel-dev-service.js
index 11f84b78af..b4b0be23fe 100644
--- a/src/panels/dev-service/ha-panel-dev-service.js
+++ b/src/panels/dev-service/ha-panel-dev-service.js
@@ -84,10 +84,7 @@ class HaPanelDevService extends PolymerElement {
-
+
Services
@@ -187,16 +184,6 @@ class HaPanelDevService extends PolymerElement {
type: Object,
},
- narrow: {
- type: Boolean,
- value: false,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
domainService: {
type: String,
observer: "_domainServiceChanged",
diff --git a/src/panels/dev-state/ha-panel-dev-state.js b/src/panels/dev-state/ha-panel-dev-state.js
index 3d51cdd7d1..559081448f 100644
--- a/src/panels/dev-state/ha-panel-dev-state.js
+++ b/src/panels/dev-state/ha-panel-dev-state.js
@@ -73,10 +73,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
-
+
States
@@ -183,16 +180,6 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
type: Object,
},
- narrow: {
- type: Boolean,
- value: false,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
_entityId: {
type: String,
value: "",
diff --git a/src/panels/dev-template/ha-panel-dev-template.js b/src/panels/dev-template/ha-panel-dev-template.js
index f493f2b19e..47a597ef90 100644
--- a/src/panels/dev-template/ha-panel-dev-template.js
+++ b/src/panels/dev-template/ha-panel-dev-template.js
@@ -70,10 +70,7 @@ class HaPanelDevTemplate extends PolymerElement {
-
+
Templates
@@ -125,16 +122,6 @@ class HaPanelDevTemplate extends PolymerElement {
type: Object,
},
- narrow: {
- type: Boolean,
- value: false,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
error: {
type: Boolean,
value: false,
diff --git a/src/panels/history/ha-panel-history.js b/src/panels/history/ha-panel-history.js
index a09ee7bf8f..fd99ce9ae6 100644
--- a/src/panels/history/ha-panel-history.js
+++ b/src/panels/history/ha-panel-history.js
@@ -65,10 +65,7 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) {
-
+
[[localize('panel.history')]]
@@ -123,15 +120,6 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) {
type: Object,
},
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
stateHistory: {
type: Object,
value: null,
diff --git a/src/panels/iframe/ha-panel-iframe.js b/src/panels/iframe/ha-panel-iframe.js
index 3a352cab00..8bd544f782 100644
--- a/src/panels/iframe/ha-panel-iframe.js
+++ b/src/panels/iframe/ha-panel-iframe.js
@@ -16,10 +16,7 @@ class HaPanelIframe extends PolymerElement {
}
-
+
[[panel.title]]
@@ -38,14 +35,6 @@ class HaPanelIframe extends PolymerElement {
panel: {
type: Object,
},
-
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- },
};
}
}
diff --git a/src/panels/logbook/ha-panel-logbook.js b/src/panels/logbook/ha-panel-logbook.js
index aa7ecdebe1..78af42fc50 100644
--- a/src/panels/logbook/ha-panel-logbook.js
+++ b/src/panels/logbook/ha-panel-logbook.js
@@ -89,10 +89,7 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
-
+
[[localize('panel.logbook')]]
;
public hass?: HomeAssistant;
public narrow?: boolean;
- public showMenu?: boolean;
public route?: Route;
private _columns?: number;
private _state?: "loading" | "loaded" | "error" | "yaml-editor";
@@ -38,8 +37,6 @@ class LovelacePanel extends LitElement {
return {
hass: {},
lovelace: {},
- narrow: {},
- showMenu: {},
route: {},
_columns: {},
_state: {},
@@ -60,8 +57,6 @@ class LovelacePanel extends LitElement {
if (state === "loaded") {
return html`
+
Reload Lovelace
`;
@@ -95,16 +85,25 @@ class LovelacePanel extends LitElement {
}
return html`
-
+
`;
}
public updated(changedProps: PropertyValues): void {
super.updated(changedProps);
- if (changedProps.has("narrow") || changedProps.has("showMenu")) {
+
+ if (changedProps.has("narrow")) {
+ this._updateColumns();
+ return;
+ }
+
+ if (!changedProps.has("hass")) {
+ return;
+ }
+
+ const oldHass = changedProps.get("hass") as this["hass"];
+
+ if (oldHass && this.hass!.dockedSidebar !== oldHass.dockedSidebar) {
this._updateColumns();
}
}
@@ -144,7 +143,7 @@ class LovelacePanel extends LitElement {
// Do -1 column if the menu is docked and open
this._columns = Math.max(
1,
- matchColumns - Number(!this.narrow && this.showMenu)
+ matchColumns - Number(!this.narrow && this.hass!.dockedSidebar)
);
}
diff --git a/src/panels/lovelace/hui-root.ts b/src/panels/lovelace/hui-root.ts
index 631416ddfa..219d5a27de 100644
--- a/src/panels/lovelace/hui-root.ts
+++ b/src/panels/lovelace/hui-root.ts
@@ -1,11 +1,11 @@
import {
html,
LitElement,
- PropertyDeclarations,
PropertyValues,
TemplateResult,
CSSResult,
css,
+ property,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "@polymer/app-layout/app-header-layout/app-header-layout";
@@ -60,39 +60,22 @@ const JS_CACHE = {};
let loadedUnusedEntities = false;
class HUIRoot extends LitElement {
- public narrow?: boolean;
- public showMenu?: boolean;
- public hass?: HomeAssistant;
- public lovelace?: Lovelace;
- public columns?: number;
- public route?: { path: string; prefix: string };
- private _routeData?: { view: string };
- private _curView?: number | "hass-unused-entities";
- private _notificationsOpen: boolean;
- private _persistentNotifications?: Notification[];
+ @property() public hass?: HomeAssistant;
+ @property() public lovelace?: Lovelace;
+ @property() public columns?: number;
+ @property() public narrow?: boolean;
+ @property() public route?: { path: string; prefix: string };
+ @property() private _routeData?: { view: string };
+ @property() private _curView?: number | "hass-unused-entities";
+ @property() private _notificationsOpen = false;
+ @property() private _persistentNotifications?: Notification[];
private _viewCache?: { [viewId: string]: HUIView };
private _debouncedConfigChanged: () => void;
private _unsubNotifications?: () => void;
- static get properties(): PropertyDeclarations {
- return {
- narrow: {},
- showMenu: {},
- hass: {},
- lovelace: {},
- columns: {},
- route: {},
- _routeData: {},
- _curView: {},
- _notificationsOpen: {},
- _persistentNotifications: {},
- };
- }
-
constructor() {
super();
- this._notificationsOpen = false;
// The view can trigger a re-render when it knows that certain
// web components have been loaded.
this._debouncedConfigChanged = debounce(
@@ -181,10 +164,7 @@ class HUIRoot extends LitElement {
`
: html`
-
+
${this.config.title || "Home Assistant"}
-
+
[[localize('panel.mailbox')]]
@@ -135,16 +132,6 @@ class HaPanelMailbox extends EventsMixin(LocalizeMixin(PolymerElement)) {
type: Object,
},
- narrow: {
- type: Boolean,
- value: false,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
-
platforms: {
type: Array,
},
diff --git a/src/panels/map/ha-panel-map.js b/src/panels/map/ha-panel-map.js
index 6110c89064..5abcd46892 100644
--- a/src/panels/map/ha-panel-map.js
+++ b/src/panels/map/ha-panel-map.js
@@ -27,10 +27,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
-
+
[[localize('panel.map')]]
@@ -44,15 +41,6 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
type: Object,
observer: "drawEntities",
},
-
- narrow: {
- type: Boolean,
- },
-
- showMenu: {
- type: Boolean,
- value: false,
- },
};
}
diff --git a/src/panels/profile/ha-panel-profile.js b/src/panels/profile/ha-panel-profile.js
index f6672183c5..8e8c159054 100644
--- a/src/panels/profile/ha-panel-profile.js
+++ b/src/panels/profile/ha-panel-profile.js
@@ -52,10 +52,7 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
-
+
[[localize('panel.profile')]]
@@ -121,7 +118,6 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
return {
hass: Object,
narrow: Boolean,
- showMenu: Boolean,
_refreshTokens: Array,
};
}
diff --git a/src/panels/shopping-list/ha-panel-shopping-list.js b/src/panels/shopping-list/ha-panel-shopping-list.js
index 9e7935429d..6900d21de9 100644
--- a/src/panels/shopping-list/ha-panel-shopping-list.js
+++ b/src/panels/shopping-list/ha-panel-shopping-list.js
@@ -70,10 +70,7 @@ class HaPanelShoppingList extends LocalizeMixin(PolymerElement) {
-
+
[[localize('panel.shopping_list')]]
-
+
[[computeTitle(views, defaultView, locationName)]]
@@ -160,10 +157,6 @@ class PartialCards extends EventsMixin(NavigateMixin(PolymerElement)) {
value: false,
},
- showMenu: {
- type: Boolean,
- },
-
panelVisible: {
type: Boolean,
value: false,
@@ -215,7 +208,7 @@ class PartialCards extends EventsMixin(NavigateMixin(PolymerElement)) {
}
static get observers() {
- return ["_updateColumns(narrow, showMenu)"];
+ return ["_updateColumns(narrow, hass.dockedSidebar)"];
}
ready() {
@@ -239,7 +232,10 @@ class PartialCards extends EventsMixin(NavigateMixin(PolymerElement)) {
_updateColumns() {
const matchColumns = this.mqls.reduce((cols, mql) => cols + mql.matches, 0);
// Do -1 column if the menu is docked and open
- this._columns = Math.max(1, matchColumns - (!this.narrow && this.showMenu));
+ this._columns = Math.max(
+ 1,
+ matchColumns - (!this.narrow && this.hass.dockedSidebar)
+ );
}
areTabsHidden(views, showTabs) {
diff --git a/src/resources/styles.ts b/src/resources/styles.ts
index 66a8b2adc7..7a834dd380 100644
--- a/src/resources/styles.ts
+++ b/src/resources/styles.ts
@@ -18,6 +18,7 @@ export const haStyle = css`
}
app-toolbar ha-menu-button + [main-title],
+ app-toolbar ha-paper-icon-button-arrow-prev + [main-title],
app-toolbar paper-icon-button + [main-title] {
margin-left: 24px;
}
diff --git a/src/types.ts b/src/types.ts
index 9666032908..317f12f2bc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -225,7 +225,6 @@ export interface Route {
export interface PanelElement extends HTMLElement {
hass?: HomeAssistant;
narrow?: boolean;
- showMenu?: boolean;
route?: Route | null;
panel?: Panel;
}
diff --git a/translations/ar.json b/translations/ar.json
index 060ef96c59..b19ac37034 100644
--- a/translations/ar.json
+++ b/translations/ar.json
@@ -250,6 +250,15 @@
"on": "مشغل",
"paused": "موقّف مؤقتا",
"returning": "العودة"
+ },
+ "timer": {
+ "active": "مفعل",
+ "idle": "خامل",
+ "paused": "موقّف مؤقتا"
+ },
+ "person": {
+ "home": "في المنزل",
+ "not_home": "خارج المنزل"
}
},
"state_badge": {
@@ -272,6 +281,10 @@
"device_tracker": {
"home": "في المنزل",
"not_home": "خارج المنزل"
+ },
+ "person": {
+ "home": "في المنزل",
+ "not_home": "خارج المنزل"
}
},
"ui": {
@@ -340,7 +353,8 @@
"introduction": "محرر المتحكم الآلي يسمح لك بإنشاء وتحرير المتحكمات الآلية. يرجى قراءة [الإرشادات] (https:\/\/home-assistant.io\/docs\/automation\/editor\/) للتأكد من إعدادك Home Assistant بشكل صحيح.",
"pick_automation": "أختر متحكم آلي للتعديل",
"no_automations": "لم تعثر على أي متحكم آلي قابل للتعديل",
- "add_automation": "أضف متحكم آلي"
+ "add_automation": "أضف متحكم آلي",
+ "learn_more": "معرفة المزيد عن التحكم الآلي"
},
"editor": {
"introduction": "استخدم المتحكمات الآلية لتجعل منزلك ينبض بالحياة",
@@ -414,7 +428,8 @@
"minutes": "الدقائق",
"seconds": "ثواني"
}
- }
+ },
+ "learn_more": "معرفة المزيد عن المشغلات"
},
"conditions": {
"header": "الشروط",
@@ -459,7 +474,8 @@
"entity": "الجهاز في المنطقة",
"zone": "المنطقة"
}
- }
+ },
+ "learn_more": "معرفة المزيد عن الشروط"
},
"actions": {
"header": "الإجراءات",
@@ -492,7 +508,8 @@
"event": "الحدث",
"service_data": "بيانات الخدمة"
}
- }
+ },
+ "learn_more": "معرفة المزيد عن الإجراءات"
}
}
},
@@ -515,7 +532,15 @@
"change_password": "تغيير كلمة السر",
"activate_user": "تفعيل المستخدم",
"deactivate_user": "إلغاء تنشيط المستخدم",
- "delete_user": "حذف المستخدم"
+ "delete_user": "حذف المستخدم",
+ "caption": "عرض المستخدم"
+ },
+ "add_user": {
+ "caption": "أضف مستخدم",
+ "name": "الاسم",
+ "username": "اسم المستخدم",
+ "password": "كلمه السر",
+ "create": "إنشاء"
}
},
"cloud": {
@@ -540,7 +565,24 @@
"hub": "متصل من خلال",
"firmware": "نظام التشغيل {version}",
"device_unavailable": "الجهاز غير متوفر",
- "entity_unavailable": "العنصر غير متوفر"
+ "entity_unavailable": "العنصر غير متوفر",
+ "no_area": "لا توجد منطقة"
+ }
+ },
+ "person": {
+ "detail": {
+ "device_tracker_pick": "اختر جهاز لتتبع"
+ }
+ },
+ "area_registry": {
+ "picker": {
+ "no_areas": "يبدو أنه لا توجد أي مناطق معرفة!",
+ "create_area": "إنشاء منطقة"
+ }
+ },
+ "zha": {
+ "services": {
+ "updateDeviceName": "تعيين اسم مخصص لهذا الجهاز في سجل الأجهزة."
}
}
},
@@ -599,7 +641,11 @@
"page-onboarding": {
"user": {
"error": {
- "required_fields": "املأ جميع الحقول المطلوبة"
+ "required_fields": "املأ جميع الحقول المطلوبة",
+ "password_not_match": "كلمة السر غير مطابقة"
+ },
+ "data": {
+ "password_confirm": "تأكيد كلمة السر"
}
}
},
@@ -607,10 +653,36 @@
"editor": {
"edit_card": {
"edit": "تصحيح",
- "delete": "حذف"
+ "delete": "حذف",
+ "move": "نقل"
},
"save_config": {
"cancel": "لا يهم"
+ },
+ "raw_editor": {
+ "header": "تعديل",
+ "save": "حفظ",
+ "unsaved_changes": "التغييرات غير محفوظة",
+ "saved": "تم الحفظ"
+ }
+ },
+ "warning": {
+ "entity_not_found": "الجهاز غير متوفر: {entity}"
+ }
+ },
+ "page-authorize": {
+ "form": {
+ "providers": {
+ "command_line": {
+ "step": {
+ "init": {
+ "data": {
+ "username": "اسم المستخدم",
+ "password": "كلمة السر"
+ }
+ }
+ }
+ }
}
}
}
@@ -681,6 +753,9 @@
"target_temperature": "درجة الحرارة المستهدفة",
"operation": "تشغيل",
"away_mode": "حالة خارج المنزل"
+ },
+ "alarm_control_panel": {
+ "arm_custom_bypass": "تجاوز مخصص"
}
},
"components": {
@@ -750,7 +825,8 @@
"updater": "تحديث",
"weblink": "Weblink",
"zwave": "Z-Wave",
- "vacuum": "مكنسة كهرباء"
+ "vacuum": "مكنسة كهرباء",
+ "person": "شخص"
},
"attribute": {
"weather": {
@@ -758,5 +834,19 @@
"visibility": "الرؤية",
"wind_speed": "سرعة الرياح"
}
+ },
+ "state_attributes": {
+ "climate": {
+ "fan_mode": {
+ "off": "مطفأ",
+ "on": "مفعل",
+ "auto": "آلي"
+ }
+ }
+ },
+ "groups": {
+ "system-admin": "مسؤولين",
+ "system-users": "مستخدمين",
+ "system-read-only": "مستخدمين للعرض فقط"
}
}
\ No newline at end of file
diff --git a/translations/bg.json b/translations/bg.json
index 233414b26b..e013bf43b2 100644
--- a/translations/bg.json
+++ b/translations/bg.json
@@ -270,6 +270,10 @@
"active": "активен",
"idle": "неработещ",
"paused": "в пауза"
+ },
+ "person": {
+ "home": "Вкъщи",
+ "not_home": "Отсъства"
}
},
"state_badge": {
@@ -292,6 +296,10 @@
"device_tracker": {
"home": "Вкъщи",
"not_home": "Отсъства"
+ },
+ "person": {
+ "home": "Вкъщи",
+ "not_home": "Отсъства"
}
},
"ui": {
@@ -313,7 +321,7 @@
"empty": "Нямате съобщения",
"playback_title": "Възпроизвеждане на съобщение",
"delete_prompt": "Изтриване на това съобщение?",
- "delete_button": "Изтрий"
+ "delete_button": "Изтриване"
},
"config": {
"header": "Конфигуриране на Home Assistant",
@@ -362,23 +370,24 @@
"description": "Създаване и редактиране на автоматизации",
"picker": {
"header": "Редактор на автоматизации",
- "introduction": "Редакторът на автоматизации позволява да създавате и редактирате автоматизации. Моля, прочетете [указанията](https:\/\/home-assistant.io\/docs\/automation\/editor\/), за да се уверите, че Home Assistant е правилно конфигуриран.",
+ "introduction": "Редакторът на автоматизации позволява да създавате и редактирате автоматизации. Моля, последвайте препратката по-долу за да прочетете инструкциите за да се уверите, че Home Assistant е правилно конфигуриран.",
"pick_automation": "Изберете автоматизация за редактиране",
"no_automations": "Не можахме да намерим никакви автоматизации подлежащи на редакция",
- "add_automation": "Добавяне на автоматизация"
+ "add_automation": "Добавяне на автоматизация",
+ "learn_more": "Научете повече за автоматизациите"
},
"editor": {
"introduction": "Използвайте автоматизации, за да съживите дома си",
"default_name": "Нова автоматизация",
- "save": "Запази",
+ "save": "Запазване",
"unsaved_confirm": "Имате незапазени промени. Сигурни ли сте, че искате да напуснете?",
"alias": "Име",
"triggers": {
"header": "Тригери",
- "introduction": "Тригерите са това, което стартира обработката на правило за автоматизация. Възможно е да се зададат множество тригери за едно и също правило. След като тригера стартира, Home Assistant ще провери условията, ако има такива, и ще стартира действието. \n\n[Научете повече за тригерите.](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
+ "introduction": "Тригерите са това, което стартира обработката на правило за автоматизация. Възможно е да се зададат множество тригери за едно и също правило. След като тригера стартира, Home Assistant ще провери условията, ако има такива, и ще стартира действието.",
"add": "Добавяне на тригер",
- "duplicate": "Копирай",
- "delete": "Изтрий",
+ "duplicate": "Копиране",
+ "delete": "Изтриване",
"delete_confirm": "Сигурни ли сте, че искате да изтриете?",
"unsupported_platform": "Неподдържана платформа: {platform}",
"type_select": "Тип на тригера",
@@ -452,14 +461,15 @@
"enter": "Влизане",
"leave": "Излизане"
}
- }
+ },
+ "learn_more": "Научете повече за тригерите"
},
"conditions": {
"header": "Условия",
- "introduction": "Условията са незадължителна част от правилото за автоматизация и могат да се използват, за да се предотврати настъпването на действие, когато се задейства тригера. Условията изглеждат много близки до тригерите, но са много различни. Тригерът ще разглежда събитията, случващи се в системата, докато условието разглежда само как системата изглежда в момента. Тригера може да наблюдава включването на ключ. Условието може само да види дали ключ е включен или изключен в момента. \n\n[Научете повече за условията.](https:\/\/home-assistant.io\/docs\/scripts\/conditions\/)",
+ "introduction": "Условията са незадължителна част от правилото за автоматизация и могат да се използват, за да се предотврати настъпването на действие, когато се задейства тригера. Условията изглеждат много близки до тригерите, но са много различни. Тригерът ще разглежда събитията, случващи се в системата, докато условието разглежда само как системата изглежда в момента. Тригера може да наблюдава включването на ключ. Условието може само да види дали ключ е включен или изключен в момента.",
"add": "Добавяне на условие",
- "duplicate": "Копирай",
- "delete": "Изтрий",
+ "duplicate": "Копиране",
+ "delete": "Изтриване",
"delete_confirm": "Сигурни ли сте, че искате да изтриете?",
"unsupported_condition": "Неподдържано условие: {condition}",
"type_select": "Тип на условие",
@@ -497,11 +507,12 @@
"entity": "Обект с местоположение",
"zone": "Зона"
}
- }
+ },
+ "learn_more": "Научете повече за условията"
},
"actions": {
"header": "Действия",
- "introduction": "Действията са това, което Home Assistant ще направи, когато се активира автоматизацията. \n\n[Научете повече за действията.](https:\/\/home-assistant.io\/docs\/automation\/action\/)",
+ "introduction": "Действията са това, което Home Assistant ще направи, когато се активира автоматизацията.",
"add": "Добавяне на действие",
"duplicate": "Копиране",
"delete": "Изтриване",
@@ -530,7 +541,8 @@
"event": "Събитие",
"service_data": "Сервизна информация"
}
- }
+ },
+ "learn_more": "Научете повече за действията"
}
}
},
@@ -561,18 +573,18 @@
"name": "Име",
"username": "Потребителско име",
"password": "Парола",
- "create": "Създай"
+ "create": "Създаване"
}
},
"cloud": {
- "caption": "HomeHome Assistant Cloud",
+ "caption": "Home Assistant Cloud",
"description_login": "Влезли сте като {email}",
"description_not_login": "Не сте влезли"
},
"integrations": {
"caption": "Интеграции",
"description": "Управление на свързани устройства и услуги",
- "discovered": "Открит",
+ "discovered": "Открити",
"configured": "Конфигуриран",
"new": "Настройте нова интеграция",
"configure": "Конфигуриране",
@@ -585,8 +597,8 @@
"manuf": "от {manufacturer}",
"hub": "Свързан чрез",
"firmware": "Фърмуер: {version}",
- "device_unavailable": "Устройството е недостъпно",
- "entity_unavailable": "Недостъпен",
+ "device_unavailable": "недостъпно устройство",
+ "entity_unavailable": "недостъпен",
"no_area": "Без област"
}
},
@@ -594,7 +606,8 @@
"caption": "ZHA",
"description": "Управление на Zigbee мрежата за домашна автоматизация",
"services": {
- "reconfigure": "Преконфигурирайте ZHA устройство (оздравяване на устройство). Използвайте това, ако имате проблеми с устройството. Ако въпросното устройство е захранвано с батерии, моля, уверете се, че е будно и приема команди, когато използвате тази услуга."
+ "reconfigure": "Преконфигурирайте ZHA устройство (оздравяване на устройство). Използвайте това, ако имате проблеми с устройството. Ако въпросното устройство е захранвано с батерии, моля, уверете се, че е будно и приема команди, когато използвате тази услуга.",
+ "updateDeviceName": "Задайте персонализирано име за това устройство в регистъра на устройствата."
}
},
"area_registry": {
@@ -603,7 +616,10 @@
"picker": {
"header": "Регистър на областите",
"introduction": "Областите се използват за организиране на местоположението на устройствата. Тази информация ще се използва в Home Assistant, за да ви помогне при организирането на Вашия интерфейс, права за достъп и интеграции с други системи.",
- "introduction2": "За да поставите устройства в дадена област, използвайте връзката по-долу, за да отидете на страницата за интеграции, след което кликнете върху конфигурирана интеграция, за да видите картите на устройството."
+ "introduction2": "За да поставите устройства в дадена област, използвайте връзката по-долу, за да отидете на страницата за интеграции, след което кликнете върху конфигурирана интеграция, за да видите картите на устройството.",
+ "integrations_page": "Интеграции",
+ "no_areas": "Изглежда, че все още нямате области!",
+ "create_area": "СЪЗДАВАНЕ НА ОБЛАСТ"
},
"no_areas": "Изглежда, че все още нямате области!",
"create_area": "СЪЗДАВАНЕ НА ОБЛАСТ",
@@ -621,7 +637,8 @@
"header": "Регистър на обектите",
"unavailable": "(недостъпен)",
"introduction": "Home Assistant поддържа регистър на всички обекти, които някога е виждал, които могат да бъдат идентифицирани уникално. Всеки от тези обекти ще има идентификатор на обект, който ще бъде резервиран само за този обект.",
- "introduction2": "Използвайте регистъра на обектите, за да промените името, идентификатора на обекта или да премахнете записа от Home Assistant. Моля имайте на предвид, че премахването на записа от регистъра на обектите няма да премахне обекта. За да направите това, следвайте препратката по-долу и я премахнете от страницата за интеграции."
+ "introduction2": "Използвайте регистъра на обектите, за да промените името, идентификатора на обекта или да премахнете записа от Home Assistant. Моля имайте на предвид, че премахването на записа от регистъра на обектите няма да премахне обекта. За да направите това, следвайте препратката по-долу и я премахнете от страницата за интеграции.",
+ "integrations_page": "Интеграции"
},
"editor": {
"unavailable": "Този обект не е достъпeн към момента.",
@@ -696,7 +713,7 @@
"new_password": "Нова парола",
"confirm_new_password": "Потвърждение на новата парола",
"error_required": "Задължително",
- "submit": "Изпращане"
+ "submit": "Промяна"
},
"mfa": {
"header": "Модули за много-факторна аутентикация",
@@ -816,11 +833,13 @@
"data": {
"name": "Име",
"username": "Потребителско име",
- "password": "Парола"
+ "password": "Парола",
+ "password_confirm": "Потвърждение на парола"
},
"create_account": "Създай акаунт",
"error": {
- "required_fields": "Попълнете всички задължителни полета"
+ "required_fields": "Попълнете всички задължителни полета",
+ "password_not_match": "Паролите не съвпадат"
}
}
},
@@ -828,7 +847,7 @@
"cards": {
"shopping-list": {
"checked_items": "Отметнати артикули",
- "clear_items": "Изтрий отметнатите артикули",
+ "clear_items": "Изчистване на отметнатите артикули",
"add_item": "Добави артикул"
},
"empty_state": {
@@ -840,13 +859,13 @@
"editor": {
"edit_card": {
"header": "Конфигуриране на Карта",
- "save": "Запази",
+ "save": "Запазване",
"toggle_editor": "Превключете редактора",
"pick_card": "Изберете картата, която искате да добавите.",
"add": "Добавяне на карта",
"edit": "Редактиране",
"delete": "Изтриване",
- "move": "Премести"
+ "move": "Преместване"
},
"migrate": {
"header": "Несъвместима конфигурация",
@@ -870,6 +889,12 @@
},
"menu": {
"raw_editor": "Текстов редактор на конфурацията"
+ },
+ "raw_editor": {
+ "header": "Редактиране на конфигурацията",
+ "save": "Запазване",
+ "unsaved_changes": "Незапазени промени",
+ "saved": "Запазено"
}
},
"menu": {
@@ -891,7 +916,7 @@
"common": {
"loading": "Зареждане",
"cancel": "Отмени",
- "save": "Запази"
+ "save": "Запазване"
},
"duration": {
"day": "{count}{count, plural,\n one {ден}\n other {дни}\n}",
@@ -1048,7 +1073,7 @@
},
"dialogs": {
"more_info_settings": {
- "save": "Запази",
+ "save": "Запазване",
"name": "Име",
"entity_id": "Идентификация на обект"
},
@@ -1118,7 +1143,8 @@
"hassio": "Hass.io",
"homeassistant": "Home Assistant",
"lovelace": "Lovelace",
- "system_health": "Здраве на системата"
+ "system_health": "Здраве на системата",
+ "person": "Човек"
},
"attribute": {
"weather": {
@@ -1135,5 +1161,10 @@
"auto": "Автоматичен"
}
}
+ },
+ "groups": {
+ "system-admin": "Администратори",
+ "system-users": "Потребители",
+ "system-read-only": "Потребители с достъп само за четене"
}
}
\ No newline at end of file
diff --git a/translations/da.json b/translations/da.json
index 82cf3c30c5..ed791beb4e 100644
--- a/translations/da.json
+++ b/translations/da.json
@@ -616,7 +616,8 @@
"header": "Område registrering",
"introduction": "Områder bruges til at organisere hvor enhederne er. Disse oplysninger vil blive brugt i Home Assistant til at hjælpe dig med at organisere din grænseflade, tilladelser og integrationer med andre systemer.",
"introduction2": "Hvis du vil placere enheder i et område, skal du bruge linket herunder til at navigere til integrationssiden og derefter klikke på en konfigureret integration for at komme til enhedskortene.",
- "integrations_page": "Integrations side"
+ "integrations_page": "Integrations side",
+ "create_area": "OPRET OMRÅDE"
},
"no_areas": "Du ikke har ingen områder endnu!",
"create_area": "OPRET OMRÅDE",
@@ -1156,5 +1157,9 @@
"auto": "Automatisk"
}
}
+ },
+ "groups": {
+ "system-admin": "Administratorer",
+ "system-users": "Brugere"
}
}
\ No newline at end of file
diff --git a/translations/el.json b/translations/el.json
index fb93fff710..ed64cb3ab8 100644
--- a/translations/el.json
+++ b/translations/el.json
@@ -29,10 +29,10 @@
"armed_home": "Σπίτι Οπλισμένο",
"armed_away": "Οπλισμένος μακριά",
"armed_night": "Οπλισμένο βράδυ",
- "pending": "εκκρεμής",
+ "pending": "Εκκρεμής",
"arming": "Όπλιση",
"disarming": "Αφόπλιση",
- "triggered": "παραβίαση",
+ "triggered": "Παραβίαση",
"armed_custom_bypass": "Προσαρμοσμένη παράκαμψη ενεργή"
},
"automation": {
@@ -126,16 +126,16 @@
"on": "Ενεργοποιημένο"
},
"camera": {
- "recording": "Εγγραφή",
+ "recording": "Καταγράφει",
"streaming": "Μετάδοση Ροής",
- "idle": "Σε αδράνεια"
+ "idle": "Αδρανές"
},
"climate": {
"off": "Ανενεργό",
"on": "Ενεργό",
"heat": "Θερμό",
"cool": "Δροσερό",
- "idle": "Σε αδράνεια",
+ "idle": "Αδρανές",
"auto": "Αυτόματο",
"dry": "Ξηρό",
"fan_only": "Ανεμιστήρας μόνο",
@@ -144,7 +144,7 @@
"performance": "Απόδοση",
"high_demand": "Υψηλή ζήτηση",
"heat_pump": "Αντλία θερμότητας",
- "gas": "Φυσικού αερίου",
+ "gas": "Αέριο",
"manual": "Εγχειρίδιο"
},
"configurator": {
@@ -287,7 +287,7 @@
"armed_home": "Οπλισμένο",
"armed_away": "Οπλισμένο",
"armed_night": "Οπλισμένο",
- "pending": "Εκκρεμής",
+ "pending": "Εκκρ",
"arming": "Όπλιση",
"disarming": "Αφόπλιση",
"triggered": "Ενεργ",
@@ -606,7 +606,8 @@
"caption": "ZHA",
"description": "Διαχείριση του δικτύου ZigBee Home Automation",
"services": {
- "reconfigure": "Ρυθμίστε ξανά τη συσκευή ZHA (heal συσκευή). Χρησιμοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε ζητήματα με τη συσκευή. Εάν η συγκεκριμένη συσκευή τροφοδοτείται απο μπαταρία βεβαιωθείτε ότι είναι ενεργοποιημένη και δέχεται εντολές όταν χρησιμοποιείτε αυτή την υπηρεσία."
+ "reconfigure": "Ρυθμίστε ξανά τη συσκευή ZHA (heal συσκευή). Χρησιμοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε ζητήματα με τη συσκευή. Εάν η συγκεκριμένη συσκευή τροφοδοτείται απο μπαταρία βεβαιωθείτε ότι είναι ενεργοποιημένη και δέχεται εντολές όταν χρησιμοποιείτε αυτή την υπηρεσία.",
+ "updateDeviceName": "Ορίστε ένα προσαρμοσμένο όνομα γι αυτήν τη συσκευή στο μητρώο συσκευών."
}
},
"area_registry": {
@@ -616,7 +617,9 @@
"header": "Περιοχή Μητρώου",
"introduction": "Οι περιοχές χρησιμοποιούνται για την οργάνωση της τοποθεσίας των συσκευών. Αυτές οι πληροφορίες θα χρησιμοποιηθούν σε όλο το Home Assistant για να σας βοηθήσουν στην οργάνωση της διασύνδεσης, των αδειών και των ενσωματώσεών σας σε άλλα συστήματα.",
"introduction2": "Για να τοποθετήσετε συσκευές σε μια περιοχή, χρησιμοποιήστε τον παρακάτω σύνδεσμο για να μεταβείτε στη σελίδα ενοποιήσεων και στη συνέχεια κάντε κλικ στην ρυθμισμένη ενωποίηση για να μεταβείτε στις κάρτες της συσκευής.",
- "integrations_page": "Σελίδα ενσωματώσεων"
+ "integrations_page": "Σελίδα ενσωματώσεων",
+ "no_areas": "Φαίνεται ότι δεν έχετε ορίσει ακόμα κάποια περιοχή!",
+ "create_area": "ΔΗΜΙΟΥΡΓΙΑ ΠΕΡΙΟΧΗΣ"
},
"no_areas": "Φαίνεται ότι δεν έχετε ορίσει ακόμα κάποια περιοχή!",
"create_area": "ΔΗΜΙΟΥΡΓΙΑ ΠΕΡΙΟΧΗΣ",
@@ -830,11 +833,13 @@
"data": {
"name": "Όνομα",
"username": "Όνομα χρήστη",
- "password": "Κωδικός"
+ "password": "Κωδικός",
+ "password_confirm": "Επιβεβαίωση Κωδικού"
},
"create_account": "Δημιουργία Λογαριασμού",
"error": {
- "required_fields": "Συμπληρώστε όλα τα υποχρεωτικά πεδία"
+ "required_fields": "Συμπληρώστε όλα τα υποχρεωτικά πεδία",
+ "password_not_match": "Οι κωδικοί πρόσβασης δεν ταιριάζουν"
}
}
},
@@ -1156,5 +1161,10 @@
"auto": "Αυτόματο"
}
}
+ },
+ "groups": {
+ "system-admin": "Διαχειριστές",
+ "system-users": "Χρήστες",
+ "system-read-only": "Χρήστες μόνο για ανάγνωση"
}
}
\ No newline at end of file
diff --git a/translations/fr.json b/translations/fr.json
index 3a2535cc43..d379fb80c3 100644
--- a/translations/fr.json
+++ b/translations/fr.json
@@ -606,7 +606,8 @@
"caption": "ZHA",
"description": "Gestion de réseau domotique ZigBee",
"services": {
- "reconfigure": "Reconfigurer le périphérique ZHA. Utilisez cette option si vous rencontrez des problèmes avec le périphérique. Si l'appareil en question est un appareil alimenté par batterie, assurez-vous qu'il soit allumé et qu'il accepte les commandes lorsque vous utilisez ce service."
+ "reconfigure": "Reconfigurer le périphérique ZHA. Utilisez cette option si vous rencontrez des problèmes avec le périphérique. Si l'appareil en question est un appareil alimenté par batterie, assurez-vous qu'il soit allumé et qu'il accepte les commandes lorsque vous utilisez ce service.",
+ "updateDeviceName": "Définissez un nom personnalisé pour ce périphérique dans le registre de périphériques."
}
},
"area_registry": {
@@ -616,7 +617,9 @@
"header": "Registre des pièces",
"introduction": "Les zones sont utilisées pour organiser l'emplacement des périphériques. Ces informations seront utilisées partout dans Home Assistant pour vous aider à organiser votre interface, vos autorisations et vos intégrations avec d'autres systèmes.",
"introduction2": "Pour placer des périphériques dans une zone, utilisez le lien ci-dessous pour accéder à la page des intégrations, puis cliquez sur une intégration configurée pour accéder aux cartes de périphérique.",
- "integrations_page": "Page des intégrations"
+ "integrations_page": "Page des intégrations",
+ "no_areas": "Vous n'avez pas encore configuré de pièce !",
+ "create_area": "CRÉER UNE PIÈCE"
},
"no_areas": "Vous n'avez pas encore configuré de pièce !",
"create_area": "CRÉER UNE PIÈCE",
@@ -830,11 +833,13 @@
"data": {
"name": "Nom",
"username": "Nom d'utilisateur",
- "password": "Mot de passe"
+ "password": "Mot de passe",
+ "password_confirm": "Confirmez le mot de passe"
},
"create_account": "Créer un compte",
"error": {
- "required_fields": "Remplissez tous les champs requis"
+ "required_fields": "Remplissez tous les champs requis",
+ "password_not_match": "Les mots de passe ne correspondent pas"
}
}
},
@@ -1156,5 +1161,10 @@
"auto": "Auto"
}
}
+ },
+ "groups": {
+ "system-admin": "Administrateurs",
+ "system-users": "Utilisateurs",
+ "system-read-only": "Utilisateurs en lecture seule"
}
}
\ No newline at end of file
diff --git a/translations/nl.json b/translations/nl.json
index 3159bc52cf..f08fb87d04 100644
--- a/translations/nl.json
+++ b/translations/nl.json
@@ -616,7 +616,8 @@
"header": "Gebiedenregister",
"introduction": "Gebieden worden gebruikt om te bepalen waar apparaten zijn. Deze informatie wordt overal in de Home Assistant gebruikt om u te helpen bij het organiseren van uw interface, machtigingen en integraties met andere systemen.",
"introduction2": "Als u apparaten in een gebied wilt plaatsen, gebruikt u de onderstaande koppeling om naar de integratiespagina te gaan en vervolgens op een geconfigureerde integratie te klikken om naar de apparaatkaarten te gaan.",
- "integrations_page": "Integratiespagina"
+ "integrations_page": "Integratiespagina",
+ "create_area": "MAAK RUIMTE"
},
"no_areas": "Het lijkt erop dat je nog geen gebieden hebt!",
"create_area": "MAAK GEBIED",
@@ -830,11 +831,13 @@
"data": {
"name": "Naam",
"username": "Gebruikersnaam",
- "password": "Wachtwoord"
+ "password": "Wachtwoord",
+ "password_confirm": "Bevestig wachtwoord"
},
"create_account": "Account aanmaken",
"error": {
- "required_fields": "Vul alle verplichte velden in"
+ "required_fields": "Vul alle verplichte velden in",
+ "password_not_match": "Wachtwoorden komen niet overeen"
}
}
},
@@ -1156,5 +1159,8 @@
"auto": "Auto"
}
}
+ },
+ "groups": {
+ "system-users": "Gebruikers"
}
}
\ No newline at end of file
diff --git a/translations/pl.json b/translations/pl.json
index 45478e9696..f93ce17d31 100644
--- a/translations/pl.json
+++ b/translations/pl.json
@@ -370,7 +370,7 @@
"description": "Twórz i edytuj reguły automatyzacji",
"picker": {
"header": "Edytor automatyzacji",
- "introduction": "Edytor automatyzacji pozwala tworzyć i edytować reguły automatyzacji. Przeczytaj [instrukcje](https:\/\/home-assistant.io\/docs\/automation\/editor\/), aby upewnić się, że poprawnie skonfigurowałeś Home Assistant'a.",
+ "introduction": "Edytor automatyzacji pozwala tworzyć i edytować reguły automatyzacji. Kliknij poniższy link, aby przeczytać instrukcję, jak poprawnie skonfigurować reguły automatyzacji w Home Assistant.",
"pick_automation": "Wybierz regułę automatyzacji do edycji",
"no_automations": "Nie znaleziono żadnych edytowalnych reguł automatyzacji",
"add_automation": "Dodaj regułę automatyzacji",
diff --git a/translations/ru.json b/translations/ru.json
index 844b0731a2..1faa0a1cc8 100644
--- a/translations/ru.json
+++ b/translations/ru.json
@@ -606,7 +606,8 @@
"caption": "ZHA",
"description": "Управляйте сетью Zigbee Home Automation",
"services": {
- "reconfigure": "Перенастройка устройства ZHA. Используйте эту службу, если у Вас есть проблемы с устройством. Если рассматриваемое устройство работает от батареи, пожалуйста, убедитесь, что оно не находится в режиме сна и принимает команды, когда вы запускаете эту службу."
+ "reconfigure": "Перенастройка устройства ZHA. Используйте эту службу, если у Вас есть проблемы с устройством. Если рассматриваемое устройство работает от батареи, пожалуйста, убедитесь, что оно не находится в режиме сна и принимает команды, когда вы запускаете эту службу.",
+ "updateDeviceName": "Установите имя для этого устройства в реестре устройств."
}
},
"area_registry": {
@@ -616,9 +617,11 @@
"header": "Управление помещениями",
"introduction": "Этот раздел используется для определения местоположения устройств. Данная информация будет использоваться в Home Assistant, чтобы помочь вам в организации вашего интерфейса, определении прав доступа и интеграции с другими системами.",
"introduction2": "Чтобы назначить устройству местоположение, используйте указанную ниже ссылку для перехода на страницу интеграций, а затем откройте уже настроенную интеграцию.",
- "integrations_page": "Страница интеграций"
+ "integrations_page": "Страница интеграций",
+ "no_areas": "У Вас еще нет добавленных помещений.",
+ "create_area": "ДОБАВИТЬ ПОМЕЩЕНИЕ"
},
- "no_areas": "Похоже, что у Вас пока ещё нет добавленных помещений!",
+ "no_areas": "У Вас еще нет добавленных помещений.",
"create_area": "ДОБАВИТЬ",
"editor": {
"default_name": "Новое помещение",
@@ -830,11 +833,13 @@
"data": {
"name": "Имя",
"username": "Логин",
- "password": "Пароль"
+ "password": "Пароль",
+ "password_confirm": "Подтвердите пароль"
},
"create_account": "Создать учетную запись",
"error": {
- "required_fields": "Заполните все обязательные поля"
+ "required_fields": "Заполните все обязательные поля",
+ "password_not_match": "Пароли не совпадают"
}
}
},
@@ -847,7 +852,7 @@
},
"empty_state": {
"title": "Добро пожаловать домой",
- "no_devices": "Эта страница позволяет вам управлять вашими устройствами, однако, похоже, у вас еще нет настроенных устройств. Перейдите на страницу интеграций, чтобы начать.",
+ "no_devices": "На этой странице можно управлять Вашими устройствами, однако похоже, что ни одно устройство еще не добавлено. Для начала перейдите на страницу интеграций.",
"go_to_integrations_page": "Перейти на страницу интеграций"
}
},
@@ -1156,5 +1161,10 @@
"auto": "Авто"
}
}
+ },
+ "groups": {
+ "system-admin": "Администраторы",
+ "system-users": "Пользователи",
+ "system-read-only": "Системные пользователи"
}
}
\ No newline at end of file
diff --git a/translations/th.json b/translations/th.json
index 8053625fe6..750602b54a 100644
--- a/translations/th.json
+++ b/translations/th.json
@@ -82,7 +82,7 @@
},
"presence": {
"off": "ไม่อยู่",
- "on": "อยูบ้าน"
+ "on": "อยู่บ้าน"
},
"battery": {
"off": "ปกติ",
@@ -173,11 +173,11 @@
"not_home": "ไม่อยู่บ้าน",
"open": "เปิด",
"opening": "กำลังเปิด",
- "closed": "ปิด",
+ "closed": "ปิดแล้ว",
"closing": "กำลังปิด",
- "stopped": "หยุด",
- "locked": "ล็อค",
- "unlocked": "ปลดล็อค",
+ "stopped": "หยุดแล้ว",
+ "locked": "ล็อคแล้ว",
+ "unlocked": "ปลดล็อคแล้ว",
"ok": "พร้อมใช้งาน",
"problem": "มีปัญหา"
},
@@ -236,7 +236,7 @@
"ready": "พร้อมใช้งาน"
},
"query_stage": {
- "initializing": "กำลังเริ่มต้น ({query_stage})",
+ "initializing": "กำลังเตรียมการ ({query_stage})",
"dead": "ไม่พร้อมใช้งาน ({query_stage})"
}
},
@@ -284,9 +284,9 @@
"alarm_control_panel": {
"armed": "เปิดการป้องกัน",
"disarmed": "ปลดการป้องกัน",
- "armed_home": "เปิดการป้องกัน-โหมดอยู่บ้าน",
- "armed_away": "เปิดการป้องกัน-โหมดไม่อยู่บ้าน",
- "armed_night": "เปิดการป้องกัน-โหมดกลางคืน",
+ "armed_home": "เฝ้าระวังอยู่",
+ "armed_away": "เฝ้าระวังอยู่",
+ "armed_night": "เฝ้าระวังอยู่",
"pending": "ค้างอยู่",
"arming": "กำลังเปิดการป้องกัน",
"disarming": "กำลังปลดการป้องกัน",
@@ -370,7 +370,7 @@
"description": "สร้างและแก้ไขระบบอัตโนมัติ",
"picker": {
"header": "เครื่องมือแก้ไข ระบบอัตโนมัติ",
- "introduction": "ตัวแก้ไขอัตโนมัติช่วยให้คุณสามารถสร้างและแก้ไขระบบอัตโนมัติได้. \nโปรดอ่าน [คำแนะนำ] (https:\/\/home-assistant.io\/docs\/automation\/editor\/) เพื่อให้แน่ใจว่าคุณได้กำหนดค่า Home Assistant อย่างถูกต้อง.",
+ "introduction": "เป็นตัวช่วยแก้ไขที่ทำให้คุณสามารถสร้างหรือแก้ไขการทำงานอัตโนมัติ\nโปรดอ่าน [คำแนะนำ] (https:\/\/home-assistant.io\/docs\/automation\/editor\/) เพื่อให้การกำหนดค่าของคุณเป็นไปได้อย่างถูกต้อง",
"pick_automation": "เลือกระบบอัตโนมัติเพื่อแก้ไข",
"no_automations": "เราไม่พบระบบอัตโนมัติใด ๆ สามารถแก้ไขได้",
"add_automation": "เพิ่มระบบอัตโนมัติ",
@@ -471,7 +471,7 @@
"duplicate": "แยกออกมาเป็นอันใหม่",
"delete": "ลบ",
"delete_confirm": "คุณแน่ใจหรือไม่ว่าจะลบสิ่งนี้ทิ้ง?",
- "unsupported_condition": "เงื่อนไขที่ไม่สนับสนุน: {condition}",
+ "unsupported_condition": "ไม่รองรับเงื่อนไข: {condition}",
"type_select": "ประเภทเงื่อนไข",
"type": {
"state": {
@@ -485,7 +485,7 @@
"value_template": "ค่าของรูปแบบ (ปล่อยว่างได้)"
},
"sun": {
- "label": "พระอาทิตย์",
+ "label": "ดวงอาทิตย์",
"before": "ก่อน:",
"after": "หลังจาก:",
"before_offset": "ก่อนช่วงเวลา(เลือกเพิ่ม)",
@@ -512,7 +512,7 @@
},
"actions": {
"header": "การกระทำ",
- "introduction": "การดำเนินการเป็นสิ่งที่ผู้ช่วยโฮมจะทำเมื่อระบบอัตโนมัติถูกเรียกใช้งาน \n\n [เรียนรู้เพิ่มเติมเกี่ยวกับการดำเนินการ] (https:\/\/home-assistant.io\/docs\/automation\/action\/)",
+ "introduction": "ใช้สำหรับการกระทำที่ Home Assistant จะทำต่อเมื่อระบบอัตโนมัติมีการเรียกใช้งาน",
"add": "เพิ่มการกระทำ",
"duplicate": "แยกออกมาเป็นอันใหม่",
"delete": "ลบ",
@@ -606,7 +606,8 @@
"caption": "ZHA",
"description": "การจัดการระบบอัติโนมัติของ Zigbee",
"services": {
- "reconfigure": "กำหนดค่าอุปกรณ์ ZHA อีกครั้ง (เพื่อรักษาอุปกรณ์) ใช้สิ่งนี้หากคุณมีปัญหากับอุปกรณ์ หากอุปกรณ์ดังกล่าวเป็นอุปกรณ์ที่ใช้พลังงานจากแบตเตอรี่โปรดตรวจสอบให้แน่ใจว่าอุปกรณ์นั้นเปิดอยู่และยอมรับคำสั่งเมื่อคุณใช้บริการ"
+ "reconfigure": "กำหนดค่าอุปกรณ์ ZHA อีกครั้ง (เพื่อรักษาอุปกรณ์) ใช้สิ่งนี้หากคุณมีปัญหากับอุปกรณ์ หากอุปกรณ์ดังกล่าวเป็นอุปกรณ์ที่ใช้พลังงานจากแบตเตอรี่โปรดตรวจสอบให้แน่ใจว่าอุปกรณ์นั้นเปิดอยู่และยอมรับคำสั่งเมื่อคุณใช้บริการ",
+ "updateDeviceName": "ตั้งชื่อที่กำหนดเองสำหรับอุปกรณ์นี้ในการลงทะเบียนอุปกรณ์"
}
},
"area_registry": {
@@ -616,7 +617,9 @@
"header": "ค่าของห้อง",
"introduction": "Areas are used to organize where devices are. This information will be used throughout Home Assistant to help you in organizing your interface, permissions and integrations with other systems.",
"introduction2": "สำหรับการวางอุปกรณ์นี้ลงในห้องนี้ ใช้ลิงค์ด้านล่างเพื่อไปยังหน้า 'การทำงานร่วมกัน' และคลิ๊กทีุ่่ม 'ตั้งค่าให้ทำงานร่วมกัน' เพื่อที่จะให้แสดงการ์ดสำหรับอุปกรณ์นั้น",
- "integrations_page": "หน้าการทำงานร่วมกัน"
+ "integrations_page": "หน้าการทำงานร่วมกัน",
+ "no_areas": "ดูเหมือนว่าคุณยังไม่มีห้องเลย!",
+ "create_area": "สร้างห้องใหม่"
},
"no_areas": "ดูเหมือนว่าคุณยังไม่มีห้องเลย!",
"create_area": "สร้างห้องใหม่",
@@ -829,11 +832,13 @@
"data": {
"name": "ชื่อ",
"username": "ชื่อผู้ใช้",
- "password": "รหัสผ่าน"
+ "password": "รหัสผ่าน",
+ "password_confirm": "ยืนยันรหัสผ่าน"
},
"create_account": "สร้างบัญชี",
"error": {
- "required_fields": "กรอกข้อมูลในฟิลด์ที่จำเป็นทั้งหมด"
+ "required_fields": "กรอกข้อมูลในฟิลด์ที่จำเป็นทั้งหมด",
+ "password_not_match": "รหัสผ่านไม่ตรงกัน"
}
}
},
@@ -1154,5 +1159,10 @@
"auto": "อัตโนมัติ"
}
}
+ },
+ "groups": {
+ "system-admin": "ผู้ดูแลระบบ",
+ "system-users": "ผู้ใช้",
+ "system-read-only": "ผู้ใช้ที่สามารถดูได้อย่างเดียว"
}
}
\ No newline at end of file
diff --git a/translations/zh-Hans.json b/translations/zh-Hans.json
index 258fb67e01..2a82edf3c9 100644
--- a/translations/zh-Hans.json
+++ b/translations/zh-Hans.json
@@ -606,7 +606,8 @@
"caption": "ZHA",
"description": "Zigbee 智能家居(ZHA) 网络管理",
"services": {
- "reconfigure": "重新配置ZHA设备(唤醒设备)。如果您的设备遇到问题,请使用此项。如果有问题的设备是电池供电的,请确保在使用此服务时它处于唤醒状态并可以接受指令。"
+ "reconfigure": "重新配置ZHA设备(唤醒设备)。如果您的设备遇到问题,请使用此项。如果有问题的设备是电池供电的,请确保在使用此服务时它处于唤醒状态并可以接受指令。",
+ "updateDeviceName": "在设备注册表中为此设备设置自定义名称。"
}
},
"area_registry": {
@@ -616,7 +617,9 @@
"header": "区域注册表",
"introduction": "区域用于组织设备所在的位置。此信息将用于 Home Assistant 的各个地方,以帮助您组织界面、权限和与其他系统的集成。",
"introduction2": "要将设备置入某个区域,请使用下面的链接导航到集成页面,然后点击一个已配置的集成以进入设备卡片。",
- "integrations_page": "集成页面"
+ "integrations_page": "集成页面",
+ "no_areas": "看来你还没有建立区域!",
+ "create_area": "创建区域"
},
"no_areas": "看来你还没有建立区域!",
"create_area": "创建区域",
@@ -830,11 +833,13 @@
"data": {
"name": "姓名",
"username": "用户名",
- "password": "密码"
+ "password": "密码",
+ "password_confirm": "确认密码"
},
"create_account": "创建帐户",
"error": {
- "required_fields": "请填写所有必填字段"
+ "required_fields": "请填写所有必填字段",
+ "password_not_match": "密码不匹配"
}
}
},
@@ -1156,5 +1161,10 @@
"auto": "自动"
}
}
+ },
+ "groups": {
+ "system-admin": "管理员",
+ "system-users": "用户",
+ "system-read-only": "只读用户"
}
}
\ No newline at end of file
diff --git a/translations/zh-Hant.json b/translations/zh-Hant.json
index 345c860903..5bce666634 100644
--- a/translations/zh-Hant.json
+++ b/translations/zh-Hant.json
@@ -606,7 +606,8 @@
"caption": "ZHA",
"description": "Zigbee 家庭自動化網路管理",
"services": {
- "reconfigure": "重新設定 ZHA Zibgee 裝置(健康裝置)。假如遇到裝置問題,請使用此選項。假如有問題的裝置為使用電池的裝置,請先確定裝置已喚醒並處於接受命令狀態。"
+ "reconfigure": "重新設定 ZHA Zibgee 裝置(健康裝置)。假如遇到裝置問題,請使用此選項。假如有問題的裝置為使用電池的裝置,請先確定裝置已喚醒並處於接受命令狀態。",
+ "updateDeviceName": "於物件 ID 中自訂此設備名稱。"
}
},
"area_registry": {
@@ -616,7 +617,9 @@
"header": "分區 ID",
"introduction": "分區主要用以管理裝置所在位置。此資訊將會於 Home Assistant 中使用以協助您管理介面、權限,並與其他系統進行整合。",
"introduction2": "欲於分區中放置裝置,請使用下方連結至整合頁面,並點選設定整合以設定裝置卡片。",
- "integrations_page": "整合頁面"
+ "integrations_page": "整合頁面",
+ "no_areas": "看起來你還沒有建立分區!",
+ "create_area": "建立分區"
},
"no_areas": "看起來你還沒有建立分區!",
"create_area": "建立分區",
@@ -830,11 +833,13 @@
"data": {
"name": "名字",
"username": "使用者名稱",
- "password": "使用者密碼"
+ "password": "使用者密碼",
+ "password_confirm": "確認密碼"
},
"create_account": "創建帳號",
"error": {
- "required_fields": "填寫所有所需欄位"
+ "required_fields": "填寫所有所需欄位",
+ "password_not_match": "密碼不相符"
}
}
},
@@ -1156,5 +1161,10 @@
"auto": "自動模式"
}
}
+ },
+ "groups": {
+ "system-admin": "管理員",
+ "system-users": "用戶",
+ "system-read-only": "唯讀用戶"
}
}
\ No newline at end of file