Merge branch 'dev' of https://github.com/home-assistant/home-assistant-polymer into RTL-arrow-components

This commit is contained in:
Yosi Levy 2019-02-19 20:01:30 +02:00
commit 5824e0b706
219 changed files with 5531 additions and 2846 deletions

View File

@ -1,8 +1,9 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"eg2.tslint",
"esbenp.prettier-vscode",
"bierner.lit-html"
]
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode",
"bierner.lit-html",
"runem.lit-plugin"
]
}

View File

@ -7,7 +7,7 @@ import {
} from "lit-element";
import { until } from "lit-html/directives/until";
import "@polymer/paper-icon-button";
import "@polymer/paper-button";
import "@material/mwc-button";
import "@polymer/paper-spinner/paper-spinner-lite";
import "../../../src/components/ha-card";
import "../../../src/components/ha-paper-icon-button-next";
@ -84,7 +84,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
</div>
<div class="actions">
<a href="https://www.home-assistant.io" target="_blank">
<paper-button>Learn more about Home Assistant</paper-button>
<mwc-button>Learn more about Home Assistant</mwc-button>
</a>
</div>
</ha-card>
@ -145,12 +145,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
}
.actions {
padding-left: 5px;
}
.actions paper-button {
color: var(--primary-color);
font-weight: 500;
padding-left: 8px;
}
`,
];

View File

@ -2,6 +2,17 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { getEntity } from "../../../src/fake_data/entity";
const ENTITIES = [
getEntity("light", "kitchen_lights", "on", {
friendly_name: "Kitchen Lights",
}),
getEntity("light", "bed_light", "off", {
friendly_name: "Bed Light",
}),
];
const CONFIGS = [
{
@ -10,6 +21,8 @@ const CONFIGS = [
- type: picture-entity
image: /images/kitchen.png
entity: light.kitchen_lights
tap_action:
action: toggle
`,
},
{
@ -18,6 +31,8 @@ const CONFIGS = [
- type: picture-entity
image: /images/bed.png
entity: light.bed_light
tap_action:
action: toggle
`,
},
{
@ -68,7 +83,7 @@ const CONFIGS = [
class DemoPicEntity extends PolymerElement {
static get template() {
return html`
<demo-cards configs="[[_configs]]"></demo-cards>
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
@ -80,6 +95,12 @@ class DemoPicEntity extends PolymerElement {
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-picture-entity-card", DemoPicEntity);

View File

@ -2,6 +2,25 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/demo-cards";
import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass";
const ENTITIES = [
getEntity("switch", "decorative_lights", "on", {
friendly_name: "Decorative Lights",
}),
getEntity("light", "ceiling_lights", "on", {
friendly_name: "Ceiling Lights",
}),
getEntity("binary_sensor", "movement_backyard", "on", {
friendly_name: "Movement Backyard",
device_class: "moving",
}),
getEntity("binary_sensor", "basement_floor_wet", "off", {
friendly_name: "Basement Floor Wet",
device_class: "moisture",
}),
];
const CONFIGS = [
{
@ -105,7 +124,7 @@ const CONFIGS = [
class DemoPicGlance extends PolymerElement {
static get template() {
return html`
<demo-cards configs="[[_configs]]"></demo-cards>
<demo-cards id="demos" configs="[[_configs]]"></demo-cards>
`;
}
@ -117,6 +136,12 @@ class DemoPicGlance extends PolymerElement {
},
};
}
public ready() {
super.ready();
const hass = provideHass(this.$.demos);
hass.addEntities(ENTITIES);
}
}
customElements.define("demo-hui-picture-glance-card", DemoPicGlance);

View File

@ -1,5 +1,5 @@
import { html, LitElement, TemplateResult } from "lit-element";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "../../../src/components/ha-card";
import { longPress } from "../../../src/panels/lovelace/common/directives/long-press-directive";
@ -11,13 +11,13 @@ export class DemoUtilLongPress extends LitElement {
${[1, 2, 3].map(
() => html`
<ha-card>
<paper-button
<mwc-button
@ha-click="${this._handleTap}"
@ha-hold="${this._handleHold}"
.longPress="${longPress()}"
>
(long) press me!
</paper-button>
</mwc-button>
<textarea></textarea>
@ -60,11 +60,6 @@ export class DemoUtilLongPress extends LitElement {
margin-bottom: 16px;
}
paper-button {
font-weight: bold;
color: var(--primary-color);
}
textarea {
height: 50px;
}

View File

@ -1,7 +1,7 @@
const path = require("path");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { babelLoaderConfig } = require("../config/babel.js");
const webpackBase = require("../config/babel.js");
const webpackBase = require("../config/webpack.js");
const isProd = process.env.NODE_ENV === "production";
const chunkFilename = isProd ? "chunk.[chunkhash].js" : "[name].chunk.js";

View File

@ -1,6 +1,6 @@
import "web-animations-js/web-animations-next-lite.min";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
@ -65,7 +65,7 @@ class HassioAddonAudio extends EventsMixin(PolymerElement) {
</paper-dropdown-menu>
</div>
<div class="card-actions">
<paper-button on-click="_saveSettings">Save</paper-button>
<mwc-button on-click="_saveSettings">Save</mwc-button>
</div>
</paper-card>
`;

View File

@ -1,5 +1,5 @@
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -50,8 +50,8 @@ class HassioAddonConfig extends PolymerElement {
data="[[resetData]]"
>Reset to defaults</ha-call-api-button
>
<paper-button on-click="saveTapped" disabled="[[!configParsed]]"
>Save</paper-button
<mwc-button on-click="saveTapped" disabled="[[!configParsed]]"
>Save</mwc-button
>
</div>
</paper-card>

View File

@ -1,5 +1,5 @@
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-toggle-button/paper-toggle-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
@ -77,7 +77,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
color: white;
--paper-card-header-color: white;
}
paper-card.warning paper-button {
paper-card.warning mwc-button {
color: white !important;
}
.warning {
@ -169,7 +169,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
>Update</ha-call-api-button
>
<template is="dom-if" if="[[addon.changelog]]">
<paper-button on-click="openChangelog">Changelog</paper-button>
<mwc-button on-click="openChangelog">Changelog</mwc-button>
</template>
</div>
</paper-card>
@ -219,7 +219,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
Protection mode on this addon is disabled! This gives the add-on full access to the entire system, which adds security risks, and could damage your system when used incorrectly. Only disable the protection mode if you know, need AND trust the source of this addon.
</div>
<div class="card-actions">
<paper-button on-click="protectionToggled">Enable Protection mode</paper-button>
<mwc-button on-click="protectionToggled">Enable Protection mode</mwc-button>
</div>
</div>
</paper-card>
@ -250,7 +250,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
<ha-label-badge
on-click="showMoreInfo"
id="full_access"
icon="hassio:chip"
icon="hassio:chip"
label="hardware"
description=""
></ha-label-badge>
@ -337,8 +337,8 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
</div>
<div class="card-actions">
<template is="dom-if" if="[[addon.version]]">
<paper-button class="warning" on-click="_unistallClicked"
>Uninstall</paper-button
<mwc-button class="warning" on-click="_unistallClicked"
>Uninstall</mwc-button
>
<template is="dom-if" if="[[addon.build]]">
<ha-call-api-button
@ -378,7 +378,7 @@ class HassioAddonInfo extends EventsMixin(PolymerElement) {
tabindex="-1"
target="_blank"
class="right"
><paper-button>Open web UI</paper-button></a
><mwc-button>Open web UI</mwc-button></a
>
</template>
</template>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -24,7 +24,7 @@ class HassioAddonLogs extends PolymerElement {
<paper-card heading="Log">
<div class="card-content" id="content"></div>
<div class="card-actions">
<paper-button on-click="refresh">Refresh</paper-button>
<mwc-button on-click="refresh">Refresh</mwc-button>
</div>
</paper-card>
`;

View File

@ -60,7 +60,7 @@ class HassioAddonNetwork extends EventsMixin(PolymerElement) {
data="[[resetData]]"
>Reset to defaults</ha-call-api-button
>
<paper-button on-click="saveTapped">Save</paper-button>
<mwc-button on-click="saveTapped">Save</mwc-button>
</div>
</paper-card>
`;

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -56,7 +56,7 @@ class HassioHassUpdate extends PolymerElement {
<a
href="https://github.com/home-assistant/home-assistant/releases"
target="_blank"
><paper-button>Release notes</paper-button></a
><mwc-button>Release notes</mwc-button></a
>
</div>
</paper-card>

View File

@ -1,5 +1,5 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-dialog/paper-dialog";
@ -123,12 +123,12 @@ class HassioSnapshot extends PolymerElement {
class="download"
title="Download snapshot"
></paper-icon-button>
<paper-button on-click="_partialRestoreClicked"
>Restore selected</paper-button
<mwc-button on-click="_partialRestoreClicked"
>Restore selected</mwc-button
>
<template is="dom-if" if="[[_isFullSnapshot(snapshot.type)]]">
<paper-button on-click="_fullRestoreClicked"
>Wipe &amp; restore</paper-button
<mwc-button on-click="_fullRestoreClicked"
>Wipe &amp; restore</mwc-button
>
</template>
</div>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input";
@ -90,10 +90,10 @@ class HassioSnapshots extends EventsMixin(PolymerElement) {
</template>
</div>
<div class="card-actions">
<paper-button
<mwc-button
disabled="[[creatingSnapshot]]"
on-click="_createSnapshot"
>Create</paper-button
>Create</mwc-button
>
</div>
</paper-card>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -39,7 +39,7 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
color: var(--google-red-500);
margin-top: 16px;
}
paper-button.info {
mwc-button.info {
max-width: calc(50% - 12px);
}
table.info {
@ -67,13 +67,13 @@ class HassioHostInfo extends EventsMixin(PolymerElement) {
</template>
</tbody>
</table>
<paper-button raised on-click="_showHardware" class="info">
<mwc-button raised on-click="_showHardware" class="info">
Hardware
</paper-button>
</mwc-button>
<template is="dom-if" if="[[_featureAvailable(data, 'hostname')]]">
<paper-button raised on-click="_changeHostnameClicked" class="info">
<mwc-button raised on-click="_changeHostnameClicked" class="info">
Change hostname
</paper-button>
</mwc-button>
</template>
<template is="dom-if" if="[[errors]]">
<div class="errors">Error: [[errors]]</div>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -80,11 +80,11 @@ class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
>
</template>
<template is="dom-if" if='[[_equals(data.channel, "stable")]]'>
<paper-button
<mwc-button
on-click="_joinBeta"
class="warning"
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>Join beta channel</paper-button
>Join beta channel</mwc-button
>
</template>
</div>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -24,7 +24,7 @@ class HassioSupervisorLog extends PolymerElement {
<paper-card>
<div class="card-content" id="content"></div>
<div class="card-actions">
<paper-button on-click="refresh">Refresh</paper-button>
<mwc-button on-click="refresh">Refresh</mwc-button>
</div>
</paper-card>
`;

View File

@ -12,6 +12,7 @@ class HassioSystem extends PolymerElement {
<style include="iron-flex ha-style">
.content {
margin: 4px;
color: var(--primary-text-color);
}
.title {
margin-top: 24px;

View File

@ -17,6 +17,8 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@material/mwc-button": "^0.3.6",
"@material/mwc-ripple": "^0.3.6",
"@mdi/svg": "^3.0.39",
"@polymer/app-layout": "^3.0.1",
"@polymer/app-localize-behavior": "^3.0.1",
@ -35,7 +37,6 @@
"@polymer/iron-pages": "^3.0.1",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/neon-animation": "^3.0.1",
"@polymer/paper-button": "^3.0.1",
"@polymer/paper-card": "^3.0.1",
"@polymer/paper-checkbox": "^3.0.1",
"@polymer/paper-dialog": "^3.0.1",
@ -81,6 +82,7 @@
"lit-html": "^1.0.0",
"marked": "^0.6.0",
"mdn-polyfills": "^5.12.0",
"memoize-one": "^5.0.0",
"moment": "^2.22.2",
"preact": "^8.3.1",
"preact-compat": "^3.18.4",
@ -105,6 +107,7 @@
"@gfx/zopfli": "^1.0.9",
"@types/chai": "^4.1.7",
"@types/codemirror": "^0.0.71",
"@types/memoize-one": "^4.1.0",
"@types/mocha": "^5.2.5",
"babel-eslint": "^10",
"babel-loader": "^8.0.4",

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20190203.0",
version="20190219.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",

View File

@ -1,5 +1,5 @@
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import "../components/ha-form";
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
@ -55,8 +55,8 @@ class HaAuthFlow extends localizeLiteMixin(PolymerElement) {
></ha-form>
</template>
<div class="action">
<paper-button raised on-click="_handleSubmit"
>[[_computeSubmitCaption(_step.type)]]</paper-button
<mwc-button raised on-click="_handleSubmit"
>[[_computeSubmitCaption(_step.type)]]</mwc-button
>
</div>
</template>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -38,16 +38,15 @@ class HaPersistentNotificationCard extends LocalizeMixin(PolymerElement) {
ha-markdown img {
max-width: 100%;
}
paper-button {
mwc-button {
margin: 8px;
font-weight: 500;
}
</style>
<ha-card header="[[computeTitle(stateObj)]]">
<ha-markdown content="[[stateObj.attributes.message]]"></ha-markdown>
<paper-button on-click="dismissTap"
>[[localize('ui.card.persistent_notification.dismiss')]]</paper-button
<mwc-button on-click="dismissTap"
>[[localize('ui.card.persistent_notification.dismiss')]]</mwc-button
>
</ha-card>
`;

View File

@ -28,6 +28,7 @@ const fixedIcons = {
light: "hass:lightbulb",
mailbox: "hass:mailbox",
notify: "hass:comment-alert",
person: "hass:account",
plant: "hass:flower",
proximity: "hass:apple-safari",
remote: "hass:remote",

View File

@ -0,0 +1,20 @@
// https://stackoverflow.com/a/16245768
export const b64toBlob = (b64Data, contentType = "", sliceSize = 512) => {
const byteCharacters = atob(b64Data);
const byteArrays: Uint8Array[] = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: contentType });
};

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-spinner/paper-spinner";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -12,26 +12,22 @@ class HaProgressButton extends PolymerElement {
display: inline-block;
}
paper-button {
mwc-button {
transition: all 1s;
}
.success paper-button {
color: white;
.success mwc-button {
--mdc-theme-primary: white;
background-color: var(--google-green-500);
transition: none;
}
.error paper-button {
color: white;
.error mwc-button {
--mdc-theme-primary: white;
background-color: var(--google-red-500);
transition: none;
}
paper-button[disabled] {
color: #c8c8c8;
}
.progress {
@apply --layout;
@apply --layout-center-center;
@ -43,13 +39,13 @@ class HaProgressButton extends PolymerElement {
}
</style>
<div class="container" id="container">
<paper-button
<mwc-button
id="button"
disabled="[[computeDisabled(disabled, progress)]]"
on-click="buttonTapped"
>
<slot></slot>
</paper-button>
</mwc-button>
<template is="dom-if" if="[[progress]]">
<div class="progress"><paper-spinner active=""></paper-spinner></div>
</template>

View File

@ -0,0 +1,121 @@
import {
LitElement,
TemplateResult,
property,
html,
customElement,
} from "lit-element";
import "@polymer/paper-icon-button/paper-icon-button-light";
import { HomeAssistant } from "../../types";
import { PolymerChangedEvent } from "../../polymer-types";
import { fireEvent } from "../../common/dom/fire_event";
import isValidEntityId from "../../common/entity/valid_entity_id";
import "./ha-entity-picker";
// Not a duplicate, type import
// tslint:disable-next-line
import { HaEntityPickerEntityFilterFunc } from "./ha-entity-picker";
import { HassEntity } from "home-assistant-js-websocket";
@customElement("ha-entities-picker")
class HaEntitiesPickerLight extends LitElement {
@property() public hass?: HomeAssistant;
@property() public value?: string[];
@property({ attribute: "domain-filter" }) public domainFilter?: string;
@property({ attribute: "picked-entity-label" })
public pickedEntityLabel?: string;
@property({ attribute: "pick-entity-label" }) public pickEntityLabel?: string;
protected render(): TemplateResult | void {
if (!this.hass) {
return;
}
const currentEntities = this._currentEntities;
return html`
${currentEntities.map(
(entityId) => html`
<div>
<ha-entity-picker
allow-custom-entity
.curValue=${entityId}
.hass=${this.hass}
.domainFilter=${this.domainFilter}
.entityFilter=${this._entityFilter}
.value=${entityId}
.label=${this.pickedEntityLabel}
@value-changed=${this._entityChanged}
></ha-entity-picker>
</div>
`
)}
<div>
<ha-entity-picker
.hass=${this.hass}
.domainFilter=${this.domainFilter}
.entityFilter=${this._entityFilter}
.label=${this.pickEntityLabel}
@value-changed=${this._addEntity}
></ha-entity-picker>
</div>
`;
}
private _entityFilter: HaEntityPickerEntityFilterFunc = (
stateObj: HassEntity
) => !this.value || !this.value.includes(stateObj.entity_id);
private get _currentEntities() {
return this.value || [];
}
private async _updateEntities(entities) {
fireEvent(this, "value-changed", {
value: entities,
});
this.value = entities;
}
private _entityChanged(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const curValue = (event.currentTarget as any).curValue;
const newValue = event.detail.value;
if (
newValue === curValue ||
(newValue !== "" && !isValidEntityId(newValue))
) {
return;
}
if (newValue === "") {
this._updateEntities(
this._currentEntities.filter((ent) => ent !== curValue)
);
} else {
this._updateEntities(
this._currentEntities.map((ent) => (ent === curValue ? newValue : ent))
);
}
}
private async _addEntity(event: PolymerChangedEvent<string>) {
event.stopPropagation();
const toAdd = event.detail.value;
(event.currentTarget as any).value = "";
if (!toAdd) {
return;
}
const currentEntities = this._currentEntities;
if (currentEntities.includes(toAdd)) {
return;
}
this._updateEntities([...currentEntities, toAdd]);
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-entities-picker": HaEntitiesPickerLight;
}
}

View File

@ -1,179 +0,0 @@
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
import "./state-badge";
import computeStateName from "../../common/entity/compute_state_name";
import LocalizeMixin from "../../mixins/localize-mixin";
import EventsMixin from "../../mixins/events-mixin";
/*
* @appliesMixin LocalizeMixin
*/
class HaEntityPicker extends EventsMixin(LocalizeMixin(PolymerElement)) {
static get template() {
return html`
<style>
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
</style>
<vaadin-combo-box-light
items="[[_states]]"
item-value-path="entity_id"
item-label-path="entity_id"
value="{{value}}"
opened="{{opened}}"
allow-custom-value="[[allowCustomEntity]]"
on-change="_fireChanged"
>
<paper-input
autofocus="[[autofocus]]"
label="[[_computeLabel(label, localize)]]"
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
value="[[value]]"
disabled="[[disabled]]"
>
<paper-icon-button
slot="suffix"
class="clear-button"
icon="hass:close"
no-ripple=""
hidden$="[[!value]]"
>Clear</paper-icon-button
>
<paper-icon-button
slot="suffix"
class="toggle-button"
icon="[[_computeToggleIcon(opened)]]"
hidden="[[!_states.length]]"
>Toggle</paper-icon-button
>
</paper-input>
<template>
<style>
paper-icon-item {
margin: -10px;
padding: 0;
}
</style>
<paper-icon-item>
<state-badge state-obj="[[item]]" slot="item-icon"></state-badge>
<paper-item-body two-line="">
<div>[[_computeStateName(item)]]</div>
<div secondary="">[[item.entity_id]]</div>
</paper-item-body>
</paper-icon-item>
</template>
</vaadin-combo-box-light>
`;
}
static get properties() {
return {
allowCustomEntity: {
type: Boolean,
value: false,
},
hass: {
type: Object,
observer: "_hassChanged",
},
_hass: Object,
_states: {
type: Array,
computed: "_computeStates(_hass, domainFilter, entityFilter)",
},
autofocus: Boolean,
label: {
type: String,
},
value: {
type: String,
notify: true,
},
opened: {
type: Boolean,
value: false,
observer: "_openedChanged",
},
domainFilter: {
type: String,
value: null,
},
entityFilter: {
type: Function,
value: null,
},
disabled: Boolean,
};
}
_computeLabel(label, localize) {
return label === undefined
? localize("ui.components.entity.entity-picker.entity")
: label;
}
_computeStates(hass, domainFilter, entityFilter) {
if (!hass) return [];
let entityIds = Object.keys(hass.states);
if (domainFilter) {
entityIds = entityIds.filter(
(eid) => eid.substr(0, eid.indexOf(".")) === domainFilter
);
}
let entities = entityIds.sort().map((key) => hass.states[key]);
if (entityFilter) {
entities = entities.filter(entityFilter);
}
return entities;
}
_computeStateName(state) {
return computeStateName(state);
}
_openedChanged(newVal) {
if (!newVal) {
this._hass = this.hass;
}
}
_hassChanged(newVal) {
if (!this.opened) {
this._hass = newVal;
}
}
_computeToggleIcon(opened) {
return opened ? "hass:menu-up" : "hass:menu-down";
}
_fireChanged(ev) {
ev.stopPropagation();
this.fire("change");
}
}
customElements.define("ha-entity-picker", HaEntityPicker);

View File

@ -0,0 +1,201 @@
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
import memoizeOne from "memoize-one";
import "./state-badge";
import computeStateName from "../../common/entity/compute_state_name";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
property,
PropertyValues,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { HassEntity } from "home-assistant-js-websocket";
import { PolymerChangedEvent } from "../../polymer-types";
import { fireEvent } from "../../common/dom/fire_event";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
const rowRenderer = (
root: HTMLElement,
_owner,
model: { item: HassEntity }
) => {
if (!root.firstElementChild) {
root.innerHTML = `
<style>
paper-icon-item {
margin: -10px;
padding: 0;
}
</style>
<paper-icon-item>
<state-badge state-obj="[[item]]" slot="item-icon"></state-badge>
<paper-item-body two-line="">
<div class='name'>[[_computeStateName(item)]]</div>
<div secondary>[[item.entity_id]]</div>
</paper-item-body>
</paper-icon-item>
`;
}
root.querySelector("state-badge")!.stateObj = model.item;
root.querySelector(".name")!.textContent = computeStateName(model.item);
root.querySelector("[secondary]")!.textContent = model.item.entity_id;
};
class HaEntityPicker extends LitElement {
@property({ type: Boolean }) public autofocus?: boolean;
@property({ type: Boolean }) public disabled?: boolean;
@property({ type: Boolean, attribute: "allow-custom-entity" })
public allowCustomEntity;
@property() public hass?: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property({ attribute: "domain-filter" }) public domainFilter?: string;
@property() public entityFilter?: HaEntityPickerEntityFilterFunc;
@property({ type: Boolean }) private _opened?: boolean;
@property() private _hass?: HomeAssistant;
private _getStates = memoizeOne(
(
hass: this["hass"],
domainFilter: this["domainFilter"],
entityFilter: this["entityFilter"]
) => {
let states: HassEntity[] = [];
if (!hass) {
return [];
}
let entityIds = Object.keys(hass.states);
if (domainFilter) {
entityIds = entityIds.filter(
(eid) => eid.substr(0, eid.indexOf(".")) === domainFilter
);
}
states = entityIds.sort().map((key) => hass!.states[key]);
if (entityFilter) {
states = states.filter(
(stateObj) =>
// We always want to include the entity of the current value
stateObj.entity_id === this.value || entityFilter!(stateObj)
);
}
return states;
}
);
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (changedProps.has("hass") && !this._opened) {
this._hass = this.hass;
}
}
protected render(): TemplateResult | void {
const states = this._getStates(
this._hass,
this.domainFilter,
this.entityFilter
);
return html`
<vaadin-combo-box-light
item-value-path="entity_id"
item-label-path="entity_id"
.items=${states}
.value=${this._value}
.allowCustomValue=${this.allowCustomEntity}
.renderer=${rowRenderer}
@opened-changed=${this._openedChanged}
@value-changed=${this._valueChanged}
>
<paper-input
.autofocus=${this.autofocus}
.label=${this.label === undefined && this._hass
? this._hass.localize("ui.components.entity.entity-picker.entity")
: this.label}
.value=${this._value}
.disabled=${this.disabled}
class="input"
autocapitalize="none"
autocomplete="off"
autocorrect="off"
spellcheck="false"
>
${this.value
? html`
<paper-icon-button
slot="suffix"
class="clear-button"
icon="hass:close"
no-ripple
>
Clear
</paper-icon-button>
`
: ""}
${states.length > 0
? html`
<paper-icon-button
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</paper-icon-button>
`
: ""}
</paper-input>
</vaadin-combo-box-light>
`;
}
private get _value() {
return this.value || "";
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
this._opened = ev.detail.value;
}
private _valueChanged(ev: PolymerChangedEvent<string>) {
const newValue = ev.detail.value;
if (newValue !== this._value) {
this.value = ev.detail.value;
setTimeout(() => {
fireEvent(this, "value-changed", { value: this.value });
fireEvent(this, "change");
}, 0);
}
}
static get styles(): CSSResult {
return css`
paper-input > paper-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
display: none;
}
`;
}
}
customElements.define("ha-entity-picker", HaEntityPicker);

View File

@ -1,42 +0,0 @@
import "@polymer/paper-styles/element-styles/paper-material-styles";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HaCard extends PolymerElement {
static get template() {
return html`
<style include="paper-material-styles">
:host {
@apply --paper-material-elevation-1;
display: block;
border-radius: var(--ha-card-border-radius, 2px);
transition: all 0.3s ease-out;
background: var(
--ha-card-background,
var(--paper-card-background-color, white)
);
color: var(--primary-text-color);
}
.header {
@apply --paper-font-headline;
@apply --paper-font-common-expensive-kerning;
opacity: var(--dark-primary-opacity);
padding: 24px 16px 16px;
}
</style>
<template is="dom-if" if="[[header]]">
<div class="header">[[header]]</div>
</template>
<slot></slot>
`;
}
static get properties() {
return {
header: String,
};
}
}
customElements.define("ha-card", HaCard);

45
src/components/ha-card.ts Normal file
View File

@ -0,0 +1,45 @@
import {
css,
CSSResult,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
class HaCard extends LitElement {
@property() public header?: string;
static get styles(): CSSResult {
return css`
:host {
background: var(
--ha-card-background,
var(--paper-card-background-color, white)
);
border-radius: var(--ha-card-border-radius, 2px);
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
color: var(--primary-text-color);
display: block;
transition: all 0.3s ease-out;
}
.header:not(:empty) {
font-size: 24px;
letter-spacing: -0.012em;
line-height: 32px;
opacity: 0.87;
padding: 24px 16px 16px;
}
`;
}
protected render(): TemplateResult {
return html`
<div class="header">${this.header}</div>
<slot></slot>
`;
}
}
customElements.define("ha-card", HaCard);

View File

@ -6,7 +6,6 @@ import {
PropertyValues,
property,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-item/paper-icon-item";
@ -14,27 +13,12 @@ import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "./ha-icon";
import "../components/user/ha-user-badge";
import isComponentLoaded from "../common/config/is_component_loaded";
import { HomeAssistant, Panel } from "../types";
import { fireEvent } from "../common/dom/fire_event";
import { DEFAULT_PANEL } from "../common/const";
const computeInitials = (name: string) => {
if (!name) {
return "user";
}
return (
name
.trim()
// Split by space and take first 3 words
.split(" ")
.slice(0, 3)
// Of each word, take first letter
.map((s) => s.substr(0, 1))
.join("")
);
};
const computeUrl = (urlPath) => `/${urlPath}`;
const computePanels = (hass: HomeAssistant) => {
@ -93,22 +77,13 @@ class HaSidebar extends LitElement {
return html``;
}
const initials = hass.user ? computeInitials(hass.user.name) : "";
return html`
<app-toolbar>
<div main-title>Home Assistant</div>
${hass.user
? html`
<a
href="/profile"
class="${classMap({
"profile-badge": true,
long: initials.length > 2,
})}"
>
<paper-ripple></paper-ripple>
${initials}
<a href="/profile">
<ha-user-badge .user=${hass.user}></ha-user-badge>
</a>
`
: ""}
@ -344,23 +319,6 @@ class HaSidebar extends LitElement {
.dev-tools a {
color: var(--sidebar-icon-color);
}
.profile-badge {
/* for ripple */
position: relative;
box-sizing: border-box;
width: 40px;
line-height: 40px;
border-radius: 50%;
text-align: center;
background-color: var(--light-primary-color);
text-decoration: none;
color: var(--primary-text-color);
}
.profile-badge.long {
font-size: 80%;
}
`;
}
}

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -38,21 +38,19 @@ class HaVacuumState extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style>
paper-button {
color: var(--primary-color);
font-weight: 500;
mwc-button {
top: 3px;
height: 37px;
margin-right: -0.57em;
}
paper-button[disabled] {
mwc-button[disabled] {
background-color: transparent;
color: var(--secondary-text-color);
}
</style>
<paper-button on-click="_callService" disabled="[[!_interceptable]]"
>[[_computeLabel(stateObj.state, _interceptable)]]</paper-button
<mwc-button on-click="_callService" disabled="[[!_interceptable]]"
>[[_computeLabel(stateObj.state, _interceptable)]]</mwc-button
>
`;
}

View File

@ -0,0 +1,77 @@
import {
LitElement,
TemplateResult,
css,
CSSResult,
html,
property,
customElement,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import { User } from "../../data/auth";
import { CurrentUser } from "../../types";
const computeInitials = (name: string) => {
if (!name) {
return "user";
}
return (
name
.trim()
// Split by space and take first 3 words
.split(" ")
.slice(0, 3)
// Of each word, take first letter
.map((s) => s.substr(0, 1))
.join("")
);
};
@customElement("ha-user-badge")
class StateBadge extends LitElement {
@property() public user?: User | CurrentUser;
protected render(): TemplateResult | void {
const user = this.user;
const initials = user ? computeInitials(user.name) : "?";
return html`
<div
class="${classMap({
"profile-badge": true,
long: initials.length > 2,
})}"
>
${initials}
</div>
`;
}
static get styles(): CSSResult {
return css`
.profile-badge {
display: inline-block;
box-sizing: border-box;
width: 40px;
line-height: 40px;
border-radius: 50%;
text-align: center;
background-color: var(--light-primary-color);
text-decoration: none;
color: var(--primary-text-color);
overflow: hidden;
}
.profile-badge.long {
font-size: 80%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-user-badge": StateBadge;
}
}

View File

@ -0,0 +1,104 @@
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu-light";
import "@polymer/paper-listbox/paper-listbox";
import memoizeOne from "memoize-one";
import {
LitElement,
TemplateResult,
html,
css,
CSSResult,
property,
} from "lit-element";
import { HomeAssistant } from "../../types";
import { fireEvent } from "../../common/dom/fire_event";
import { User, fetchUsers } from "../../data/auth";
import compare from "../../common/string/compare";
class HaEntityPicker extends LitElement {
public hass?: HomeAssistant;
@property() public label?: string;
@property() public value?: string;
@property() public users?: User[];
private _sortedUsers = memoizeOne((users?: User[]) => {
if (!users || users.length === 1) {
return users || [];
}
const sorted = [...users];
sorted.sort((a, b) => compare(a.name, b.name));
return sorted;
});
protected render(): TemplateResult | void {
return html`
<paper-dropdown-menu-light .label=${this.label}>
<paper-listbox
slot="dropdown-content"
.selected=${this._value}
attr-for-selected="data-user-id"
@iron-select=${this._userChanged}
>
<paper-icon-item data-user-id="">
No user
</paper-icon-item>
${this._sortedUsers(this.users).map(
(user) => html`
<paper-icon-item data-user-id=${user.id}>
<ha-user-badge .user=${user} slot="item-icon"></ha-user-badge>
${user.name}
</paper-icon-item>
`
)}
</paper-listbox>
</paper-dropdown-menu-light>
`;
}
private get _value() {
return this.value || "";
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
if (this.users === undefined) {
fetchUsers(this.hass!).then((users) => {
this.users = users;
});
}
}
private _userChanged(ev) {
const newValue = ev.detail.item.dataset.userId;
if (newValue !== this._value) {
this.value = ev.detail.value;
setTimeout(() => {
fireEvent(this, "value-changed", { value: newValue });
fireEvent(this, "change");
}, 0);
}
}
static get styles(): CSSResult {
return css`
:host {
display: inline-block;
}
paper-dropdown-menu-light {
display: block;
}
paper-listbox {
min-width: 200px;
}
paper-icon-item {
cursor: pointer;
}
`;
}
}
customElements.define("ha-user-picker", HaEntityPicker);

View File

@ -1,5 +1,26 @@
import { HomeAssistant } from "../types";
export interface AuthProvider {
name: string;
id: string;
type: string;
}
interface Credential {
type: string;
}
export interface User {
id: string;
name: string;
is_owner: boolean;
is_active: boolean;
system_generated: boolean;
group_ids: string[];
credentials: Credential[];
}
export const fetchUsers = async (hass: HomeAssistant) =>
hass.callWS<User[]>({
type: "config/auth/list",
});

12
src/data/camera.ts Normal file
View File

@ -0,0 +1,12 @@
import { HomeAssistant } from "../types";
export interface CameraThumbnail {
content_type: string;
content: string;
}
export const fetchThumbnail = (hass: HomeAssistant, entityId: string) =>
hass.callWS<CameraThumbnail>({
type: "camera_thumbnail",
entity_id: entityId,
});

View File

@ -5,7 +5,6 @@ export interface LovelaceConfig {
views: LovelaceViewConfig[];
background?: string;
resources?: Array<{ type: "css" | "js" | "module" | "html"; url: string }>;
excluded_entities?: string[];
}
export interface LovelaceViewConfig {
@ -34,7 +33,10 @@ export interface ToggleActionConfig {
export interface CallServiceActionConfig {
action: "call-service";
service: string;
service_data?: { [key: string]: any };
service_data?: {
entity_id?: string | [string];
[key: string]: any;
};
}
export interface NavigateActionConfig {

View File

@ -3,12 +3,17 @@ import {
Connection,
getCollection,
} from "home-assistant-js-websocket";
import { User } from "../types";
import { CurrentUser } from "../types";
export const userCollection = (conn: Connection) =>
getCollection(conn, "_usr", () => getUser(conn) as Promise<User>, undefined);
getCollection(
conn,
"_usr",
() => getUser(conn) as Promise<CurrentUser>,
undefined
);
export const subscribeUser = (
conn: Connection,
onChange: (user: User) => void
onChange: (user: CurrentUser) => void
) => userCollection(conn).subscribe(onChange);

View File

@ -7,8 +7,19 @@ export interface ZHADeviceEntity extends HassEntity {
};
}
export interface ZHAEntities {
[key: string]: HassEntity[];
export interface ZHAEntityReference extends HassEntity {
name: string;
}
export interface ZHADevice {
name: string;
ieee: string;
manufacturer: string;
model: string;
quirk_applied: boolean;
quirk_class: string;
entities: ZHAEntityReference[];
manufacturer_code: number;
}
export interface Attribute {
@ -19,6 +30,7 @@ export interface Attribute {
export interface Cluster {
name: string;
id: number;
endpoint_id: number;
type: string;
}
@ -29,7 +41,8 @@ export interface Command {
}
export interface ReadAttributeServiceData {
entity_id: string;
ieee: string;
endpoint_id: number;
cluster_id: number;
cluster_type: string;
attribute: number;
@ -41,64 +54,60 @@ export const reconfigureNode = (
ieeeAddress: string
): Promise<void> =>
hass.callWS({
type: "zha/nodes/reconfigure",
type: "zha/devices/reconfigure",
ieee: ieeeAddress,
});
export const fetchAttributesForCluster = (
hass: HomeAssistant,
entityId: string,
ieeeAddress: string,
endpointId: number,
clusterId: number,
clusterType: string
): Promise<Attribute[]> =>
hass.callWS({
type: "zha/entities/clusters/attributes",
entity_id: entityId,
type: "zha/devices/clusters/attributes",
ieee: ieeeAddress,
endpoint_id: endpointId,
cluster_id: clusterId,
cluster_type: clusterType,
});
export const fetchDevices = (hass: HomeAssistant): Promise<ZHADevice[]> =>
hass.callWS({
type: "zha/devices",
});
export const readAttributeValue = (
hass: HomeAssistant,
data: ReadAttributeServiceData
): Promise<string> => {
return hass.callWS({
...data,
type: "zha/entities/clusters/attributes/value",
type: "zha/devices/clusters/attributes/value",
});
};
export const fetchCommandsForCluster = (
hass: HomeAssistant,
entityId: string,
ieeeAddress: string,
endpointId: number,
clusterId: number,
clusterType: string
): Promise<Command[]> =>
hass.callWS({
type: "zha/entities/clusters/commands",
entity_id: entityId,
type: "zha/devices/clusters/commands",
ieee: ieeeAddress,
endpoint_id: endpointId,
cluster_id: clusterId,
cluster_type: clusterType,
});
export const fetchClustersForZhaNode = (
hass: HomeAssistant,
entityId: string,
ieeeAddress: string
): Promise<Cluster[]> =>
hass.callWS({
type: "zha/entities/clusters",
entity_id: entityId,
type: "zha/devices/clusters",
ieee: ieeeAddress,
});
export const fetchEntitiesForZhaNode = (
hass: HomeAssistant
): Promise<ZHAEntities> =>
hass.callWS({
type: "zha/entities",
});

View File

@ -18,6 +18,10 @@ class HaStoreAuth extends LocalizeMixin(PolymerElement) {
right: 16px;
}
.card-content {
color: var(--primary-text-color);
}
.card-actions {
text-align: right;
border-top: 0;
@ -33,11 +37,11 @@ class HaStoreAuth extends LocalizeMixin(PolymerElement) {
<paper-card elevation="4">
<div class="card-content">[[localize('ui.auth_store.ask')]]</div>
<div class="card-actions">
<paper-button on-click="_done"
>[[localize('ui.auth_store.decline')]]</paper-button
<mwc-button on-click="_done"
>[[localize('ui.auth_store.decline')]]</mwc-button
>
<paper-button primary on-click="_save"
>[[localize('ui.auth_store.confirm')]]</paper-button
<mwc-button raised on-click="_save"
>[[localize('ui.auth_store.confirm')]]</mwc-button
>
</div>
</paper-card>

View File

@ -1,5 +1,5 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -27,15 +27,14 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(
display: flex;
flex-direction: column;
}
.pad paper-button {
.pad mwc-button {
width: 80px;
}
.actions paper-button {
.actions mwc-button {
min-width: 160px;
margin-bottom: 16px;
color: var(--primary-color);
}
paper-button.disarm {
mwc-button.disarm {
color: var(--google-red-500);
}
</style>
@ -51,87 +50,87 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(
<template is="dom-if" if="[[_isNumber(_codeFormat)]]">
<div class="pad">
<div>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="1"
raised
>1</paper-button
>1</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="4"
raised
>4</paper-button
>4</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="7"
raised
>7</paper-button
>7</mwc-button
>
</div>
<div>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="2"
raised
>2</paper-button
>2</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="5"
raised
>5</paper-button
>5</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="8"
raised
>8</paper-button
>8</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="0"
raised
>0</paper-button
>0</mwc-button
>
</div>
<div>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="3"
raised
>3</paper-button
>3</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="6"
raised
>6</paper-button
>6</mwc-button
>
<paper-button
<mwc-button
on-click="_digitClicked"
disabled="[[!_inputEnabled]]"
data-digit="9"
raised
>9</paper-button
>9</mwc-button
>
<paper-button
<mwc-button
on-click="_clearEnteredCode"
disabled="[[!_inputEnabled]]"
raised
>
[[localize('ui.card.alarm_control_panel.clear_code')]]
</paper-button>
</mwc-button>
</div>
</div>
</template>
@ -139,7 +138,7 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(
<div class="layout horizontal center-justified actions">
<template is="dom-if" if="[[_disarmVisible]]">
<paper-button
<mwc-button
raised
class="disarm"
on-click="_callService"
@ -147,25 +146,25 @@ class MoreInfoAlarmControlPanel extends LocalizeMixin(
disabled="[[!_codeValid]]"
>
[[localize('ui.card.alarm_control_panel.disarm')]]
</paper-button>
</mwc-button>
</template>
<template is="dom-if" if="[[_armVisible]]">
<paper-button
<mwc-button
raised
on-click="_callService"
data-service="alarm_arm_home"
disabled="[[!_codeValid]]"
>
[[localize('ui.card.alarm_control_panel.arm_home')]]
</paper-button>
<paper-button
</mwc-button>
<mwc-button
raised
on-click="_callService"
data-service="alarm_arm_away"
disabled="[[!_codeValid]]"
>
[[localize('ui.card.alarm_control_panel.arm_away')]]
</paper-button>
</mwc-button>
</template>
</div>
`;

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -10,10 +10,6 @@ class MoreInfoAutomation extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
<style>
paper-button {
color: var(--primary-color);
font-weight: 500;
}
.flex {
display: flex;
justify-content: space-between;
@ -33,9 +29,9 @@ class MoreInfoAutomation extends LocalizeMixin(PolymerElement) {
</div>
<div class="actions">
<paper-button on-click="handleTriggerTapped">
<mwc-button on-click="handleTriggerTapped">
[[localize('ui.card.automation.trigger')]]
</paper-button>
</mwc-button>
</div>
`;
}

View File

@ -227,7 +227,7 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
items="[[stateObj.attributes.fan_list]]"
on-dom-change="handleFanListUpdate"
>
<paper-item>[[item]]</paper-item>
<paper-item>[[_localizeFanMode(localize, item)]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
@ -553,6 +553,10 @@ class MoreInfoClimate extends LocalizeMixin(EventsMixin(PolymerElement)) {
_localizeOperationMode(localize, mode) {
return localize(`state.climate.${mode}`) || mode;
}
_localizeFanMode(localize, mode) {
return localize(`state_attributes.climate.fan_mode.${mode}`) || mode;
}
}
customElements.define("more-info-climate", MoreInfoClimate);

View File

@ -1,6 +1,6 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-spinner/paper-spinner";
import { html } from "@polymer/polymer/lib/utils/html-tag";
@ -69,7 +69,7 @@ class MoreInfoConfigurator extends PolymerElement {
</template>
<p class="submit" hidden$="[[!stateObj.attributes.submit_caption]]">
<paper-button
<mwc-button
raised=""
disabled="[[isConfiguring]]"
on-click="submitClicked"
@ -80,7 +80,7 @@ class MoreInfoConfigurator extends PolymerElement {
alt="Configuring"
></paper-spinner>
[[stateObj.attributes.submit_caption]]
</paper-button>
</mwc-button>
</p>
</template>
</div>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -26,17 +26,17 @@ class MoreInfoLock extends LocalizeMixin(PolymerElement) {
pattern="[[stateObj.attributes.code_format]]"
type="password"
></paper-input>
<paper-button
<mwc-button
on-click="callService"
data-service="unlock"
hidden$="[[!isLocked]]"
>[[localize('ui.card.lock.unlock')]]</paper-button
>[[localize('ui.card.lock.unlock')]]</mwc-button
>
<paper-button
<mwc-button
on-click="callService"
data-service="lock"
hidden$="[[isLocked]]"
>[[localize('ui.card.lock.lock')]]</paper-button
>[[localize('ui.card.lock.lock')]]</mwc-button
>
</template>
<ha-attributes

View File

@ -105,8 +105,11 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
<template is="dom-if" if="[[_showValue(item.condition)]]">
<iron-icon icon="[[getWeatherIcon(item.condition)]]"></iron-icon>
</template>
<div class="main">[[computeDateTime(item.datetime)]]</div>
<template is="dom-if" if="[[!_showValue(item.templow)]]">
<div class="main">[[computeDateTime(item.datetime)]]</div>
</template>
<template is="dom-if" if="[[_showValue(item.templow)]]">
<div class="main">[[computeDate(item.datetime)]]</div>
<div class="templow">
[[item.templow]] [[getUnit('temperature')]]
</div>
@ -170,21 +173,8 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
};
}
computeDateTime(data) {
computeDate(data) {
const date = new Date(data);
const provider = this.stateObj.attributes.attribution;
if (
provider === "Powered by Dark Sky" ||
provider === "Data provided by OpenWeatherMap"
) {
if (new Date().getDay() === date.getDay()) {
return date.toLocaleTimeString(this.hass.language, { hour: "numeric" });
}
return date.toLocaleDateString(this.hass.language, {
weekday: "long",
hour: "numeric",
});
}
return date.toLocaleDateString(this.hass.language, {
weekday: "long",
month: "short",
@ -192,6 +182,14 @@ class MoreInfoWeather extends LocalizeMixin(PolymerElement) {
});
}
computeDateTime(data) {
const date = new Date(data);
return date.toLocaleDateString(this.hass.language, {
weekday: "long",
hour: "numeric",
});
}
getUnit(measure) {
const lengthUnit = this.hass.config.unit_system.length || "";
switch (measure) {

View File

@ -1,5 +1,5 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
@ -34,7 +34,7 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
@apply --ha-more-info-app-toolbar-title;
}
app-toolbar paper-button {
app-toolbar mwc-button {
font-size: 0.8em;
margin: 0;
}
@ -49,8 +49,8 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
on-click="_backTapped"
></ha-paper-icon-button-arrow-prev>
<div main-title="">[[_computeStateName(stateObj)]]</div>
<paper-button on-click="_save" disabled="[[_computeInvalid(_entityId)]]"
>[[localize('ui.dialogs.more_info_settings.save')]]</paper-button
<mwc-button on-click="_save" disabled="[[_computeInvalid(_entityId)]]"
>[[localize('ui.dialogs.more_info_settings.save')]]</mwc-button
>
</app-toolbar>

View File

@ -1,13 +1,14 @@
import { Constructor, LitElement } from "lit-element";
import { HassBaseEl } from "./hass-base-mixin";
import { HaToast } from "../../components/ha-toast";
import { computeRTL } from "../../common/util/compute_rtl";
export default (superClass: Constructor<LitElement & HassBaseEl>) =>
class extends superClass {
private _discToast?: HaToast;
protected hassConnected() {
super.hassConnected();
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
// Need to load in advance because when disconnected, can't dynamically load code.
import(/* webpackChunkName: "ha-toast" */ "../../components/ha-toast");
}
@ -24,10 +25,13 @@ export default (superClass: Constructor<LitElement & HassBaseEl>) =>
if (!this._discToast) {
const el = document.createElement("ha-toast");
el.duration = 0;
el.text = this.hass!.localize("ui.notification_toast.connection_lost");
this._discToast = el;
this.shadowRoot!.appendChild(el as any);
}
this._discToast.dir = computeRTL(this.hass!);
this._discToast.text = this.hass!.localize(
"ui.notification_toast.connection_lost"
);
this._discToast.opened = true;
}
};

View File

@ -1,4 +1,5 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import "@material/mwc-button";
import {
LitElement,
@ -8,9 +9,6 @@ import {
css,
} from "lit-element";
/*
* @appliesMixin LocalizeMixin
*/
class HaInitPage extends LitElement {
public error?: boolean;
@ -30,22 +28,21 @@ class HaInitPage extends LitElement {
${this.error
? html`
Unable to connect to Home Assistant.
<paper-button @click=${this._retry}>Retry</paper-button>
<mwc-button @click=${this._retry}>Retry</mwc-button>
`
: "Loading data"}
</div>
`;
}
private _retry() {
location.reload();
}
static get styles(): CSSResult {
return css`
div {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
@ -54,23 +51,8 @@ class HaInitPage extends LitElement {
paper-spinner-lite {
margin-bottom: 10px;
}
paper-button {
font-weight: 500;
color: var(--primary-color);
}
`;
}
protected updated(changedProps) {
super.updated(changedProps);
if (changedProps.has("error") && this.error) {
import(/* webpackChunkName: "paper-button" */ "@polymer/paper-button/paper-button");
}
}
private _retry() {
location.reload();
}
}
customElements.define("ha-init-page", HaInitPage);

View File

@ -1,56 +0,0 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-button/paper-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
class HassErrorScreen extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
.placeholder {
height: 100%;
}
.layout {
height: calc(100% - 64px);
}
paper-button {
font-weight: bold;
color: var(--primary-color);
}
</style>
<div class="placeholder">
<app-toolbar> <div main-title="">[[title]]</div> </app-toolbar>
<div class="layout vertical center-center">
<h3>[[error]]</h3>
<slot
><paper-button on-click="backTapped">go back</paper-button></slot
>
</div>
</div>
`;
}
static get properties() {
return {
title: {
type: String,
value: "Home Assistant",
},
error: {
type: String,
value: "Oops! It looks like something went wrong.",
},
};
}
backTapped() {
history.back();
}
}
customElements.define("hass-error-screen", HassErrorScreen);

View File

@ -0,0 +1,54 @@
import {
LitElement,
CSSResultArray,
css,
TemplateResult,
html,
property,
customElement,
} from "lit-element";
import "@material/mwc-button";
import "./hass-subpage";
@customElement("hass-error-screen")
class HassErrorScreen extends LitElement {
@property()
public error?: string;
protected render(): TemplateResult | void {
return html`
<hass-subpage>
<div class="content">
<h3>${this.error}</h3>
<slot>
<mwc-button @click=${this._backTapped}>go back</mwc-button>
</slot>
</div>
</hass-subpage>
`;
}
private _backTapped(): void {
history.back();
}
static get styles(): CSSResultArray {
return [
css`
.content {
height: calc(100% - 64px);
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hass-error-screen": HassErrorScreen;
}
}

View File

@ -1,57 +0,0 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-spinner/paper-spinner-lite";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../components/ha-menu-button";
class HassLoadingScreen extends PolymerElement {
static get template() {
return html`
<style include="iron-flex ha-style">
.placeholder {
height: 100%;
}
.layout {
height: calc(100% - 64px);
}
</style>
<div class="placeholder">
<app-toolbar>
<ha-menu-button
narrow="[[narrow]]"
show-menu="[[showMenu]]"
></ha-menu-button>
<div main-title>[[title]]</div>
</app-toolbar>
<div class="layout horizontal center-center">
<paper-spinner-lite active></paper-spinner-lite>
</div>
</div>
`;
}
static get properties() {
return {
narrow: {
type: Boolean,
value: false,
},
showMenu: {
type: Boolean,
value: false,
},
title: {
type: String,
value: "",
},
};
}
}
customElements.define("hass-loading-screen", HassLoadingScreen);

View File

@ -0,0 +1,56 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-spinner/paper-spinner-lite";
import {
LitElement,
TemplateResult,
html,
property,
CSSResultArray,
css,
customElement,
} from "lit-element";
import "../components/ha-menu-button";
import { haStyle } from "../resources/styles";
@customElement("hass-loading-screen")
class HassLoadingScreen extends LitElement {
@property({ type: Boolean })
public narrow?: boolean;
@property({ type: Boolean })
public showMenu?: boolean;
protected render(): TemplateResult | void {
return html`
<app-toolbar>
<ha-menu-button
.narrow=${this.narrow}
.showMenu=${this.showMenu}
></ha-menu-button>
</app-toolbar>
<div class="content">
<paper-spinner-lite active></paper-spinner-lite>
</div>
`;
}
static get styles(): CSSResultArray {
return [
haStyle,
css`
.content {
height: calc(100% - 64px);
display: flex;
align-items: center;
justify-content: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"hass-loading-screen": HassLoadingScreen;
}
}

View File

@ -0,0 +1,52 @@
import "@polymer/app-layout/app-header-layout/app-header-layout";
import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import {
LitElement,
property,
TemplateResult,
html,
customElement,
CSSResult,
} from "lit-element";
import { haStyle } from "../resources/styles";
@customElement("hass-subpage")
class HassSubpage extends LitElement {
@property()
public header?: string;
protected render(): TemplateResult | void {
return html`
<app-header-layout has-scrolling-region>
<app-header slot="header" fixed>
<app-toolbar>
<paper-icon-button
icon="hass:arrow-left"
@click=${this._backTapped}
></paper-icon-button>
<div main-title>${this.header}</div>
<slot name="toolbar-icon"></slot>
</app-toolbar>
</app-header>
<slot></slot>
</app-header-layout>
`;
}
private _backTapped(): void {
history.back();
}
static get styles(): CSSResult {
return haStyle;
}
}
declare global {
interface HTMLElementTagNameMap {
"hass-subpage": HassSubpage;
}
}

View File

@ -1,6 +1,7 @@
import { LitElement, html, PropertyValues, property } from "lit-element";
import "./hass-loading-screen";
import "./hass-error-screen";
import { HomeAssistant, Panel, PanelElement, Route } from "../types";
// Cache of panel loading promises.
@ -122,11 +123,10 @@ class PartialPanelResolver extends LitElement {
if (this._error) {
return html`
<hass-error-screen
title=""
error="Error while loading this panel."
.narrow=${this.narrow}
.showMenu=${this.showMenu}
/>
></hass-error-screen>
`;
}
@ -144,13 +144,6 @@ class PartialPanelResolver extends LitElement {
`;
}
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
// Load it before it's needed, because it will be shown if user is offline
// and a panel has to be loaded.
import(/* webpackChunkName: "hass-error-screen" */ "./hass-error-screen");
}
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);
if (!this.hass) {
@ -214,6 +207,7 @@ class PartialPanelResolver extends LitElement {
this._cache[panel.component_name] = this._panelEl;
}
this._error = false;
this._updatePanel();
},
(err) => {

View File

@ -1,7 +1,7 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
@ -64,9 +64,9 @@ class HaOnboarding extends localizeLiteMixin(PolymerElement) {
<template is='dom-if' if='[[!_loading]]'>
<p class='action'>
<paper-button raised on-click='_submitForm'>
<mwc-button raised on-click='_submitForm'>
[[localize('ui.panel.page-onboarding.user.create_account')]]
</paper-button>
</mwc-button>
</p>
</template>
</div>

View File

@ -12,7 +12,7 @@ import "@polymer/paper-input/paper-input";
import { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail";
import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/ha-style";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { AreaRegistryEntryMutableParams } from "../../../data/area_registry";
@ -51,7 +51,13 @@ class DialogAreaDetail extends LitElement {
opened
@opened-changed="${this._openedChanged}"
>
<h2>${this._params.entry ? this._params.entry.name : "New Area"}</h2>
<h2>
${this._params.entry
? this._params.entry.name
: this.hass.localize(
"ui.panel.config.area_registry.editor.default_name"
)}
</h2>
<paper-dialog-scrollable>
${this._error
? html`
@ -62,7 +68,7 @@ class DialogAreaDetail extends LitElement {
<paper-input
.value=${this._name}
@value-changed=${this._nameChanged}
label="Name"
.label=${this.hass.localize("ui.dialogs.more_info_settings.name")}
error-message="Name is required"
.invalid=${nameInvalid}
></paper-input>
@ -71,21 +77,29 @@ class DialogAreaDetail extends LitElement {
<div class="paper-dialog-buttons">
${this._params.entry
? html`
<paper-button
class="danger"
<mwc-button
class="warning"
@click="${this._deleteEntry}"
.disabled=${this._submitting}
>
DELETE
</paper-button>
${this.hass.localize(
"ui.panel.config.area_registry.editor.delete"
)}
</mwc-button>
`
: html``}
<paper-button
<mwc-button
@click="${this._updateEntry}"
.disabled=${nameInvalid || this._submitting}
>
${this._params.entry ? "UPDATE" : "CREATE"}
</paper-button>
${this._params.entry
? this.hass.localize(
"ui.panel.config.area_registry.editor.update"
)
: this.hass.localize(
"ui.panel.config.area_registry.editor.create"
)}
</mwc-button>
</div>
</paper-dialog>
`;
@ -142,13 +156,7 @@ class DialogAreaDetail extends LitElement {
.form {
padding-bottom: 24px;
}
paper-button {
font-weight: 500;
}
paper-button.danger {
font-weight: 500;
color: var(--google-red-500);
margin-left: -12px;
mwc-button.warning {
margin-right: auto;
}
.error {

View File

@ -50,7 +50,9 @@ class HaConfigAreaRegistry extends LitElement {
return html`
<hass-subpage header="Area Registry">
<ha-config-section .isWide=${this.isWide}>
<span slot="header">Area Registry</span>
<span slot="header">
${this.hass.localize("ui.panel.config.area_registry.picker.header")}
</span>
<span slot="introduction">
Areas are used to organize where devices are. This information will
be used throughout Home Assistant to help you in organizing your
@ -74,10 +76,14 @@ class HaConfigAreaRegistry extends LitElement {
${this._items.length === 0
? html`
<div class="empty">
Looks like you have no areas yet!
<paper-button @click=${this._createArea}>
CREATE AREA</paper-button
>
${this.hass.localize(
"ui.panel.config.area_registry.picker.no_areas"
)}
<mwc-button @click=${this._createArea}>
${this.hass.localize(
"ui.panel.config.area_registry.picker.create_area"
)}
</mwc-button>
</div>
`
: html``}

View File

@ -11,6 +11,7 @@ import "@polymer/app-layout/app-header/app-header";
import "@polymer/app-layout/app-toolbar/app-toolbar";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-fab/paper-fab";
import { classMap } from "lit-html/directives/class-map";
import { h, render } from "preact";
@ -21,10 +22,11 @@ import Automation from "../js/automation";
import unmountPreact from "../../../common/preact/unmount";
import computeStateName from "../../../common/entity/compute_state_name";
import { haStyle } from "../../../resources/ha-style";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { AutomationEntity, AutomationConfig } from "../../../data/automation";
import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl";
function AutomationEditor(mountEl, props, mergeEl) {
return render(h(Automation, props), mountEl, mergeEl);
@ -92,7 +94,12 @@ class HaAutomationEditor extends LitElement {
<div class="errors">${this._errors}</div>
`
: ""}
<div id="root"></div>
<div
id="root"
class="${classMap({
rtl: computeRTL(this.hass),
})}"
></div>
</div>
<paper-fab
slot="fab"
@ -235,7 +242,7 @@ class HaAutomationEditor extends LitElement {
.script paper-card {
margin-top: 16px;
}
.add-card paper-button {
.add-card mwc-button {
display: block;
text-align: center;
}
@ -246,6 +253,10 @@ class HaAutomationEditor extends LitElement {
z-index: 1;
color: var(--primary-text-color);
}
.rtl .card-menu {
right: auto;
left: 0;
}
.card-menu paper-item {
cursor: pointer;
}

View File

@ -4,7 +4,7 @@ import {
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-toggle-button/paper-toggle-button";
// tslint:disable-next-line

View File

@ -4,7 +4,7 @@ import {
PropertyDeclarations,
TemplateResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-toggle-button/paper-toggle-button";
// tslint:disable-next-line

View File

@ -6,7 +6,7 @@ import {
CSSResult,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-dialog/paper-dialog";
@ -18,7 +18,7 @@ import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { HomeAssistant } from "../../../types";
import { WebhookDialogParams } from "./types";
import { haStyle } from "../../../resources/ha-style";
import { haStyle } from "../../../resources/styles";
const inputLabel = "Public URL Click to copy to clipboard";
@ -69,9 +69,9 @@ export class CloudWebhookManageDialog extends LitElement {
<div class="paper-dialog-buttons">
<a href="${docsUrl}" target="_blank"
><paper-button>VIEW DOCUMENTATION</paper-button></a
><mwc-button>VIEW DOCUMENTATION</mwc-button></a
>
<paper-button @click="${this._closeDialog}">CLOSE</paper-button>
<mwc-button @click="${this._closeDialog}">CLOSE</mwc-button>
</div>
</paper-dialog>
`;
@ -129,10 +129,6 @@ export class CloudWebhookManageDialog extends LitElement {
button.link {
color: var(--primary-color);
}
paper-button {
color: var(--primary-color);
font-weight: 500;
}
`,
];
}

View File

@ -122,8 +122,8 @@ export class CloudWebhooks extends LitElement {
`
: this._cloudHooks![entry.webhook_id]
? html`
<paper-button @click="${this._handleManageButton}"
>Manage</paper-button
<mwc-button @click="${this._handleManageButton}"
>Manage</mwc-button
>
`
: html`
@ -226,10 +226,6 @@ export class CloudWebhooks extends LitElement {
flex-direction: column;
justify-content: center;
}
paper-button {
font-weight: 500;
color: var(--primary-color);
}
.footer {
padding: 16px;
}

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-toggle-button/paper-toggle-button";
@ -39,6 +39,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
}
.content {
padding-bottom: 24px;
direction: ltr;
}
paper-card {
display: block;
@ -47,7 +48,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
display: flex;
padding: 0 16px;
}
paper-button {
mwc-button {
align-self: center;
}
.soon {
@ -65,10 +66,6 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
text-transform: capitalize;
padding: 16px;
}
paper-button {
color: var(--primary-color);
font-weight: 500;
}
</style>
<hass-subpage header="Home Assistant Cloud">
<div class="content">
@ -99,10 +96,10 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
<div class="card-actions">
<a href="https://account.nabucasa.com" target="_blank"
><paper-button>Manage Account</paper-button></a
><mwc-button>Manage Account</mwc-button></a
>
<paper-button style="float: right" on-click="handleLogout"
>Sign out</paper-button
<mwc-button style="float: right" on-click="handleLogout"
>Sign out</mwc-button
>
</div>
</paper-card>

View File

@ -17,6 +17,7 @@ class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
<style include="iron-flex ha-style">
.content {
padding-bottom: 24px;
direction: ltr;
}
paper-card {

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import "@polymer/paper-input/paper-input";
@ -26,6 +26,7 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
<style include="iron-flex ha-style">
.content {
padding-bottom: 24px;
direction: ltr;
}
[slot="introduction"] {
margin: -1em 0;

View File

@ -16,6 +16,10 @@ class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style include="iron-flex ha-style">
.content {
direction: ltr;
}
[slot=introduction] {
margin: -1em 0;
}

View File

@ -1,6 +1,6 @@
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
import "@polymer/paper-tooltip/paper-tooltip";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/iron-icon/iron-icon";
import "@polymer/paper-item/paper-item";
@ -30,9 +30,7 @@ class HaConfigManagerDashboard extends LocalizeMixin(
static get template() {
return html`
<style include="iron-flex ha-style">
paper-button {
color: var(--primary-color);
font-weight: 500;
mwc-button {
top: 3px;
margin-right: -0.57em;
}
@ -66,8 +64,8 @@ class HaConfigManagerDashboard extends LocalizeMixin(
<paper-item-body>
[[_computeIntegrationTitle(localize, item.handler)]]
</paper-item-body>
<paper-button on-click="_continueFlow"
>[[localize('ui.panel.config.integrations.configure')]]</paper-button
<mwc-button on-click="_continueFlow"
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
>
</div>
</template>
@ -129,8 +127,8 @@ class HaConfigManagerDashboard extends LocalizeMixin(
<paper-item-body>
[[_computeIntegrationTitle(localize, item)]]
</paper-item-body>
<paper-button on-click="_createFlow"
>[[localize('ui.panel.config.integrations.configure')]]</paper-button
<mwc-button on-click="_createFlow"
>[[localize('ui.panel.config.integrations.configure')]]</mwc-button
>
</div>
</template>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-tooltip/paper-tooltip";
@ -102,10 +102,10 @@ class HaConfigFlow extends LocalizeMixin(EventsMixin(PolymerElement)) {
</paper-dialog-scrollable>
<div class="buttons">
<template is="dom-if" if="[[_equals(_step.type, 'abort')]]">
<paper-button on-click="_flowDone">Close</paper-button>
<mwc-button on-click="_flowDone">Close</mwc-button>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'create_entry')]]">
<paper-button on-click="_flowDone">Close</paper-button>
<mwc-button on-click="_flowDone">Close</mwc-button>
</template>
<template is="dom-if" if="[[_equals(_step.type, 'form')]]">
<template is="dom-if" if="[[_loading]]">
@ -115,8 +115,8 @@ class HaConfigFlow extends LocalizeMixin(EventsMixin(PolymerElement)) {
</template>
<template is="dom-if" if="[[!_loading]]">
<div>
<paper-button on-click="_submitStep" disabled="[[!_canSubmit]]"
>Submit</paper-button
<mwc-button on-click="_submitStep" disabled="[[!_canSubmit]]"
>Submit</mwc-button
>
<template is="dom-if" if="[[!_canSubmit]]">
<paper-tooltip position="left">

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
@ -40,7 +40,7 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
font-weight: 500;
}
.config-invalid paper-button {
.config-invalid mwc-button {
float: right;
}
@ -70,9 +70,9 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
[[localize('ui.panel.config.core.section.core.validation.valid')]]
</div>
</template>
<paper-button raised="" on-click="validateConfig">
<mwc-button raised="" on-click="validateConfig">
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
</paper-button>
</mwc-button>
</template>
<template is="dom-if" if="[[validating]]">
<paper-spinner active=""></paper-spinner>
@ -84,9 +84,9 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
<span class="text">
[[localize('ui.panel.config.core.section.core.validation.invalid')]]
</span>
<paper-button raised="" on-click="validateConfig">
<mwc-button raised="" on-click="validateConfig">
[[localize('ui.panel.config.core.section.core.validation.check_config')]]
</paper-button>
</mwc-button>
</div>
<div id="configLog" class="validate-log">[[validateLog]]</div>
</template>

View File

@ -39,11 +39,11 @@ class HaConfigCustomize extends LocalizeMixin(PolymerElement) {
<div class$="[[computeClasses(isWide)]]">
<ha-config-section is-wide="[[isWide]]">
<span slot="header">Customization</span>
<span slot="header">
[[localize('ui.panel.config.customize.picker.header')]]
</span>
<span slot="introduction">
Tweak per-entity attributes.<br />
Added/edited customizations will take effect immediately. Removed
customizations will take effect when the entity is updated.
[[localize('ui.panel.config.customize.picker.introduction')]]
</span>
<ha-entity-config
hass="[[hass]]"

View File

@ -12,7 +12,7 @@ import "@polymer/paper-input/paper-input";
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/ha-style";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import computeDomain from "../../../common/entity/compute_domain";
import { HassEntity } from "home-assistant-js-websocket";
@ -65,7 +65,11 @@ class DialogEntityRegistryDetail extends LitElement {
<paper-dialog-scrollable>
${!stateObj
? html`
<div>This entity is not currently available.</div>
<div>
${this.hass!.localize(
"ui.panel.config.entity_registry.editor.unavailable"
)}
</div>
`
: ""}
${this._error
@ -94,19 +98,23 @@ class DialogEntityRegistryDetail extends LitElement {
</div>
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<paper-button
class="danger"
<mwc-button
class="warning"
@click="${this._deleteEntry}"
.disabled=${this._submitting}
>
DELETE
</paper-button>
<paper-button
${this.hass.localize(
"ui.panel.config.entity_registry.editor.delete"
)}
</mwc-button>
<mwc-button
@click="${this._updateEntry}"
.disabled=${invalidDomainUpdate || this._submitting}
>
UPDATE
</paper-button>
${this.hass.localize(
"ui.panel.config.entity_registry.editor.update"
)}
</mwc-button>
</div>
</paper-dialog>
`;
@ -164,13 +172,7 @@ class DialogEntityRegistryDetail extends LitElement {
.form {
padding-bottom: 24px;
}
paper-button {
font-weight: 500;
}
paper-button.danger {
font-weight: 500;
color: var(--google-red-500);
margin-left: -12px;
mwc-button.warning {
margin-right: auto;
}
.error {

View File

@ -53,7 +53,11 @@ class HaConfigEntityRegistry extends LitElement {
return html`
<hass-subpage header="Entity Registry">
<ha-config-section .isWide=${this.isWide}>
<span slot="header">Entity Registry</span>
<span slot="header">
${this.hass.localize(
"ui.panel.config.entity_registry.picker.header"
)}
</span>
<span slot="introduction">
Home Assistant keeps a registry of every entity it has ever seen
that can be uniquely identified. Each of these entities will have an
@ -79,7 +83,9 @@ class HaConfigEntityRegistry extends LitElement {
<paper-item-body two-line>
<div class="name">
${computeEntityRegistryName(this.hass!, entry) ||
"(unavailable)"}
this.hass!.localize(
"ui.panel.config.entity_registry.picker.unavailable"
)}
</div>
<div class="secondary entity-id">
${entry.entity_id}
@ -150,6 +156,7 @@ Deleting an entry will not remove the entity from Home Assistant. To do this, yo
}
paper-card {
display: block;
direction: ltr;
}
paper-icon-item {
cursor: pointer;

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
@ -15,6 +15,7 @@ class HaEntityConfig extends PolymerElement {
<style include="iron-flex ha-style">
paper-card {
display: block;
direction: ltr;
}
.device-picker {
@ -74,17 +75,17 @@ class HaEntityConfig extends PolymerElement {
</div>
</div>
<div class="card-actions">
<paper-button
<mwc-button
on-click="saveEntity"
disabled="[[computeShowPlaceholder(formState)]]"
>SAVE</paper-button
>SAVE</mwc-button
>
<template is="dom-if" if="[[allowDelete]]">
<paper-button
<mwc-button
class="warning"
on-click="deleteEntity"
disabled="[[computeShowPlaceholder(formState)]]"
>DELETE</paper-button
>DELETE</mwc-button
>
</template>
</div>

View File

@ -3,8 +3,6 @@ import "@polymer/iron-media-query/iron-media-query";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../layouts/hass-error-screen";
import isComponentLoaded from "../../common/config/is_component_loaded";
import EventsMixin from "../../mixins/events-mixin";
import NavigateMixin from "../../mixins/navigate-mixin";

View File

@ -1,6 +1,6 @@
import { h, Component } from "preact";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import ConditionRow from "./condition_row";
@ -46,9 +46,9 @@ export default class Condition extends Component {
))}
<paper-card>
<div class="card-actions add-card">
<paper-button onTap={this.addCondition}>
<mwc-button onTap={this.addCondition}>
{localize("ui.panel.config.automation.editor.conditions.add")}
</paper-button>
</mwc-button>
</div>
</paper-card>
</div>

View File

@ -54,6 +54,7 @@ export default class NumericStateCondition extends Component {
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
dir="ltr"
/>
</div>
);

View File

@ -22,6 +22,7 @@ export default class TemplateCondition extends Component {
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
dir="ltr"
/>
</div>
);

View File

@ -53,6 +53,7 @@ export default class JSONTextArea extends Component {
value={value}
style={style}
onvalue-changed={this.onChange}
dir="ltr"
/>
);
}

View File

@ -1,6 +1,6 @@
import { h, Component } from "preact";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import ActionRow from "./action_row";
@ -46,9 +46,9 @@ export default class Script extends Component {
))}
<paper-card>
<div class="card-actions add-card">
<paper-button onTap={this.addAction}>
<mwc-button onTap={this.addAction}>
{localize("ui.panel.config.automation.editor.actions.add")}
</paper-button>
</mwc-button>
</div>
</paper-card>
</div>

View File

@ -36,6 +36,7 @@ export default class WaitAction extends Component {
name="wait_template"
value={wait_template}
onvalue-changed={this.onTemplateChange}
dir="ltr"
/>
<paper-input
label={localize(

View File

@ -1,6 +1,6 @@
import { h, Component } from "preact";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import TriggerRow from "./trigger_row";
import StateTrigger from "./state";
@ -47,9 +47,9 @@ export default class Trigger extends Component {
))}
<paper-card>
<div class="card-actions add-card">
<paper-button onTap={this.addTrigger}>
<mwc-button onTap={this.addTrigger}>
{localize("ui.panel.config.automation.editor.triggers.add")}
</paper-button>
</mwc-button>
</div>
</paper-card>
</div>

View File

@ -67,6 +67,7 @@ export default class NumericStateTrigger extends Component {
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
dir="ltr"
/>
<paper-input
label={localize(

View File

@ -23,6 +23,7 @@ export default class TemplateTrigger extends Component {
name="value_template"
value={value_template}
onvalue-changed={this.onChange}
dir="ltr"
/>
</div>
);

View File

@ -2,39 +2,44 @@ import {
LitElement,
html,
css,
PropertyDeclarations,
CSSResult,
TemplateResult,
property,
} from "lit-element";
import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "@polymer/paper-input/paper-input";
import "@material/mwc-button";
import "../../../components/entity/ha-entities-picker";
import "../../../components/user/ha-user-picker";
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
import { PolymerChangedEvent } from "../../../polymer-types";
import { haStyleDialog } from "../../../resources/ha-style";
import { haStyleDialog } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import { PersonMutableParams } from "../../../data/person";
class DialogPersonDetail extends LitElement {
public hass!: HomeAssistant;
private _name!: string;
private _error?: string;
private _params?: PersonDetailDialogParams;
private _submitting?: boolean;
static get properties(): PropertyDeclarations {
return {
_error: {},
_name: {},
_params: {},
};
}
@property() public hass!: HomeAssistant;
@property() private _name!: string;
@property() private _userId?: string;
@property() private _deviceTrackers!: string[];
@property() private _error?: string;
@property() private _params?: PersonDetailDialogParams;
@property() private _submitting: boolean = false;
public async showDialog(params: PersonDetailDialogParams): Promise<void> {
this._params = params;
this._error = undefined;
this._name = this._params.entry ? this._params.entry.name : "";
if (this._params.entry) {
this._name = this._params.entry.name || "";
this._userId = this._params.entry.user_id || undefined;
this._deviceTrackers = this._params.entry.device_trackers || [];
} else {
this._name = "";
this._userId = undefined;
this._deviceTrackers = [];
}
await this.updateComplete;
}
@ -64,26 +69,50 @@ class DialogPersonDetail extends LitElement {
error-message="Name is required"
.invalid=${nameInvalid}
></paper-input>
<ha-user-picker
label="Linked User"
.hass=${this.hass}
.value=${this._userId}
.users=${this._params.users}
@value-changed=${this._userChanged}
></ha-user-picker>
<p>
${this.hass.localize(
"ui.panel.config.person.detail.device_tracker_intro"
)}
</p>
<ha-entities-picker
.hass=${this.hass}
.value=${this._deviceTrackers}
domainFilter="device_tracker"
.pickedEntityLabel=${this.hass.localize(
"ui.panel.config.person.detail.device_tracker_picked"
)}
.pickEntityLabel=${this.hass.localize(
"ui.panel.config.person.detail.device_tracker_pick"
)}
@value-changed=${this._deviceTrackersChanged}
></ha-entities-picker>
</div>
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
${this._params.entry
? html`
<paper-button
class="danger"
<mwc-button
class="warning"
@click="${this._deleteEntry}"
.disabled=${this._submitting}
>
DELETE
</paper-button>
</mwc-button>
`
: html``}
<paper-button
<mwc-button
@click="${this._updateEntry}"
.disabled=${nameInvalid || this._submitting}
>
${this._params.entry ? "UPDATE" : "CREATE"}
</paper-button>
</mwc-button>
</div>
</paper-dialog>
`;
@ -94,14 +123,23 @@ class DialogPersonDetail extends LitElement {
this._name = ev.detail.value;
}
private _userChanged(ev: PolymerChangedEvent<string>) {
this._error = undefined;
this._userId = ev.detail.value;
}
private _deviceTrackersChanged(ev: PolymerChangedEvent<string[]>) {
this._error = undefined;
this._deviceTrackers = ev.detail.value;
}
private async _updateEntry() {
this._submitting = true;
try {
const values: PersonMutableParams = {
name: this._name.trim(),
// Temp, we will add this in a future PR.
user_id: null,
device_trackers: [],
device_trackers: this._deviceTrackers,
user_id: this._userId || null,
};
if (this._params!.entry) {
await this._params!.updateEntry(values);
@ -110,7 +148,7 @@ class DialogPersonDetail extends LitElement {
}
this._params = undefined;
} catch (err) {
this._error = err;
this._error = err ? err.message : "Unknown error";
} finally {
this._submitting = false;
}
@ -143,13 +181,10 @@ class DialogPersonDetail extends LitElement {
.form {
padding-bottom: 24px;
}
paper-button {
font-weight: 500;
ha-user-picker {
margin-top: 16px;
}
paper-button.danger {
font-weight: 500;
color: var(--google-red-500);
margin-left: -12px;
mwc-button.warning {
margin-right: auto;
}
.error {

View File

@ -27,12 +27,14 @@ import {
showPersonDetailDialog,
loadPersonDetailDialog,
} from "./show-dialog-person-detail";
import { User, fetchUsers } from "../../../data/auth";
class HaConfigPerson extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
private _storageItems?: Person[];
private _configItems?: Person[];
private _usersLoad?: Promise<User[]>;
static get properties(): PropertyDeclarations {
return {
@ -62,7 +64,7 @@ class HaConfigPerson extends LitElement {
${this._configItems.length > 0
? html`
<p>
Note: people configured via configuration.yaml cannot be
Note: persons configured via configuration.yaml cannot be
edited via the UI.
</p>
`
@ -81,9 +83,9 @@ class HaConfigPerson extends LitElement {
${this._storageItems.length === 0
? html`
<div class="empty">
Looks like you have no people yet!
<paper-button @click=${this._createPerson}>
CREATE PERSON</paper-button
Looks like you have not created any persons yet.
<mwc-button @click=${this._createPerson}>
CREATE PERSON</mwc-button
>
</div>
`
@ -91,7 +93,7 @@ class HaConfigPerson extends LitElement {
</paper-card>
${this._configItems.length > 0
? html`
<paper-card heading="Configuration.yaml people">
<paper-card heading="Configuration.yaml persons">
${this._configItems.map((entry) => {
return html`
<paper-item>
@ -123,6 +125,7 @@ class HaConfigPerson extends LitElement {
}
private async _fetchData() {
this._usersLoad = fetchUsers(this.hass!);
const personData = await fetchPersons(this.hass!);
this._storageItems = personData.storage.sort((ent1, ent2) =>
@ -142,9 +145,27 @@ class HaConfigPerson extends LitElement {
this._openDialog(entry);
}
private _openDialog(entry?: Person) {
private _allowedUsers(users: User[], currentPerson?: Person) {
const used = new Set();
for (const coll of [this._configItems, this._storageItems]) {
for (const pers of coll!) {
if (pers.user_id) {
used.add(pers.user_id);
}
}
}
const currentUserId = currentPerson ? currentPerson.user_id : undefined;
return users.filter(
(user) => user.id === currentUserId || !used.has(user.id)
);
}
private async _openDialog(entry?: Person) {
const users = await this._usersLoad!;
showPersonDetailDialog(this, {
entry,
users: this._allowedUsers(users, entry),
createEntry: async (values) => {
const created = await createPerson(this.hass!, values);
this._storageItems = this._storageItems!.concat(created).sort(
@ -191,6 +212,7 @@ All devices in this area will become unassigned.`)
}
.empty {
text-align: center;
padding: 8px;
}
paper-item {
padding-top: 4px;

View File

@ -1,8 +1,10 @@
import { fireEvent } from "../../../common/dom/fire_event";
import { Person, PersonMutableParams } from "../../../data/person";
import { User } from "../../../data/auth";
export interface PersonDetailDialogParams {
entry?: Person;
users: User[];
createEntry: (values: PersonMutableParams) => Promise<unknown>;
updateEntry: (updates: Partial<PersonMutableParams>) => Promise<unknown>;
removeEntry: () => Promise<boolean>;

View File

@ -48,7 +48,7 @@ class HaScriptEditor extends LocalizeMixin(NavigateMixin(PolymerElement)) {
.script paper-card {
margin-top: 16px;
}
.add-card paper-button {
.add-card mwc-button {
display: block;
text-align: center;
}

View File

@ -116,4 +116,4 @@ class HaUserPicker extends EventsMixin(
}
}
customElements.define("ha-user-picker", HaUserPicker);
customElements.define("ha-config-user-picker", HaUserPicker);

View File

@ -6,9 +6,10 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import NavigateMixin from "../../../mixins/navigate-mixin";
import "./ha-user-picker";
import "./ha-config-user-picker";
import "./ha-user-editor";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchUsers } from "../../../data/auth";
/*
* @appliesMixin NavigateMixin
@ -23,7 +24,10 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
></app-route>
<template is="dom-if" if='[[_equals(_routeData.user, "picker")]]'>
<ha-user-picker hass="[[hass]]" users="[[_users]]"></ha-user-picker>
<ha-config-user-picker
hass="[[hass]]"
users="[[_users]]"
></ha-config-user-picker>
</template>
<template
is="dom-if"
@ -93,9 +97,7 @@ class HaConfigUsers extends NavigateMixin(PolymerElement) {
}
async _loadData() {
this._users = await this.hass.callWS({
type: "config/auth/list",
});
this._users = await fetchUsers(this.hass);
}
}

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-dialog/paper-dialog";
import "@polymer/paper-spinner/paper-spinner";
import { html } from "@polymer/polymer/lib/utils/html-tag";
@ -71,7 +71,7 @@ class HaDialogAddUser extends LocalizeMixin(PolymerElement) {
</div>
</template>
<template is="dom-if" if="[[!_loading]]">
<paper-button on-click="_createUser">Create</paper-button>
<mwc-button on-click="_createUser">Create</mwc-button>
</template>
</div>
</paper-dialog>

View File

@ -1,4 +1,4 @@
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element";
@ -18,7 +18,7 @@ class HaUserEditor extends EventsMixin(
) {
static get template() {
return html`
<style>
<style include="ha-style">
paper-card {
display: block;
max-width: 600px;
@ -30,6 +30,9 @@ class HaUserEditor extends EventsMixin(
paper-card:last-child {
margin-bottom: 16px;
}
hass-subpage paper-card:first-of-type {
direction: ltr;
}
</style>
<hass-subpage header="View user">
@ -55,12 +58,13 @@ class HaUserEditor extends EventsMixin(
</paper-card>
<paper-card>
<div class="card-actions">
<paper-button
<mwc-button
class="warning"
on-click="_deleteUser"
disabled="[[user.system_generated]]"
>
[[localize('ui.panel.config.users.editor.delete_user')]]
</paper-button>
</mwc-button>
<template is="dom-if" if="[[user.system_generated]]">
Unable to remove system generated users.
</template>

View File

@ -0,0 +1,7 @@
export const formatAsPaddedHex = (value: string | number): string => {
let hex = value;
if (typeof value === "string") {
hex = parseInt(value, 16);
}
return "0x" + hex.toString(16).padStart(4, "0");
};

View File

@ -13,13 +13,9 @@ import { HASSDomEvent } from "../../../common/dom/fire_event";
import { Cluster } from "../../../data/zha";
import "../../../layouts/ha-app-layout";
import "../../../components/ha-paper-icon-button-arrow-prev";
import { haStyle } from "../../../resources/ha-style";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import {
ZHAClusterSelectedParams,
ZHAEntitySelectedParams,
ZHANodeSelectedParams,
} from "./types";
import { ZHAClusterSelectedParams, ZHANodeSelectedParams } from "./types";
import "./zha-cluster-attributes";
import "./zha-cluster-commands";
import "./zha-network";
@ -30,14 +26,12 @@ export class HaConfigZha extends LitElement {
public isWide?: boolean;
private _selectedNode?: HassEntity;
private _selectedCluster?: Cluster;
private _selectedEntity?: HassEntity;
static get properties(): PropertyDeclarations {
return {
hass: {},
isWide: {},
_selectedCluster: {},
_selectedEntity: {},
_selectedNode: {},
};
}
@ -64,7 +58,6 @@ export class HaConfigZha extends LitElement {
.hass="${this.hass}"
@zha-cluster-selected="${this._onClusterSelected}"
@zha-node-selected="${this._onNodeSelected}"
@zha-entity-selected="${this._onEntitySelected}"
></zha-node>
${this._selectedCluster
? html`
@ -72,7 +65,6 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
.selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}"
></zha-cluster-attributes>
@ -80,7 +72,6 @@ export class HaConfigZha extends LitElement {
.isWide="${this.isWide}"
.hass="${this.hass}"
.selectedNode="${this._selectedNode}"
.selectedEntity="${this._selectedEntity}"
.selectedCluster="${this._selectedCluster}"
></zha-cluster-commands>
`
@ -100,13 +91,6 @@ export class HaConfigZha extends LitElement {
): void {
this._selectedNode = selectedNodeEvent.detail.node;
this._selectedCluster = undefined;
this._selectedEntity = undefined;
}
private _onEntitySelected(
selectedEntityEvent: HASSDomEvent<ZHAEntitySelectedParams>
): void {
this._selectedEntity = selectedEntityEvent.detail.entity;
}
static get styles(): CSSResult[] {

View File

@ -1,4 +1,3 @@
import { HassEntity } from "home-assistant-js-websocket";
import { ZHADeviceEntity, Cluster } from "../../../data/zha";
export interface PickerTarget extends EventTarget {
@ -17,7 +16,8 @@ export interface ChangeEvent {
}
export interface SetAttributeServiceData {
entity_id: string;
ieee: string;
endpoint_id: number;
cluster_id: number;
cluster_type: string;
attribute: number;
@ -26,17 +26,14 @@ export interface SetAttributeServiceData {
}
export interface IssueCommandServiceData {
entity_id: string;
ieee: string;
endpoint_id: number;
cluster_id: number;
cluster_type: string;
command: number;
command_type: string;
}
export interface ZHAEntitySelectedParams {
entity: HassEntity;
}
export interface ZHANodeSelectedParams {
node: ZHADeviceEntity;
}

View File

@ -7,10 +7,9 @@ import {
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-icon-button/paper-icon-button";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
@ -19,9 +18,9 @@ import {
fetchAttributesForCluster,
ReadAttributeServiceData,
readAttributeValue,
ZHADeviceEntity,
ZHADevice,
} from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import {
@ -29,13 +28,13 @@ import {
ItemSelectedEvent,
SetAttributeServiceData,
} from "./types";
import { formatAsPaddedHex } from "./functions";
export class ZHAClusterAttributes extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showHelp: boolean;
public selectedNode?: HassEntity;
public selectedEntity?: ZHADeviceEntity;
public selectedNode?: ZHADevice;
public selectedCluster?: Cluster;
private _attributes: Attribute[];
private _selectedAttributeIndex: number;
@ -57,7 +56,6 @@ export class ZHAClusterAttributes extends LitElement {
isWide: {},
showHelp: {},
selectedNode: {},
selectedEntity: {},
selectedCluster: {},
_attributes: {},
_selectedAttributeIndex: {},
@ -105,7 +103,10 @@ export class ZHAClusterAttributes extends LitElement {
${this._attributes.map(
(entry) => html`
<paper-item
>${entry.name + " (id: " + entry.id + ")"}</paper-item
>${entry.name +
" (id: " +
formatAsPaddedHex(entry.id) +
")"}</paper-item
>
`
)}
@ -148,8 +149,8 @@ export class ZHAClusterAttributes extends LitElement {
></paper-input>
</div>
<div class="card-actions">
<paper-button @click="${this._onGetZigbeeAttributeClick}"
>Get Zigbee Attribute</paper-button
<mwc-button @click="${this._onGetZigbeeAttributeClick}"
>Get Zigbee Attribute</mwc-button
>
<ha-call-service-button
.hass="${this.hass}"
@ -172,49 +173,54 @@ export class ZHAClusterAttributes extends LitElement {
}
private async _fetchAttributesForCluster(): Promise<void> {
if (this.selectedEntity && this.selectedCluster && this.hass) {
if (this.selectedNode && this.selectedCluster && this.hass) {
this._attributes = await fetchAttributesForCluster(
this.hass,
this.selectedEntity!.entity_id,
this.selectedEntity!.device_info!.identifiers[0][1],
this.selectedNode!.ieee,
this.selectedCluster!.endpoint_id,
this.selectedCluster!.id,
this.selectedCluster!.type
);
this._attributes.sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
}
private _computeReadAttributeServiceData():
| ReadAttributeServiceData
| undefined {
if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) {
if (!this.selectedCluster || !this.selectedNode) {
return;
}
return {
entity_id: this.selectedEntity!.entity_id,
ieee: this.selectedNode!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.attributes.manufacturer_code,
: this.selectedNode!.manufacturer_code,
};
}
private _computeSetAttributeServiceData():
| SetAttributeServiceData
| undefined {
if (!this.selectedEntity || !this.selectedCluster || !this.selectedNode) {
if (!this.selectedCluster || !this.selectedNode) {
return;
}
return {
entity_id: this.selectedEntity!.entity_id,
ieee: this.selectedNode!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
attribute: this._attributes[this._selectedAttributeIndex].id,
value: this._attributeValue,
manufacturer: this._manufacturerCodeOverride
? parseInt(this._manufacturerCodeOverride as string, 10)
: this.selectedNode!.attributes.manufacturer_code,
: this.selectedNode!.manufacturer_code,
};
}
@ -306,8 +312,7 @@ export class ZHAClusterAttributes extends LitElement {
[hidden] {
display: none;
}
</style>
`,
`,
];
}
}

View File

@ -8,16 +8,15 @@ import {
css,
} from "lit-element";
import "@polymer/paper-card/paper-card";
import { HassEntity } from "home-assistant-js-websocket";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Cluster,
Command,
fetchCommandsForCluster,
ZHADeviceEntity,
ZHADevice,
} from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import {
@ -25,12 +24,12 @@ import {
IssueCommandServiceData,
ItemSelectedEvent,
} from "./types";
import { formatAsPaddedHex } from "./functions";
export class ZHAClusterCommands extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public selectedNode?: HassEntity;
public selectedEntity?: ZHADeviceEntity;
public selectedNode?: ZHADevice;
public selectedCluster?: Cluster;
private _showHelp: boolean;
private _commands: Command[];
@ -50,7 +49,6 @@ export class ZHAClusterCommands extends LitElement {
hass: {},
isWide: {},
selectedNode: {},
selectedEntity: {},
selectedCluster: {},
_showHelp: {},
_commands: {},
@ -97,7 +95,10 @@ export class ZHAClusterCommands extends LitElement {
${this._commands.map(
(entry) => html`
<paper-item
>${entry.name + " (id: " + entry.id + ")"}</paper-item
>${entry.name +
" (id: " +
formatAsPaddedHex(entry.id) +
")"}</paper-item
>
`
)}
@ -146,25 +147,29 @@ export class ZHAClusterCommands extends LitElement {
}
private async _fetchCommandsForCluster(): Promise<void> {
if (this.selectedEntity && this.selectedCluster && this.hass) {
if (this.selectedNode && this.selectedCluster && this.hass) {
this._commands = await fetchCommandsForCluster(
this.hass,
this.selectedEntity!.entity_id,
this.selectedEntity!.device_info!.identifiers[0][1],
this.selectedNode!.ieee,
this.selectedCluster!.endpoint_id,
this.selectedCluster!.id,
this.selectedCluster!.type
);
this._commands.sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
}
private _computeIssueClusterCommandServiceData():
| IssueCommandServiceData
| undefined {
if (!this.selectedEntity || !this.selectedCluster) {
if (!this.selectedNode || !this.selectedCluster) {
return;
}
return {
entity_id: this.selectedEntity!.entity_id,
ieee: this.selectedNode!.ieee,
endpoint_id: this.selectedCluster!.endpoint_id,
cluster_id: this.selectedCluster!.id,
cluster_type: this.selectedCluster!.type,
command: this._commands[this._selectedCommandIndex].id,
@ -257,8 +262,7 @@ export class ZHAClusterCommands extends LitElement {
[hidden] {
display: none;
}
</style>
`,
`,
];
}
}

View File

@ -11,15 +11,12 @@ import "@polymer/paper-card/paper-card";
import { fireEvent } from "../../../common/dom/fire_event";
import "../../../components/buttons/ha-call-service-button";
import "../../../components/ha-service-description";
import {
Cluster,
fetchClustersForZhaNode,
ZHADeviceEntity,
} from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { Cluster, fetchClustersForZhaNode, ZHADevice } from "../../../data/zha";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../ha-config-section";
import { ItemSelectedEvent } from "./types";
import { formatAsPaddedHex } from "./functions";
declare global {
// for fire event
@ -31,14 +28,16 @@ declare global {
}
const computeClusterKey = (cluster: Cluster): string => {
return `${cluster.name} (id: ${cluster.id}, type: ${cluster.type})`;
return `${cluster.name} (Endpoint id: ${
cluster.endpoint_id
}, Id: ${formatAsPaddedHex(cluster.id)}, Type: ${cluster.type})`;
};
export class ZHAClusters extends LitElement {
public hass?: HomeAssistant;
public isWide?: boolean;
public showHelp: boolean;
public selectedEntity?: ZHADeviceEntity;
public selectedDevice?: ZHADevice;
private _selectedClusterIndex: number;
private _clusters: Cluster[];
@ -54,14 +53,14 @@ export class ZHAClusters extends LitElement {
hass: {},
isWide: {},
showHelp: {},
selectedEntity: {},
selectedDevice: {},
_selectedClusterIndex: {},
_clusters: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedEntity")) {
if (changedProperties.has("selectedDevice")) {
this._clusters = [];
this._selectedClusterIndex = -1;
fireEvent(this, "zha-cluster-selected", {
@ -103,9 +102,11 @@ export class ZHAClusters extends LitElement {
if (this.hass) {
this._clusters = await fetchClustersForZhaNode(
this.hass,
this.selectedEntity!.entity_id,
this.selectedEntity!.device_info!.identifiers[0][1]
this.selectedDevice!.ieee
);
this._clusters.sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
}

View File

@ -0,0 +1,118 @@
import {
html,
LitElement,
property,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { fireEvent } from "../../../common/dom/fire_event";
import { haStyle } from "../../../resources/styles";
import { HomeAssistant } from "../../../types";
import "../../../components/entity/state-badge";
import { ZHADevice } from "../../../data/zha";
class ZHADeviceCard extends LitElement {
@property() public hass?: HomeAssistant;
@property() public narrow?: boolean;
@property() public device?: ZHADevice;
protected render(): TemplateResult | void {
return html`
<paper-card>
<div class="card-content">
<dl>
<dt class="label">IEEE:</dt>
<dd class="info">${this.device!.ieee}</dd>
<dt class="label">Quirk applied:</dt>
<dd class="info">${this.device!.quirk_applied}</dd>
<dt class="label">Quirk:</dt>
<dd class="info">${this.device!.quirk_class}</dd>
</dl>
</div>
<div class="device-entities">
${this.device!.entities.map(
(entity) => html`
<paper-icon-item
@click="${this._openMoreInfo}"
.entity="${entity}"
>
<state-badge
.stateObj="${this.hass!.states[entity.entity_id]}"
slot="item-icon"
></state-badge>
<paper-item-body>
<div class="name">${entity.name}</div>
<div class="secondary entity-id">${entity.entity_id}</div>
</paper-item-body>
</paper-icon-item>
`
)}
</div>
</paper-card>
`;
}
private _openMoreInfo(ev: MouseEvent): void {
fireEvent(this, "hass-more-info", {
entityId: (ev.currentTarget as any).entity.entity_id,
});
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
:host(:not([narrow])) .device-entities {
max-height: 225px;
overflow: auto;
}
paper-card {
flex: 1 0 100%;
padding-bottom: 10px;
min-width: 0;
}
.device {
width: 30%;
}
.label {
font-weight: bold;
}
.info {
color: var(--secondary-text-color);
font-weight: bold;
}
dl dt {
float: left;
width: 100px;
text-align: left;
}
dt dd {
margin-left: 10px;
text-align: left;
}
paper-icon-item {
cursor: pointer;
padding-top: 4px;
padding-bottom: 4px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-device-card": ZHADeviceCard;
}
}
customElements.define("zha-device-card", ZHADeviceCard);

View File

@ -1,170 +0,0 @@
import {
html,
LitElement,
PropertyDeclarations,
PropertyValues,
TemplateResult,
CSSResult,
css,
} from "lit-element";
import "@polymer/paper-button/paper-button";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { HassEntity } from "home-assistant-js-websocket";
import { fireEvent } from "../../../common/dom/fire_event";
import { fetchEntitiesForZhaNode } from "../../../data/zha";
import { haStyle } from "../../../resources/ha-style";
import { HomeAssistant } from "../../../types";
import { ItemSelectedEvent } from "./types";
declare global {
// for fire event
interface HASSDomEvents {
"zha-entity-selected": {
entity?: HassEntity;
};
}
}
export class ZHAEntities extends LitElement {
public hass?: HomeAssistant;
public showHelp?: boolean;
public selectedNode?: HassEntity;
private _selectedEntityIndex: number;
private _entities: HassEntity[];
constructor() {
super();
this._entities = [];
this._selectedEntityIndex = -1;
}
static get properties(): PropertyDeclarations {
return {
hass: {},
showHelp: {},
selectedNode: {},
_selectedEntityIndex: {},
_entities: {},
};
}
protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has("selectedNode")) {
this._entities = [];
this._selectedEntityIndex = -1;
fireEvent(this, "zha-entity-selected", {
entity: undefined,
});
this._fetchEntitiesForZhaNode();
}
super.update(changedProperties);
}
protected render(): TemplateResult | void {
return html`
<div class="node-picker">
<paper-dropdown-menu label="Entities" class="flex">
<paper-listbox
slot="dropdown-content"
.selected="${this._selectedEntityIndex}"
@iron-select="${this._selectedEntityChanged}"
>
${this._entities.map(
(entry) => html`
<paper-item>${entry.entity_id}</paper-item>
`
)}
</paper-listbox>
</paper-dropdown-menu>
</div>
${this.showHelp
? html`
<div class="helpText">
Select entity to view per-entity options
</div>
`
: ""}
${this._selectedEntityIndex !== -1
? html`
<div class="actions">
<paper-button @click="${this._showEntityInformation}"
>Entity Information</paper-button
>
</div>
`
: ""}
`;
}
private async _fetchEntitiesForZhaNode(): Promise<void> {
if (this.hass) {
const fetchedEntities = await fetchEntitiesForZhaNode(this.hass);
this._entities = fetchedEntities[this.selectedNode!.attributes.ieee];
}
}
private _selectedEntityChanged(event: ItemSelectedEvent): void {
this._selectedEntityIndex = event.target!.selected;
fireEvent(this, "zha-entity-selected", {
entity: this._entities[this._selectedEntityIndex],
});
}
private _showEntityInformation(): void {
fireEvent(this, "hass-more-info", {
entityId: this._entities[this._selectedEntityIndex].entity_id,
});
}
static get styles(): CSSResult[] {
return [
haStyle,
css`
.flex {
-ms-flex: 1 1 0.000000001px;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0.000000001px;
flex-basis: 0.000000001px;
}
.node-picker {
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-ms-flex-direction: row;
-webkit-flex-direction: row;
flex-direction: row;
-ms-flex-align: center;
-webkit-align-items: center;
align-items: center;
padding-left: 28px;
padding-right: 28px;
padding-bottom: 10px;
}
.actions {
border-top: 1px solid #e8e8e8;
padding: 5px 16px;
position: relative;
}
.actions paper-button:not([disabled]) {
color: var(--primary-color);
font-weight: 500;
}
.helpText {
color: grey;
padding: 16px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-entities": ZHAEntities;
}
}
customElements.define("zha-entities", ZHAEntities);

Some files were not shown because too many files have changed in this diff Show More