mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-02 14:07:55 +00:00
commit
9974510067
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20190626.0",
|
version="20190627.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -188,6 +188,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
|
|
||||||
<div class="controls layout horizontal justified">
|
<div class="controls layout horizontal justified">
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Turn off"
|
||||||
icon="hass:power"
|
icon="hass:power"
|
||||||
on-click="handleTogglePower"
|
on-click="handleTogglePower"
|
||||||
invisible$="[[computeHidePowerButton(playerObj)]]"
|
invisible$="[[computeHidePowerButton(playerObj)]]"
|
||||||
@ -196,12 +197,14 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
|
|
||||||
<div class="playback-controls">
|
<div class="playback-controls">
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Previous track"
|
||||||
icon="hass:skip-previous"
|
icon="hass:skip-previous"
|
||||||
invisible$="[[!playerObj.supportsPreviousTrack]]"
|
invisible$="[[!playerObj.supportsPreviousTrack]]"
|
||||||
disabled="[[playerObj.isOff]]"
|
disabled="[[playerObj.isOff]]"
|
||||||
on-click="handlePrevious"
|
on-click="handlePrevious"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Play or Pause"
|
||||||
class="primary"
|
class="primary"
|
||||||
icon="[[computePlaybackControlIcon(playerObj)]]"
|
icon="[[computePlaybackControlIcon(playerObj)]]"
|
||||||
invisible$="[[!computePlaybackControlIcon(playerObj)]]"
|
invisible$="[[!computePlaybackControlIcon(playerObj)]]"
|
||||||
@ -209,6 +212,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
on-click="handlePlaybackControl"
|
on-click="handlePlaybackControl"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Next track"
|
||||||
icon="hass:skip-next"
|
icon="hass:skip-next"
|
||||||
invisible$="[[!playerObj.supportsNextTrack]]"
|
invisible$="[[!playerObj.supportsNextTrack]]"
|
||||||
disabled="[[playerObj.isOff]]"
|
disabled="[[playerObj.isOff]]"
|
||||||
@ -217,6 +221,7 @@ class HaMediaPlayerCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="More information."
|
||||||
icon="hass:dots-vertical"
|
icon="hass:dots-vertical"
|
||||||
on-click="handleOpenMoreInfo"
|
on-click="handleOpenMoreInfo"
|
||||||
class="self-center secondary"
|
class="self-center secondary"
|
||||||
|
@ -7,6 +7,7 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
|
|||||||
|
|
||||||
const fixedIcons = {
|
const fixedIcons = {
|
||||||
alert: "hass:alert",
|
alert: "hass:alert",
|
||||||
|
alexa: "hass:amazon-alexa",
|
||||||
automation: "hass:playlist-play",
|
automation: "hass:playlist-play",
|
||||||
calendar: "hass:calendar",
|
calendar: "hass:calendar",
|
||||||
camera: "hass:video",
|
camera: "hass:video",
|
||||||
@ -15,6 +16,7 @@ const fixedIcons = {
|
|||||||
conversation: "hass:text-to-speech",
|
conversation: "hass:text-to-speech",
|
||||||
device_tracker: "hass:account",
|
device_tracker: "hass:account",
|
||||||
fan: "hass:fan",
|
fan: "hass:fan",
|
||||||
|
google_assistant: "hass:google-assistant",
|
||||||
group: "hass:google-circles-communities",
|
group: "hass:google-circles-communities",
|
||||||
history_graph: "hass:chart-line",
|
history_graph: "hass:chart-line",
|
||||||
homeassistant: "hass:home-assistant",
|
homeassistant: "hass:home-assistant",
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { forwardHaptic } from "../../data/haptics";
|
import { forwardHaptic } from "../../data/haptics";
|
||||||
|
import computeStateName from "../../common/entity/compute_state_name";
|
||||||
|
|
||||||
const isOn = (stateObj?: HassEntity) =>
|
const isOn = (stateObj?: HassEntity) =>
|
||||||
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
|
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
|
||||||
@ -35,11 +36,13 @@ class HaEntityToggle extends LitElement {
|
|||||||
if (this.stateObj.attributes.assumed_state) {
|
if (this.stateObj.attributes.assumed_state) {
|
||||||
return html`
|
return html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label=${`Turn ${computeStateName(this.stateObj)} off`}
|
||||||
icon="hass:flash-off"
|
icon="hass:flash-off"
|
||||||
@click=${this._turnOff}
|
@click=${this._turnOff}
|
||||||
?state-active=${!this._isOn}
|
?state-active=${!this._isOn}
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label=${`Turn ${computeStateName(this.stateObj)} on`}
|
||||||
icon="hass:flash"
|
icon="hass:flash"
|
||||||
@click=${this._turnOn}
|
@click=${this._turnOn}
|
||||||
?state-active=${this._isOn}
|
?state-active=${this._isOn}
|
||||||
@ -49,6 +52,9 @@ class HaEntityToggle extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-toggle-button
|
<paper-toggle-button
|
||||||
|
aria-label=${`Toggle ${computeStateName(this.stateObj)} ${
|
||||||
|
this._isOn ? "off" : "on"
|
||||||
|
}`}
|
||||||
.checked=${this._isOn}
|
.checked=${this._isOn}
|
||||||
@change=${this._toggleChanged}
|
@change=${this._toggleChanged}
|
||||||
></paper-toggle-button>
|
></paper-toggle-button>
|
||||||
|
@ -55,7 +55,9 @@ class HaCameraStream extends LitElement {
|
|||||||
.src=${__DEMO__
|
.src=${__DEMO__
|
||||||
? `/api/camera_proxy_stream/${this.stateObj.entity_id}`
|
? `/api/camera_proxy_stream/${this.stateObj.entity_id}`
|
||||||
: computeMJPEGStreamUrl(this.stateObj)}
|
: computeMJPEGStreamUrl(this.stateObj)}
|
||||||
.alt=${computeStateName(this.stateObj)}
|
.alt=${`Preview of the ${computeStateName(
|
||||||
|
this.stateObj
|
||||||
|
)} camera.`}
|
||||||
/>
|
/>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
|
@ -66,11 +66,29 @@ const computePanels = (hass: HomeAssistant) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderPanel = (hass, panel) => html`
|
||||||
|
<a
|
||||||
|
aria-role="option"
|
||||||
|
href="${computeUrl(panel.url_path)}"
|
||||||
|
data-panel="${panel.url_path}"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<paper-icon-item>
|
||||||
|
<ha-icon slot="item-icon" .icon="${panel.icon}"></ha-icon>
|
||||||
|
<span class="item-text">
|
||||||
|
${hass.localize(`panel.${panel.title}`) || panel.title}
|
||||||
|
</span>
|
||||||
|
</paper-icon-item>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @appliesMixin LocalizeMixin
|
* @appliesMixin LocalizeMixin
|
||||||
*/
|
*/
|
||||||
class HaSidebar extends LitElement {
|
class HaSidebar extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
@property({ type: Boolean }) public alwaysExpand = false;
|
||||||
|
@property({ type: Boolean, reflect: true }) public expanded = false;
|
||||||
@property() public _defaultPage?: string =
|
@property() public _defaultPage?: string =
|
||||||
localStorage.defaultPage || DEFAULT_PANEL;
|
localStorage.defaultPage || DEFAULT_PANEL;
|
||||||
@property() private _externalConfig?: ExternalConfig;
|
@property() private _externalConfig?: ExternalConfig;
|
||||||
@ -82,20 +100,33 @@ class HaSidebar extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const panels = computePanels(hass);
|
||||||
|
const configPanelIdx = panels.findIndex(
|
||||||
|
(panel) => panel.component_name === "config"
|
||||||
|
);
|
||||||
|
const configPanel =
|
||||||
|
configPanelIdx === -1 ? undefined : panels.splice(configPanelIdx, 1)[0];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<app-toolbar>
|
${this.expanded
|
||||||
<div main-title>Home Assistant</div>
|
? html`
|
||||||
${hass.user
|
<app-toolbar>
|
||||||
? html`
|
<div main-title>Home Assistant</div>
|
||||||
<a href="/profile">
|
</app-toolbar>
|
||||||
<ha-user-badge .user=${hass.user}></ha-user-badge>
|
`
|
||||||
</a>
|
: html`
|
||||||
`
|
<div class="logo">
|
||||||
: ""}
|
<img
|
||||||
</app-toolbar>
|
id="logo"
|
||||||
|
src="/static/icons/favicon-192x192.png"
|
||||||
|
alt="Home Assistant logo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`}
|
||||||
|
|
||||||
<paper-listbox attr-for-selected="data-panel" .selected=${hass.panelUrl}>
|
<paper-listbox attr-for-selected="data-panel" .selected=${hass.panelUrl}>
|
||||||
<a
|
<a
|
||||||
|
aria-role="option"
|
||||||
href="${computeUrl(this._defaultPage)}"
|
href="${computeUrl(this._defaultPage)}"
|
||||||
data-panel=${this._defaultPage}
|
data-panel=${this._defaultPage}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@ -106,66 +137,19 @@ class HaSidebar extends LitElement {
|
|||||||
</paper-icon-item>
|
</paper-icon-item>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
${computePanels(hass).map(
|
${panels.map((panel) => renderPanel(hass, panel))}
|
||||||
(panel) => html`
|
|
||||||
<a
|
|
||||||
href="${computeUrl(panel.url_path)}"
|
|
||||||
data-panel="${panel.url_path}"
|
|
||||||
tabindex="-1"
|
|
||||||
>
|
|
||||||
<paper-icon-item>
|
|
||||||
<ha-icon slot="item-icon" .icon="${panel.icon}"></ha-icon>
|
|
||||||
<span class="item-text"
|
|
||||||
>${hass.localize(`panel.${panel.title}`) || panel.title}</span
|
|
||||||
>
|
|
||||||
</paper-icon-item>
|
|
||||||
</a>
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
${this._externalConfig && this._externalConfig.hasSettingsScreen
|
|
||||||
? html`
|
|
||||||
<a
|
|
||||||
aria-label="App Configuration"
|
|
||||||
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">
|
|
||||||
<ha-icon slot="item-icon" icon="hass:exit-to-app"></ha-icon>
|
|
||||||
<span class="item-text"
|
|
||||||
>${hass.localize("ui.sidebar.log_out")}</span
|
|
||||||
>
|
|
||||||
</paper-icon-item>
|
|
||||||
`
|
|
||||||
: html``}
|
|
||||||
</paper-listbox>
|
|
||||||
|
|
||||||
${hass.user && hass.user.is_admin
|
<div class="spacer" disabled></div>
|
||||||
? html`
|
|
||||||
<div>
|
|
||||||
<div class="divider"></div>
|
|
||||||
|
|
||||||
<div class="subheader">
|
${this.expanded && hass.user && hass.user.is_admin
|
||||||
|
? html`
|
||||||
|
<div class="divider" disabled></div>
|
||||||
|
|
||||||
|
<div class="subheader" disabled>
|
||||||
${hass.localize("ui.sidebar.developer_tools")}
|
${hass.localize("ui.sidebar.developer_tools")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dev-tools">
|
<div class="dev-tools" disabled>
|
||||||
<a href="/dev-service" tabindex="-1">
|
<a href="/dev-service" tabindex="-1">
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
icon="hass:remote"
|
icon="hass:remote"
|
||||||
@ -213,14 +197,76 @@ class HaSidebar extends LitElement {
|
|||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="divider" disabled></div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
${this._externalConfig && this._externalConfig.hasSettingsScreen
|
||||||
|
? html`
|
||||||
|
<a
|
||||||
|
aria-role="option"
|
||||||
|
aria-label="App Configuration"
|
||||||
|
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>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${configPanel ? renderPanel(hass, configPanel) : ""}
|
||||||
|
${hass.user
|
||||||
|
? html`
|
||||||
|
<a
|
||||||
|
href="/profile"
|
||||||
|
data-panel="panel"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-role="option"
|
||||||
|
aria-label=${hass.localize("panel.profile")}
|
||||||
|
>
|
||||||
|
<paper-icon-item class="profile">
|
||||||
|
<ha-user-badge
|
||||||
|
slot="item-icon"
|
||||||
|
.user=${hass.user}
|
||||||
|
></ha-user-badge>
|
||||||
|
|
||||||
|
<span class="item-text">
|
||||||
|
${hass.user.name}
|
||||||
|
</span>
|
||||||
|
</paper-icon-item>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<paper-icon-item
|
||||||
|
@click=${this._handleLogOut}
|
||||||
|
class="logout"
|
||||||
|
aria-role="option"
|
||||||
|
>
|
||||||
|
<ha-icon slot="item-icon" icon="hass:exit-to-app"></ha-icon>
|
||||||
|
<span class="item-text"
|
||||||
|
>${hass.localize("ui.sidebar.log_out")}</span
|
||||||
|
>
|
||||||
|
</paper-icon-item>
|
||||||
|
`}
|
||||||
|
</paper-listbox>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||||
if (changedProps.has("_externalConfig")) {
|
if (
|
||||||
|
changedProps.has("_externalConfig") ||
|
||||||
|
changedProps.has("expanded") ||
|
||||||
|
changedProps.has("alwaysExpand")
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!this.hass || !changedProps.has("hass")) {
|
if (!this.hass || !changedProps.has("hass")) {
|
||||||
@ -247,6 +293,26 @@ class HaSidebar extends LitElement {
|
|||||||
this._externalConfig = conf;
|
this._externalConfig = conf;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
this.shadowRoot!.querySelector("paper-listbox")!.addEventListener(
|
||||||
|
"mouseenter",
|
||||||
|
() => {
|
||||||
|
this.expanded = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.addEventListener("mouseleave", () => {
|
||||||
|
this._contract();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
if (changedProps.has("alwaysExpand")) {
|
||||||
|
this.expanded = this.alwaysExpand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _contract() {
|
||||||
|
this.expanded = this.alwaysExpand || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleLogOut() {
|
private _handleLogOut() {
|
||||||
@ -265,7 +331,7 @@ class HaSidebar extends LitElement {
|
|||||||
:host {
|
:host {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
overflow: auto;
|
overflow: hidden auto;
|
||||||
-ms-user-select: none;
|
-ms-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
@ -274,9 +340,27 @@ class HaSidebar extends LitElement {
|
|||||||
--sidebar-background-color,
|
--sidebar-background-color,
|
||||||
var(--primary-background-color)
|
var(--primary-background-color)
|
||||||
);
|
);
|
||||||
|
width: 64px;
|
||||||
|
transition: width 0.2s ease-in;
|
||||||
|
will-change: width;
|
||||||
|
contain: strict;
|
||||||
|
}
|
||||||
|
:host([expanded]) {
|
||||||
|
width: 256px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 65px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.logo img {
|
||||||
|
width: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
app-toolbar {
|
app-toolbar {
|
||||||
|
white-space: nowrap;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
@ -288,7 +372,11 @@ class HaSidebar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paper-listbox {
|
paper-listbox {
|
||||||
padding: 0;
|
padding: 4px 0;
|
||||||
|
height: calc(100% - 65px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
paper-listbox > a {
|
paper-listbox > a {
|
||||||
@ -299,10 +387,15 @@ class HaSidebar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
paper-icon-item {
|
paper-icon-item {
|
||||||
margin: 8px;
|
box-sizing: border-box;
|
||||||
padding-left: 9px;
|
margin: 4px 8px;
|
||||||
|
padding-left: 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
--paper-item-min-height: 40px;
|
--paper-item-min-height: 40px;
|
||||||
|
width: 48px;
|
||||||
|
}
|
||||||
|
:host([expanded]) paper-icon-item {
|
||||||
|
width: 240px;
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[slot="item-icon"] {
|
ha-icon[slot="item-icon"] {
|
||||||
@ -342,10 +435,29 @@ class HaSidebar extends LitElement {
|
|||||||
color: var(--sidebar-selected-text-color);
|
color: var(--sidebar-selected-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a .item-text {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
:host([expanded]) a .item-text {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
paper-icon-item.logout {
|
paper-icon-item.logout {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
paper-icon-item.profile {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
.profile .item-text {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.divider {
|
.divider {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
background-color: var(--divider-color);
|
background-color: var(--divider-color);
|
||||||
@ -357,6 +469,7 @@ class HaSidebar extends LitElement {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dev-tools {
|
.dev-tools {
|
||||||
@ -364,6 +477,8 @@ class HaSidebar extends LitElement {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
|
width: 256px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dev-tools a {
|
.dev-tools a {
|
||||||
|
@ -14,6 +14,7 @@ class HaStartVoiceButton extends EventsMixin(PolymerElement) {
|
|||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Start conversation"
|
||||||
icon="hass:microphone"
|
icon="hass:microphone"
|
||||||
hidden$="[[!canListen]]"
|
hidden$="[[!canListen]]"
|
||||||
on-click="handleListenClick"
|
on-click="handleListenClick"
|
||||||
|
@ -7,7 +7,6 @@ import {
|
|||||||
property,
|
property,
|
||||||
customElement,
|
customElement,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { classMap } from "lit-html/directives/class-map";
|
|
||||||
import { User } from "../../data/user";
|
import { User } from "../../data/user";
|
||||||
import { CurrentUser } from "../../types";
|
import { CurrentUser } from "../../types";
|
||||||
|
|
||||||
@ -33,24 +32,23 @@ class StateBadge extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
const user = this.user;
|
const user = this.user;
|
||||||
|
|
||||||
const initials = user ? computeInitials(user.name) : "?";
|
const initials = user ? computeInitials(user.name) : "?";
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
${initials}
|
||||||
class="${classMap({
|
|
||||||
"profile-badge": true,
|
|
||||||
long: initials.length > 2,
|
|
||||||
})}"
|
|
||||||
>
|
|
||||||
${initials}
|
|
||||||
</div>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps) {
|
||||||
|
super.updated(changedProps);
|
||||||
|
this.toggleAttribute(
|
||||||
|
"long",
|
||||||
|
(this.user ? computeInitials(this.user.name) : "?").length > 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
.profile-badge {
|
:host {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@ -63,7 +61,7 @@ class StateBadge extends LitElement {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-badge.long {
|
:host([long]) {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
74
src/data/collection.ts
Normal file
74
src/data/collection.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import {
|
||||||
|
Collection,
|
||||||
|
Connection,
|
||||||
|
getCollection,
|
||||||
|
UnsubscribeFunc,
|
||||||
|
} from "home-assistant-js-websocket";
|
||||||
|
import { Store } from "home-assistant-js-websocket/dist/store";
|
||||||
|
|
||||||
|
interface OptimisticCollection<T> extends Collection<T> {
|
||||||
|
save(data: T): Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an optimistic collection that includes a save function.
|
||||||
|
* When the collection is saved, the collection is optimistically updated.
|
||||||
|
* The update is reversed when the update failed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const getOptimisticCollection = <StateType>(
|
||||||
|
saveCollection: (conn: Connection, data: StateType) => Promise<unknown>,
|
||||||
|
conn: Connection,
|
||||||
|
key: string,
|
||||||
|
fetchCollection: (conn: Connection) => Promise<StateType>,
|
||||||
|
subscribeUpdates?: (
|
||||||
|
conn: Connection,
|
||||||
|
store: Store<StateType>
|
||||||
|
) => Promise<UnsubscribeFunc>
|
||||||
|
): OptimisticCollection<StateType> => {
|
||||||
|
const updateKey = `${key}-optimistic`;
|
||||||
|
|
||||||
|
const collection = getCollection<StateType>(
|
||||||
|
conn,
|
||||||
|
key,
|
||||||
|
fetchCollection,
|
||||||
|
async (_conn, store) => {
|
||||||
|
// Subscribe to original updates
|
||||||
|
const subUpResult = subscribeUpdates
|
||||||
|
? subscribeUpdates(conn, store)
|
||||||
|
: undefined;
|
||||||
|
// Store the store
|
||||||
|
conn[updateKey] = store;
|
||||||
|
|
||||||
|
// Unsub function to undo both
|
||||||
|
return () => {
|
||||||
|
if (subUpResult) {
|
||||||
|
subUpResult.then((unsub) => unsub());
|
||||||
|
}
|
||||||
|
conn[updateKey] = undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...collection,
|
||||||
|
async save(data: StateType) {
|
||||||
|
const store: Store<StateType> | undefined = conn[updateKey];
|
||||||
|
let current;
|
||||||
|
|
||||||
|
// Can be undefined if currently no subscribers
|
||||||
|
if (store) {
|
||||||
|
current = store.state;
|
||||||
|
store.setState(data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await saveCollection(conn, data);
|
||||||
|
} catch (err) {
|
||||||
|
if (store) {
|
||||||
|
store.setState(current as any, true);
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
@ -1,8 +1,14 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { Connection } from "home-assistant-js-websocket";
|
||||||
|
import { getOptimisticCollection } from "./collection";
|
||||||
|
|
||||||
|
export interface CoreFrontendUserData {
|
||||||
|
showAdvanced?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// tslint:disable-next-line
|
interface FrontendUserData {
|
||||||
interface FrontendUserData {}
|
core: CoreFrontendUserData;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ValidUserDataKey = keyof FrontendUserData;
|
export type ValidUserDataKey = keyof FrontendUserData;
|
||||||
@ -10,10 +16,10 @@ export type ValidUserDataKey = keyof FrontendUserData;
|
|||||||
export const fetchFrontendUserData = async <
|
export const fetchFrontendUserData = async <
|
||||||
UserDataKey extends ValidUserDataKey
|
UserDataKey extends ValidUserDataKey
|
||||||
>(
|
>(
|
||||||
hass: HomeAssistant,
|
conn: Connection,
|
||||||
key: UserDataKey
|
key: UserDataKey
|
||||||
): Promise<FrontendUserData[UserDataKey] | null> => {
|
): Promise<FrontendUserData[UserDataKey] | null> => {
|
||||||
const result = await hass.callWS<{
|
const result = await conn.sendMessagePromise<{
|
||||||
value: FrontendUserData[UserDataKey] | null;
|
value: FrontendUserData[UserDataKey] | null;
|
||||||
}>({
|
}>({
|
||||||
type: "frontend/get_user_data",
|
type: "frontend/get_user_data",
|
||||||
@ -25,12 +31,31 @@ export const fetchFrontendUserData = async <
|
|||||||
export const saveFrontendUserData = async <
|
export const saveFrontendUserData = async <
|
||||||
UserDataKey extends ValidUserDataKey
|
UserDataKey extends ValidUserDataKey
|
||||||
>(
|
>(
|
||||||
hass: HomeAssistant,
|
conn: Connection,
|
||||||
key: UserDataKey,
|
key: UserDataKey,
|
||||||
value: FrontendUserData[UserDataKey]
|
value: FrontendUserData[UserDataKey]
|
||||||
): Promise<void> =>
|
): Promise<void> =>
|
||||||
hass.callWS<void>({
|
conn.sendMessagePromise<void>({
|
||||||
type: "frontend/set_user_data",
|
type: "frontend/set_user_data",
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getOptimisticFrontendUserDataCollection = <
|
||||||
|
UserDataKey extends ValidUserDataKey
|
||||||
|
>(
|
||||||
|
conn: Connection,
|
||||||
|
userDataKey: UserDataKey
|
||||||
|
) =>
|
||||||
|
getOptimisticCollection(
|
||||||
|
(_conn, data) =>
|
||||||
|
saveFrontendUserData(
|
||||||
|
conn,
|
||||||
|
userDataKey,
|
||||||
|
// @ts-ignore
|
||||||
|
data
|
||||||
|
),
|
||||||
|
conn,
|
||||||
|
`_frontendUserData-${userDataKey}`,
|
||||||
|
() => fetchFrontendUserData(conn, userDataKey)
|
||||||
|
);
|
||||||
|
@ -12,12 +12,12 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||||
fetchFrontendUserData(hass, "language");
|
fetchFrontendUserData(hass.connection, "language");
|
||||||
|
|
||||||
export const saveTranslationPreferences = (
|
export const saveTranslationPreferences = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
data: FrontendTranslationData
|
data: FrontendTranslationData
|
||||||
) => saveFrontendUserData(hass, "language", data);
|
) => saveFrontendUserData(hass.connection, "language", data);
|
||||||
|
|
||||||
export const getHassTranslations = async (
|
export const getHassTranslations = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -18,6 +18,8 @@ import "./partial-panel-resolver";
|
|||||||
import { HomeAssistant, Route } from "../types";
|
import { HomeAssistant, Route } from "../types";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
|
// tslint:disable-next-line: no-duplicate-imports
|
||||||
|
import { AppDrawerLayoutElement } from "@polymer/app-layout/app-drawer-layout/app-drawer-layout";
|
||||||
|
|
||||||
const NON_SWIPABLE_PANELS = ["kiosk", "map"];
|
const NON_SWIPABLE_PANELS = ["kiosk", "map"];
|
||||||
|
|
||||||
@ -29,9 +31,9 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HomeAssistantMain extends LitElement {
|
class HomeAssistantMain extends LitElement {
|
||||||
@property() public hass?: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public route?: Route;
|
@property() public route?: Route;
|
||||||
@property() private _narrow?: boolean;
|
@property({ type: Boolean }) private narrow?: boolean;
|
||||||
|
|
||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
const hass = this.hass;
|
const hass = this.hass;
|
||||||
@ -40,7 +42,8 @@ class HomeAssistantMain extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableSwipe = NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1;
|
const disableSwipe =
|
||||||
|
!this.narrow || NON_SWIPABLE_PANELS.indexOf(hass.panelUrl) !== -1;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<iron-media-query
|
<iron-media-query
|
||||||
@ -50,7 +53,7 @@ class HomeAssistantMain extends LitElement {
|
|||||||
|
|
||||||
<app-drawer-layout
|
<app-drawer-layout
|
||||||
fullbleed
|
fullbleed
|
||||||
.forceNarrow=${this._narrow || !hass.dockedSidebar}
|
.forceNarrow=${this.narrow}
|
||||||
responsive-width="0"
|
responsive-width="0"
|
||||||
>
|
>
|
||||||
<app-drawer
|
<app-drawer
|
||||||
@ -59,13 +62,16 @@ class HomeAssistantMain extends LitElement {
|
|||||||
slot="drawer"
|
slot="drawer"
|
||||||
.disableSwipe=${disableSwipe}
|
.disableSwipe=${disableSwipe}
|
||||||
.swipeOpen=${!disableSwipe}
|
.swipeOpen=${!disableSwipe}
|
||||||
.persistent=${hass.dockedSidebar}
|
.persistent=${!this.narrow}
|
||||||
>
|
>
|
||||||
<ha-sidebar .hass=${hass}></ha-sidebar>
|
<ha-sidebar
|
||||||
|
.hass=${hass}
|
||||||
|
.alwaysExpand=${this.narrow || hass.dockedSidebar}
|
||||||
|
></ha-sidebar>
|
||||||
</app-drawer>
|
</app-drawer>
|
||||||
|
|
||||||
<partial-panel-resolver
|
<partial-panel-resolver
|
||||||
.narrow=${this._narrow}
|
.narrow=${this.narrow}
|
||||||
.hass=${hass}
|
.hass=${hass}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
></partial-panel-resolver>
|
></partial-panel-resolver>
|
||||||
@ -77,19 +83,17 @@ class HomeAssistantMain extends LitElement {
|
|||||||
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
|
import(/* webpackChunkName: "ha-sidebar" */ "../components/ha-sidebar");
|
||||||
|
|
||||||
this.addEventListener("hass-toggle-menu", () => {
|
this.addEventListener("hass-toggle-menu", () => {
|
||||||
const shouldOpen = !this.drawer.opened;
|
if (this.narrow) {
|
||||||
|
if (this.drawer.opened) {
|
||||||
if (shouldOpen) {
|
this.drawer.close();
|
||||||
if (this._narrow) {
|
|
||||||
this.drawer.open();
|
|
||||||
} else {
|
} else {
|
||||||
fireEvent(this, "hass-dock-sidebar", { dock: true });
|
this.drawer.open();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.drawer.close();
|
fireEvent(this, "hass-dock-sidebar", {
|
||||||
if (this.hass!.dockedSidebar) {
|
dock: !this.hass.dockedSidebar,
|
||||||
fireEvent(this, "hass-dock-sidebar", { dock: false });
|
});
|
||||||
}
|
setTimeout(() => this.appLayout.resetLayout());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -97,7 +101,9 @@ class HomeAssistantMain extends LitElement {
|
|||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
|
|
||||||
if (changedProps.has("route") && this._narrow) {
|
this.toggleAttribute("expanded", this.narrow || this.hass.dockedSidebar);
|
||||||
|
|
||||||
|
if (changedProps.has("route") && this.narrow) {
|
||||||
this.drawer.close();
|
this.drawer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,19 +116,27 @@ class HomeAssistantMain extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _narrowChanged(ev: PolymerChangedEvent<boolean>) {
|
private _narrowChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
this._narrow = ev.detail.value;
|
this.narrow = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get drawer(): AppDrawerElement {
|
private get drawer(): AppDrawerElement {
|
||||||
return this.shadowRoot!.querySelector("app-drawer")!;
|
return this.shadowRoot!.querySelector("app-drawer")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private get appLayout(): AppDrawerLayoutElement {
|
||||||
|
return this.shadowRoot!.querySelector("app-drawer-layout")!;
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
--app-drawer-width: 64px;
|
||||||
|
}
|
||||||
|
:host([expanded]) {
|
||||||
|
--app-drawer-width: 256px;
|
||||||
}
|
}
|
||||||
partial-panel-resolver,
|
partial-panel-resolver,
|
||||||
ha-sidebar {
|
ha-sidebar {
|
||||||
|
@ -37,6 +37,7 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
|
|||||||
<div class$="[[computeClasses(isWide)]]">
|
<div class$="[[computeClasses(isWide)]]">
|
||||||
<ha-config-section-core
|
<ha-config-section-core
|
||||||
is-wide="[[isWide]]"
|
is-wide="[[isWide]]"
|
||||||
|
show-advanced="[[showAdvanced]]"
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
></ha-config-section-core>
|
></ha-config-section-core>
|
||||||
</div>
|
</div>
|
||||||
@ -48,6 +49,7 @@ class HaConfigCore extends LocalizeMixin(PolymerElement) {
|
|||||||
return {
|
return {
|
||||||
hass: Object,
|
hass: Object,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
|
showAdvanced: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,79 +63,80 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
|||||||
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
|
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
|
||||||
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
|
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
|
||||||
|
|
||||||
<ha-card
|
<template is="dom-if" if="[[showAdvanced]]">
|
||||||
header="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
|
<ha-card
|
||||||
>
|
header="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
|
||||||
<div class="card-content">
|
>
|
||||||
[[localize('ui.panel.config.core.section.core.validation.introduction')]]
|
<div class="card-content">
|
||||||
<template is="dom-if" if="[[!validateLog]]">
|
[[localize('ui.panel.config.core.section.core.validation.introduction')]]
|
||||||
<div class="validate-container">
|
<template is="dom-if" if="[[!validateLog]]">
|
||||||
<template is="dom-if" if="[[!validating]]">
|
<div class="validate-container">
|
||||||
<template is="dom-if" if="[[isValid]]">
|
<template is="dom-if" if="[[!validating]]">
|
||||||
<div class="validate-result" id="result">
|
<template is="dom-if" if="[[isValid]]">
|
||||||
[[localize('ui.panel.config.core.section.core.validation.valid')]]
|
<div class="validate-result" id="result">
|
||||||
</div>
|
[[localize('ui.panel.config.core.section.core.validation.valid')]]
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<mwc-button raised="" on-click="validateConfig">
|
||||||
|
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
||||||
|
</mwc-button>
|
||||||
</template>
|
</template>
|
||||||
|
<template is="dom-if" if="[[validating]]">
|
||||||
|
<paper-spinner active=""></paper-spinner>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template is="dom-if" if="[[validateLog]]">
|
||||||
|
<div class="config-invalid">
|
||||||
|
<span class="text">
|
||||||
|
[[localize('ui.panel.config.core.section.core.validation.invalid')]]
|
||||||
|
</span>
|
||||||
<mwc-button raised="" on-click="validateConfig">
|
<mwc-button raised="" on-click="validateConfig">
|
||||||
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</template>
|
</div>
|
||||||
<template is="dom-if" if="[[validating]]">
|
<div id="configLog" class="validate-log">[[validateLog]]</div>
|
||||||
<paper-spinner active=""></paper-spinner>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</ha-card>
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[validateLog]]">
|
|
||||||
<div class="config-invalid">
|
|
||||||
<span class="text">
|
|
||||||
[[localize('ui.panel.config.core.section.core.validation.invalid')]]
|
|
||||||
</span>
|
|
||||||
<mwc-button raised="" on-click="validateConfig">
|
|
||||||
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
<div id="configLog" class="validate-log">[[validateLog]]</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
|
|
||||||
<ha-card
|
|
||||||
header="[[localize('ui.panel.config.core.section.core.reloading.heading')]]"
|
|
||||||
>
|
|
||||||
<div class="card-content">
|
|
||||||
[[localize('ui.panel.config.core.section.core.reloading.introduction')]]
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="homeassistant"
|
|
||||||
service="reload_core_config"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.core')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="group"
|
|
||||||
service="reload"
|
|
||||||
hidden$="[[!groupLoaded(hass)]]"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.group')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="automation"
|
|
||||||
service="reload"
|
|
||||||
hidden$="[[!automationLoaded(hass)]]"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.automation')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
<ha-call-service-button
|
|
||||||
hass="[[hass]]"
|
|
||||||
domain="script"
|
|
||||||
service="reload"
|
|
||||||
hidden$="[[!scriptLoaded(hass)]]"
|
|
||||||
>[[localize('ui.panel.config.core.section.core.reloading.script')]]
|
|
||||||
</ha-call-service-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
|
|
||||||
|
<ha-card
|
||||||
|
header="[[localize('ui.panel.config.core.section.core.reloading.heading')]]"
|
||||||
|
>
|
||||||
|
<div class="card-content">
|
||||||
|
[[localize('ui.panel.config.core.section.core.reloading.introduction')]]
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="homeassistant"
|
||||||
|
service="reload_core_config"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.core')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="group"
|
||||||
|
service="reload"
|
||||||
|
hidden$="[[!groupLoaded(hass)]]"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.group')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="automation"
|
||||||
|
service="reload"
|
||||||
|
hidden$="[[!automationLoaded(hass)]]"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.automation')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
<ha-call-service-button
|
||||||
|
hass="[[hass]]"
|
||||||
|
domain="script"
|
||||||
|
service="reload"
|
||||||
|
hidden$="[[!scriptLoaded(hass)]]"
|
||||||
|
>[[localize('ui.panel.config.core.section.core.reloading.script')]]
|
||||||
|
</ha-call-service-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</template>
|
||||||
<ha-card
|
<ha-card
|
||||||
header="[[localize('ui.panel.config.core.section.core.server_management.heading')]]"
|
header="[[localize('ui.panel.config.core.section.core.server_management.heading')]]"
|
||||||
>
|
>
|
||||||
@ -188,6 +189,8 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
|||||||
type: String,
|
type: String,
|
||||||
value: "",
|
value: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showAdvanced: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,17 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
.content {
|
.content {
|
||||||
padding-bottom: 32px;
|
padding-bottom: 32px;
|
||||||
}
|
}
|
||||||
a {
|
ha-card a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
.promo-advanced {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
.promo-advanced a {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<app-header-layout has-scrolling-region="">
|
<app-header-layout has-scrolling-region="">
|
||||||
@ -99,7 +106,16 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
</a>
|
</a>
|
||||||
</ha-card>
|
</ha-card>
|
||||||
|
|
||||||
<ha-config-navigation hass="[[hass]]"></ha-config-navigation>
|
<ha-config-navigation
|
||||||
|
hass="[[hass]]"
|
||||||
|
show-advanced="[[showAdvanced]]"
|
||||||
|
></ha-config-navigation>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[!showAdvanced]]'>
|
||||||
|
<div class='promo-advanced'>
|
||||||
|
Missing config options? Enable advanced mode on <a href="/profile">your profile page.</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ha-config-section>
|
</ha-config-section>
|
||||||
</div>
|
</div>
|
||||||
</app-header-layout>
|
</app-header-layout>
|
||||||
@ -111,6 +127,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
hass: Object,
|
hass: Object,
|
||||||
isWide: Boolean,
|
isWide: Boolean,
|
||||||
cloudStatus: Object,
|
cloudStatus: Object,
|
||||||
|
showAdvanced: Boolean,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
import "@polymer/iron-icon/iron-icon";
|
|
||||||
import "@polymer/paper-item/paper-item-body";
|
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
|
|
||||||
import NavigateMixin from "../../../mixins/navigate-mixin";
|
|
||||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
|
||||||
|
|
||||||
import isComponentLoaded from "../../../common/config/is_component_loaded";
|
|
||||||
|
|
||||||
import "../../../components/ha-card";
|
|
||||||
import "../../../components/ha-icon-next";
|
|
||||||
|
|
||||||
const CORE_PAGES = ["core", "customize", "entity_registry", "area_registry"];
|
|
||||||
/*
|
|
||||||
* @appliesMixin LocalizeMixin
|
|
||||||
* @appliesMixin NavigateMixin
|
|
||||||
*/
|
|
||||||
class HaConfigNavigation extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex">
|
|
||||||
ha-card {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
paper-item {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-card>
|
|
||||||
<template is="dom-repeat" items="[[pages]]">
|
|
||||||
<template is="dom-if" if="[[_computeLoaded(hass, item)]]">
|
|
||||||
<paper-item on-click="_navigate">
|
|
||||||
<paper-item-body two-line="">
|
|
||||||
[[_computeCaption(item, localize)]]
|
|
||||||
<div secondary="">[[_computeDescription(item, localize)]]</div>
|
|
||||||
</paper-item-body>
|
|
||||||
<ha-icon-next></ha-icon-next>
|
|
||||||
</paper-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</ha-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
|
|
||||||
pages: {
|
|
||||||
type: Array,
|
|
||||||
value: [
|
|
||||||
"core",
|
|
||||||
"person",
|
|
||||||
"entity_registry",
|
|
||||||
"area_registry",
|
|
||||||
"automation",
|
|
||||||
"script",
|
|
||||||
"zha",
|
|
||||||
"zwave",
|
|
||||||
"customize",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeLoaded(hass, page) {
|
|
||||||
return CORE_PAGES.includes(page) || isComponentLoaded(hass, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeCaption(page, localize) {
|
|
||||||
return localize(`ui.panel.config.${page}.caption`);
|
|
||||||
}
|
|
||||||
|
|
||||||
_computeDescription(page, localize) {
|
|
||||||
return localize(`ui.panel.config.${page}.description`);
|
|
||||||
}
|
|
||||||
|
|
||||||
_navigate(ev) {
|
|
||||||
this.navigate("/config/" + ev.model.item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-config-navigation", HaConfigNavigation);
|
|
82
src/panels/config/dashboard/ha-config-navigation.ts
Normal file
82
src/panels/config/dashboard/ha-config-navigation.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import "@polymer/iron-icon/iron-icon";
|
||||||
|
import "@polymer/paper-item/paper-item-body";
|
||||||
|
import "@polymer/paper-item/paper-item";
|
||||||
|
|
||||||
|
import isComponentLoaded from "../../../common/config/is_component_loaded";
|
||||||
|
|
||||||
|
import "../../../components/ha-card";
|
||||||
|
import "../../../components/ha-icon-next";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
html,
|
||||||
|
TemplateResult,
|
||||||
|
property,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
|
const PAGES: Array<{
|
||||||
|
page: string;
|
||||||
|
core?: boolean;
|
||||||
|
advanced?: boolean;
|
||||||
|
}> = [
|
||||||
|
{ page: "core", core: true },
|
||||||
|
{ page: "person" },
|
||||||
|
{ page: "entity_registry", core: true },
|
||||||
|
{ page: "area_registry", core: true },
|
||||||
|
{ page: "automation" },
|
||||||
|
{ page: "script" },
|
||||||
|
{ page: "zha" },
|
||||||
|
{ page: "zwave" },
|
||||||
|
{ page: "customize", core: true, advanced: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("ha-config-navigation")
|
||||||
|
class HaConfigNavigation extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public showAdvanced!: boolean;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
${PAGES.map(({ page, core, advanced }) =>
|
||||||
|
(core || isComponentLoaded(this.hass, page)) &&
|
||||||
|
(!advanced || this.showAdvanced)
|
||||||
|
? html`
|
||||||
|
<a href=${`/config/${page}`}>
|
||||||
|
<paper-item>
|
||||||
|
<paper-item-body two-line="">
|
||||||
|
${this.hass.localize(`ui.panel.config.${page}.caption`)}
|
||||||
|
<div secondary>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.${page}.description`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</paper-item-body>
|
||||||
|
<ha-icon-next></ha-icon-next>
|
||||||
|
</paper-item>
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-config-navigation": HaConfigNavigation;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,10 @@ import { HomeAssistant } from "../../types";
|
|||||||
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
||||||
import { listenMediaQuery } from "../../common/dom/media_query";
|
import { listenMediaQuery } from "../../common/dom/media_query";
|
||||||
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
||||||
|
import {
|
||||||
|
CoreFrontendUserData,
|
||||||
|
getOptimisticFrontendUserDataCollection,
|
||||||
|
} from "../../data/frontend";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -17,8 +21,6 @@ declare global {
|
|||||||
class HaPanelConfig extends HassRouterPage {
|
class HaPanelConfig extends HassRouterPage {
|
||||||
@property() public hass!: HomeAssistant;
|
@property() public hass!: HomeAssistant;
|
||||||
@property() public narrow!: boolean;
|
@property() public narrow!: boolean;
|
||||||
@property() public _wideSidebar: boolean = false;
|
|
||||||
@property() public _wide: boolean = false;
|
|
||||||
|
|
||||||
protected routerOptions: RouterOptions = {
|
protected routerOptions: RouterOptions = {
|
||||||
defaultPage: "dashboard",
|
defaultPage: "dashboard",
|
||||||
@ -93,6 +95,9 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@property() private _wideSidebar: boolean = false;
|
||||||
|
@property() private _wide: boolean = false;
|
||||||
|
@property() private _coreUserData?: CoreFrontendUserData;
|
||||||
@property() private _cloudStatus?: CloudStatus;
|
@property() private _cloudStatus?: CloudStatus;
|
||||||
|
|
||||||
private _listeners: Array<() => void> = [];
|
private _listeners: Array<() => void> = [];
|
||||||
@ -109,6 +114,14 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
this._wideSidebar = matches;
|
this._wideSidebar = matches;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
this._listeners.push(
|
||||||
|
getOptimisticFrontendUserDataCollection(
|
||||||
|
this.hass.connection,
|
||||||
|
"core"
|
||||||
|
).subscribe((coreUserData) => {
|
||||||
|
this._coreUserData = coreUserData || {};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback() {
|
public disconnectedCallback() {
|
||||||
@ -131,6 +144,7 @@ class HaPanelConfig extends HassRouterPage {
|
|||||||
protected updatePageEl(el) {
|
protected updatePageEl(el) {
|
||||||
el.route = this.routeTail;
|
el.route = this.routeTail;
|
||||||
el.hass = this.hass;
|
el.hass = this.hass;
|
||||||
|
el.showAdvanced = !!(this._coreUserData && this._coreUserData.showAdvanced);
|
||||||
el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide;
|
el.isWide = this.hass.dockedSidebar ? this._wideSidebar : this._wide;
|
||||||
el.narrow = this.narrow;
|
el.narrow = this.narrow;
|
||||||
el.cloudStatus = this._cloudStatus;
|
el.cloudStatus = this._cloudStatus;
|
||||||
|
@ -64,9 +64,14 @@ class HaPanelDevInfo extends LitElement {
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="about">
|
<div class="about">
|
||||||
<p class="version">
|
<p class="version">
|
||||||
<a href="https://www.home-assistant.io" target="_blank"
|
<a href="https://www.home-assistant.io" target="_blank">
|
||||||
><img src="/static/icons/favicon-192x192.png" height="192"/></a
|
<img
|
||||||
><br />
|
src="/static/icons/favicon-192x192.png"
|
||||||
|
height="192"
|
||||||
|
alt="Home Assistant logo"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
Home Assistant<br />
|
Home Assistant<br />
|
||||||
${hass.config.version}
|
${hass.config.version}
|
||||||
</p>
|
</p>
|
||||||
|
@ -41,6 +41,7 @@ class HuiEntitiesToggle extends LitElement {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<paper-toggle-button
|
<paper-toggle-button
|
||||||
|
aria-label="Toggle entities."
|
||||||
?checked="${this._toggleEntities!.some((entityId) => {
|
?checked="${this._toggleEntities!.some((entityId) => {
|
||||||
const stateObj = this.hass!.states[entityId];
|
const stateObj = this.hass!.states[entityId];
|
||||||
return stateObj && stateObj.state === "on";
|
return stateObj && stateObj.state === "on";
|
||||||
|
@ -23,6 +23,7 @@ class HuiNotificationsButton extends LitElement {
|
|||||||
protected render(): TemplateResult | void {
|
protected render(): TemplateResult | void {
|
||||||
return html`
|
return html`
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Show Notifications"
|
||||||
icon="hass:bell"
|
icon="hass:bell"
|
||||||
@click="${this._clicked}"
|
@click="${this._clicked}"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
|
@ -144,6 +144,7 @@ class HUIRoot extends LitElement {
|
|||||||
horizontal-offset="-5"
|
horizontal-offset="-5"
|
||||||
>
|
>
|
||||||
<paper-icon-button
|
<paper-icon-button
|
||||||
|
aria-label="Open Lovelace menu"
|
||||||
icon="hass:dots-vertical"
|
icon="hass:dots-vertical"
|
||||||
slot="dropdown-trigger"
|
slot="dropdown-trigger"
|
||||||
></paper-icon-button>
|
></paper-icon-button>
|
||||||
|
65
src/panels/profile/ha-advanced-mode-card.ts
Normal file
65
src/panels/profile/ha-advanced-mode-card.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
customElement,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
} from "lit-element";
|
||||||
|
import "../../components/ha-card";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import {
|
||||||
|
CoreFrontendUserData,
|
||||||
|
getOptimisticFrontendUserDataCollection,
|
||||||
|
} from "../../data/frontend";
|
||||||
|
|
||||||
|
@customElement("ha-advanced-mode-card")
|
||||||
|
class AdvancedModeCard extends LitElement {
|
||||||
|
@property() public hass!: HomeAssistant;
|
||||||
|
@property() public coreUserData?: CoreFrontendUserData;
|
||||||
|
|
||||||
|
protected render(): TemplateResult | void {
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="title">Advanced mode</div>
|
||||||
|
<paper-toggle-button
|
||||||
|
.checked=${this.coreUserData && this.coreUserData.showAdvanced}
|
||||||
|
.disabled=${this.coreUserData === undefined}
|
||||||
|
@change=${this._advancedToggled}
|
||||||
|
></paper-toggle-button>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
Home Assistant hides advanced features and options by default. You can
|
||||||
|
make these features accessible by checking this toggle. This is a
|
||||||
|
user-specific setting and does not impact other users using Home
|
||||||
|
Assistant.
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _advancedToggled(ev) {
|
||||||
|
getOptimisticFrontendUserDataCollection(this.hass.connection, "core").save({
|
||||||
|
showAdvanced: ev.currentTarget.checked,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-advanced-mode-card": AdvancedModeCard;
|
||||||
|
}
|
||||||
|
}
|
@ -11,11 +11,14 @@ import "../../components/ha-card";
|
|||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../resources/ha-style";
|
import "../../resources/ha-style";
|
||||||
|
|
||||||
|
import { getOptimisticFrontendUserDataCollection } from "../../data/frontend";
|
||||||
|
|
||||||
import { EventsMixin } from "../../mixins/events-mixin";
|
import { EventsMixin } from "../../mixins/events-mixin";
|
||||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||||
|
|
||||||
import "./ha-change-password-card";
|
import "./ha-change-password-card";
|
||||||
import "./ha-mfa-modules-card";
|
import "./ha-mfa-modules-card";
|
||||||
|
import "./ha-advanced-mode-card";
|
||||||
import "./ha-refresh-tokens-card";
|
import "./ha-refresh-tokens-card";
|
||||||
import "./ha-long-lived-access-tokens-card";
|
import "./ha-long-lived-access-tokens-card";
|
||||||
|
|
||||||
@ -98,6 +101,11 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
mfa-modules="[[hass.user.mfa_modules]]"
|
mfa-modules="[[hass.user.mfa_modules]]"
|
||||||
></ha-mfa-modules-card>
|
></ha-mfa-modules-card>
|
||||||
|
|
||||||
|
<ha-advanced-mode-card
|
||||||
|
hass="[[hass]]"
|
||||||
|
core-user-data="[[_coreUserData]]"
|
||||||
|
></ha-mfa-modules-card>
|
||||||
|
|
||||||
<ha-refresh-tokens-card
|
<ha-refresh-tokens-card
|
||||||
hass="[[hass]]"
|
hass="[[hass]]"
|
||||||
refresh-tokens="[[_refreshTokens]]"
|
refresh-tokens="[[_refreshTokens]]"
|
||||||
@ -119,12 +127,27 @@ class HaPanelProfile extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
hass: Object,
|
hass: Object,
|
||||||
narrow: Boolean,
|
narrow: Boolean,
|
||||||
_refreshTokens: Array,
|
_refreshTokens: Array,
|
||||||
|
_coreUserData: Object,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
this._refreshRefreshTokens();
|
this._refreshRefreshTokens();
|
||||||
|
this._unsubCoreData = getOptimisticFrontendUserDataCollection(
|
||||||
|
this.hass.connection,
|
||||||
|
"core"
|
||||||
|
).subscribe((coreUserData) => {
|
||||||
|
this._coreUserData = coreUserData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
if (this._unsubCoreData) {
|
||||||
|
this._unsubCoreData();
|
||||||
|
this._unsubCoreData = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _refreshRefreshTokens() {
|
async _refreshRefreshTokens() {
|
||||||
|
@ -44,7 +44,7 @@ export const connectionMixin = (
|
|||||||
localize: () => "",
|
localize: () => "",
|
||||||
|
|
||||||
translationMetadata,
|
translationMetadata,
|
||||||
dockedSidebar: false,
|
dockedSidebar: true,
|
||||||
moreInfoEntityId: null,
|
moreInfoEntityId: null,
|
||||||
callService: async (domain, service, serviceData = {}) => {
|
callService: async (domain, service, serviceData = {}) => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
@ -9,9 +9,11 @@ import { HassBaseEl } from "./hass-base-mixin";
|
|||||||
import { computeLocalize } from "../common/translations/localize";
|
import { computeLocalize } from "../common/translations/localize";
|
||||||
import { computeRTL } from "../common/util/compute_rtl";
|
import { computeRTL } from "../common/util/compute_rtl";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { saveFrontendUserData } from "../data/frontend";
|
|
||||||
import { storeState } from "../util/ha-pref-storage";
|
import { storeState } from "../util/ha-pref-storage";
|
||||||
import { getHassTranslations } from "../data/translation";
|
import {
|
||||||
|
getHassTranslations,
|
||||||
|
saveTranslationPreferences,
|
||||||
|
} from "../data/translation";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* superClass needs to contain `this.hass` and `this._updateHass`.
|
* superClass needs to contain `this.hass` and `this._updateHass`.
|
||||||
@ -65,7 +67,7 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
|
|||||||
this._updateHass({ language, selectedLanguage: language });
|
this._updateHass({ language, selectedLanguage: language });
|
||||||
storeState(this.hass);
|
storeState(this.hass);
|
||||||
if (saveToBackend) {
|
if (saveToBackend) {
|
||||||
saveFrontendUserData(this.hass, "language", { language });
|
saveTranslationPreferences(this.hass, { language });
|
||||||
}
|
}
|
||||||
|
|
||||||
this._applyTranslations(this.hass);
|
this._applyTranslations(this.hass);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { translationMetadata } from "../resources/translations-metadata";
|
import { translationMetadata } from "../resources/translations-metadata";
|
||||||
import { fetchFrontendUserData } from "../data/frontend";
|
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { fetchTranslationPreferences } from "../data/translation";
|
||||||
|
|
||||||
const STORAGE = window.localStorage || {};
|
const STORAGE = window.localStorage || {};
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ function findAvailableLanguage(language: string) {
|
|||||||
* Get user selected language from backend
|
* Get user selected language from backend
|
||||||
*/
|
*/
|
||||||
export async function getUserLanguage(hass: HomeAssistant) {
|
export async function getUserLanguage(hass: HomeAssistant) {
|
||||||
const result = await fetchFrontendUserData(hass, "language");
|
const result = await fetchTranslationPreferences(hass);
|
||||||
const language = result ? result.language : null;
|
const language = result ? result.language : null;
|
||||||
if (language) {
|
if (language) {
|
||||||
const availableLanguage = findAvailableLanguage(language);
|
const availableLanguage = findAvailableLanguage(language);
|
||||||
|
@ -338,7 +338,7 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"heading": "Validació de la configuració",
|
"heading": "Validació de la configuració",
|
||||||
"introduction": "Valida la configuració si recentment has fet algun canvi a la configuració i vols assegurar-te de que no té problemes.",
|
"introduction": "Valida la configuració si recentment has fet algun canvi a la configuració i vols assegurar-te de que no té problemes.",
|
||||||
"check_config": "Comprovar la configuració",
|
"check_config": "Comprova la configuració",
|
||||||
"valid": "La configuració és vàlida!",
|
"valid": "La configuració és vàlida!",
|
||||||
"invalid": "La configuració és invàlida"
|
"invalid": "La configuració és invàlida"
|
||||||
},
|
},
|
||||||
@ -353,8 +353,8 @@
|
|||||||
"server_management": {
|
"server_management": {
|
||||||
"heading": "Gestió del servidor",
|
"heading": "Gestió del servidor",
|
||||||
"introduction": "Controla el servidor de Home Assistant... des de Home Assistant.",
|
"introduction": "Controla el servidor de Home Assistant... des de Home Assistant.",
|
||||||
"restart": "Reiniciar",
|
"restart": "Reinicia",
|
||||||
"stop": "Aturar"
|
"stop": "Atura"
|
||||||
},
|
},
|
||||||
"core_config": {
|
"core_config": {
|
||||||
"edit_requires_storage": "L'editor està desactivat ja que la configuració es troba a configuration.yaml.",
|
"edit_requires_storage": "L'editor està desactivat ja que la configuració es troba a configuration.yaml.",
|
||||||
|
@ -3,13 +3,14 @@
|
|||||||
"config": "Stillingar",
|
"config": "Stillingar",
|
||||||
"states": "Yfirlit",
|
"states": "Yfirlit",
|
||||||
"map": "Kort",
|
"map": "Kort",
|
||||||
"logbook": "Logbók",
|
"logbook": "Breytingarsaga",
|
||||||
"history": "Saga",
|
"history": "Saga",
|
||||||
"mailbox": "Pósthólf",
|
"mailbox": "Pósthólf",
|
||||||
"shopping_list": "Innkaupalisti",
|
"shopping_list": "Innkaupalisti",
|
||||||
"dev-services": "Þjónustur",
|
"dev-services": "Þjónustur",
|
||||||
"dev-states": "Stöður",
|
"dev-states": "Stöður",
|
||||||
"dev-events": "Viðburðir",
|
"dev-events": "Viðburðir",
|
||||||
|
"dev-templates": "Skapalón",
|
||||||
"dev-mqtt": "MQTT",
|
"dev-mqtt": "MQTT",
|
||||||
"dev-info": "Upplýsingar",
|
"dev-info": "Upplýsingar",
|
||||||
"calendar": "Dagatal",
|
"calendar": "Dagatal",
|
||||||
@ -75,6 +76,10 @@
|
|||||||
"off": "Aftengdur",
|
"off": "Aftengdur",
|
||||||
"on": "Tengdur"
|
"on": "Tengdur"
|
||||||
},
|
},
|
||||||
|
"cold": {
|
||||||
|
"off": "Venjulegt",
|
||||||
|
"on": "Kalt"
|
||||||
|
},
|
||||||
"door": {
|
"door": {
|
||||||
"off": "Lokuð",
|
"off": "Lokuð",
|
||||||
"on": "Opin"
|
"on": "Opin"
|
||||||
@ -83,6 +88,10 @@
|
|||||||
"off": "Lokuð",
|
"off": "Lokuð",
|
||||||
"on": "Opin"
|
"on": "Opin"
|
||||||
},
|
},
|
||||||
|
"heat": {
|
||||||
|
"off": "Venjulegt",
|
||||||
|
"on": "Heitt"
|
||||||
|
},
|
||||||
"window": {
|
"window": {
|
||||||
"off": "Loka",
|
"off": "Loka",
|
||||||
"on": "Opna"
|
"on": "Opna"
|
||||||
@ -301,6 +310,7 @@
|
|||||||
"stop": "Stöðva"
|
"stop": "Stöðva"
|
||||||
},
|
},
|
||||||
"core_config": {
|
"core_config": {
|
||||||
|
"edit_requires_storage": "Ritill er óvirkur af því að stillingar eru vistaðar í configuration.yaml.",
|
||||||
"location_name": "Nafnið á Home Assistant uppsetningunni",
|
"location_name": "Nafnið á Home Assistant uppsetningunni",
|
||||||
"latitude": "Breiddargráða",
|
"latitude": "Breiddargráða",
|
||||||
"longitude": "Lengdargráða",
|
"longitude": "Lengdargráða",
|
||||||
@ -475,7 +485,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"learn_more": "Læra meira um aðgerðir"
|
"learn_more": "Læra meira um aðgerðir"
|
||||||
}
|
},
|
||||||
|
"load_error_not_editable": "Eingöngu er hægt að breyta sjálfvirkni í automations.yaml",
|
||||||
|
"load_error_unknown": "Villa kom upp við að hlaða inn sjálfvirkni ({err_no})."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"script": {
|
"script": {
|
||||||
@ -537,6 +549,7 @@
|
|||||||
},
|
},
|
||||||
"config_flow": {
|
"config_flow": {
|
||||||
"external_step": {
|
"external_step": {
|
||||||
|
"description": "Þetta skref krefst þess að þú heimsækir ytri vefsíðu svo hægt sé að ljúka þessu skrefi.",
|
||||||
"open_site": "Opna vefsíðu"
|
"open_site": "Opna vefsíðu"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -785,6 +798,7 @@
|
|||||||
"finish": "Ljúka"
|
"finish": "Ljúka"
|
||||||
},
|
},
|
||||||
"core-config": {
|
"core-config": {
|
||||||
|
"intro": "Hæ {name}, velkomin(n) í Home Assistant. Hvað á heimilið þitt að heita?",
|
||||||
"location_name_default": "Heima",
|
"location_name_default": "Heima",
|
||||||
"button_detect": "Uppgötva",
|
"button_detect": "Uppgötva",
|
||||||
"finish": "Næsta"
|
"finish": "Næsta"
|
||||||
@ -869,12 +883,14 @@
|
|||||||
"cards": {
|
"cards": {
|
||||||
"demo": {
|
"demo": {
|
||||||
"demo_by": "eftir {name}",
|
"demo_by": "eftir {name}",
|
||||||
|
"next_demo": "Næsta sýnidæmi",
|
||||||
"learn_more": "Læra meira um Home Assistant"
|
"learn_more": "Læra meira um Home Assistant"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"arsaboo": {
|
"arsaboo": {
|
||||||
"names": {
|
"names": {
|
||||||
|
"family_room": "Fjölskyldurými",
|
||||||
"kitchen": "Eldhús",
|
"kitchen": "Eldhús",
|
||||||
"left": "Vinstri",
|
"left": "Vinstri",
|
||||||
"right": "Hægri",
|
"right": "Hægri",
|
||||||
@ -885,7 +901,10 @@
|
|||||||
"information": "Upplýsingar",
|
"information": "Upplýsingar",
|
||||||
"entertainment": "Skemmtun",
|
"entertainment": "Skemmtun",
|
||||||
"activity": "Virkni",
|
"activity": "Virkni",
|
||||||
|
"hdmi_input": "HDMI inntak",
|
||||||
|
"hdmi_switcher": "HDMI rofi",
|
||||||
"volume": "Hljóðstyrkur",
|
"volume": "Hljóðstyrkur",
|
||||||
|
"total_tv_time": "Heildar sjónvarpstími",
|
||||||
"turn_tv_off": "Slökkva á sjónvarpi"
|
"turn_tv_off": "Slökkva á sjónvarpi"
|
||||||
},
|
},
|
||||||
"unit": {
|
"unit": {
|
||||||
@ -985,6 +1004,7 @@
|
|||||||
"currently": "Er núna",
|
"currently": "Er núna",
|
||||||
"on_off": "Kveikt \/ slökkt",
|
"on_off": "Kveikt \/ slökkt",
|
||||||
"operation": "Aðgerð",
|
"operation": "Aðgerð",
|
||||||
|
"fan_mode": "Viftuhamur",
|
||||||
"swing_mode": "Sveifluhamur"
|
"swing_mode": "Sveifluhamur"
|
||||||
},
|
},
|
||||||
"lock": {
|
"lock": {
|
||||||
@ -1104,6 +1124,7 @@
|
|||||||
"updater": "Uppfærsluálfur",
|
"updater": "Uppfærsluálfur",
|
||||||
"weblink": "Vefslóð",
|
"weblink": "Vefslóð",
|
||||||
"zwave": "Z-Wave",
|
"zwave": "Z-Wave",
|
||||||
|
"vacuum": "Ryksuga",
|
||||||
"zha": "ZHA",
|
"zha": "ZHA",
|
||||||
"hassio": "Hass.io",
|
"hassio": "Hass.io",
|
||||||
"homeassistant": "Home Assistant",
|
"homeassistant": "Home Assistant",
|
||||||
|
@ -849,7 +849,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"code": "Kod uwierzytelniania dwuskładnikowego"
|
"code": "Kod uwierzytelniania dwuskładnikowego"
|
||||||
},
|
},
|
||||||
"description": "Otwórz **{mfa_module_name}** na swoim urządzeniu by zobaczyć kod dwuskładnikowego uwierzytelniania i zweryfikować swoją toższamość:"
|
"description": "Otwórz **{mfa_module_name}** na swoim urządzeniu by zobaczyć kod dwuskładnikowego uwierzytelniania i zweryfikować swoją tożsamość:"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user