mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 01:06:35 +00:00
commit
30471b7cfb
@ -110,7 +110,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
display: block;
|
||||
}
|
||||
.state div {
|
||||
width: 150px;
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
}
|
||||
paper-toggle-button {
|
||||
@ -311,7 +311,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
id="apparmor"
|
||||
icon="hassio:shield"
|
||||
label="apparmor"
|
||||
description="[[addon.apparmor]]"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
</template>
|
||||
<template is="dom-if" if="[[addon.auth_api]]">
|
||||
@ -355,6 +355,15 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
checked="[[addon.protected]]"
|
||||
></paper-toggle-button>
|
||||
</div>
|
||||
<template is="dom-if" if="[[addon.ingress]]">
|
||||
<div class="state">
|
||||
<div>Show in Sidebar</div>
|
||||
<paper-toggle-button
|
||||
on-change="panelToggled"
|
||||
checked="[[addon.ingress_panel]]"
|
||||
></paper-toggle-button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
@ -522,6 +531,11 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
|
||||
this.set("addon.protected", !this.addon.protected);
|
||||
}
|
||||
|
||||
panelToggled() {
|
||||
const data = { ingress_panel: !this.addon.ingress_panel };
|
||||
this.hass.callApi("POST", `hassio/addons/${this.addonSlug}/options`, data);
|
||||
}
|
||||
|
||||
showMoreInfo(e) {
|
||||
const id = e.target.getAttribute("id");
|
||||
showHassioMarkdownDialog(this, {
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20190419.0",
|
||||
version="20190424.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { forwardHaptic } from "../../util/haptics";
|
||||
|
||||
const isOn = (stateObj?: HassEntity) =>
|
||||
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
|
||||
@ -89,6 +90,7 @@ class HaEntityToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic(this, "light");
|
||||
const stateDomain = computeStateDomain(this.stateObj);
|
||||
let serviceDomain;
|
||||
let service;
|
||||
|
@ -18,6 +18,10 @@ import isComponentLoaded from "../common/config/is_component_loaded";
|
||||
import { HomeAssistant, PanelInfo } from "../types";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { DEFAULT_PANEL } from "../common/const";
|
||||
import {
|
||||
getExternalConfig,
|
||||
ExternalConfig,
|
||||
} from "../external_app/external_config";
|
||||
|
||||
const computeUrl = (urlPath) => `/${urlPath}`;
|
||||
|
||||
@ -69,6 +73,7 @@ class HaSidebar extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@property() public _defaultPage?: string =
|
||||
localStorage.defaultPage || DEFAULT_PANEL;
|
||||
@property() private _externalConfig?: ExternalConfig;
|
||||
|
||||
protected render() {
|
||||
const hass = this.hass;
|
||||
@ -117,6 +122,27 @@ class HaSidebar extends LitElement {
|
||||
</a>
|
||||
`
|
||||
)}
|
||||
${this._externalConfig && this._externalConfig.hasSettingsScreen
|
||||
? html`
|
||||
<a
|
||||
href="#external-app-configuration"
|
||||
tabindex="-1"
|
||||
@click=${this._handleExternalAppConfiguration}
|
||||
>
|
||||
<paper-icon-item>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
icon="hass:cellphone-settings-variant"
|
||||
></ha-icon>
|
||||
<span class="item-text"
|
||||
>${hass.localize(
|
||||
"ui.sidebar.external_app_configuration"
|
||||
)}</span
|
||||
>
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
`
|
||||
: ""}
|
||||
${!hass.user
|
||||
? html`
|
||||
<paper-icon-item @click=${this._handleLogOut} class="logout">
|
||||
@ -193,6 +219,9 @@ class HaSidebar extends LitElement {
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("_externalConfig")) {
|
||||
return true;
|
||||
}
|
||||
if (!this.hass || !changedProps.has("hass")) {
|
||||
return false;
|
||||
}
|
||||
@ -210,10 +239,26 @@ class HaSidebar extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
if (this.hass && this.hass.auth.external) {
|
||||
getExternalConfig(this.hass.auth.external).then((conf) => {
|
||||
this._externalConfig = conf;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _handleLogOut() {
|
||||
fireEvent(this, "hass-logout");
|
||||
}
|
||||
|
||||
private _handleExternalAppConfiguration(ev: Event) {
|
||||
ev.preventDefault();
|
||||
this.hass!.auth.external!.fireMessage({
|
||||
type: "config_screen/show",
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
@ -259,7 +304,7 @@ class HaSidebar extends LitElement {
|
||||
--paper-item-min-height: 40px;
|
||||
}
|
||||
|
||||
a ha-icon {
|
||||
ha-icon[slot="item-icon"] {
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ const isExternal = location.search.includes("external_auth=1");
|
||||
|
||||
const authProm = isExternal
|
||||
? () =>
|
||||
import(/* webpackChunkName: "external_auth" */ "../common/auth/external_auth").then(
|
||||
import(/* webpackChunkName: "external_auth" */ "../external_app/external_auth").then(
|
||||
(mod) => new mod.default(hassUrl)
|
||||
)
|
||||
: () =>
|
||||
|
@ -2,6 +2,7 @@
|
||||
* Auth class that connects to a native app for authentication.
|
||||
*/
|
||||
import { Auth } from "home-assistant-js-websocket";
|
||||
import { ExternalMessaging, InternalMessage } from "./external_messaging";
|
||||
|
||||
const CALLBACK_SET_TOKEN = "externalAuthSetToken";
|
||||
const CALLBACK_REVOKE_TOKEN = "externalAuthRevokeToken";
|
||||
@ -20,6 +21,7 @@ declare global {
|
||||
externalApp?: {
|
||||
getExternalAuth(payload: string);
|
||||
revokeExternalAuth(payload: string);
|
||||
externalBus(payload: string);
|
||||
};
|
||||
webkit?: {
|
||||
messageHandlers: {
|
||||
@ -29,6 +31,9 @@ declare global {
|
||||
revokeExternalAuth: {
|
||||
postMessage(payload: BasePayload);
|
||||
};
|
||||
externalBus: {
|
||||
postMessage(payload: InternalMessage);
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -41,6 +46,8 @@ if (!window.externalApp && !window.webkit) {
|
||||
}
|
||||
|
||||
export default class ExternalAuth extends Auth {
|
||||
public external = new ExternalMessaging();
|
||||
|
||||
constructor(hassUrl) {
|
||||
super({
|
||||
hassUrl,
|
||||
@ -51,19 +58,10 @@ export default class ExternalAuth extends Auth {
|
||||
// This will trigger connection to do a refresh right away
|
||||
expires: 0,
|
||||
});
|
||||
this.external.attach();
|
||||
}
|
||||
|
||||
public async refreshAccessToken() {
|
||||
const responseProm = new Promise<RefreshTokenResponse>(
|
||||
(resolve, reject) => {
|
||||
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
||||
success ? resolve(data) : reject(data);
|
||||
}
|
||||
);
|
||||
|
||||
// Allow promise to set resolve on window object.
|
||||
await 0;
|
||||
|
||||
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
|
||||
|
||||
if (window.externalApp) {
|
||||
@ -74,21 +72,18 @@ export default class ExternalAuth extends Auth {
|
||||
);
|
||||
}
|
||||
|
||||
const tokens = await responseProm;
|
||||
const tokens = await new Promise<RefreshTokenResponse>(
|
||||
(resolve, reject) => {
|
||||
window[CALLBACK_SET_TOKEN] = (success, data) =>
|
||||
success ? resolve(data) : reject(data);
|
||||
}
|
||||
);
|
||||
|
||||
this.data.access_token = tokens.access_token;
|
||||
this.data.expires = tokens.expires_in * 1000 + Date.now();
|
||||
}
|
||||
|
||||
public async revoke() {
|
||||
const responseProm = new Promise((resolve, reject) => {
|
||||
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
|
||||
success ? resolve(data) : reject(data);
|
||||
});
|
||||
|
||||
// Allow promise to set resolve on window object.
|
||||
await 0;
|
||||
|
||||
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
|
||||
|
||||
if (window.externalApp) {
|
||||
@ -99,6 +94,9 @@ export default class ExternalAuth extends Auth {
|
||||
);
|
||||
}
|
||||
|
||||
await responseProm;
|
||||
await new Promise((resolve, reject) => {
|
||||
window[CALLBACK_REVOKE_TOKEN] = (success, data) =>
|
||||
success ? resolve(data) : reject(data);
|
||||
});
|
||||
}
|
||||
}
|
16
src/external_app/external_config.ts
Normal file
16
src/external_app/external_config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { ExternalMessaging } from "./external_messaging";
|
||||
|
||||
export interface ExternalConfig {
|
||||
hasSettingsScreen: boolean;
|
||||
}
|
||||
|
||||
export const getExternalConfig = (
|
||||
bus: ExternalMessaging
|
||||
): Promise<ExternalConfig> => {
|
||||
if (!bus.cache.cfg) {
|
||||
bus.cache.cfg = bus.sendMessage<ExternalConfig>({
|
||||
type: "config/get",
|
||||
});
|
||||
}
|
||||
return bus.cache.cfg;
|
||||
};
|
15
src/external_app/external_events_forwarder.ts
Normal file
15
src/external_app/external_events_forwarder.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { ExternalMessaging } from "./external_messaging";
|
||||
|
||||
export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
||||
document.addEventListener("connection-status", (ev) =>
|
||||
bus.fireMessage({
|
||||
type: "connection-status",
|
||||
payload: { event: ev.detail },
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const externalForwardHaptics = (bus: ExternalMessaging) =>
|
||||
document.addEventListener("haptic", (ev) =>
|
||||
bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } })
|
||||
);
|
111
src/external_app/external_messaging.ts
Normal file
111
src/external_app/external_messaging.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import {
|
||||
externalForwardConnectionEvents,
|
||||
externalForwardHaptics,
|
||||
} from "./external_events_forwarder";
|
||||
|
||||
const CALLBACK_EXTERNAL_BUS = "externalBus";
|
||||
|
||||
interface CommandInFlight {
|
||||
resolve: (data: any) => void;
|
||||
reject: (err: ExternalError) => void;
|
||||
}
|
||||
|
||||
export interface InternalMessage {
|
||||
id?: number;
|
||||
type: string;
|
||||
payload?: unknown;
|
||||
}
|
||||
|
||||
interface ExternalError {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface ExternalMessageResult {
|
||||
id: number;
|
||||
type: "result";
|
||||
success: true;
|
||||
result: unknown;
|
||||
}
|
||||
|
||||
interface ExternalMessageResultError {
|
||||
id: number;
|
||||
type: "result";
|
||||
success: false;
|
||||
error: ExternalError;
|
||||
}
|
||||
|
||||
type ExternalMessage = ExternalMessageResult | ExternalMessageResultError;
|
||||
|
||||
export class ExternalMessaging {
|
||||
public commands: { [msgId: number]: CommandInFlight } = {};
|
||||
public cache: { [key: string]: any } = {};
|
||||
public msgId = 0;
|
||||
|
||||
public attach() {
|
||||
externalForwardConnectionEvents(this);
|
||||
externalForwardHaptics(this);
|
||||
window[CALLBACK_EXTERNAL_BUS] = (msg) => this.receiveMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to external app that expects a response.
|
||||
* @param msg message to send
|
||||
*/
|
||||
public sendMessage<T>(msg: InternalMessage): Promise<T> {
|
||||
const msgId = ++this.msgId;
|
||||
msg.id = msgId;
|
||||
|
||||
this.fireMessage(msg);
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.commands[msgId] = { resolve, reject };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to external app without expecting a response.
|
||||
* @param msg message to send
|
||||
*/
|
||||
public fireMessage(msg: InternalMessage) {
|
||||
if (!msg.id) {
|
||||
msg.id = ++this.msgId;
|
||||
}
|
||||
this._sendExternal(msg);
|
||||
}
|
||||
|
||||
public receiveMessage(msg: ExternalMessage) {
|
||||
if (__DEV__) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.log("Receiving message from external app", msg);
|
||||
}
|
||||
|
||||
const pendingCmd = this.commands[msg.id];
|
||||
|
||||
if (!pendingCmd) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.warn(`Received unknown msg ID`, msg.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "result") {
|
||||
if (msg.success) {
|
||||
pendingCmd.resolve(msg.result);
|
||||
} else {
|
||||
pendingCmd.reject(msg.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _sendExternal(msg: InternalMessage) {
|
||||
if (__DEV__) {
|
||||
// tslint:disable-next-line: no-console
|
||||
console.log("Sending message to external app", msg);
|
||||
}
|
||||
if (window.externalApp) {
|
||||
window.externalApp.externalBus(JSON.stringify(msg));
|
||||
} else {
|
||||
window.webkit!.messageHandlers.externalBus.postMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ import { getLocalLanguage } from "../../util/hass-translation";
|
||||
import { fetchWithAuth } from "../../util/fetch-with-auth";
|
||||
import hassCallApi from "../../util/hass-call-api";
|
||||
import { subscribePanels } from "../../data/ws-panels";
|
||||
import { forwardHaptic } from "../../util/haptics";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export default (superClass) =>
|
||||
class extends EventsMixin(LocalizeMixin(superClass)) {
|
||||
@ -75,6 +77,7 @@ export default (superClass) =>
|
||||
err
|
||||
);
|
||||
}
|
||||
forwardHaptic(this, "error");
|
||||
const message =
|
||||
this.hass.localize(
|
||||
"ui.notification_toast.service_call_failed",
|
||||
@ -126,11 +129,16 @@ export default (superClass) =>
|
||||
|
||||
const conn = this.hass.connection;
|
||||
|
||||
fireEvent(document, "connection-status", "connected");
|
||||
|
||||
conn.addEventListener("ready", () => this.hassReconnected());
|
||||
conn.addEventListener("disconnected", () => this.hassDisconnected());
|
||||
// If we reconnect after losing connection and auth is no longer valid.
|
||||
conn.addEventListener("reconnect-error", (_conn, err) => {
|
||||
if (err === ERR_INVALID_AUTH) location.reload();
|
||||
if (err === ERR_INVALID_AUTH) {
|
||||
fireEvent(document, "connection-status", "auth-invalid");
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
subscribeEntities(conn, (states) => this._updateHass({ states }));
|
||||
@ -142,10 +150,12 @@ export default (superClass) =>
|
||||
hassReconnected() {
|
||||
super.hassReconnected();
|
||||
this._updateHass({ connected: true });
|
||||
fireEvent(document, "connection-status", "connected");
|
||||
}
|
||||
|
||||
hassDisconnected() {
|
||||
super.hassDisconnected();
|
||||
this._updateHass({ connected: false });
|
||||
fireEvent(document, "connection-status", "disconnected");
|
||||
}
|
||||
};
|
||||
|
@ -64,7 +64,7 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
<template is="dom-repeat" items="[[progress]]">
|
||||
<div class="config-entry-row">
|
||||
<paper-item-body>
|
||||
[[_computeIntegrationTitle(localize, item.handler)]]
|
||||
[[_computeActiveFlowTitle(localize, item)]]
|
||||
</paper-item-body>
|
||||
<mwc-button on-click="_continueFlow"
|
||||
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
|
||||
@ -190,6 +190,23 @@ class HaConfigManagerDashboard extends LocalizeMixin(
|
||||
return localize(`component.${integration}.config.title`);
|
||||
}
|
||||
|
||||
_computeActiveFlowTitle(localize, integration) {
|
||||
const placeholders = integration.context.title_placeholders || {};
|
||||
const placeholderKeys = Object.keys(placeholders);
|
||||
if (placeholderKeys.length === 0) {
|
||||
return localize(`component.${integration.handler}.config.title`);
|
||||
}
|
||||
const args = [];
|
||||
placeholderKeys.forEach((key) => {
|
||||
args.push(key);
|
||||
args.push(placeholders[key]);
|
||||
});
|
||||
return localize(
|
||||
`component.${integration.handler}.config.flow_title`,
|
||||
...args
|
||||
);
|
||||
}
|
||||
|
||||
_computeConfigEntryEntities(hass, configEntry, entities) {
|
||||
if (!entities) {
|
||||
return [];
|
||||
|
@ -13,6 +13,7 @@ import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-tog
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { turnOnOffEntities } from "../common/entity/turn-on-off-entities";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { forwardHaptic } from "../../../util/haptics";
|
||||
|
||||
@customElement("hui-entities-toggle")
|
||||
class HuiEntitiesToggle extends LitElement {
|
||||
@ -65,6 +66,7 @@ class HuiEntitiesToggle extends LitElement {
|
||||
}
|
||||
|
||||
private _callService(ev: MouseEvent): void {
|
||||
forwardHaptic(this, "light");
|
||||
const turnOn = (ev.target as PaperToggleButtonElement).checked;
|
||||
turnOnOffEntities(this.hass!, this._toggleEntities!, turnOn!);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import { HomeAssistant } from "../../../types";
|
||||
import { EntityRow, EntityConfig } from "./types";
|
||||
import { setOption } from "../../../data/input-select";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { forwardHaptic } from "../../../util/haptics";
|
||||
|
||||
@customElement("hui-input-select-entity-row")
|
||||
class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||
@ -97,6 +98,7 @@ class HuiInputSelectEntityRow extends LitElement implements EntityRow {
|
||||
}
|
||||
|
||||
private _selectedChanged(ev): void {
|
||||
forwardHaptic(this, "light");
|
||||
// Selected Option will transition to '' before transitioning to new value
|
||||
const stateObj = this.hass!.states[this._config!.entity];
|
||||
if (
|
||||
|
13
src/types.ts
13
src/types.ts
@ -9,6 +9,8 @@ import {
|
||||
HassServices,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { LocalizeFunc } from "./common/translations/localize";
|
||||
import { ExternalMessaging } from "./external_app/external_messaging";
|
||||
import { HASSDomEvent } from "./common/dom/fire_event";
|
||||
|
||||
declare global {
|
||||
var __DEV__: boolean;
|
||||
@ -16,9 +18,7 @@ declare global {
|
||||
var __BUILD__: "latest" | "es5";
|
||||
var __VERSION__: string;
|
||||
var __STATIC_PATH__: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
// Custom panel entry point url
|
||||
customPanelJS: string;
|
||||
@ -38,9 +38,16 @@ declare global {
|
||||
value: unknown;
|
||||
};
|
||||
change: undefined;
|
||||
"connection-status": ConnectionStatus;
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"connection-status": HASSDomEvent<ConnectionStatus>;
|
||||
}
|
||||
}
|
||||
|
||||
type ConnectionStatus = "connected" | "auth-invalid" | "disconnected";
|
||||
|
||||
export interface WebhookError {
|
||||
code: number;
|
||||
message: string;
|
||||
@ -109,7 +116,7 @@ export interface Resources {
|
||||
}
|
||||
|
||||
export interface HomeAssistant {
|
||||
auth: Auth;
|
||||
auth: Auth & { external?: ExternalMessaging };
|
||||
connection: Connection;
|
||||
connected: boolean;
|
||||
states: HassEntities;
|
||||
|
32
src/util/haptics.ts
Normal file
32
src/util/haptics.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Utility function that enables haptic feedback
|
||||
*/
|
||||
|
||||
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||
|
||||
// Allowed types are from iOS HIG.
|
||||
// https://developer.apple.com/design/human-interface-guidelines/ios/user-interaction/feedback/#haptics
|
||||
// Implementors on platforms other than iOS should attempt to match the patterns (shown in HIG) as closely as possible.
|
||||
export type HapticType =
|
||||
| "success"
|
||||
| "warning"
|
||||
| "failure"
|
||||
| "light"
|
||||
| "medium"
|
||||
| "heavy"
|
||||
| "selection";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
haptic: HapticType;
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
haptic: HASSDomEvent<HapticType>;
|
||||
}
|
||||
}
|
||||
|
||||
export const forwardHaptic = (el: HTMLElement, hapticType: HapticType) => {
|
||||
fireEvent(el, "haptic", hapticType);
|
||||
};
|
77
test-mocha/external_app/external_messaging.spec.ts
Normal file
77
test-mocha/external_app/external_messaging.spec.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import * as assert from "assert";
|
||||
|
||||
import {
|
||||
ExternalMessaging,
|
||||
InternalMessage,
|
||||
} from "../../src/external_app/external_messaging";
|
||||
|
||||
// @ts-ignore
|
||||
global.__DEV__ = true;
|
||||
|
||||
class MockExternalMessaging extends ExternalMessaging {
|
||||
public mockSent: InternalMessage[] = [];
|
||||
|
||||
protected _sendExternal(msg: InternalMessage) {
|
||||
this.mockSent.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
describe("ExternalMessaging", () => {
|
||||
let bus: MockExternalMessaging;
|
||||
|
||||
beforeEach(() => {
|
||||
bus = new MockExternalMessaging();
|
||||
});
|
||||
|
||||
it("Send success results", async () => {
|
||||
const sendMessageProm = bus.sendMessage({
|
||||
type: "config/get",
|
||||
});
|
||||
|
||||
assert.equal(bus.mockSent.length, 1);
|
||||
assert.deepEqual(bus.mockSent[0], {
|
||||
id: 1,
|
||||
type: "config/get",
|
||||
});
|
||||
|
||||
bus.receiveMessage({
|
||||
id: 1,
|
||||
type: "result",
|
||||
success: true,
|
||||
result: {
|
||||
hello: "world",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await sendMessageProm;
|
||||
assert.deepEqual(result, {
|
||||
hello: "world",
|
||||
});
|
||||
});
|
||||
|
||||
it("Send fail results", async () => {
|
||||
const sendMessageProm = bus.sendMessage({
|
||||
type: "config/get",
|
||||
});
|
||||
|
||||
bus.receiveMessage({
|
||||
id: 1,
|
||||
type: "result",
|
||||
success: false,
|
||||
error: {
|
||||
code: "no_auth",
|
||||
message: "There is no authentication.",
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await sendMessageProm;
|
||||
assert.fail("Should have raised");
|
||||
} catch (err) {
|
||||
assert.deepEqual(err, {
|
||||
code: "no_auth",
|
||||
message: "There is no authentication.",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
@ -307,7 +307,7 @@
|
||||
"ui": {
|
||||
"panel": {
|
||||
"shopping-list": {
|
||||
"clear_completed": "Suprimir completats",
|
||||
"clear_completed": "Elimina els completats",
|
||||
"add_item": "Afegir article",
|
||||
"microphone_tip": "Clica el micròfon a la part superior dreta i digues \"Add candy to my shopping list\""
|
||||
},
|
||||
@ -322,8 +322,8 @@
|
||||
"mailbox": {
|
||||
"empty": "No tens missatges",
|
||||
"playback_title": "Reproducció de missatges",
|
||||
"delete_prompt": "Vols suprimir aquest missatge?",
|
||||
"delete_button": "Suprimir"
|
||||
"delete_prompt": "Vols eliminar aquest missatge?",
|
||||
"delete_button": "Elimina"
|
||||
},
|
||||
"config": {
|
||||
"header": "Configuració de Home Assistant",
|
||||
@ -389,8 +389,8 @@
|
||||
"introduction": "Els activadors són les regles que fan que es dispari un automatisme. Pots definir més d'un activador per a cada automatisme. Una vegada s'iniciï un activador, el Home Assistant validarà les condicions (si n'hi ha) i finalment cridarà l'acció.\n\n[Més informació sobre activadors](https:\/\/home-assistant.io\/docs\/automation\/trigger\/)",
|
||||
"add": "Afegir activador",
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Suprimir",
|
||||
"delete_confirm": "Segur que vols suprimir-ho?",
|
||||
"delete": "Elimina",
|
||||
"delete_confirm": "Segur que vols eliminar-ho?",
|
||||
"unsupported_platform": "Plataforma {platform} no suportada.",
|
||||
"type_select": "Tipus d'activador",
|
||||
"type": {
|
||||
@ -471,8 +471,8 @@
|
||||
"introduction": "Les condicions són una part opcional d'un automatisme i es poden utilitzar per permetre o evitar que es produeixi l'acció quan s'activa l'automatisme. Les condicions són similars als activadors però funcionen de forma diferent: un activador analitza els esdeveniments que ocorren en el sistema mentre que una condició només observa com està el sistema en un moment determinat, per exemple, un activador se n'adonarà de quan s'està activant un interruptor mentre que una condició només pot veure si un interruptor està activat o desactivat.\n\n[Més informació sobre les condicions](https:\/\/home-assistant.io\/docs\/scripts\/conditions\/).",
|
||||
"add": "Afegir condició",
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Suprimir",
|
||||
"delete_confirm": "Segur que vols suprimir-ho?",
|
||||
"delete": "Elimina",
|
||||
"delete_confirm": "Segur que vols eliminar-ho?",
|
||||
"unsupported_condition": "Condició {condition} no suportada.",
|
||||
"type_select": "Tipus de condició",
|
||||
"type": {
|
||||
@ -517,8 +517,8 @@
|
||||
"introduction": "Les accions són allò que farà Home Assistant quan s'activi l'automatisme i es compleixin totes les condicions (si n'hi ha).\n\n[Més informació sobre les accions](https:\/\/home-assistant.io\/docs\/automation\/action\/).",
|
||||
"add": "Afegir acció",
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Suprimir",
|
||||
"delete_confirm": "Segur que vols suprimir-ho?",
|
||||
"delete": "Elimina",
|
||||
"delete_confirm": "Segur que vols eliminar-ho?",
|
||||
"unsupported_action": "Acció {action} no suportada.",
|
||||
"type_select": "Tipus d'acció",
|
||||
"type": {
|
||||
@ -595,7 +595,7 @@
|
||||
"config_entry": {
|
||||
"no_devices": "Aquesta integració no té dispositius.",
|
||||
"no_device": "Entitats sense dispositius",
|
||||
"delete_confirm": "Estas segur que vols suprimir aquesta integració?",
|
||||
"delete_confirm": "Estas segur que vols eliminar aquesta integració?",
|
||||
"restart_confirm": "Reinicia el Home Assistant per acabar d'eliminar aquesta integració",
|
||||
"manuf": "de {manufacturer}",
|
||||
"hub": "Connectat a través de",
|
||||
@ -639,7 +639,7 @@
|
||||
"create_area": "CREA ÀREA",
|
||||
"editor": {
|
||||
"default_name": "Àrea nova",
|
||||
"delete": "SUPRIMIR",
|
||||
"delete": "ELIMINA",
|
||||
"update": "ACTUALITZAR",
|
||||
"create": "CREAR"
|
||||
}
|
||||
@ -657,7 +657,7 @@
|
||||
"editor": {
|
||||
"unavailable": "Aquesta entitat no està disponible actualment.",
|
||||
"default_name": "Àrea nova",
|
||||
"delete": "SUPRIMIR",
|
||||
"delete": "ELIMINA",
|
||||
"update": "ACTUALITZAR"
|
||||
}
|
||||
},
|
||||
@ -697,19 +697,19 @@
|
||||
"description": "Cada testimoni d'autenticació d'actualització representa un inici de sessió diferent. Els testimonis d'autenticació d'actualització s'eliminaran automàticament quan tanquis la sessió. A sota hi ha una llista de testimonis d'autenticació d'actualització que estan actius actualment al teu compte.",
|
||||
"token_title": "Refresca el testimoni d'autenticació del {clientId}",
|
||||
"created_at": "Creat el {date}",
|
||||
"confirm_delete": "Estas segur que vols suprimir el testimoni d'autenticació d'actualització per {name}?",
|
||||
"delete_failed": "No s'ha pogut suprimir el testimoni d'autenticació d'actualització.",
|
||||
"confirm_delete": "Estas segur que vols eliminar el testimoni d'autenticació d'actualització per {name}?",
|
||||
"delete_failed": "No s'ha pogut eliminar el testimoni d'autenticació d'actualització.",
|
||||
"last_used": "Darrer ús el {date} des de {location}",
|
||||
"not_used": "Mai no s'ha utilitzat",
|
||||
"current_token_tooltip": "No s'ha pogut suprimir el testimoni d'autenticació d'actualització."
|
||||
"current_token_tooltip": "No s'ha pogut eliminar el testimoni d'autenticació d'actualització."
|
||||
},
|
||||
"long_lived_access_tokens": {
|
||||
"header": "Testimonis d'autenticació d'accés de llarga durada",
|
||||
"description": "Crea testimonis d'autenticació d'accés de llarga durada per permetre als teus programes (scripts) interactuar amb la instància de Home Assistant. Cada testimoni d'autenticació serà vàlid durant deu anys després de la seva creació. Els següents testimonis d'autenticació d'accés de llarga durada estan actius actualment.",
|
||||
"learn_auth_requests": "Aprèn a fer sol·licituds autenticades.",
|
||||
"created_at": "Creat el {date}",
|
||||
"confirm_delete": "Estàs segur que vols suprimir el testimoni d'autenticació d'accés per {name}?",
|
||||
"delete_failed": "No s'ha pogut suprimir el testimoni d'autenticació d'accés.",
|
||||
"confirm_delete": "Estàs segur que vols eliminar el testimoni d'autenticació d'accés per {name}?",
|
||||
"delete_failed": "No s'ha pogut eliminar el testimoni d'autenticació d'accés.",
|
||||
"create": "Crea un testimoni d'autenticació",
|
||||
"create_failed": "No s'ha pogut crear el testimoni d'autenticació d'accés.",
|
||||
"prompt_name": "Nom?",
|
||||
@ -727,7 +727,7 @@
|
||||
"new_password": "Contrasenya nova",
|
||||
"confirm_new_password": "Confirmar contrasenya nova",
|
||||
"error_required": "Obligatori",
|
||||
"submit": "Enviar"
|
||||
"submit": "Envia"
|
||||
},
|
||||
"mfa": {
|
||||
"header": "Mòduls d'autenticació amb múltiples passos",
|
||||
@ -740,7 +740,7 @@
|
||||
"title_success": "Èxit!",
|
||||
"step_done": "Configuració feta per a {step}",
|
||||
"close": "Tanca",
|
||||
"submit": "Enviar"
|
||||
"submit": "Envia"
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
@ -878,8 +878,8 @@
|
||||
"pick_card": "Tria la targeta que vols afegir.",
|
||||
"add": "Afegir targeta",
|
||||
"edit": "Editar",
|
||||
"delete": "Suprimir",
|
||||
"move": "Moure"
|
||||
"delete": "Elimina",
|
||||
"move": "Mou"
|
||||
},
|
||||
"migrate": {
|
||||
"header": "Configuració incompatible",
|
||||
@ -892,7 +892,7 @@
|
||||
"header": "Configuració de la visualització",
|
||||
"add": "Afegeix visualització",
|
||||
"edit": "Editar visualització",
|
||||
"delete": "Suprimir visualització"
|
||||
"delete": "Eliminar visualització"
|
||||
},
|
||||
"save_config": {
|
||||
"header": "Pren el control de la interfície d'usuari Lovelace",
|
||||
@ -929,7 +929,7 @@
|
||||
},
|
||||
"common": {
|
||||
"loading": "Carregant",
|
||||
"cancel": "Cancel·lar",
|
||||
"cancel": "Cancel·la",
|
||||
"save": "Desar"
|
||||
},
|
||||
"duration": {
|
||||
|
@ -581,7 +581,8 @@
|
||||
"cloud": {
|
||||
"caption": "Home Assistant Cloud",
|
||||
"description_login": "Přihlášen jako {email}",
|
||||
"description_not_login": "Nepřihlášen"
|
||||
"description_not_login": "Nepřihlášen",
|
||||
"description_features": "Ovládejte vzdáleně, integrace a Alexou a Google Asistent"
|
||||
},
|
||||
"integrations": {
|
||||
"caption": "Integrace",
|
||||
@ -613,7 +614,9 @@
|
||||
"remove": "Odstranit zařízení ze sítě Zigbee."
|
||||
},
|
||||
"device_card": {
|
||||
"area_picker_label": "Oblast"
|
||||
"device_name_placeholder": "Zvolené jméno",
|
||||
"area_picker_label": "Oblast",
|
||||
"update_name_button": "Aktualizovat název"
|
||||
},
|
||||
"add_device_page": {
|
||||
"header": "Zigbee Home Automation - Přidat zařízení",
|
||||
@ -1175,6 +1178,7 @@
|
||||
},
|
||||
"groups": {
|
||||
"system-admin": "Správci",
|
||||
"system-users": "Uživatelé"
|
||||
"system-users": "Uživatelé",
|
||||
"system-read-only": "Uživatelé jen pro čtení"
|
||||
}
|
||||
}
|
@ -518,7 +518,7 @@
|
||||
"add": "Προσθήκη ενέργειας",
|
||||
"duplicate": "Διπλότυπο",
|
||||
"delete": "Διαγραφή",
|
||||
"delete_confirm": "Επιθυμείτε την διαγραφή σιγουρα;",
|
||||
"delete_confirm": "Επιθυμείτε την διαγραφή σίγουρα;",
|
||||
"unsupported_action": "Μη υποστηριζόμενη ενέργεια: {action}",
|
||||
"type_select": "Τύπος ενέργειας",
|
||||
"type": {
|
||||
@ -609,7 +609,7 @@
|
||||
"caption": "ZHA",
|
||||
"description": "Διαχείριση του δικτύου ZigBee Home Automation",
|
||||
"services": {
|
||||
"reconfigure": "Ρυθμίστε ξανά τη συσκευή ZHA (heal συσκευή). Χρησιμοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε ζητήματα με τη συσκευή. Εάν η συγκεκριμένη συσκευή τροφοδοτείται απο μπαταρία βεβαιωθείτε ότι είναι ενεργοποιημένη και δέχεται εντολές όταν χρησιμοποιείτε αυτή την υπηρεσία.",
|
||||
"reconfigure": "Ρυθμίστε ξανά τη συσκευή ZHA (heal συσκευή). Χρησιμοποιήστε αυτήν την επιλογή εάν αντιμετωπίζετε ζητήματα με τη συσκευή. Εάν η συγκεκριμένη συσκευή τροφοδοτείται από μπαταρία βεβαιωθείτε ότι είναι ενεργοποιημένη και δέχεται εντολές όταν χρησιμοποιείτε αυτή την υπηρεσία.",
|
||||
"updateDeviceName": "Ορίστε ένα προσαρμοσμένο όνομα γι αυτήν τη συσκευή στο μητρώο συσκευών.",
|
||||
"remove": "Καταργήστε μια συσκευή από το δίκτυο ZigBee."
|
||||
},
|
||||
@ -629,8 +629,8 @@
|
||||
"description": "Επισκόπηση όλων των περιοχών στο σπίτι σας.",
|
||||
"picker": {
|
||||
"header": "Περιοχή Μητρώου",
|
||||
"introduction": "Οι περιοχές χρησιμοποιούνται για την οργάνωση της τοποθεσίας των συσκευών. Αυτές οι πληροφορίες θα χρησιμοποιηθούν σε όλο το Home Assistant για να σας βοηθήσουν στην οργάνωση της διασύνδεσης, των αδειών και των ενσωματώσεών σας σε άλλα συστήματα.",
|
||||
"introduction2": "Για να τοποθετήσετε συσκευές σε μια περιοχή, χρησιμοποιήστε τον παρακάτω σύνδεσμο για να μεταβείτε στη σελίδα ενοποιήσεων και στη συνέχεια κάντε κλικ στην ρυθμισμένη ενωποίηση για να μεταβείτε στις κάρτες της συσκευής.",
|
||||
"introduction": "Οι περιοχές χρησιμοποιούνται για την οργάνωση της τοποθεσίας των συσκευών. Αυτές οι πληροφορίες θα χρησιμοποιηθούν σε όλο το Home Assistant για να σας βοηθήσουν στην οργάνωση της διασύνδεσης, των αδειών και των ενσωματώσεων σας σε άλλα συστήματα.",
|
||||
"introduction2": "Για να τοποθετήσετε συσκευές σε μια περιοχή, χρησιμοποιήστε τον παρακάτω σύνδεσμο για να μεταβείτε στη σελίδα ενοποιήσεων και στη συνέχεια κάντε κλικ στην ρυθμισμένη ενοποίηση για να μεταβείτε στις κάρτες της συσκευής.",
|
||||
"integrations_page": "Σελίδα ενσωματώσεων",
|
||||
"no_areas": "Φαίνεται ότι δεν έχετε ορίσει ακόμα κάποια περιοχή!",
|
||||
"create_area": "ΔΗΜΙΟΥΡΓΙΑ ΠΕΡΙΟΧΗΣ"
|
||||
@ -884,7 +884,7 @@
|
||||
"migrate": {
|
||||
"header": "Μη συμβατή διαμόρφωση",
|
||||
"para_no_id": "Αυτό το στοιχείο δεν έχει αναγνωριστικό. Προσθέστε ένα αναγνωριστικό σε αυτό το στοιχείο στο 'ui-lovelace.yaml'.",
|
||||
"para_migrate": "Ο Home Assistant μπορεί να προσθέσει αυτόματα όλα τα αναγνωριστικά απο τις κάρτες και τις προβολές σας πατώντας το πλήκτρο 'Ρυθμίσεις μετεγκατάστασης'.",
|
||||
"para_migrate": "Ο Home Assistant μπορεί να προσθέσει αυτόματα όλα τα αναγνωριστικά από τις κάρτες και τις προβολές σας πατώντας το πλήκτρο 'Ρυθμίσεις μετεγκατάστασης'.",
|
||||
"migrate": "Ρυθμίσεις μετεγκατάστασης"
|
||||
},
|
||||
"header": "Επεξεργασία περιβάλλοντος χρήστη",
|
||||
@ -896,7 +896,7 @@
|
||||
},
|
||||
"save_config": {
|
||||
"header": "Πάρτε τον έλεγχο του περιβάλλοντος χρήστη στο Lovelace",
|
||||
"para": "Από προεπιλογή, το Home Assistant θα διατηρήσει το περιβάλλον χρήστη που έχετε ενημερώνοντάς το όταν θα γίνονται διαθέσιμες νέες οντότητες ή στοιχεία Lovelace. Αν πάρετε τον έλεγχο, δεν θα πραγματοποιούμε πλέον αλλαγές για εσάς.",
|
||||
"para": "Από προεπιλογή, το Home Assistant θα διατηρήσει το περιβάλλον χρήστη που έχετε ενημερώνοντας το όταν θα γίνονται διαθέσιμες νέες οντότητες ή στοιχεία Lovelace. Αν πάρετε τον έλεγχο, δεν θα πραγματοποιούμε πλέον αλλαγές για εσάς.",
|
||||
"para_sure": "Είστε βέβαιος ότι θέλετε να πάρετε τον έλεγχο του περιβάλλοντος χρήστη;",
|
||||
"cancel": "Δεν πειράζει",
|
||||
"save": "Πάρτε τον έλεγχο"
|
||||
@ -1088,7 +1088,7 @@
|
||||
"dialogs": {
|
||||
"more_info_settings": {
|
||||
"save": "Αποθήκευση",
|
||||
"name": "Επιβολή Oνόματος",
|
||||
"name": "Επιβολή Ονόματος",
|
||||
"entity_id": "Αναγνωριστικό οντότητας"
|
||||
},
|
||||
"more_info_control": {
|
||||
|
@ -202,7 +202,8 @@
|
||||
"not_home": "Fjarverandi"
|
||||
},
|
||||
"default": {
|
||||
"error": "Villa"
|
||||
"error": "Villa",
|
||||
"entity_not_found": "Eining fannst ekki"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
@ -327,6 +328,7 @@
|
||||
},
|
||||
"geo_location": {
|
||||
"source": "Uppruni",
|
||||
"zone": "Öryggissvæði",
|
||||
"event": "Viðburður:",
|
||||
"enter": "Koma",
|
||||
"leave": "Brottför"
|
||||
@ -425,7 +427,8 @@
|
||||
"cloud": {
|
||||
"caption": "Home Assistant skýið",
|
||||
"description_login": "Innskráð(ur) sem {email}",
|
||||
"description_not_login": "Ekki skráð(ur) inn"
|
||||
"description_not_login": "Ekki skráð(ur) inn",
|
||||
"description_features": "Stjórna heimili að heiman með Alexa og Google Assistant."
|
||||
},
|
||||
"integrations": {
|
||||
"caption": "Samþættingar",
|
||||
@ -444,7 +447,8 @@
|
||||
"hub": "Tengt gegnum",
|
||||
"firmware": "Fastbúnaðarútgáfa: {version}",
|
||||
"device_unavailable": "Tæki ekki tiltækt",
|
||||
"entity_unavailable": "Eining ekki tiltæk"
|
||||
"entity_unavailable": "Eining ekki tiltæk",
|
||||
"no_area": "Ekkert svæði"
|
||||
}
|
||||
},
|
||||
"zha": {
|
||||
@ -456,6 +460,7 @@
|
||||
},
|
||||
"device_card": {
|
||||
"device_name_placeholder": "Nafn notanda",
|
||||
"area_picker_label": "Svæði",
|
||||
"update_name_button": "Uppfæra nafn"
|
||||
},
|
||||
"add_device_page": {
|
||||
@ -465,14 +470,20 @@
|
||||
}
|
||||
},
|
||||
"area_registry": {
|
||||
"caption": "Svæðaskrá",
|
||||
"description": "Yfirlit yfir öll svæði á heimilinu þínu.",
|
||||
"no_areas": "Það virðist sem að þú hafir engin svæði ennþá!",
|
||||
"create_area": "BÚA TIL SVÆÐI",
|
||||
"editor": {
|
||||
"default_name": "Nýtt svæði",
|
||||
"delete": "EYÐA",
|
||||
"update": "UPPFÆRA",
|
||||
"create": "STOFNA"
|
||||
},
|
||||
"picker": {
|
||||
"integrations_page": "Samþættingar síða"
|
||||
"integrations_page": "Samþættingar síða",
|
||||
"no_areas": "Það virðist sem að þú hafir engin svæði ennþá!",
|
||||
"create_area": "BÚA TIL SVÆÐI"
|
||||
}
|
||||
},
|
||||
"entity_registry": {
|
||||
@ -485,6 +496,7 @@
|
||||
},
|
||||
"editor": {
|
||||
"unavailable": "Þessi eining er ekki tiltæk eins og er.",
|
||||
"default_name": "Nýtt svæði",
|
||||
"delete": "EYÐA",
|
||||
"update": "UPPFÆRA"
|
||||
}
|
||||
|
@ -441,7 +441,7 @@
|
||||
"label": "Strefa",
|
||||
"entity": "Encja z lokalizacją",
|
||||
"zone": "Strefa",
|
||||
"event": "Zdarzenie",
|
||||
"event": "Zdarzenie:",
|
||||
"enter": "Wprowadź",
|
||||
"leave": "Opuść"
|
||||
},
|
||||
@ -1088,7 +1088,7 @@
|
||||
"dialogs": {
|
||||
"more_info_settings": {
|
||||
"save": "Zapisz",
|
||||
"name": "Nazwa",
|
||||
"name": "Nadpisanie nazwy",
|
||||
"entity_id": "Identyfikator encji"
|
||||
},
|
||||
"more_info_control": {
|
||||
|
@ -581,7 +581,8 @@
|
||||
"cloud": {
|
||||
"caption": "Home Assistant Cloud",
|
||||
"description_login": "Inloggad som {email}",
|
||||
"description_not_login": "Inte inloggad"
|
||||
"description_not_login": "Inte inloggad",
|
||||
"description_features": "Styra även när du inte är hemma, integrera med Alexa och Google Assistant."
|
||||
},
|
||||
"integrations": {
|
||||
"caption": "Integrationer",
|
||||
@ -649,7 +650,7 @@
|
||||
"picker": {
|
||||
"header": "Enhetsregister",
|
||||
"unavailable": "(ej tillgänglig)",
|
||||
"introduction": "Home Assistanthar ett enhetsregister som innehåller varje enhet som någonsin har setts vars identifieras kan vara unikt. Var och en av dessa enheter kommer att ha ett ID-nummer som är reserverat för bara den enheten.",
|
||||
"introduction": "Home Assistant har ett enhetsregister som innehåller varje enhet som någonsin har setts vars identifieras kan vara unikt. Var och en av dessa enheter kommer att ha ett ID-nummer som är reserverat för bara den enheten.",
|
||||
"introduction2": "Använd enhetsregistret för att skriva över namnet, ändra ID eller ta bort posten ifrån Home Assistant. Obs! Att ta bort posten ifrån entity registry (enhetsregister) kommer inte att ta bort enheten. För att göra det, följ länken nedan och ta bort det från integreringssidan.",
|
||||
"integrations_page": "Integrationssida"
|
||||
},
|
||||
@ -860,7 +861,7 @@
|
||||
"cards": {
|
||||
"shopping-list": {
|
||||
"checked_items": "Markerade objekt",
|
||||
"clear_items": "Rensa avbockade objekt",
|
||||
"clear_items": "Rensa markerade objekt",
|
||||
"add_item": "Lägg till objekt"
|
||||
},
|
||||
"empty_state": {
|
||||
@ -881,7 +882,7 @@
|
||||
"move": "Flytta"
|
||||
},
|
||||
"migrate": {
|
||||
"header": "Konfigurationen är inte giltig",
|
||||
"header": "Ogiltig konfiguration",
|
||||
"para_no_id": "Det här elementet har inget ID. Lägg till ett ID till det här elementet i \"ui-lovelace.yaml\".",
|
||||
"para_migrate": "Home Assistant kan automatiskt lägga till ID till alla dina kort och vyer genom att du klickar på \"Migrera konfiguration\".",
|
||||
"migrate": "Migrera konfigurationen"
|
||||
@ -894,7 +895,7 @@
|
||||
"delete": "Radera vy"
|
||||
},
|
||||
"save_config": {
|
||||
"header": "Ta kontroll över ditt Lovelace användargränssnitt",
|
||||
"header": "Ta kontroll över Lovelace UI",
|
||||
"para": "Home Assistant kommer som standard att behålla ditt användargränssnitt, uppdatera det när nya enheter eller Lovelace-komponenter blir tillgängliga. Om du tar kontroll kommer vi inte längre göra ändringar automatiskt för dig.",
|
||||
"para_sure": "Är du säker på att du vill ta kontroll över ditt användargränssnitt?",
|
||||
"cancel": "Glöm det",
|
||||
|
Loading…
x
Reference in New Issue
Block a user