mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 03:36:44 +00:00
commit
18df636573
@ -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) {
|
||||
|
7
demo/src/stubs/frontend.ts
Normal file
7
demo/src/stubs/frontend.ts
Normal file
@ -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,
|
||||
}));
|
||||
};
|
@ -60,11 +60,7 @@ class HassioAddonView extends PolymerElement {
|
||||
<app-header-layout has-scrolling-region="">
|
||||
<app-header fixed="" slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
hassio
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button hassio></ha-menu-button>
|
||||
<paper-icon-button
|
||||
icon="hassio:arrow-left"
|
||||
on-click="backTapped"
|
||||
@ -119,8 +115,6 @@ class HassioAddonView extends PolymerElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
showMenu: Boolean,
|
||||
narrow: Boolean,
|
||||
route: Object,
|
||||
routeData: {
|
||||
type: Object,
|
||||
|
@ -8,12 +8,7 @@ class HassioApp extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[hass]]">
|
||||
<hassio-main
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
route="[[route]]"
|
||||
></hassio-main>
|
||||
<hassio-main hass="[[hass]]" route="[[route]]"></hassio-main>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
@ -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() {
|
||||
|
@ -28,18 +28,13 @@ class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
|
||||
></hassio-data>
|
||||
|
||||
<template is="dom-if" if="[[!loaded]]">
|
||||
<hass-loading-screen
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></hass-loading-screen>
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[loaded]]">
|
||||
<template is="dom-if" if="[[!equalsAddon(routeData.page)]]">
|
||||
<hassio-pages-with-tabs
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
page="[[routeData.page]]"
|
||||
supervisor-info="[[supervisorInfo]]"
|
||||
hass-info="[[hassInfo]]"
|
||||
@ -49,8 +44,6 @@ class HassioMain extends EventsMixin(NavigateMixin(PolymerElement)) {
|
||||
<template is="dom-if" if="[[equalsAddon(routeData.page)]]">
|
||||
<hassio-addon-view
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
route="[[route]]"
|
||||
></hassio-addon-view>
|
||||
</template>
|
||||
@ -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
|
||||
|
@ -37,11 +37,7 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
<app-header-layout id="layout" has-scrolling-region>
|
||||
<app-header fixed slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
hassio
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button hassio></ha-menu-button>
|
||||
<div main-title>Hass.io</div>
|
||||
<template is="dom-if" if="[[showRefreshButton(page)]]">
|
||||
<paper-icon-button
|
||||
@ -107,8 +103,6 @@ class HassioPagesWithTabs extends NavigateMixin(PolymerElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
showMenu: Boolean,
|
||||
narrow: Boolean,
|
||||
page: String,
|
||||
supervisorInfo: Object,
|
||||
hostInfo: Object,
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20190313.0",
|
||||
version="20190315.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -53,7 +53,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
protected render() {
|
||||
if (!this._authProviders) {
|
||||
return html`
|
||||
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
|
||||
<p>${this.localize("ui.panel.page-authorize.initializing")}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
|
16
src/common/dom/media_query.ts
Normal file
16
src/common/dom/media_query.ts
Normal file
@ -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);
|
||||
};
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,9 @@ export interface CloudWebhook {
|
||||
managed?: boolean;
|
||||
}
|
||||
|
||||
export const fetchCloudStatus = (hass: HomeAssistant) =>
|
||||
hass.callWS<CloudStatus>({ type: "cloud/status" });
|
||||
|
||||
export const createCloudhook = (hass: HomeAssistant, webhookId: string) =>
|
||||
hass.callWS<CloudWebhook>({
|
||||
type: "cloud/cloudhook/create",
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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`
|
||||
<div class="init-spinner">
|
||||
<paper-spinner active></paper-spinner>
|
||||
</div>
|
||||
`;
|
||||
} else if (step.type === "abort") {
|
||||
descriptionKey = `component.${step.handler}.config.abort.${step.reason}`;
|
||||
headerContent = "Aborted";
|
||||
bodyContent = html``;
|
||||
buttonContent = html`
|
||||
<mwc-button @click="${this._flowDone}">Close</mwc-button>
|
||||
`;
|
||||
} else if (step.type === "create_entry") {
|
||||
descriptionKey = `component.${
|
||||
step.handler
|
||||
}.config.create_entry.${step.description || "default"}`;
|
||||
headerContent = "Success!";
|
||||
bodyContent = html`
|
||||
<p>Created config for ${step.title}</p>
|
||||
`;
|
||||
buttonContent = html`
|
||||
<mwc-button @click="${this._flowDone}">Close</mwc-button>
|
||||
`;
|
||||
} 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`
|
||||
<ha-form
|
||||
.data=${this._stepData}
|
||||
@data-changed=${this._stepDataChanged}
|
||||
.schema=${step.data_schema}
|
||||
.error=${step.errors}
|
||||
.computeLabel=${this._labelCallback}
|
||||
.computeError=${this._errorCallback}
|
||||
></ha-form>
|
||||
`;
|
||||
|
||||
const allRequiredInfoFilledIn =
|
||||
this._stepData &&
|
||||
step.data_schema.every(
|
||||
(field) =>
|
||||
field.optional ||
|
||||
!["", undefined].includes(this._stepData![field.name])
|
||||
);
|
||||
|
||||
buttonContent = this._loading
|
||||
? html`
|
||||
<div class="submit-spinner">
|
||||
<paper-spinner active></paper-spinner>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div>
|
||||
<mwc-button
|
||||
@click=${this._submitStep}
|
||||
.disabled=${!allRequiredInfoFilledIn}
|
||||
>
|
||||
Submit
|
||||
</mwc-button>
|
||||
|
||||
${!allRequiredInfoFilledIn
|
||||
? html`
|
||||
<paper-tooltip position="left">
|
||||
Not all required fields are filled in.
|
||||
</paper-tooltip>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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`
|
||||
<paper-dialog
|
||||
with-backdrop
|
||||
.opened=${true}
|
||||
@opened-changed=${this._openedChanged}
|
||||
>
|
||||
<h2>
|
||||
${headerContent}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
${this._errorMsg
|
||||
<paper-dialog with-backdrop opened @opened-changed=${this._openedChanged}>
|
||||
${this._loading
|
||||
? html`
|
||||
<div class="error">${this._errorMsg}</div>
|
||||
<step-flow-loading></step-flow-loading>
|
||||
`
|
||||
: ""}
|
||||
${description
|
||||
: 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`
|
||||
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
||||
<step-flow-form
|
||||
.step=${this._step}
|
||||
.hass=${this._params.hass}
|
||||
></step-flow-form>
|
||||
`
|
||||
: ""}
|
||||
${bodyContent}
|
||||
</paper-dialog-scrollable>
|
||||
<div class="buttons">
|
||||
${buttonContent}
|
||||
</div>
|
||||
: this._step.type === "abort"
|
||||
? html`
|
||||
<step-flow-abort
|
||||
.step=${this._step}
|
||||
.hass=${this._params.hass}
|
||||
></step-flow-abort>
|
||||
`
|
||||
: this._devices === undefined || this._areas === undefined
|
||||
? // When it's a create entry result, we will fetch device & area registry
|
||||
html`
|
||||
<step-flow-loading></step-flow-loading>
|
||||
`
|
||||
: html`
|
||||
<step-flow-create-entry
|
||||
.step=${this._step}
|
||||
.hass=${this._params.hass}
|
||||
.devices=${this._devices}
|
||||
.areas=${this._areas}
|
||||
></step-flow-create-entry>
|
||||
`}
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
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<void> {
|
||||
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
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
if (curInstance !== this._instance) {
|
||||
private async _fetchAreas() {
|
||||
this._areas = await fetchAreaRegistry(this._params!.hass);
|
||||
}
|
||||
|
||||
private async _processStep(step: ConfigFlowStep): Promise<void> {
|
||||
if (step === undefined) {
|
||||
this._flowDone();
|
||||
return;
|
||||
}
|
||||
|
||||
this._processStep(step);
|
||||
} catch (err) {
|
||||
this._errorMsg =
|
||||
(err && err.body && err.body.message) || "Unknown error occurred";
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _processStep(step: ConfigFlowStep): void {
|
||||
this._step = undefined;
|
||||
await this.updateComplete;
|
||||
this._step = step;
|
||||
|
||||
// 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 _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<boolean>): void {
|
||||
@ -320,51 +222,17 @@ class ConfigFlowDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _stepDataChanged(ev: PolymerChangedEvent<any>): 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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
66
src/dialogs/config-flow/step-flow-abort.ts
Normal file
66
src/dialogs/config-flow/step-flow-abort.ts
Normal file
@ -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`
|
||||
<h2>Aborted</h2>
|
||||
<div class="content">
|
||||
${
|
||||
description
|
||||
? html`
|
||||
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<mwc-button @click="${this._flowDone}">Close</mwc-button>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _flowDone(): void {
|
||||
fireEvent(this, "flow-update", { step: undefined });
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return configFlowContentStyles;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"step-flow-abort": StepFlowAbort;
|
||||
}
|
||||
}
|
191
src/dialogs/config-flow/step-flow-create-entry.ts
Normal file
191
src/dialogs/config-flow/step-flow-create-entry.ts
Normal file
@ -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`
|
||||
<h2>Success!</h2>
|
||||
<div class="content">
|
||||
${
|
||||
description
|
||||
? html`
|
||||
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
<p>Created config for ${step.title}.</p>
|
||||
${
|
||||
this.devices.length === 0
|
||||
? ""
|
||||
: html`
|
||||
<p>We found the following devices:</p>
|
||||
<div class="devices">
|
||||
${this.devices.map(
|
||||
(device) =>
|
||||
html`
|
||||
<div class="device">
|
||||
<b>${device.name}</b><br />
|
||||
${device.model} (${device.manufacturer})
|
||||
|
||||
<paper-dropdown-menu-light
|
||||
label="Area"
|
||||
.device=${device.id}
|
||||
@selected-item-changed=${this._handleAreaChanged}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="0"
|
||||
>
|
||||
<paper-item>
|
||||
${localize(
|
||||
"ui.panel.config.integrations.config_entry.no_area"
|
||||
)}
|
||||
</paper-item>
|
||||
${this.areas.map(
|
||||
(area) => html`
|
||||
<paper-item .area=${area.area_id}>
|
||||
${area.name}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu-light>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
${
|
||||
this.devices.length > 0
|
||||
? html`
|
||||
<mwc-button @click="${this._addArea}">Add Area</mwc-button>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<mwc-button @click="${this._flowDone}">Finish</mwc-button>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
222
src/dialogs/config-flow/step-flow-form.ts
Normal file
222
src/dialogs/config-flow/step-flow-form.ts
Normal file
@ -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`
|
||||
<h2>
|
||||
${localize(
|
||||
`component.${step.handler}.config.step.${step.step_id}.title`
|
||||
)}
|
||||
</h2>
|
||||
<div class="content">
|
||||
${this._errorMsg
|
||||
? html`
|
||||
<div class="error">${this._errorMsg}</div>
|
||||
`
|
||||
: ""}
|
||||
${description
|
||||
? html`
|
||||
<ha-markdown .content=${description} allow-svg></ha-markdown>
|
||||
`
|
||||
: ""}
|
||||
<ha-form
|
||||
.data=${this._stepDataProcessed}
|
||||
@data-changed=${this._stepDataChanged}
|
||||
.schema=${step.data_schema}
|
||||
.error=${step.errors}
|
||||
.computeLabel=${this._labelCallback}
|
||||
.computeError=${this._errorCallback}
|
||||
></ha-form>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
${this._loading
|
||||
? html`
|
||||
<div class="submit-spinner">
|
||||
<paper-spinner active></paper-spinner>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div>
|
||||
<mwc-button
|
||||
@click=${this._submitStep}
|
||||
.disabled=${!allRequiredInfoFilledIn}
|
||||
>
|
||||
Submit
|
||||
</mwc-button>
|
||||
|
||||
${!allRequiredInfoFilledIn
|
||||
? html`
|
||||
<paper-tooltip position="left">
|
||||
Not all required fields are filled in.
|
||||
</paper-tooltip>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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<void> {
|
||||
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<any>): 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;
|
||||
}
|
||||
}
|
35
src/dialogs/config-flow/step-flow-loading.ts
Normal file
35
src/dialogs/config-flow/step-flow-loading.ts
Normal file
@ -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`
|
||||
<div class="init-spinner">
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.init-spinner {
|
||||
padding: 50px 100px;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"step-flow-loading": StepFlowLoading;
|
||||
}
|
||||
}
|
33
src/dialogs/config-flow/styles.ts
Normal file
33
src/dialogs/config-flow/styles.ts
Normal file
@ -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;
|
||||
}
|
||||
`;
|
@ -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;
|
||||
|
@ -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)
|
||||
);
|
||||
|
@ -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`
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
.narrow=${this.narrow}
|
||||
.showMenu=${this.showMenu}
|
||||
></ha-menu-button>
|
||||
${this.isRoot
|
||||
? html`
|
||||
<ha-menu-button></ha-menu-button>
|
||||
`
|
||||
: html`
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
@click=${this._handleBack}
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
`}
|
||||
</app-toolbar>
|
||||
<div class="content">
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
@ -34,6 +36,10 @@ class HassLoadingScreen extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleBack() {
|
||||
history.back();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
|
192
src/layouts/hass-router-page.ts
Normal file
192
src/layouts/hass-router-page.ts
Normal file
@ -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<unknown>;
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
@ -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}
|
||||
></partial-panel-resolver>
|
||||
</app-drawer-layout>
|
||||
`;
|
||||
@ -79,18 +77,21 @@ class HomeAssistantMain extends LitElement {
|
||||
protected firstUpdated() {
|
||||
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
|
||||
|
||||
this.addEventListener("hass-open-menu", () => {
|
||||
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 });
|
||||
}
|
||||
});
|
||||
this.addEventListener("hass-close-menu", () => {
|
||||
} else {
|
||||
this.drawer.close();
|
||||
if (this.hass!.dockedSidebar) {
|
||||
fireEvent(this, "hass-dock-sidebar", { dock: false });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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<void> } = {};
|
||||
|
||||
// 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<void> | 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`
|
||||
<hass-error-screen
|
||||
error="Error while loading this panel."
|
||||
.narrow=${this.narrow}
|
||||
.showMenu=${this.showMenu}
|
||||
></hass-error-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
if (!this._panelEl) {
|
||||
return html`
|
||||
<hass-loading-screen
|
||||
.narrow=${this.narrow}
|
||||
.showMenu=${this.showMenu}
|
||||
></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -67,10 +67,7 @@ class HaPanelCalendar extends LocalizeMixin(PolymerElement) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.calendar')]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -145,11 +142,6 @@ class HaPanelCalendar extends LocalizeMixin(PolymerElement) {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -128,15 +128,6 @@ class HaAutomationPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
automations: {
|
||||
type: Array,
|
||||
},
|
||||
|
@ -31,8 +31,6 @@ class HaConfigAutomation extends PolymerElement {
|
||||
<template is="dom-if" if="[[!showEditor]]">
|
||||
<ha-automation-picker
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
automations="[[automations]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-automation-picker>
|
||||
@ -52,8 +50,6 @@ class HaConfigAutomation extends PolymerElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
route: Object,
|
||||
isWide: Boolean,
|
||||
_routeData: Object,
|
||||
|
@ -40,7 +40,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
||||
<app-header-layout has-scrolling-region="">
|
||||
<app-header slot="header" fixed="">
|
||||
<app-toolbar>
|
||||
<ha-menu-button narrow="[[narrow]]" show-menu="[[showMenu]]"></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title="">[[localize('panel.config')]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -111,8 +111,6 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
||||
hass: Object,
|
||||
isWide: Boolean,
|
||||
cloudStatus: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -75,11 +75,6 @@ class HaConfigSection extends PolymerElement {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
isWide: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
|
@ -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`
|
||||
<app-route
|
||||
route="[[route]]"
|
||||
pattern="/:page"
|
||||
data="{{_routeData}}"
|
||||
></app-route>
|
||||
|
||||
<iron-media-query query="(min-width: 1040px)" query-matches="{{wide}}">
|
||||
</iron-media-query>
|
||||
<iron-media-query
|
||||
query="(min-width: 1296px)"
|
||||
query-matches="{{wideSidebar}}"
|
||||
>
|
||||
</iron-media-query>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(_routeData.page, "area_registry")]]'
|
||||
restamp
|
||||
>
|
||||
<ha-config-area-registry
|
||||
page-name="area_registry"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-area-registry>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "core")]]' restamp>
|
||||
<ha-config-core
|
||||
page-name="core"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-core>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "cloud")]]' restamp>
|
||||
<ha-config-cloud
|
||||
page-name="cloud"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
cloud-status="[[_cloudStatus]]"
|
||||
></ha-config-cloud>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "dashboard")]]'>
|
||||
<ha-config-dashboard
|
||||
page-name="dashboard"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
cloud-status="[[_cloudStatus]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-config-dashboard>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(_routeData.page, "automation")]]'
|
||||
restamp
|
||||
>
|
||||
<ha-config-automation
|
||||
page-name="automation"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-automation>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "script")]]' restamp>
|
||||
<ha-config-script
|
||||
page-name="script"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-script>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(_routeData.page, "entity_registry")]]'
|
||||
restamp
|
||||
>
|
||||
<ha-config-entity-registry
|
||||
page-name="entity_registry"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-entity-registry>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "zha")]]' restamp>
|
||||
<ha-config-zha
|
||||
page-name="zha"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-zha>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "zwave")]]' restamp>
|
||||
<ha-config-zwave
|
||||
page-name="zwave"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-zwave>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "person")]]' restamp>
|
||||
<ha-config-person
|
||||
page-name="person"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-person>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(_routeData.page, "customize")]]'
|
||||
restamp
|
||||
>
|
||||
<ha-config-customize
|
||||
page-name="customize"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-config-customize>
|
||||
</template>
|
||||
|
||||
<template
|
||||
is="dom-if"
|
||||
if='[[_equals(_routeData.page, "integrations")]]'
|
||||
restamp
|
||||
>
|
||||
<ha-config-entries
|
||||
route="[[route]]"
|
||||
page-name="integrations"
|
||||
hass="[[hass]]"
|
||||
is-wide="[[isWide]]"
|
||||
narrow="[[narrow]]"
|
||||
></ha-config-entries>
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if='[[_equals(_routeData.page, "users")]]' restamp>
|
||||
<ha-config-users
|
||||
page-name="users"
|
||||
route="[[route]]"
|
||||
hass="[[hass]]"
|
||||
></ha-config-users>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
142
src/panels/config/ha-panel-config.ts
Normal file
142
src/panels/config/ha-panel-config.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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`
|
||||
<app-route
|
||||
@ -158,4 +158,4 @@ class HaConfigEntries extends NavigateMixin(PolymerElement) {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-config-entries", HaConfigEntries);
|
||||
customElements.define("ha-config-integrations", HaConfigIntegrations);
|
@ -32,8 +32,6 @@ class HaConfigScript extends PolymerElement {
|
||||
<template is="dom-if" if="[[!showEditor]]">
|
||||
<ha-script-picker
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
scripts="[[scripts]]"
|
||||
is-wide="[[isWide]]"
|
||||
></ha-script-picker>
|
||||
@ -53,8 +51,6 @@ class HaConfigScript extends PolymerElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
route: Object,
|
||||
isWide: Boolean,
|
||||
_routeData: Object,
|
||||
|
@ -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,
|
||||
|
@ -124,15 +124,6 @@ class HaScriptPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
scripts: {
|
||||
type: Array,
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -52,10 +52,7 @@ class HaPanelDevEvent extends EventsMixin(PolymerElement) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>Events</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -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: "",
|
||||
|
@ -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 {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>About</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
@ -41,10 +41,7 @@ class HaPanelDevMqtt extends PolymerElement {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>MQTT</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -80,8 +77,6 @@ class HaPanelDevMqtt extends PolymerElement {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
topic: String,
|
||||
payload: String,
|
||||
};
|
||||
|
@ -84,10 +84,7 @@ class HaPanelDevService extends PolymerElement {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>Services</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -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",
|
||||
|
@ -73,10 +73,7 @@ class HaPanelDevState extends EventsMixin(PolymerElement) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>States</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -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: "",
|
||||
|
@ -70,10 +70,7 @@ class HaPanelDevTemplate extends PolymerElement {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>Templates</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -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,
|
||||
|
@ -65,10 +65,7 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.history')]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -123,15 +120,6 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
stateHistory: {
|
||||
type: Object,
|
||||
value: null,
|
||||
|
@ -16,10 +16,7 @@ class HaPanelIframe extends PolymerElement {
|
||||
}
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[panel.title]]</div>
|
||||
</app-toolbar>
|
||||
|
||||
@ -38,14 +35,6 @@ class HaPanelIframe extends PolymerElement {
|
||||
panel: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -89,10 +89,7 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.logbook')]]</div>
|
||||
<paper-icon-button
|
||||
icon="hass:refresh"
|
||||
@ -164,16 +161,6 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
// ISO8601 formatted date string
|
||||
_currentDate: {
|
||||
type: String,
|
||||
|
@ -20,11 +20,13 @@ import stateIcon from "../../../common/entity/state_icon";
|
||||
import computeStateDomain from "../../../common/entity/compute_state_domain";
|
||||
import computeStateName from "../../../common/entity/compute_state_name";
|
||||
import applyThemesOnElement from "../../../common/dom/apply_themes_on_element";
|
||||
import computeDomain from "../../../common/entity/compute_domain";
|
||||
import { HomeAssistant, LightEntity } from "../../../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig, ActionConfig } from "../../../data/lovelace";
|
||||
import { longPress } from "../common/directives/long-press-directive";
|
||||
import { handleClick } from "../common/handle-click";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
|
||||
export interface Config extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
@ -44,8 +46,8 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return {
|
||||
tap_action: { action: "more-info" },
|
||||
hold_action: { action: "none" },
|
||||
tap_action: { action: "toggle" },
|
||||
hold_action: { action: "more-info" },
|
||||
};
|
||||
}
|
||||
|
||||
@ -62,7 +64,27 @@ class HuiEntityButtonCard extends LitElement implements LovelaceCard {
|
||||
throw new Error("Invalid Entity");
|
||||
}
|
||||
|
||||
this._config = { theme: "default", ...config };
|
||||
this._config = {
|
||||
theme: "default",
|
||||
hold_action: { action: "more-info" },
|
||||
...config,
|
||||
};
|
||||
|
||||
if (DOMAINS_TOGGLE.has(computeDomain(config.entity))) {
|
||||
this._config = {
|
||||
tap_action: {
|
||||
action: "toggle",
|
||||
},
|
||||
...this._config,
|
||||
};
|
||||
} else {
|
||||
this._config = {
|
||||
tap_action: {
|
||||
action: "more-info",
|
||||
},
|
||||
...this._config,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
|
@ -26,7 +26,6 @@ class LovelacePanel extends LitElement {
|
||||
public panel?: PanelInfo<LovelacePanelConfig>;
|
||||
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`
|
||||
<hui-root
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
.hass="${this.hass}"
|
||||
.lovelace="${this.lovelace}"
|
||||
.route="${this.route}"
|
||||
@ -73,12 +68,7 @@ class LovelacePanel extends LitElement {
|
||||
|
||||
if (state === "error") {
|
||||
return html`
|
||||
<hass-error-screen
|
||||
title="Lovelace"
|
||||
.error="${this._errorMsg}"
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
>
|
||||
<hass-error-screen title="Lovelace" .error="${this._errorMsg}">
|
||||
<mwc-button on-click="_forceFetchConfig">Reload Lovelace</mwc-button>
|
||||
</hass-error-screen>
|
||||
`;
|
||||
@ -95,16 +85,25 @@ class LovelacePanel extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-loading-screen
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
></hass-loading-screen>
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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`
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
.narrow="${this.narrow}"
|
||||
.showMenu="${this.showMenu}"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>${this.config.title || "Home Assistant"}</div>
|
||||
<hui-notifications-button
|
||||
.hass="${this.hass}"
|
||||
|
@ -80,10 +80,7 @@ class HaPanelMailbox extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.mailbox')]]</div>
|
||||
</app-toolbar>
|
||||
<div sticky hidden$="[[areTabsHidden(platforms)]]">
|
||||
@ -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,
|
||||
},
|
||||
|
@ -27,10 +27,7 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
||||
</style>
|
||||
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.map')]]</div>
|
||||
</app-toolbar>
|
||||
|
||||
@ -44,15 +41,6 @@ class HaPanelMap extends LocalizeMixin(PolymerElement) {
|
||||
type: Object,
|
||||
observer: "drawEntities",
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
showMenu: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -52,10 +52,7 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.profile')]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
@ -121,7 +118,6 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
_refreshTokens: Array,
|
||||
};
|
||||
}
|
||||
|
@ -70,10 +70,7 @@ class HaPanelShoppingList extends LocalizeMixin(PolymerElement) {
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title>[[localize('panel.shopping_list')]]</div>
|
||||
<ha-start-voice-button
|
||||
hass="[[hass]]"
|
||||
@ -145,8 +142,6 @@ class HaPanelShoppingList extends LocalizeMixin(PolymerElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
canListen: Boolean,
|
||||
items: {
|
||||
type: Array,
|
||||
|
@ -65,10 +65,7 @@ class PartialCards extends EventsMixin(NavigateMixin(PolymerElement)) {
|
||||
<ha-app-layout id="layout">
|
||||
<app-header effects="waterfall" condenses="" fixed="" slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
narrow="[[narrow]]"
|
||||
show-menu="[[showMenu]]"
|
||||
></ha-menu-button>
|
||||
<ha-menu-button></ha-menu-button>
|
||||
<div main-title="">
|
||||
[[computeTitle(views, defaultView, locationName)]]
|
||||
</div>
|
||||
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -225,7 +225,6 @@ export interface Route {
|
||||
export interface PanelElement extends HTMLElement {
|
||||
hass?: HomeAssistant;
|
||||
narrow?: boolean;
|
||||
showMenu?: boolean;
|
||||
route?: Route | null;
|
||||
panel?: Panel;
|
||||
}
|
||||
|
@ -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": "مستخدمين للعرض فقط"
|
||||
}
|
||||
}
|
@ -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": "Потребители с достъп само за четене"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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": "Χρήστες μόνο για ανάγνωση"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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": "Системные пользователи"
|
||||
}
|
||||
}
|
@ -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": "ผู้ใช้ที่สามารถดูได้อย่างเดียว"
|
||||
}
|
||||
}
|
@ -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": "只读用户"
|
||||
}
|
||||
}
|
@ -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": "唯讀用戶"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user