Compare commits

...

38 Commits

Author SHA1 Message Date
Bram Kragten
de1ffe67b1 Merge pull request #5840 from home-assistant/dev 2020-05-12 22:24:40 +02:00
Zack Arnett
eb2b24d57c Weather Card: Fix overwritten changes (#5841) 2020-05-12 22:24:20 +02:00
Bram Kragten
825db8a56a Bumped version to 20200512.0 2020-05-12 21:59:06 +02:00
David F. Mulcahey
577a21fc5c Rework ZHA group adds and removes (#5602) 2020-05-12 21:42:29 +02:00
Bram Kragten
1b2841eef9 Recreate cards only on config change (#5839) 2020-05-12 11:09:30 -07:00
Ian Richardson
845511e322 Add 'brightness' as a secondary_info option (#5731) 2020-05-12 12:29:43 -04:00
Bram Kragten
96ab057853 Ignore ResizeObserver loop limit exceeded error (#5838) 2020-05-12 12:17:41 +02:00
HomeAssistant Azure
f85cf0a238 [ci skip] Translation update 2020-05-12 00:33:00 +00:00
Bram Kragten
84a2676a9c compress icons (#5836) 2020-05-11 23:59:29 +02:00
Zack Arnett
466a1af902 Weather Card/Row: Weather Icons as SVG, Themeable, user definable (#5736)
* SVG

* no-unneeded-ternary

* declared ubnused

* moving stuff around

* Few updates

* All svgs in | update row

* No slots

* Remove public/static/images/weather

* style for user defined

* few updates to missing fils

* classes

* wind color
2020-05-11 23:58:17 +02:00
Zack Arnett
ebbe7e805f Gauge Card: Convert to Round Slider (#5510)
* Use round slider for gauge

* Update guage to slider

* Add severity back

* Remove Base Unit

* fix merge

* resize observer

* Update src/panels/lovelace/cards/hui-gauge-card.ts

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Update Install Resize Observer to be a helper

* Type import

* Reviews

* Updates to other cards

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2020-05-11 17:14:02 -04:00
Zack Arnett
c861ee025e Weather Card: Ability to choose Secondary Attribute (#5820) 2020-05-11 22:11:48 +02:00
Paulus Schoutsen
6d0823328d Fix generated Lovelace translating domain names (#5803) 2020-05-11 10:28:05 -07:00
Joakim Sørensen
60be14dc77 Use manifest.issue_tracker for issues URL if not built_in (#5818) 2020-05-11 13:45:00 +02:00
Mat Strange
2d627819d9 Added translation to dev-tools-info-integrations (#5834) 2020-05-11 13:44:32 +02:00
Zack Arnett
cf575f83f5 Weather Card: Switch State and Name (#5795) 2020-05-11 13:44:15 +02:00
HomeAssistant Azure
79935b2d4c [ci skip] Translation update 2020-05-11 00:32:59 +00:00
HomeAssistant Azure
d1cceb2013 [ci skip] Translation update 2020-05-10 00:33:23 +00:00
Bram Kragten
f5da130d51 Merge pull request #5823 from matstrange/dev 2020-05-09 22:18:37 +02:00
Mat Strange
8768304ec5 Returned to no label on disarm/trigger as before 2020-05-09 20:30:44 +01:00
Bram Kragten
f10a5dcdbe Unused import 2020-05-09 21:22:46 +02:00
Bram Kragten
a27428ebcd Simplify enter handling 2020-05-09 21:17:55 +02:00
Bram Kragten
d19acf17c2 Merge pull request #5826 from home-assistant/dev 2020-05-09 21:17:30 +02:00
Bram Kragten
1a0bf861ee Bumped version to 20200509.0 2020-05-09 21:01:00 +02:00
Bram Kragten
db906ad4d0 Bump material (#5812) 2020-05-09 20:59:53 +02:00
Bram Kragten
75ed0f2f99 Fix translation download and add empty languages (#5824) 2020-05-09 20:59:34 +02:00
Bram Kragten
29ed1144d5 Simplify custom icon set, return only 1 icon per call (#5822) 2020-05-09 20:59:02 +02:00
Mat Strange
fa445d4066 Added translation to bade on alarm panel 2020-05-09 12:00:26 +01:00
Erik Montnemery
d10be4ef2d Show cover position slider if cover supports it (#5815) 2020-05-08 22:59:20 +02:00
Joakim Sørensen
7f66d5b8e9 Change target to currentTarget for the click event (#5816) 2020-05-08 16:31:07 -04:00
Bram Kragten
0c8cd680c2 Allow custom icon sets (#5794) 2020-05-08 21:56:25 +02:00
Maciej Bieniek
a7ba1977b4 Add translation for and word on the integration card (#5811)
* Add translation for and word on tje integration card

* Move and to ui.common
2020-05-08 17:43:21 +02:00
Bram Kragten
a960b39235 Bump lit-element and lit-html (#5810) 2020-05-08 14:00:09 +02:00
Bram Kragten
3febf059ec Codesplit Supervisor panel icons (#5809) 2020-05-08 13:10:24 +02:00
Paulus Schoutsen
20203f7bdb Bump home-assistant-js-websocket to 5.1.2 2020-05-07 18:35:14 -07:00
Franck Nijhof
a399d76d06 Add new core configuration UI for external_url & internal_url (#5755) 2020-05-07 18:32:03 -07:00
Franck Nijhof
1ca097c5a0 Add issue_tracker integration manifest property (#5744) 2020-05-07 20:51:46 -04:00
Bram Kragten
ae6243b7bf Merge pull request #5757 from home-assistant/dev 2020-05-05 17:38:14 +02:00
157 changed files with 6211 additions and 2594 deletions

View File

@@ -9,25 +9,30 @@ const paths = require("../paths");
gulp.task("compress-app", function compressApp() { gulp.task("compress-app", function compressApp() {
const jsLatest = gulp const jsLatest = gulp
.src(path.resolve(paths.output, "**/*.js")) .src(path.resolve(paths.output, "**/*.js"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.output)); .pipe(gulp.dest(paths.output));
const jsEs5 = gulp const jsEs5 = gulp
.src(path.resolve(paths.output_es5, "**/*.js")) .src(path.resolve(paths.output_es5, "**/*.js"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.output_es5)); .pipe(gulp.dest(paths.output_es5));
const polyfills = gulp const polyfills = gulp
.src(path.resolve(paths.static, "polyfills/*.js")) .src(path.resolve(paths.static, "polyfills/*.js"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.static, "polyfills"))); .pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
const translations = gulp const translations = gulp
.src(path.resolve(paths.static, "translations/*.json")) .src(path.resolve(paths.static, "translations/**/*.json"))
.pipe(zopfli()) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.static, "translations"))); .pipe(gulp.dest(path.resolve(paths.static, "translations")));
return merge(jsLatest, jsEs5, polyfills, translations); const icons = gulp
.src(path.resolve(paths.static, "mdi/*.json"))
.pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(path.resolve(paths.static, "mdi")));
return merge(jsLatest, jsEs5, polyfills, translations, icons);
}); });
gulp.task("compress-hassio", function compressApp() { gulp.task("compress-hassio", function compressApp() {

View File

@@ -1,9 +1,14 @@
const del = require("del"); const del = require("del");
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs");
const mapStream = require("map-stream"); const mapStream = require("map-stream");
const inDir = "translations/frontend"; const inDirFrontend = "translations/frontend";
const downloadDir = inDir + "/downloads"; const inDirBackend = "translations/backend";
const downloadDir = "translations/downloads";
const srcMeta = "src/translations/translationMetadata.json";
const encoding = "utf8";
const tasks = []; const tasks = [];
@@ -53,9 +58,25 @@ gulp.task(taskName, function () {
}); });
tasks.push(taskName); tasks.push(taskName);
taskName = "check-all-files-exist";
gulp.task(taskName, function () {
const file = fs.readFileSync(srcMeta, { encoding });
const meta = JSON.parse(file);
Object.keys(meta).forEach((lang) => {
if (!fs.existsSync(`${inDirFrontend}/${lang}.json`)) {
fs.writeFileSync(`${inDirFrontend}/${lang}.json`, JSON.stringify({}));
}
if (!fs.existsSync(`${inDirBackend}/${lang}.json`)) {
fs.writeFileSync(`${inDirBackend}/${lang}.json`, JSON.stringify({}));
}
});
return Promise.resolve();
});
tasks.push(taskName);
taskName = "move-downloaded-translations"; taskName = "move-downloaded-translations";
gulp.task(taskName, function () { gulp.task(taskName, function () {
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDir)); return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDirFrontend));
}); });
tasks.push(taskName); tasks.push(taskName);
@@ -65,6 +86,7 @@ gulp.task(
gulp.series( gulp.series(
"check-translations-html", "check-translations-html",
"move-downloaded-translations", "move-downloaded-translations",
"check-all-files-exist",
"clean-downloaded-translations" "clean-downloaded-translations"
) )
); );

View File

@@ -1,4 +1,4 @@
import "@polymer/paper-card/paper-card"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { import {
css, css,
CSSResultArray, CSSResultArray,
@@ -10,6 +10,7 @@ import {
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import "../../../src/components/ha-card";
import { import {
HassioAddonInfo, HassioAddonInfo,
HassioAddonRepository, HassioAddonRepository,
@@ -66,7 +67,7 @@ class HassioAddonRepositoryEl extends LitElement {
<div class="card-group"> <div class="card-group">
${addons.map( ${addons.map(
(addon) => html` (addon) => html`
<paper-card <ha-card
.addon=${addon} .addon=${addon}
class=${addon.available ? "" : "not_available"} class=${addon.available ? "" : "not_available"}
@click=${this._addonTapped} @click=${this._addonTapped}
@@ -78,8 +79,8 @@ class HassioAddonRepositoryEl extends LitElement {
.description=${addon.description} .description=${addon.description}
.available=${addon.available} .available=${addon.available}
.icon=${addon.installed && addon.installed !== addon.version .icon=${addon.installed && addon.installed !== addon.version
? "hassio:arrow-up-bold-circle" ? mdiArrowUpBoldCircle
: "hassio:puzzle"} : mdiPuzzle}
.iconTitle=${addon.installed .iconTitle=${addon.installed
? addon.installed !== addon.version ? addon.installed !== addon.version
? "New version available" ? "New version available"
@@ -111,7 +112,7 @@ class HassioAddonRepositoryEl extends LitElement {
: ""} : ""}
></hassio-card-content> ></hassio-card-content>
</div> </div>
</paper-card> </ha-card>
` `
)} )}
</div> </div>
@@ -127,7 +128,7 @@ class HassioAddonRepositoryEl extends LitElement {
return [ return [
hassioStyle, hassioStyle,
css` css`
paper-card { ha-card {
cursor: pointer; cursor: pointer;
} }
.not_available { .not_available {

View File

@@ -1,3 +1,6 @@
import "@material/mwc-icon-button/mwc-icon-button";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
@@ -6,22 +9,21 @@ import {
PropertyValues, PropertyValues,
} from "lit-element"; } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
import "../../../src/common/search/search-input";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-svg-icon";
import { import {
fetchHassioAddonsInfo, fetchHassioAddonsInfo,
HassioAddonInfo, HassioAddonInfo,
HassioAddonRepository, HassioAddonRepository,
reloadHassioAddons, reloadHassioAddons,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import "../../../src/components/ha-icon-button";
import "../../../src/layouts/loading-screen";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/loading-screen";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import "../../../src/common/search/search-input";
import "./hassio-addon-repository";
import { supervisorTabs } from "../hassio-panel";
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories"; import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
import { supervisorTabs } from "../hassio-panel";
import "./hassio-addon-repository";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => { const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
if (a.slug === "local") { if (a.slug === "local") {
@@ -94,27 +96,17 @@ class HassioAddonStore extends LitElement {
.tabs=${supervisorTabs} .tabs=${supervisorTabs}
> >
<span slot="header">Add-on store</span> <span slot="header">Add-on store</span>
<paper-menu-button <ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
close-on-activate <mwc-icon-button slot="trigger" alt="menu">
no-animations <ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
horizontal-align="right" </mwc-icon-button>
horizontal-offset="-5" <mwc-list-item @tap=${this._manageRepositories}>
slot="toolbar-icon" Repositories
> </mwc-list-item>
<ha-icon-button <mwc-list-item @tap=${this.refreshData}>
icon="hassio:dots-vertical" Reload
slot="dropdown-trigger" </mwc-list-item>
alt="menu" </ha-button-menu>
></ha-icon-button>
<paper-listbox slot="dropdown-content" role="listbox">
<paper-item @tap=${this._manageRepositories}>
Repositories
</paper-item>
<paper-item @tap=${this.refreshData}>
Reload
</paper-item>
</paper-listbox>
</paper-menu-button>
${repos.length === 0 ${repos.length === 0
? html`<loading-screen></loading-screen>` ? html`<loading-screen></loading-screen>`
: html` : html`

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
@@ -14,6 +13,7 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "web-animations-js/web-animations-next-lite.min"; import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/ha-card";
import { import {
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
@@ -23,9 +23,9 @@ import {
fetchHassioHardwareAudio, fetchHassioHardwareAudio,
HassioHardwareAudioDevice, HassioHardwareAudioDevice,
} from "../../../../src/data/hassio/hardware"; } from "../../../../src/data/hassio/hardware";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-audio") @customElement("hassio-addon-audio")
@@ -46,7 +46,7 @@ class HassioAddonAudio extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<paper-card heading="Audio"> <ha-card header="Audio">
<div class="card-content"> <div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -92,7 +92,7 @@ class HassioAddonAudio extends LitElement {
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._saveSettings}>Save</mwc-button> <mwc-button @click=${this._saveSettings}>Save</mwc-button>
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -102,7 +102,7 @@ class HassioAddonAudio extends LitElement {
hassioStyle, hassioStyle,
css` css`
:host, :host,
paper-card, ha-card,
paper-dropdown-menu { paper-dropdown-menu {
display: block; display: block;
} }

View File

@@ -8,12 +8,10 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-audio"; import "./hassio-addon-audio";
import "./hassio-addon-config"; import "./hassio-addon-config";
import "./hassio-addon-network"; import "./hassio-addon-network";

View File

@@ -1,6 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea"; import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import "@polymer/paper-card/paper-card";
import { import {
css, css,
CSSResult, CSSResult,
@@ -13,6 +12,7 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor"; import "../../../../src/components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
import { import {
@@ -23,9 +23,8 @@ import {
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-config") @customElement("hassio-addon-config")
class HassioAddonConfig extends LitElement { class HassioAddonConfig extends LitElement {
@@ -46,7 +45,7 @@ class HassioAddonConfig extends LitElement {
return html` return html`
<h1>${this.addon.name}</h1> <h1>${this.addon.name}</h1>
<paper-card heading="Configuration"> <ha-card header="Configuration">
<div class="card-content"> <div class="card-content">
<ha-yaml-editor <ha-yaml-editor
@value-changed=${this._configChanged} @value-changed=${this._configChanged}
@@ -65,7 +64,7 @@ class HassioAddonConfig extends LitElement {
Save Save
</mwc-button> </mwc-button>
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -77,7 +76,7 @@ class HassioAddonConfig extends LitElement {
:host { :host {
display: block; display: block;
} }
paper-card { ha-card {
display: block; display: block;
} }
.card-actions { .card-actions {

View File

@@ -1,4 +1,3 @@
import "@polymer/paper-card/paper-card";
import { PaperInputElement } from "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import {
css, css,
@@ -11,15 +10,15 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-card";
import { import {
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
setHassioAddonOption, setHassioAddonOption,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
interface NetworkItem { interface NetworkItem {
@@ -53,7 +52,7 @@ class HassioAddonNetwork extends LitElement {
} }
return html` return html`
<paper-card heading="Network"> <ha-card header="Network">
<div class="card-content"> <div class="card-content">
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -90,7 +89,7 @@ class HassioAddonNetwork extends LitElement {
</mwc-button> </mwc-button>
<mwc-button @click=${this._saveTapped}>Save</mwc-button> <mwc-button @click=${this._saveTapped}>Save</mwc-button>
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -102,7 +101,7 @@ class HassioAddonNetwork extends LitElement {
:host { :host {
display: block; display: block;
} }
paper-card { ha-card {
display: block; display: block;
} }
.errors { .errors {

View File

@@ -1,5 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite"; import "@polymer/paper-spinner/paper-spinner-lite";
import "@polymer/paper-card/paper-card";
import { import {
css, css,
CSSResult, CSSResult,
@@ -9,16 +8,15 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import {
HassioAddonDetails,
fetchHassioAddonDocumentation,
} from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import {
fetchHassioAddonDocumentation,
HassioAddonDetails,
} from "../../../../src/data/hassio/addon";
import "../../../../src/layouts/loading-screen"; import "../../../../src/layouts/loading-screen";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
@customElement("hassio-addon-documentation-tab") @customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement { class HassioAddonDocumentationDashboard extends LitElement {
@@ -41,14 +39,14 @@ class HassioAddonDocumentationDashboard extends LitElement {
} }
return html` return html`
<div class="content"> <div class="content">
<paper-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<div class="card-content"> <div class="card-content">
${this._content ${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>` ? html`<ha-markdown .content=${this._content}></ha-markdown>`
: html`<loading-screen></loading-screen>`} : html`<loading-screen></loading-screen>`}
</div> </div>
</paper-card> </ha-card>
</div> </div>
`; `;
} }
@@ -58,7 +56,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
paper-card { ha-card {
display: block; display: block;
} }
.content { .content {

View File

@@ -1,4 +1,9 @@
import "../../../src/components/ha-icon-button"; import {
mdiCogs,
mdiFileDocument,
mdiInformationVariant,
mdiMathLog,
} from "@mdi/js";
import "@polymer/paper-spinner/paper-spinner-lite"; import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
@@ -14,18 +19,17 @@ import {
fetchHassioAddonInfo, fetchHassioAddonInfo,
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import "../../../src/layouts/hass-tabs-subpage";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import "./config/hassio-addon-audio"; import "./config/hassio-addon-audio";
import "./config/hassio-addon-config"; import "./config/hassio-addon-config";
import "./config/hassio-addon-network";
import "./hassio-addon-router";
import "./info/hassio-addon-info"; import "./info/hassio-addon-info";
import "./log/hassio-addon-logs"; import "./log/hassio-addon-logs";
import "./config/hassio-addon-network";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import "./hassio-addon-router";
@customElement("hassio-addon-dashboard") @customElement("hassio-addon-dashboard")
class HassioAddonDashboard extends LitElement { class HassioAddonDashboard extends LitElement {
@@ -59,7 +63,7 @@ class HassioAddonDashboard extends LitElement {
{ {
name: "Info", name: "Info",
path: `/hassio/addon/${this.addon.slug}/info`, path: `/hassio/addon/${this.addon.slug}/info`,
icon: "hassio:information-variant", iconPath: mdiInformationVariant,
}, },
]; ];
@@ -67,7 +71,7 @@ class HassioAddonDashboard extends LitElement {
addonTabs.push({ addonTabs.push({
name: "Documentation", name: "Documentation",
path: `/hassio/addon/${this.addon.slug}/documentation`, path: `/hassio/addon/${this.addon.slug}/documentation`,
icon: "hassio:file-document", iconPath: mdiFileDocument,
}); });
} }
@@ -76,12 +80,12 @@ class HassioAddonDashboard extends LitElement {
{ {
name: "Configuration", name: "Configuration",
path: `/hassio/addon/${this.addon.slug}/config`, path: `/hassio/addon/${this.addon.slug}/config`,
icon: "hassio:cogs", iconPath: mdiCogs,
}, },
{ {
name: "Log", name: "Log",
path: `/hassio/addon/${this.addon.slug}/logs`, path: `/hassio/addon/${this.addon.slug}/logs`,
icon: "hassio:math-log", iconPath: mdiMathLog,
} }
); );
} }
@@ -115,7 +119,6 @@ class HassioAddonDashboard extends LitElement {
css` css`
:host { :host {
color: var(--primary-text-color); color: var(--primary-text-color);
--paper-card-header-color: var(--primary-text-color);
} }
.content { .content {
padding: 24px 0 32px; padding: 24px 0 32px;

View File

@@ -1,15 +1,15 @@
import { customElement, property } from "lit-element";
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
} from "../../../src/layouts/hass-router-page"; } from "../../../src/layouts/hass-router-page";
import { customElement, property } from "lit-element";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import "./config/hassio-addon-config-tab";
import "./documentation/hassio-addon-documentation-tab";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi // Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./info/hassio-addon-info-tab"; import "./info/hassio-addon-info-tab";
import "./config/hassio-addon-config-tab";
import "./log/hassio-addon-log-tab"; import "./log/hassio-addon-log-tab";
import "./documentation/hassio-addon-documentation-tab";
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
@customElement("hassio-addon-router") @customElement("hassio-addon-router")
class HassioAddonRouter extends HassRouterPage { class HassioAddonRouter extends HassRouterPage {

View File

@@ -8,12 +8,10 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-info"; import "./hassio-addon-info";
@customElement("hassio-addon-info-tab") @customElement("hassio-addon-info-tab")

View File

@@ -1,5 +1,20 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card"; import {
mdiArrowUpBoldCircle,
mdiCheckCircle,
mdiChip,
mdiCircle,
mdiCursorDefaultClickOutline,
mdiDocker,
mdiExclamationThick,
mdiFlask,
mdiHomeAssistant,
mdiInformation,
mdiKey,
mdiNetwork,
mdiPound,
mdiShield,
} from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import { import {
css, css,
@@ -16,10 +31,11 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate"; import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button"; import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge"; import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch"; import "../../../../src/components/ha-switch";
import "../../../../src/components/ha-icon";
import { import {
fetchHassioAddonChangelog, fetchHassioAddonChangelog,
HassioAddonDetails, HassioAddonDetails,
@@ -30,23 +46,23 @@ import {
setHassioAddonSecurity, setHassioAddonSecurity,
uninstallHassioAddon, uninstallHassioAddon,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-card-content"; import "../../components/hassio-card-content";
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown"; import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
const STAGE_ICON = { const STAGE_ICON = {
stable: "mdi:check-circle", stable: mdiCheckCircle,
experimental: "mdi:flask", experimental: mdiFlask,
deprecated: "mdi:exclamation-thick", deprecated: mdiExclamationThick,
}; };
const PERMIS_DESC = { const PERMIS_DESC = {
stage: { stage: {
title: "Add-on Stage", title: "Add-on Stage",
description: `Add-ons can have one of three stages:\n\n<ha-icon icon='${STAGE_ICON.stable}'></ha-icon>**Stable**: These are add-ons ready to be used in production.\n<ha-icon icon='${STAGE_ICON.experimental}'></ha-icon>**Experimental**: These may contain bugs, and may be unfinished.\n<ha-icon icon='${STAGE_ICON.deprecated}'></ha-icon>**Deprecated**: These add-ons will no longer receive any updates.`, description: `Add-ons can have one of three stages:\n\n<ha-svg-icon path='${STAGE_ICON.stable}'></ha-svg-icon> **Stable**: These are add-ons ready to be used in production.\n\n<ha-svg-icon path='${STAGE_ICON.experimental}'></ha-svg-icon> **Experimental**: These may contain bugs, and may be unfinished.\n\n<ha-svg-icon path='${STAGE_ICON.deprecated}'></ha-svg-icon> **Deprecated**: These add-ons will no longer receive any updates.`,
}, },
rating: { rating: {
title: "Add-on Security Rating", title: "Add-on Security Rating",
@@ -116,7 +132,7 @@ class HassioAddonInfo extends LitElement {
return html` return html`
${this._computeUpdateAvailable ${this._computeUpdateAvailable
? html` ? html`
<paper-card heading="Update available! 🎉"> <ha-card header="Update available! 🎉">
<div class="card-content"> <div class="card-content">
<hassio-card-content <hassio-card-content
.hass=${this.hass} .hass=${this.hass}
@@ -124,7 +140,7 @@ class HassioAddonInfo extends LitElement {
.version_latest} is available" .version_latest} is available"
.description="You are currently running version ${this.addon .description="You are currently running version ${this.addon
.version}" .version}"
icon="hassio:arrow-up-bold-circle" icon=${mdiArrowUpBoldCircle}
iconClass="update" iconClass="update"
></hassio-card-content> ></hassio-card-content>
${!this.addon.available ${!this.addon.available
@@ -151,12 +167,13 @@ class HassioAddonInfo extends LitElement {
` `
: ""} : ""}
</div> </div>
</paper-card> </ha-card>
` `
: ""} : ""}
${!this.addon.protected ${!this.addon.protected
? html` ? html`
<paper-card heading="Warning: Protection mode is disabled!" class="warning"> <ha-card class="warning">
<div class="card-header">Warning: Protection mode is disabled!</div>
<div class="card-content"> <div class="card-content">
Protection mode on this add-on 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 add-on. Protection mode on this add-on 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 add-on.
</div> </div>
@@ -164,11 +181,11 @@ class HassioAddonInfo extends LitElement {
<mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button> <mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button>
</div> </div>
</div> </div>
</paper-card> </ha-card>
` `
: ""} : ""}
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
<div class="addon-header"> <div class="addon-header">
${!this.narrow ? this.addon.name : ""} ${!this.narrow ? this.addon.name : ""}
@@ -177,18 +194,18 @@ class HassioAddonInfo extends LitElement {
? html` ? html`
${this._computeIsRunning ${this._computeIsRunning
? html` ? html`
<ha-icon <ha-svg-icon
title="Add-on is running" title="Add-on is running"
class="running" class="running"
icon="hassio:circle" path=${mdiCircle}
></ha-icon> ></ha-svg-icon>
` `
: html` : html`
<ha-icon <ha-svg-icon
title="Add-on is stopped" title="Add-on is stopped"
class="stopped" class="stopped"
icon="hassio:circle" path=${mdiCircle}
></ha-icon> ></ha-svg-icon>
`} `}
` `
: html` ${this.addon.version_latest} `} : html` ${this.addon.version_latest} `}
@@ -232,10 +249,11 @@ class HassioAddonInfo extends LitElement {
})} })}
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="stage" id="stage"
.icon=${STAGE_ICON[this.addon.stage]}
label="stage" label="stage"
description="" description=""
></ha-label-badge> >
<ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
</ha-label-badge>
<ha-label-badge <ha-label-badge
class=${classMap({ class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)), green: [5, 6].includes(Number(this.addon.rating)),
@@ -253,10 +271,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="host_network" id="host_network"
icon="hassio:network"
label="host" label="host"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiNetwork}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.full_access ${this.addon.full_access
@@ -264,10 +283,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="full_access" id="full_access"
icon="hassio:chip"
label="hardware" label="hardware"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiChip}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.homeassistant_api ${this.addon.homeassistant_api
@@ -275,10 +295,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="homeassistant_api" id="homeassistant_api"
icon="hassio:home-assistant"
label="hass" label="hass"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this._computeHassioApi ${this._computeHassioApi
@@ -286,10 +307,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="hassio_api" id="hassio_api"
icon="hassio:home-assistant"
label="hassio" label="hassio"
.description=${this.addon.hassio_role} .description=${this.addon.hassio_role}
></ha-label-badge> >
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.docker_api ${this.addon.docker_api
@@ -297,10 +319,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="docker_api" id="docker_api"
icon="hassio:docker"
label="docker" label="docker"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiDocker}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.host_pid ${this.addon.host_pid
@@ -308,10 +331,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="host_pid" id="host_pid"
icon="hassio:pound"
label="host pid" label="host pid"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiPound}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.apparmor ${this.addon.apparmor
@@ -320,10 +344,11 @@ class HassioAddonInfo extends LitElement {
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
class=${this._computeApparmorClassName} class=${this._computeApparmorClassName}
id="apparmor" id="apparmor"
icon="hassio:shield"
label="apparmor" label="apparmor"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiShield}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.auth_api ${this.addon.auth_api
@@ -331,10 +356,11 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="auth_api" id="auth_api"
icon="hassio:key"
label="auth" label="auth"
description="" description=""
></ha-label-badge> >
<ha-svg-icon path=${mdiKey}></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
${this.addon.ingress ${this.addon.ingress
@@ -342,10 +368,13 @@ class HassioAddonInfo extends LitElement {
<ha-label-badge <ha-label-badge
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="ingress" id="ingress"
icon="hassio:cursor-default-click-outline"
label="ingress" label="ingress"
description="" description=""
></ha-label-badge> >
<ha-svg-icon
path=${mdiCursorDefaultClickOutline}
></ha-svg-icon>
</ha-label-badge>
` `
: ""} : ""}
</div> </div>
@@ -399,7 +428,7 @@ class HassioAddonInfo extends LitElement {
<div> <div>
Protection mode Protection mode
<span> <span>
<ha-icon icon="hassio:information"></ha-icon> <ha-svg-icon path=${mdiInformation}></ha-svg-icon>
<paper-tooltip> <paper-tooltip>
Grant the add-on elevated system access. Grant the add-on elevated system access.
</paper-tooltip> </paper-tooltip>
@@ -502,17 +531,17 @@ class HassioAddonInfo extends LitElement {
</ha-progress-button> </ha-progress-button>
`} `}
</div> </div>
</paper-card> </ha-card>
${this.addon.long_description ${this.addon.long_description
? html` ? html`
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
<ha-markdown <ha-markdown
.content=${this.addon.long_description} .content=${this.addon.long_description}
></ha-markdown> ></ha-markdown>
</div> </div>
</paper-card> </ha-card>
` `
: ""} : ""}
`; `;
@@ -526,16 +555,21 @@ class HassioAddonInfo extends LitElement {
:host { :host {
display: block; display: block;
} }
paper-card { ha-card {
display: block; display: block;
margin-bottom: 16px; margin-bottom: 16px;
} }
paper-card.warning { ha-card.warning {
background-color: var(--google-red-500); background-color: var(--google-red-500);
color: white; color: white;
--paper-card-header-color: white;
} }
paper-card.warning mwc-button { ha-card.warning .card-header {
color: white;
}
ha-card.warning .card-content {
color: white;
}
ha-card.warning mwc-button {
--mdc-theme-primary: white !important; --mdc-theme-primary: white !important;
} }
.warning { .warning {
@@ -548,7 +582,7 @@ class HassioAddonInfo extends LitElement {
.addon-header { .addon-header {
padding-left: 8px; padding-left: 8px;
font-size: 24px; font-size: 24px;
color: var(--paper-card-header-color, --primary-text-color); color: var(--ha-card-header-color, --primary-text-color);
} }
.addon-version { .addon-version {
float: right; float: right;
@@ -575,7 +609,7 @@ class HassioAddonInfo extends LitElement {
width: 180px; width: 180px;
display: inline-block; display: inline-block;
} }
.state ha-icon { .state ha-svg-icon {
width: 16px; width: 16px;
height: 16px; height: 16px;
color: var(--secondary-text-color); color: var(--secondary-text-color);
@@ -583,10 +617,10 @@ class HassioAddonInfo extends LitElement {
ha-switch { ha-switch {
display: flex; display: flex;
} }
ha-icon.running { ha-svg-icon.running {
color: var(--paper-green-400); color: var(--paper-green-400);
} }
ha-icon.stopped { ha-svg-icon.stopped {
color: var(--google-red-300); color: var(--google-red-300);
} }
ha-call-api-button { ha-call-api-button {
@@ -664,7 +698,7 @@ class HassioAddonInfo extends LitElement {
} }
private _showMoreInfo(ev): void { private _showMoreInfo(ev): void {
const id = ev.target.getAttribute("id"); const id = ev.currentTarget.id;
showHassioMarkdownDialog(this, { showHassioMarkdownDialog(this, {
title: PERMIS_DESC[id].title, title: PERMIS_DESC[id].title,
content: PERMIS_DESC[id].description, content: PERMIS_DESC[id].description,

View File

@@ -1,5 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite"; import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
@@ -9,12 +8,10 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HomeAssistant } from "../../../../src/types";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { hassioStyle } from "../../resources/hassio-style";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import "./hassio-addon-logs"; import "./hassio-addon-logs";
@customElement("hassio-addon-log-tab") @customElement("hassio-addon-log-tab")

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { import {
css, css,
CSSResult, CSSResult,
@@ -9,6 +8,7 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/ha-card";
import { import {
fetchHassioAddonLogs, fetchHassioAddonLogs,
HassioAddonDetails, HassioAddonDetails,
@@ -36,7 +36,7 @@ class HassioAddonLogs extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<h1>${this.addon.name}</h1> <h1>${this.addon.name}</h1>
<paper-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<div class="card-content"> <div class="card-content">
${this._content ${this._content
@@ -48,7 +48,7 @@ class HassioAddonLogs extends LitElement {
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._refresh}>Refresh</mwc-button> <mwc-button @click=${this._refresh}>Refresh</mwc-button>
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -58,7 +58,7 @@ class HassioAddonLogs extends LitElement {
hassioStyle, hassioStyle,
css` css`
:host, :host,
paper-card { ha-card {
display: block; display: block;
} }
.errors { .errors {

View File

@@ -1,3 +1,4 @@
import { mdiHelpCircle } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
@@ -8,7 +9,7 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../src/components/ha-relative-time"; import "../../../src/components/ha-relative-time";
import "../../../src/components/ha-icon"; import "../../../src/components/ha-svg-icon";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@customElement("hassio-card-content") @customElement("hassio-card-content")
@@ -31,7 +32,7 @@ class HassioCardContent extends LitElement {
@property() public iconClass?: string; @property() public iconClass?: string;
@property() public icon = "hass:help-circle"; @property() public icon = mdiHelpCircle;
@property() public iconImage?: string; @property() public iconImage?: string;
@@ -48,11 +49,11 @@ class HassioCardContent extends LitElement {
</div> </div>
` `
: html` : html`
<ha-icon <ha-svg-icon
class=${this.iconClass} class=${this.iconClass}
.icon=${this.icon} .path=${this.icon}
.title=${this.iconTitle} .title=${this.iconTitle}
></ha-icon> ></ha-svg-icon>
`} `}
<div> <div>
<div class="title"> <div class="title">
@@ -78,25 +79,25 @@ class HassioCardContent extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-icon { ha-svg-icon {
margin-right: 24px; margin-right: 24px;
margin-left: 8px; margin-left: 8px;
margin-top: 12px; margin-top: 12px;
float: left; float: left;
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-icon.update { ha-svg-icon.update {
color: var(--paper-orange-400); color: var(--paper-orange-400);
} }
ha-icon.running, ha-svg-icon.running,
ha-icon.installed { ha-svg-icon.installed {
color: var(--paper-green-400); color: var(--paper-green-400);
} }
ha-icon.hassupdate, ha-svg-icon.hassupdate,
ha-icon.snapshot { ha-svg-icon.snapshot {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }
ha-icon.not_available { ha-svg-icon.not_available {
color: var(--google-red-500); color: var(--google-red-500);
} }
.title { .title {

View File

@@ -1,4 +1,4 @@
import "@polymer/paper-card/paper-card"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
@@ -10,6 +10,7 @@ import {
} from "lit-element"; } from "lit-element";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import "../../../src/components/ha-card";
import { HassioAddonInfo } from "../../../src/data/hassio/addon"; import { HassioAddonInfo } from "../../../src/data/hassio/addon";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -29,19 +30,19 @@ class HassioAddons extends LitElement {
<div class="card-group"> <div class="card-group">
${!this.addons ${!this.addons
? html` ? html`
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
You don't have any add-ons installed yet. Head over to You don't have any add-ons installed yet. Head over to
<a href="#" @click=${this._openStore}>the add-on store</a> <a href="#" @click=${this._openStore}>the add-on store</a>
to get started! to get started!
</div> </div>
</paper-card> </ha-card>
` `
: this.addons : this.addons
.sort((a, b) => (a.name > b.name ? 1 : -1)) .sort((a, b) => (a.name > b.name ? 1 : -1))
.map( .map(
(addon) => html` (addon) => html`
<paper-card .addon=${addon} @click=${this._addonTapped}> <ha-card .addon=${addon} @click=${this._addonTapped}>
<div class="card-content"> <div class="card-content">
<hassio-card-content <hassio-card-content
.hass=${this.hass} .hass=${this.hass}
@@ -51,8 +52,8 @@ class HassioAddons extends LitElement {
.showTopbar=${addon.installed !== addon.version} .showTopbar=${addon.installed !== addon.version}
topbarClass="update" topbarClass="update"
.icon=${addon.installed !== addon.version .icon=${addon.installed !== addon.version
? "hassio:arrow-up-bold-circle" ? mdiArrowUpBoldCircle
: "hassio:puzzle"} : mdiPuzzle}
.iconTitle=${addon.state !== "started" .iconTitle=${addon.state !== "started"
? "Add-on is stopped" ? "Add-on is stopped"
: addon.installed !== addon.version : addon.installed !== addon.version
@@ -75,7 +76,7 @@ class HassioAddons extends LitElement {
: undefined} : undefined}
></hassio-card-content> ></hassio-card-content>
</div> </div>
</paper-card> </ha-card>
` `
)} )}
</div> </div>
@@ -88,7 +89,7 @@ class HassioAddons extends LitElement {
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
paper-card { ha-card {
cursor: pointer; cursor: pointer;
} }
`, `,

View File

@@ -12,14 +12,13 @@ import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import "../../../src/layouts/hass-tabs-subpage"; import { supervisorTabs } from "../hassio-panel";
import "./hassio-addons"; import "./hassio-addons";
import "./hassio-update"; import "./hassio-update";
import { supervisorTabs } from "../hassio-panel";
@customElement("hassio-dashboard") @customElement("hassio-dashboard")
class HassioDashboard extends LitElement { class HassioDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;

View File

@@ -1,5 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card"; import { mdiHomeAssistant } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
@@ -10,15 +10,15 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../src/components/buttons/ha-call-api-button"; import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import { HassioHassOSInfo } from "../../../src/data/hassio/host"; import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import { import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import "../../../src/components/ha-icon";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import "../components/hassio-card-content";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-update") @customElement("hassio-update")
@@ -72,7 +72,7 @@ export class HassioUpdate extends LitElement {
`https://${ `https://${
this.hassInfo.version_latest.includes("b") ? "rc" : "www" this.hassInfo.version_latest.includes("b") ? "rc" : "www"
}.home-assistant.io/latest-release-notes/`, }.home-assistant.io/latest-release-notes/`,
"hassio:home-assistant" mdiHomeAssistant
)} )}
${this._renderUpdateCard( ${this._renderUpdateCard(
"Supervisor", "Supervisor",
@@ -107,12 +107,12 @@ export class HassioUpdate extends LitElement {
return html``; return html``;
} }
return html` return html`
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
${icon ${icon
? html` ? html`
<div class="icon"> <div class="icon">
<ha-icon .icon=${icon}></ha-icon> <ha-svg-icon .path=${icon}></ha-svg-icon>
</div> </div>
` `
: ""} : ""}
@@ -133,7 +133,7 @@ export class HassioUpdate extends LitElement {
Update Update
</ha-call-api-button> </ha-call-api-button>
</div> </div>
</paper-card> </ha-card>
`; `;
} }

View File

@@ -1,7 +1,3 @@
import "@polymer/app-layout/app-toolbar/app-toolbar";
import { PaperDialogElement } from "@polymer/paper-dialog";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "../../../../src/components/ha-icon-button";
import { import {
css, css,
CSSResult, CSSResult,
@@ -9,46 +5,50 @@ import {
html, html,
LitElement, LitElement,
property, property,
query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/dialog/ha-paper-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown"; import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
@customElement("dialog-hassio-markdown") @customElement("dialog-hassio-markdown")
class HassioMarkdownDialog extends LitElement { class HassioMarkdownDialog extends LitElement {
@property() public hass!: HomeAssistant;
@property() public title!: string; @property() public title!: string;
@property() public content!: string; @property() public content!: string;
@query("#dialog") private _dialog!: PaperDialogElement; @property() private _opened = false;
public showDialog(params: HassioMarkdownDialogParams) { public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title; this.title = params.title;
this.content = params.content; this.content = params.content;
this._dialog.open(); this._opened = true;
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._opened) {
return html``;
}
return html` return html`
<ha-paper-dialog id="dialog" with-backdrop=""> <ha-dialog
<app-toolbar> open
<ha-icon-button @closing=${this._closeDialog}
icon="hassio:close" .heading=${createCloseHeading(this.hass, this.title)}
dialog-dismiss="" >
></ha-icon-button> <ha-markdown .content=${this.content || ""}></ha-markdown>
<div main-title="">${this.title}</div> </ha-dialog>
</app-toolbar>
<paper-dialog-scrollable>
<ha-markdown .content=${this.content || ""}></ha-markdown>
</paper-dialog-scrollable>
</ha-paper-dialog>
`; `;
} }
private _closeDialog(): void {
this._opened = false;
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
haStyleDialog, haStyleDialog,

View File

@@ -1,9 +1,11 @@
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiDelete } from "@mdi/js";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import "@polymer/paper-spinner/paper-spinner";
import type { PaperInputElement } from "@polymer/paper-input/paper-input"; import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-spinner/paper-spinner";
import { import {
css, css,
CSSResult, CSSResult,
@@ -16,16 +18,14 @@ import {
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-icon-button"; import "../../../../src/components/ha-svg-icon";
import {
fetchHassioAddonsInfo,
HassioAddonRepository,
} from "../../../../src/data/hassio/addon";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import {
HassioAddonRepository,
fetchHassioAddonsInfo,
} from "../../../../src/data/hassio/addon";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { HassioRepositoryDialogParams } from "./show-dialog-repositories"; import { HassioRepositoryDialogParams } from "./show-dialog-repositories";
@customElement("dialog-hassio-repositories") @customElement("dialog-hassio-repositories")
@@ -84,12 +84,13 @@ class HassioRepositoriesDialog extends LitElement {
<div secondary>${repo.maintainer}</div> <div secondary>${repo.maintainer}</div>
<div secondary>${repo.url}</div> <div secondary>${repo.url}</div>
</paper-item-body> </paper-item-body>
<ha-icon-button <mwc-icon-button
.slug=${repo.slug} .slug=${repo.slug}
title="Remove" title="Remove"
@click=${this._removeRepository} @click=${this._removeRepository}
icon="hassio:delete" >
></ha-icon-button> <ha-svg-icon path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
</paper-item> </paper-item>
`; `;
}) })
@@ -194,7 +195,7 @@ class HassioRepositoriesDialog extends LitElement {
} }
private async _removeRepository(ev: Event) { private async _removeRepository(ev: Event) {
const slug = (ev.target as any).slug; const slug = (ev.currentTarget as any).slug;
const repositories = this._filteredRepositories(this._repos); const repositories = this._filteredRepositories(this._repos);
const repository = repositories.find((repo) => { const repository = repositories.find((repo) => {
return repo.slug === slug; return repo.slug === slug;

View File

@@ -1,6 +1,6 @@
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-repositories";
import { HassioAddonRepository } from "../../../../src/data/hassio/addon"; import { HassioAddonRepository } from "../../../../src/data/hassio/addon";
import "./dialog-hassio-repositories";
export interface HassioRepositoryDialogParams { export interface HassioRepositoryDialogParams {
repos: HassioAddonRepository[]; repos: HassioAddonRepository[];

View File

@@ -1,10 +1,6 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/app-layout/app-toolbar/app-toolbar"; import { mdiDelete, mdiDownload, mdiHistory } from "@mdi/js";
import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; import { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import { PaperDialogElement } from "@polymer/paper-dialog";
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
import "../../../../src/components/ha-icon-button";
import "../../../../src/components/ha-icon";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
@@ -13,10 +9,10 @@ import {
html, html,
LitElement, LitElement,
property, property,
query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/dialog/ha-paper-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
import { import {
fetchHassioSnapshotInfo, fetchHassioSnapshotInfo,
@@ -76,7 +72,7 @@ class HassioSnapshotDialog extends LitElement {
@property() private _error?: string; @property() private _error?: string;
@property() private snapshot?: HassioSnapshotDetail; @property() private _snapshot?: HassioSnapshotDetail;
@property() private _folders!: FolderItem[]; @property() private _folders!: FolderItem[];
@@ -88,49 +84,35 @@ class HassioSnapshotDialog extends LitElement {
@property() private _restoreHass: boolean | null | undefined = true; @property() private _restoreHass: boolean | null | undefined = true;
@query("#dialog") private _dialog!: PaperDialogElement;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioSnapshotDialogParams) {
this.snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._folders = _computeFolders( this._folders = _computeFolders(
this.snapshot.folders this._snapshot.folders
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1)); ).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
this._addons = _computeAddons( this._addons = _computeAddons(
this.snapshot.addons this._snapshot.addons
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1)); ).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
this._dialogParams = params; this._dialogParams = params;
try {
this._dialog.open();
} catch {
await this.showDialog(params);
}
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.snapshot) { if (!this._dialogParams || !this._snapshot) {
return html``; return html``;
} }
return html` return html`
<ha-paper-dialog <ha-dialog
id="dialog" open
with-backdrop="" stacked
.on-iron-overlay-closed=${this._dialogClosed} @closing=${this._closeDialog}
.heading=${createCloseHeading(this.hass, this._computeName)}
> >
<app-toolbar>
<ha-icon-button
icon="hassio:close"
dialog-dismiss=""
></ha-icon-button>
<div main-title="">${this._computeName}</div>
</app-toolbar>
<div class="details"> <div class="details">
${this.snapshot.type === "full" ${this._snapshot.type === "full"
? "Full snapshot" ? "Full snapshot"
: "Partial snapshot"} : "Partial snapshot"}
(${this._computeSize})<br /> (${this._computeSize})<br />
${this._formatDatetime(this.snapshot.date)} ${this._formatDatetime(this._snapshot.date)}
</div> </div>
<div>Home Assistant:</div> <div>Home Assistant:</div>
<paper-checkbox <paper-checkbox
@@ -139,7 +121,7 @@ class HassioSnapshotDialog extends LitElement {
this._restoreHass = (ev.target as PaperCheckboxElement).checked; this._restoreHass = (ev.target as PaperCheckboxElement).checked;
}}" }}"
> >
Home Assistant ${this.snapshot.homeassistant} Home Assistant ${this._snapshot.homeassistant}
</paper-checkbox> </paper-checkbox>
${this._folders.length ${this._folders.length
? html` ? html`
@@ -183,7 +165,7 @@ class HassioSnapshotDialog extends LitElement {
</paper-dialog-scrollable> </paper-dialog-scrollable>
` `
: ""} : ""}
${this.snapshot.protected ${this._snapshot.protected
? html` ? html`
<paper-input <paper-input
autofocus="" autofocus=""
@@ -197,37 +179,35 @@ class HassioSnapshotDialog extends LitElement {
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""} ${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
<div>Actions:</div> <div>Actions:</div>
<ul class="buttons">
<li> <mwc-button @click=${this._downloadClicked} slot="primaryAction">
<mwc-button @click=${this._downloadClicked}> <ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
<ha-icon icon="hassio:download" class="icon"></ha-icon> Download Snapshot
Download Snapshot </mwc-button>
</mwc-button>
</li> <mwc-button
<li> @click=${this._partialRestoreClicked}
<mwc-button @click=${this._partialRestoreClicked}> slot="secondaryAction"
<ha-icon icon="hassio:history" class="icon"> </ha-icon> >
Restore Selected <ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
</mwc-button> Restore Selected
</li> </mwc-button>
${this.snapshot.type === "full" ${this._snapshot.type === "full"
? html` ? html`
<li> <mwc-button
<mwc-button @click=${this._fullRestoreClicked}> @click=${this._fullRestoreClicked}
<ha-icon icon="hassio:history" class="icon"> </ha-icon> slot="secondaryAction"
Wipe &amp; restore >
</mwc-button> <ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
</li> Wipe &amp; restore
` </mwc-button>
: ""} `
<li> : ""}
<mwc-button @click=${this._deleteClicked}> <mwc-button @click=${this._deleteClicked} slot="secondaryAction">
<ha-icon icon="hassio:delete" class="icon warning"> </ha-icon> <ha-svg-icon path=${mdiDelete} class="icon warning"></ha-svg-icon>
<span class="warning">Delete Snapshot</span> <span class="warning">Delete Snapshot</span>
</mwc-button> </mwc-button>
</li> </ha-dialog>
</ul>
</ha-paper-dialog>
`; `;
} }
@@ -235,37 +215,10 @@ class HassioSnapshotDialog extends LitElement {
return [ return [
haStyleDialog, haStyleDialog,
css` css`
ha-paper-dialog {
min-width: 350px;
font-size: 14px;
border-radius: 2px;
}
app-toolbar {
margin: 0;
padding: 0 16px;
color: var(--primary-text-color);
background-color: var(--secondary-background-color);
}
app-toolbar [main-title] {
margin-left: 16px;
}
ha-paper-dialog-scrollable {
margin: 0;
}
paper-checkbox { paper-checkbox {
display: block; display: block;
margin: 4px; margin: 4px;
} }
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog {
max-height: 100%;
height: 100%;
}
app-toolbar {
color: var(--text-primary-color);
background-color: var(--primary-color);
}
}
.details { .details {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
@@ -336,7 +289,7 @@ class HassioSnapshotDialog extends LitElement {
folders, folders,
}; };
if (this.snapshot!.protected) { if (this._snapshot!.protected) {
data.password = this._snapshotPassword; data.password = this._snapshotPassword;
} }
@@ -344,13 +297,13 @@ class HassioSnapshotDialog extends LitElement {
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this.snapshot!.slug}/restore/partial`, `hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
data data
) )
.then( .then(
() => { () => {
alert("Snapshot restored!"); alert("Snapshot restored!");
this._dialog.close(); this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -363,20 +316,20 @@ class HassioSnapshotDialog extends LitElement {
return; return;
} }
const data = this.snapshot!.protected const data = this._snapshot!.protected
? { password: this._snapshotPassword } ? { password: this._snapshotPassword }
: undefined; : undefined;
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/snapshots/${this.snapshot!.slug}/restore/full`, `hassio/snapshots/${this._snapshot!.slug}/restore/full`,
data data
) )
.then( .then(
() => { () => {
alert("Snapshot restored!"); alert("Snapshot restored!");
this._dialog.close(); this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -391,11 +344,11 @@ class HassioSnapshotDialog extends LitElement {
this.hass this.hass
.callApi("POST", `hassio/snapshots/${this.snapshot!.slug}/remove`) .callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
.then( .then(
() => { () => {
this._dialog.close();
this._dialogParams!.onDelete(); this._dialogParams!.onDelete();
this._closeDialog();
}, },
(error) => { (error) => {
this._error = error.body.message; this._error = error.body.message;
@@ -408,7 +361,7 @@ class HassioSnapshotDialog extends LitElement {
try { try {
signedPath = await getSignedPath( signedPath = await getSignedPath(
this.hass, this.hass,
`/api/hassio/snapshots/${this.snapshot!.slug}/download` `/api/hassio/snapshots/${this._snapshot!.slug}/download`
); );
} catch (err) { } catch (err) {
alert(`Error: ${err.message}`); alert(`Error: ${err.message}`);
@@ -419,19 +372,19 @@ class HassioSnapshotDialog extends LitElement {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = signedPath.path; a.href = signedPath.path;
a.download = `Hass_io_${name}.tar`; a.download = `Hass_io_${name}.tar`;
this._dialog.appendChild(a); this.shadowRoot!.appendChild(a);
a.click(); a.click();
this._dialog.removeChild(a); this.shadowRoot!.removeChild(a);
} }
private get _computeName() { private get _computeName() {
return this.snapshot return this._snapshot
? this.snapshot.name || this.snapshot.slug ? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot"; : "Unnamed snapshot";
} }
private get _computeSize() { private get _computeSize() {
return Math.ceil(this.snapshot!.size * 10) / 10 + " MB"; return Math.ceil(this._snapshot!.size * 10) / 10 + " MB";
} }
private _formatDatetime(datetime) { private _formatDatetime(datetime) {
@@ -445,9 +398,9 @@ class HassioSnapshotDialog extends LitElement {
}); });
} }
private _dialogClosed() { private _closeDialog() {
this._dialogParams = undefined; this._dialogParams = undefined;
this.snapshot = undefined; this._snapshot = undefined;
this._snapshotPassword = ""; this._snapshotPassword = "";
this._folders = []; this._folders = [];
this._addons = []; this._addons = [];

View File

@@ -3,11 +3,11 @@ import {
HassioAddonDetails, HassioAddonDetails,
restartHassioAddon, restartHassioAddon,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { HomeAssistant } from "../../../src/types";
import { import {
showConfirmationDialog,
showAlertDialog, showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box"; } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
export const suggestAddonRestart = async ( export const suggestAddonRestart = async (
element: LitElement, element: LitElement,

View File

@@ -1,4 +1,3 @@
import "../../src/components/ha-icon-button";
import { PolymerElement } from "@polymer/polymer"; import { PolymerElement } from "@polymer/polymer";
import { customElement, property, PropertyValues } from "lit-element"; import { customElement, property, PropertyValues } from "lit-element";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
@@ -34,12 +33,6 @@ import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast. // Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel"; import "./hassio-panel";
// The register callback of the IronA11yKeysBehavior inside ha-icon-button
// is not called, causing _keyBindings to be uninitiliazed for ha-icon-button,
// causing an exception when added to DOM. When transpiled to ES5, this will
// break the build.
customElements.get("ha-icon-button").prototype._keyBindings = {};
@customElement("hassio-main") @customElement("hassio-main")
class HassioMain extends ProvideHassLitMixin(HassRouterPage) { class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;

View File

@@ -1,3 +1,4 @@
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
import { import {
customElement, customElement,
html, html,
@@ -5,37 +6,36 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host"; import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import { import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor"; } from "../../src/data/hassio/supervisor";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import "../../src/resources/ha-style"; import "../../src/resources/ha-style";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router"; import "./hassio-panel-router";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
export const supervisorTabs: PageNavigation[] = [ export const supervisorTabs: PageNavigation[] = [
{ {
name: "Dashboard", name: "Dashboard",
path: `/hassio/dashboard`, path: `/hassio/dashboard`,
icon: "hassio:view-dashboard", iconPath: mdiViewDashboard,
}, },
{ {
name: "Add-on store", name: "Add-on store",
path: `/hassio/store`, path: `/hassio/store`,
icon: "hassio:store", iconPath: mdiStore,
}, },
{ {
name: "Snapshots", name: "Snapshots",
path: `/hassio/snapshots`, path: `/hassio/snapshots`,
icon: "hassio:backup-restore", iconPath: mdiBackupRestore,
}, },
{ {
name: "System", name: "System",
path: `/hassio/system`, path: `/hassio/system`,
icon: "hassio:cogs", iconPath: mdiCogs,
}, },
]; ];

View File

@@ -86,9 +86,6 @@ class HassioIngressView extends LitElement {
height: 100%; height: 100%;
border: 0; border: 0;
} }
ha-icon-button {
color: var(--text-primary-color);
}
`; `;
} }
} }

View File

@@ -1,5 +1,6 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card"; import "@material/mwc-icon-button";
import { mdiPackageVariant, mdiPackageVariantClosed, mdiReload } from "@mdi/js";
import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-checkbox/paper-checkbox";
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox"; import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
@@ -18,6 +19,8 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon";
import { import {
createHassioFullSnapshot, createHassioFullSnapshot,
createHassioPartialSnapshot, createHassioPartialSnapshot,
@@ -28,15 +31,14 @@ import {
reloadHassioSnapshots, reloadHassioSnapshots,
} from "../../../src/data/hassio/snapshot"; } from "../../../src/data/hassio/snapshot";
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor"; import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage";
import { PolymerChangedEvent } from "../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../src/polymer-types";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import "../../../src/layouts/hass-tabs-subpage";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { hassioStyle } from "../resources/hassio-style";
import { supervisorTabs } from "../hassio-panel"; import { supervisorTabs } from "../hassio-panel";
import { hassioStyle } from "../resources/hassio-style";
interface CheckboxItem { interface CheckboxItem {
slug: string; slug: string;
@@ -98,12 +100,13 @@ class HassioSnapshots extends LitElement {
> >
<span slot="header">Snapshots</span> <span slot="header">Snapshots</span>
<ha-icon-button <mwc-icon-button
icon="hassio:reload"
slot="toolbar-icon" slot="toolbar-icon"
aria-label="Reload snapshots" aria-label="Reload snapshots"
@click=${this.refreshData} @click=${this.refreshData}
></ha-icon-button> >
<ha-svg-icon path=${mdiReload}></ha-svg-icon>
</mwc-icon-button>
<div class="content"> <div class="content">
<h1> <h1>
@@ -114,7 +117,7 @@ class HassioSnapshots extends LitElement {
Home Assistant instance. Home Assistant instance.
</p> </p>
<div class="card-group"> <div class="card-group">
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
<paper-input <paper-input
autofocus autofocus
@@ -195,7 +198,7 @@ class HassioSnapshots extends LitElement {
Create Create
</mwc-button> </mwc-button>
</div> </div>
</paper-card> </ha-card>
</div> </div>
<h1>Available snapshots</h1> <h1>Available snapshots</h1>
@@ -204,15 +207,15 @@ class HassioSnapshots extends LitElement {
? undefined ? undefined
: this._snapshots.length === 0 : this._snapshots.length === 0
? html` ? html`
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
You don't have any snapshots yet. You don't have any snapshots yet.
</div> </div>
</paper-card> </ha-card>
` `
: this._snapshots.map( : this._snapshots.map(
(snapshot) => html` (snapshot) => html`
<paper-card <ha-card
class="pointer" class="pointer"
.snapshot=${snapshot} .snapshot=${snapshot}
@click=${this._snapshotClicked} @click=${this._snapshotClicked}
@@ -224,12 +227,12 @@ class HassioSnapshots extends LitElement {
.description=${this._computeDetails(snapshot)} .description=${this._computeDetails(snapshot)}
.datetime=${snapshot.date} .datetime=${snapshot.date}
.icon=${snapshot.type === "full" .icon=${snapshot.type === "full"
? "hassio:package-variant-closed" ? mdiPackageVariantClosed
: "hassio:package-variant"} : mdiPackageVariant}
.icon-class="snapshot" .icon-class="snapshot"
></hassio-card-content> ></hassio-card-content>
</div> </div>
</paper-card> </ha-card>
` `
)} )}
</div> </div>

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { import {
css, css,
CSSResult, CSSResult,
@@ -12,23 +11,23 @@ import {
import "../../../src/components/buttons/ha-call-api-button"; import "../../../src/components/buttons/ha-call-api-button";
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware"; import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import { import {
changeHostOptions,
fetchHassioHostInfo, fetchHassioHostInfo,
HassioHassOSInfo, HassioHassOSInfo,
HassioHostInfo as HassioHostInfoType, HassioHostInfo as HassioHostInfoType,
rebootHost, rebootHost,
shutdownHost, shutdownHost,
updateOS, updateOS,
changeHostOptions,
} from "../../../src/data/hassio/host"; } from "../../../src/data/hassio/host";
import {
showAlertDialog,
showConfirmationDialog,
showPromptDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import {
showConfirmationDialog,
showAlertDialog,
showPromptDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
@customElement("hassio-host-info") @customElement("hassio-host-info")
class HassioHostInfo extends LitElement { class HassioHostInfo extends LitElement {
@@ -42,7 +41,7 @@ class HassioHostInfo extends LitElement {
public render(): TemplateResult | void { public render(): TemplateResult | void {
return html` return html`
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
<h2>Host system</h2> <h2>Host system</h2>
<table class="info"> <table class="info">
@@ -113,7 +112,7 @@ class HassioHostInfo extends LitElement {
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> ` ? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
: ""} : ""}
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -122,7 +121,7 @@ class HassioHostInfo extends LitElement {
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
paper-card { ha-card {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }

View File

@@ -1,5 +1,4 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-card/paper-card";
import { import {
css, css,
CSSResult, CSSResult,
@@ -11,15 +10,16 @@ import {
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-call-api-button"; import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/ha-card";
import { import {
HassioSupervisorInfo as HassioSupervisorInfoType, HassioSupervisorInfo as HassioSupervisorInfoType,
setSupervisorOption, setSupervisorOption,
SupervisorOptions, SupervisorOptions,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
@customElement("hassio-supervisor-info") @customElement("hassio-supervisor-info")
class HassioSupervisorInfo extends LitElement { class HassioSupervisorInfo extends LitElement {
@@ -31,7 +31,7 @@ class HassioSupervisorInfo extends LitElement {
public render(): TemplateResult | void { public render(): TemplateResult | void {
return html` return html`
<paper-card> <ha-card>
<div class="card-content"> <div class="card-content">
<h2>Supervisor</h2> <h2>Supervisor</h2>
<table class="info"> <table class="info">
@@ -92,7 +92,7 @@ class HassioSupervisorInfo extends LitElement {
` `
: ""} : ""}
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -101,7 +101,7 @@ class HassioSupervisorInfo extends LitElement {
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
paper-card { ha-card {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }

View File

@@ -2,7 +2,6 @@ import "@material/mwc-button";
import "@polymer/paper-dropdown-menu/paper-dropdown-menu"; import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item"; import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-card/paper-card";
import { import {
css, css,
CSSResult, CSSResult,
@@ -12,14 +11,13 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../src/components/ha-card";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/loading-screen";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import "../components/hassio-ansi-to-html"; import "../components/hassio-ansi-to-html";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import "../../../src/layouts/loading-screen";
interface LogProvider { interface LogProvider {
key: string; key: string;
@@ -70,7 +68,7 @@ class HassioSupervisorLog extends LitElement {
public render(): TemplateResult | void { public render(): TemplateResult | void {
return html` return html`
<paper-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${this.hass.userData?.showAdvanced ${this.hass.userData?.showAdvanced
? html` ? html`
@@ -105,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
<div class="card-actions"> <div class="card-actions">
<mwc-button @click=${this._refresh}>Refresh</mwc-button> <mwc-button @click=${this._refresh}>Refresh</mwc-button>
</div> </div>
</paper-card> </ha-card>
`; `;
} }
@@ -114,7 +112,7 @@ class HassioSupervisorLog extends LitElement {
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
paper-card { ha-card {
width: 100%; width: 100%;
} }
pre { pre {

View File

@@ -13,16 +13,15 @@ import {
HassioHostInfo, HassioHostInfo,
} from "../../../src/data/hassio/host"; } from "../../../src/data/hassio/host";
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor"; import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
import { haStyle } from "../../../src/resources/styles";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-panel";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import "./hassio-host-info"; import "./hassio-host-info";
import "./hassio-supervisor-info"; import "./hassio-supervisor-info";
import "./hassio-supervisor-log"; import "./hassio-supervisor-log";
import { supervisorTabs } from "../hassio-panel";
@customElement("hassio-system") @customElement("hassio-system")
class HassioSystem extends LitElement { class HassioSystem extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;

View File

@@ -24,19 +24,19 @@
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@fullcalendar/core": "5.0.0-beta.2", "@fullcalendar/core": "^5.0.0-beta.2",
"@fullcalendar/daygrid": "5.0.0-beta.2", "@fullcalendar/daygrid": "^5.0.0-beta.2",
"@material/chips": "^6.0.0-canary.35a32aaea.0", "@material/chips": "7.0.0-canary.d92d8c93e.0",
"@material/mwc-button": "0.14.1", "@material/mwc-button": "^0.15.0",
"@material/mwc-checkbox": "0.14.1", "@material/mwc-checkbox": "^0.15.0",
"@material/mwc-dialog": "0.14.1", "@material/mwc-dialog": "^0.15.0",
"@material/mwc-fab": "0.14.1", "@material/mwc-fab": "^0.15.0",
"@material/mwc-formfield": "0.14.1", "@material/mwc-formfield": "^0.15.0",
"@material/mwc-icon-button": "0.14.1", "@material/mwc-icon-button": "^0.15.0",
"@material/mwc-list": "0.14.1", "@material/mwc-list": "^0.15.0",
"@material/mwc-menu": "0.14.1", "@material/mwc-menu": "^0.15.0",
"@material/mwc-ripple": "0.14.1", "@material/mwc-ripple": "^0.15.0",
"@material/mwc-switch": "0.14.1", "@material/mwc-switch": "^0.15.0",
"@mdi/js": "4.9.95", "@mdi/js": "4.9.95",
"@mdi/svg": "4.9.95", "@mdi/svg": "4.9.95",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.0.2",
@@ -77,7 +77,7 @@
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.3.7", "@thomasloven/round-slider": "0.4.1",
"@vaadin/vaadin-combo-box": "^5.0.10", "@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7", "@vaadin/vaadin-date-picker": "^4.0.7",
"@webcomponents/shadycss": "^1.9.0", "@webcomponents/shadycss": "^1.9.0",
@@ -93,14 +93,14 @@
"fuse.js": "^3.4.4", "fuse.js": "^3.4.4",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^0.12.4", "hls.js": "^0.12.4",
"home-assistant-js-websocket": "5.0.0", "home-assistant-js-websocket": "^5.1.2",
"idb-keyval": "^3.2.0", "idb-keyval": "^3.2.0",
"intl-messageformat": "^8.3.9", "intl-messageformat": "^8.3.9",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"leaflet": "^1.4.0", "leaflet": "^1.4.0",
"leaflet-draw": "^1.0.4", "leaflet-draw": "^1.0.4",
"lit-element": "^2.2.1", "lit-element": "^2.3.1",
"lit-html": "^1.1.0", "lit-html": "^1.2.1",
"lit-virtualizer": "^0.4.2", "lit-virtualizer": "^0.4.2",
"marked": "^0.6.1", "marked": "^0.6.1",
"mdn-polyfills": "^5.16.0", "mdn-polyfills": "^5.16.0",
@@ -196,20 +196,21 @@
"resolutions": { "resolutions": {
"@webcomponents/webcomponentsjs": "^2.2.10", "@webcomponents/webcomponentsjs": "^2.2.10",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"lit-html": "^1.1.2", "lit-html": "1.2.1",
"@material/animation": "6.0.0", "lit-element": "2.3.1",
"@material/base": "6.0.0", "@material/animation": "7.0.0-canary.d92d8c93e.0",
"@material/checkbox": "6.0.0", "@material/base": "7.0.0-canary.d92d8c93e.0",
"@material/density": "6.0.0", "@material/checkbox": "7.0.0-canary.d92d8c93e.0",
"@material/dom": "6.0.0", "@material/density": "7.0.0-canary.d92d8c93e.0",
"@material/elevation": "6.0.0", "@material/dom": "7.0.0-canary.d92d8c93e.0",
"@material/feature-targeting": "6.0.0", "@material/elevation": "7.0.0-canary.d92d8c93e.0",
"@material/ripple": "6.0.0", "@material/feature-targeting": "7.0.0-canary.d92d8c93e.0",
"@material/rtl": "6.0.0", "@material/ripple": "7.0.0-canary.d92d8c93e.0",
"@material/shape": "6.0.0", "@material/rtl": "7.0.0-canary.d92d8c93e.0",
"@material/theme": "6.0.0", "@material/shape": "7.0.0-canary.d92d8c93e.0",
"@material/touch-target": "6.0.0", "@material/theme": "7.0.0-canary.d92d8c93e.0",
"@material/typography": "6.0.0" "@material/touch-target": "7.0.0-canary.d92d8c93e.0",
"@material/typography": "7.0.0-canary.d92d8c93e.0"
}, },
"main": "src/home-assistant.js", "main": "src/home-assistant.js",
"husky": { "husky": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

View File

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

View File

@@ -1,4 +1,3 @@
import "../../components/ha-icon-button";
import "@polymer/paper-input/paper-input"; import "@polymer/paper-input/paper-input";
import { import {
css, css,
@@ -9,8 +8,10 @@ import {
} from "lit-element"; } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
import { classMap } from "lit-html/directives/class-map"; import { classMap } from "lit-html/directives/class-map";
import "../../components/ha-icon"; import "../../components/ha-svg-icon";
import { fireEvent } from "../dom/fire_event"; import { fireEvent } from "../dom/fire_event";
import { mdiMagnify, mdiClose } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
@customElement("search-input") @customElement("search-input")
class SearchInput extends LitElement { class SearchInput extends LitElement {
@@ -47,17 +48,22 @@ class SearchInput extends LitElement {
@value-changed=${this._filterInputChanged} @value-changed=${this._filterInputChanged}
.noLabelFloat=${this.noLabelFloat} .noLabelFloat=${this.noLabelFloat}
> >
<ha-icon icon="hass:magnify" slot="prefix" class="prefix"></ha-icon> <ha-svg-icon
path=${mdiMagnify}
slot="prefix"
class="prefix"
></ha-svg-icon>
${this.filter && ${this.filter &&
html` html`
<ha-icon-button <mwc-icon-button
slot="suffix" slot="suffix"
class="suffix" class="suffix"
@click=${this._clearSearch} @click=${this._clearSearch}
icon="hass:close"
alt="Clear" alt="Clear"
title="Clear" title="Clear"
></ha-icon-button> >
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`} `}
</paper-input> </paper-input>
`; `;
@@ -77,11 +83,14 @@ class SearchInput extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-icon, ha-svg-icon,
ha-icon-button { mwc-icon-button {
color: var(--primary-text-color); color: var(--primary-text-color);
} }
ha-icon { mwc-icon-button {
--mdc-icon-button-size: 24px;
}
ha-svg-icon.prefix {
margin: 8px; margin: 8px;
} }
`; `;

View File

@@ -6,18 +6,19 @@ import {
CSSResult, CSSResult,
css, css,
query, query,
property,
} from "lit-element"; } from "lit-element";
import "@material/mwc-button"; import "@material/mwc-button";
import "@material/mwc-menu"; import "@material/mwc-menu";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import type { Menu } from "@material/mwc-menu"; import type { Menu, Corner } from "@material/mwc-menu";
import { haStyle } from "../resources/styles";
import "./ha-icon-button"; import "./ha-icon-button";
@customElement("ha-button-menu") @customElement("ha-button-menu")
export class HaButtonMenu extends LitElement { export class HaButtonMenu extends LitElement {
@property() public corner: Corner = "TOP_START";
@query("mwc-menu") private _menu?: Menu; @query("mwc-menu") private _menu?: Menu;
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -25,7 +26,7 @@ export class HaButtonMenu extends LitElement {
<div @click=${this._handleClick}> <div @click=${this._handleClick}>
<slot name="trigger"></slot> <slot name="trigger"></slot>
</div> </div>
<mwc-menu> <mwc-menu .corner=${this.corner}>
<slot></slot> <slot></slot>
</mwc-menu> </mwc-menu>
`; `;
@@ -36,15 +37,13 @@ export class HaButtonMenu extends LitElement {
this._menu!.show(); this._menu!.show();
} }
static get styles(): CSSResult[] { static get styles(): CSSResult {
return [ return css`
haStyle, :host {
css` display: block;
:host { position: relative;
position: relative; }
} `;
`,
];
} }
} }

View File

@@ -4,17 +4,19 @@ import { style } from "@material/mwc-dialog/mwc-dialog-css";
import "./ha-icon-button"; import "./ha-icon-button";
import { css, CSSResult, customElement, html } from "lit-element"; import { css, CSSResult, customElement, html } from "lit-element";
import type { Constructor, HomeAssistant } from "../types"; import type { Constructor, HomeAssistant } from "../types";
import { mdiClose } from "@mdi/js";
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>; const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
export const createCloseHeading = (hass: HomeAssistant, title: string) => html` export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
${title} ${title}
<ha-icon-button <mwc-icon-button
aria-label=${hass.localize("ui.dialogs.generic.close")} aria-label=${hass.localize("ui.dialogs.generic.close")}
icon="hass:close"
dialogAction="close" dialogAction="close"
class="close_button" class="close_button"
></ha-icon-button> >
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
`; `;
@customElement("ha-dialog") @customElement("ha-dialog")

View File

@@ -1,41 +0,0 @@
import "@material/mwc-fab";
import type { Fab } from "@material/mwc-fab";
import { ripple } from "@material/mwc-ripple/ripple-directive";
import { customElement, html, TemplateResult } from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import type { Constructor } from "../types";
import "./ha-icon";
const MwcFab = customElements.get("mwc-fab") as Constructor<Fab>;
@customElement("ha-fab")
export class HaFab extends MwcFab {
// We override the render method because we don't have an icon font and mwc-fab doesn't support our svg-icon sets.
// Based on version mwc-fab 0.8
protected render(): TemplateResult {
const classes = {
"mdc-fab--mini": this.mini,
"mdc-fab--exited": this.exited,
"mdc-fab--extended": this.extended,
};
const showLabel = this.label !== "" && this.extended;
return html`
<button
.ripple="${ripple()}"
class="mdc-fab ${classMap(classes)}"
?disabled="${this.disabled}"
aria-label="${this.label || this.icon}"
>
${showLabel && this.showIconAtEnd ? this.label : ""}
${this.icon ? html` <ha-icon .icon=${this.icon}></ha-icon> ` : ""}
${showLabel && !this.showIconAtEnd ? this.label : ""}
</button>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-fab": HaFab;
}
}

View File

@@ -1,17 +1,30 @@
import { HaIconButton } from "./ha-icon-button"; import { LitElement, property, TemplateResult, html } from "lit-element";
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
export class HaIconButtonArrowPrev extends LitElement {
@property({ type: Boolean }) public disabled = false;
@property() private _icon = mdiArrowLeft;
export class HaIconButtonArrowPrev extends HaIconButton {
public connectedCallback() { public connectedCallback() {
super.connectedCallback(); super.connectedCallback();
// wait to check for direction since otherwise direction is wrong even though top level is RTL // wait to check for direction since otherwise direction is wrong even though top level is RTL
setTimeout(() => { setTimeout(() => {
this.icon = this._icon =
window.getComputedStyle(this).direction === "ltr" window.getComputedStyle(this).direction === "ltr"
? "hass:arrow-left" ? mdiArrowLeft
: "hass:arrow-right"; : mdiArrowRight;
}, 100); }, 100);
} }
protected render(): TemplateResult {
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
} }
declare global { declare global {

View File

@@ -1,5 +1,4 @@
import "@polymer/iron-icon/iron-icon"; import "@polymer/iron-icon/iron-icon";
import { get, set, clear, Store } from "idb-keyval";
import { import {
customElement, customElement,
LitElement, LitElement,
@@ -11,84 +10,23 @@ import {
CSSResult, CSSResult,
} from "lit-element"; } from "lit-element";
import "./ha-svg-icon"; import "./ha-svg-icon";
import { customIconsets, CustomIcon } from "../data/custom_iconsets";
import {
Chunks,
MDI_PREFIXES,
getIcon,
findIconChunk,
Icons,
checkCacheVersion,
writeCache,
} from "../data/iconsets";
import { debounce } from "../common/util/debounce"; import { debounce } from "../common/util/debounce";
import { iconMetadata } from "../resources/icon-metadata";
import { IconMeta } from "../types";
interface Icons {
[key: string]: string;
}
interface Chunks {
[key: string]: Promise<Icons>;
}
const iconStore = new Store("hass-icon-db", "mdi-icon-store");
get("_version", iconStore).then((version) => {
if (!version) {
set("_version", iconMetadata.version, iconStore);
} else if (version !== iconMetadata.version) {
clear(iconStore).then(() =>
set("_version", iconMetadata.version, iconStore)
);
}
});
const chunks: Chunks = {}; const chunks: Chunks = {};
const MDI_PREFIXES = ["mdi", "hass", "hassio", "hademo"];
let toRead: Array<[string, (string) => void]> = []; checkCacheVersion();
// Queue up as many icon fetches in 1 transaction const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
const getIcon = (iconName: string) =>
new Promise<string>((resolve) => {
toRead.push([iconName, resolve]);
if (toRead.length > 1) {
return;
}
const results: Array<[(string) => void, IDBRequest]> = [];
iconStore
._withIDBStore("readonly", (store) => {
for (const [iconName_, resolve_] of toRead) {
results.push([resolve_, store.get(iconName_)]);
}
toRead = [];
})
.then(() => {
for (const [resolve_, request] of results) {
resolve_(request.result);
}
});
});
const findIconChunk = (icon): string => {
let lastChunk: IconMeta;
for (const chunk of iconMetadata.parts) {
if (chunk.start !== undefined && icon < chunk.start) {
break;
}
lastChunk = chunk;
}
return lastChunk!.file;
};
const debouncedWriteCache = debounce(async () => {
const keys = Object.keys(chunks);
const iconsSets: Icons[] = await Promise.all(Object.values(chunks));
// We do a batch opening the store just once, for (considerable) performance
iconStore._withIDBStore("readwrite", (store) => {
iconsSets.forEach((icons, idx) => {
Object.entries(icons).forEach(([name, path]) => {
store.put(path, name);
});
delete chunks[keys[idx]];
});
});
}, 2000);
@customElement("ha-icon") @customElement("ha-icon")
export class HaIcon extends LitElement { export class HaIcon extends LitElement {
@@ -96,11 +34,14 @@ export class HaIcon extends LitElement {
@property() private _path?: string; @property() private _path?: string;
@property() private _noMdi = false; @property() private _viewBox?;
@property() private _legacy = false;
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
if (changedProps.has("icon")) { if (changedProps.has("icon")) {
this._path = undefined; this._path = undefined;
this._viewBox = undefined;
this._loadIcon(); this._loadIcon();
} }
} }
@@ -109,25 +50,39 @@ export class HaIcon extends LitElement {
if (!this.icon) { if (!this.icon) {
return html``; return html``;
} }
if (this._noMdi) { if (this._legacy) {
return html`<iron-icon .icon=${this.icon}></iron-icon>`; return html`<iron-icon .icon=${this.icon}></iron-icon>`;
} }
return html`<ha-svg-icon .path=${this._path}></ha-svg-icon>`; return html`<ha-svg-icon
.path=${this._path}
.viewBox=${this._viewBox}
></ha-svg-icon>`;
} }
private async _loadIcon() { private async _loadIcon() {
if (!this.icon) { if (!this.icon) {
return; return;
} }
const icon = this.icon.split(":", 2); const [iconPrefix, iconName] = this.icon.split(":", 2);
if (!MDI_PREFIXES.includes(icon[0])) {
this._noMdi = true; if (!iconPrefix || !iconName) {
return; return;
} }
this._noMdi = false; if (!MDI_PREFIXES.includes(iconPrefix)) {
if (iconPrefix in customIconsets) {
const customIconset = customIconsets[iconPrefix];
if (customIconset) {
this._setCustomPath(customIconset(iconName));
}
return;
}
this._legacy = true;
return;
}
this._legacy = false;
const iconName = icon[1];
const cachedPath: string = await getIcon(iconName); const cachedPath: string = await getIcon(iconName);
if (cachedPath) { if (cachedPath) {
this._path = cachedPath; this._path = cachedPath;
@@ -147,6 +102,12 @@ export class HaIcon extends LitElement {
debouncedWriteCache(); debouncedWriteCache();
} }
private async _setCustomPath(promise: Promise<CustomIcon>) {
const icon = await promise;
this._path = icon.path;
this._viewBox = icon.viewBox;
}
private async _setPath(promise: Promise<Icons>, iconName: string) { private async _setPath(promise: Promise<Icons>, iconName: string) {
const iconPack = await promise; const iconPack = await promise;
this._path = iconPack[iconName]; this._path = iconPack[iconName];

View File

@@ -31,12 +31,14 @@ class HaLabelBadge extends LitElement {
big: Boolean(this.value && this.value.length > 4), big: Boolean(this.value && this.value.length > 4),
})}" })}"
> >
${this.icon && !this.value && !this.image <slot>
? html` <ha-icon .icon="${this.icon}"></ha-icon> ` ${this.icon && !this.value && !this.image
: ""} ? html` <ha-icon .icon=${this.icon}></ha-icon> `
${this.value && !this.image : ""}
? html` <span>${this.value}</span> ` ${this.value && !this.image
: ""} ? html` <span>${this.value}</span> `
: ""}
</slot>
</div> </div>
${this.label ${this.label
? html` ? html`

View File

@@ -13,6 +13,7 @@ import { computeDomain } from "../common/entity/compute_domain";
import { subscribeNotifications } from "../data/persistent_notification"; import { subscribeNotifications } from "../data/persistent_notification";
import { HomeAssistant } from "../types"; import { HomeAssistant } from "../types";
import "./ha-icon-button"; import "./ha-icon-button";
import { mdiMenu } from "@mdi/js";
@customElement("ha-menu-button") @customElement("ha-menu-button")
class HaMenuButton extends LitElement { class HaMenuButton extends LitElement {
@@ -55,11 +56,12 @@ class HaMenuButton extends LitElement {
(entityId) => computeDomain(entityId) === "configurator" (entityId) => computeDomain(entityId) === "configurator"
)); ));
return html` return html`
<ha-icon-button <mwc-icon-button
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")} aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
icon="hass:menu"
@click=${this._toggleMenu} @click=${this._toggleMenu}
></ha-icon-button> >
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
</mwc-icon-button>
${hasNotifications ? html` <div class="dot"></div> ` : ""} ${hasNotifications ? html` <div class="dot"></div> ` : ""}
`; `;
} }
@@ -133,8 +135,8 @@ class HaMenuButton extends LitElement {
background-color: var(--accent-color); background-color: var(--accent-color);
width: 12px; width: 12px;
height: 12px; height: 12px;
top: 5px; top: 9px;
right: 2px; right: 7px;
border-radius: 50%; border-radius: 50%;
border: 2px solid var(--app-header-background-color); border: 2px solid var(--app-header-background-color);
} }

View File

@@ -12,10 +12,12 @@ import {
export class HaSvgIcon extends LitElement { export class HaSvgIcon extends LitElement {
@property() public path?: string; @property() public path?: string;
@property() public viewBox?: string;
protected render(): SVGTemplateResult { protected render(): SVGTemplateResult {
return svg` return svg`
<svg <svg
viewBox="0 0 24 24" viewBox=${this.viewBox || "0 0 24 24"}
preserveAspectRatio="xMidYMid meet" preserveAspectRatio="xMidYMid meet"
focusable="false"> focusable="false">
<g> <g>

135
src/components/ha-tab.ts Normal file
View File

@@ -0,0 +1,135 @@
import {
css,
CSSResult,
customElement,
LitElement,
property,
TemplateResult,
html,
queryAsync,
internalProperty,
eventOptions,
} from "lit-element";
import "@material/mwc-ripple/mwc-ripple";
import type { Ripple } from "@material/mwc-ripple";
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import "./ha-icon";
import "./ha-svg-icon";
import { ifDefined } from "lit-html/directives/if-defined";
@customElement("ha-tab")
export class HaTab extends LitElement {
@property({ type: Boolean, reflect: true }) public active = false;
@property({ type: Boolean, reflect: true }) public narrow = false;
@property() public name?: string;
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
@internalProperty() private _shouldRenderRipple = false;
protected render(): TemplateResult {
return html`
<div
tabindex="0"
role="tab"
aria-selected=${this.active}
aria-label=${ifDefined(this.name)}
@focus=${this.handleRippleFocus}
@blur=${this.handleRippleBlur}
@mousedown=${this.handleRippleActivate}
@mouseup=${this.handleRippleDeactivate}
@mouseenter=${this.handleRippleMouseEnter}
@mouseleave=${this.handleRippleMouseLeave}
@touchstart=${this.handleRippleActivate}
@touchend=${this.handleRippleDeactivate}
@touchcancel=${this.handleRippleDeactivate}
@keydown=${this._handleKeyDown}
>
${this.narrow ? html`<slot name="icon"></slot>` : ""}
${!this.narrow || this.active
? html`<span class="name">${this.name}</span>`
: ""}
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
</div>
`;
}
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
this._shouldRenderRipple = true;
return this._ripple;
});
private _handleKeyDown(ev: KeyboardEvent): void {
if (ev.keyCode === 13) {
(ev.target as HTMLElement).click();
}
}
@eventOptions({ passive: true })
private handleRippleActivate(evt?: Event) {
this._rippleHandlers.startPress(evt);
}
private handleRippleDeactivate() {
this._rippleHandlers.endPress();
}
private handleRippleMouseEnter() {
this._rippleHandlers.startHover();
}
private handleRippleMouseLeave() {
this._rippleHandlers.endHover();
}
private handleRippleFocus() {
this._rippleHandlers.startFocus();
}
private handleRippleBlur() {
this._rippleHandlers.endFocus();
}
static get styles(): CSSResult {
return css`
div {
padding: 0 32px;
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
justify-content: center;
height: 64px;
cursor: pointer;
position: relative;
outline: none;
box-sizing: border-box;
}
.name {
white-space: nowrap;
}
:host([active]) {
color: var(--primary-color);
}
:host(:not([narrow])[active]) div {
border-bottom: 2px solid var(--primary-color);
}
:host([narrow]) {
padding: 0 16px;
width: 20%;
min-width: 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-tab": HaTab;
}
}

View File

@@ -8,6 +8,8 @@ export interface ConfigUpdateValues {
elevation: number; elevation: number;
unit_system: "metric" | "imperial"; unit_system: "metric" | "imperial";
time_zone: string; time_zone: string;
external_url?: string | null;
internal_url?: string | null;
} }
export const saveCoreConfig = ( export const saveCoreConfig = (

View File

@@ -0,0 +1,16 @@
export interface CustomIcon {
path: string;
viewBox?: string;
}
export interface CustomIconsetsWindow {
customIconsets?: { [key: string]: (name: string) => Promise<CustomIcon> };
}
const customIconsetsWindow = window as CustomIconsetsWindow;
if (!("customIconsets" in customIconsetsWindow)) {
customIconsetsWindow.customIconsets = {};
}
export const customIconsets = customIconsetsWindow.customIconsets!;

79
src/data/iconsets.ts Normal file
View File

@@ -0,0 +1,79 @@
import { iconMetadata } from "../resources/icon-metadata";
import { IconMeta } from "../types";
import { get, set, clear, Store } from "idb-keyval";
export interface Icons {
[key: string]: string;
}
export interface Chunks {
[key: string]: Promise<Icons>;
}
export const iconStore = new Store("hass-icon-db", "mdi-icon-store");
export const MDI_PREFIXES = ["mdi", "hass", "hassio", "hademo"];
let toRead: Array<[string, (string) => void]> = [];
// Queue up as many icon fetches in 1 transaction
export const getIcon = (iconName: string) =>
new Promise<string>((resolve) => {
toRead.push([iconName, resolve]);
if (toRead.length > 1) {
return;
}
const results: Array<[(string) => void, IDBRequest]> = [];
iconStore
._withIDBStore("readonly", (store) => {
for (const [iconName_, resolve_] of toRead) {
results.push([resolve_, store.get(iconName_)]);
}
toRead = [];
})
.then(() => {
for (const [resolve_, request] of results) {
resolve_(request.result);
}
});
});
export const findIconChunk = (icon): string => {
let lastChunk: IconMeta;
for (const chunk of iconMetadata.parts) {
if (chunk.start !== undefined && icon < chunk.start) {
break;
}
lastChunk = chunk;
}
return lastChunk!.file;
};
export const writeCache = async (chunks: Chunks) => {
const keys = Object.keys(chunks);
const iconsSets: Icons[] = await Promise.all(Object.values(chunks));
// We do a batch opening the store just once, for (considerable) performance
iconStore._withIDBStore("readwrite", (store) => {
iconsSets.forEach((icons, idx) => {
Object.entries(icons).forEach(([name, path]) => {
store.put(path, name);
});
delete chunks[keys[idx]];
});
});
};
export const checkCacheVersion = () => {
get("_version", iconStore).then((version) => {
if (!version) {
set("_version", iconMetadata.version, iconStore);
} else if (version !== iconMetadata.version) {
clear(iconStore).then(() =>
set("_version", iconMetadata.version, iconStore)
);
}
});
};

View File

@@ -7,6 +7,7 @@ export interface IntegrationManifest {
name: string; name: string;
config_flow: boolean; config_flow: boolean;
documentation: string; documentation: string;
issue_tracker?: string;
dependencies?: string[]; dependencies?: string[];
after_dependencies?: string[]; after_dependencies?: string[];
codeowners?: string[]; codeowners?: string[];
@@ -17,7 +18,11 @@ export interface IntegrationManifest {
quality_scale?: string; quality_scale?: string;
} }
export const integrationIssuesUrl = (domain: string) => export const integrationIssuesUrl = (
domain: string,
manifest: IntegrationManifest
) =>
manifest.issue_tracker ||
`https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`; `https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`;
export const domainToName = (localize: LocalizeFunc, domain: string) => export const domainToName = (localize: LocalizeFunc, domain: string) =>

View File

@@ -1,26 +1,52 @@
import { HomeAssistant, WeatherEntity } from "../types"; import { SVGTemplateResult, svg, html, TemplateResult, css } from "lit-element";
import { styleMap } from "lit-html/directives/style-map";
export const weatherImages = { import type { HomeAssistant, WeatherEntity } from "../types";
"clear-night": "/static/images/weather/night.png",
cloudy: "/static/images/weather/cloudy.png", export const weatherSVGs = new Set<string>([
fog: "/static/images/weather/cloudy.png", "clear-night",
lightning: "/static/images/weather/lightning.png", "cloudy",
"lightning-rainy": "/static/images/weather/lightning-rainy.png", "fog",
partlycloudy: "/static/images/weather/partly-cloudy.png", "lightning",
pouring: "/static/images/weather/pouring.png", "lightning-rainy",
rainy: "/static/images/weather/rainy.png", "partlycloudy",
hail: "/static/images/weather/rainy.png", "pouring",
snowy: "/static/images/weather/snowy.png", "rainy",
"snowy-rainy": "/static/images/weather/snowy.png", "hail",
sunny: "/static/images/weather/sunny.png", "snowy",
windy: "/static/images/weather/windy.png", "snowy-rainy",
"windy-variant": "/static/images/weather/windy.png", "sunny",
}; "windy",
"windy-variant",
]);
export const weatherIcons = { export const weatherIcons = {
exceptional: "hass:alert-circle-outline", exceptional: "hass:alert-circle-outline",
}; };
const cloudyStates = new Set<string>([
"partlycloudy",
"cloudy",
"fog",
"windy",
"windy-variant",
"hail",
"rainy",
"snowy",
"snowy-rainy",
"pouring",
"lightning",
"lightning-rainy",
]);
const rainStates = new Set<string>(["hail", "rainy", "pouring"]);
const windyStates = new Set<string>(["windy", "windy-variant"]);
const snowyStates = new Set<string>(["snowy", "snowy-rainy"]);
const lightningStates = new Set<string>(["lightning", "lightning-rainy"]);
export const cardinalDirections = [ export const cardinalDirections = [
"N", "N",
"NNE", "NNE",
@@ -164,3 +190,183 @@ const getWeatherExtrema = (
} }
`; `;
}; };
export const weatherSVGStyles = css`
.rain {
fill: var(--weather-icon-rain-color, #30b3ff);
}
.sun {
fill: var(--weather-icon-sun-color, #fdd93c);
}
.moon {
fill: var(--weather-icon-moon-color, #fdf9cc);
}
.cloud-back {
fill: var(--weather-icon-cloud-back-color, #d4d4d4);
}
.cloud-front {
fill: var(--weather-icon-cloud-front-color, #f9f9f9);
}
`;
export const getWeatherStateSVG = (state: string): SVGTemplateResult => {
return svg`
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 17 17"
>
${
state === "sunny"
? svg`
<path
class="sun"
d="m 14.39303,8.4033507 c 0,3.3114723 -2.684145,5.9956173 -5.9956169,5.9956173 -3.3114716,0 -5.9956168,-2.684145 -5.9956168,-5.9956173 0,-3.311471 2.6841452,-5.995617 5.9956168,-5.995617 3.3114719,0 5.9956169,2.684146 5.9956169,5.995617"
/>
`
: ""
}
${
state === "clear-night"
? svg`
<path
class="moon"
d="m 13.502891,11.382935 c -1.011285,1.859223 -2.976664,3.121381 -5.2405751,3.121381 -3.289929,0 -5.953329,-2.663833 -5.953329,-5.9537625 0,-2.263911 1.261724,-4.228856 3.120948,-5.240575 -0.452782,0.842738 -0.712753,1.806363 -0.712753,2.832381 0,3.289928 2.663833,5.9533275 5.9533291,5.9533275 1.026017,0 1.989641,-0.259969 2.83238,-0.712752"
/>
`
: ""
}
${
state === "partlycloudy"
? svg`
<path
class="sun"
d="m14.981 4.2112c0 1.9244-1.56 3.4844-3.484 3.4844-1.9244 0-3.4844-1.56-3.4844-3.4844s1.56-3.484 3.4844-3.484c1.924 0 3.484 1.5596 3.484 3.484"
/>
`
: ""
}
${
cloudyStates.has(state)
? svg`
<path
class="cloud-back"
d="m3.8863 5.035c-0.54892 0.16898-1.04 0.46637-1.4372 0.8636-0.63077 0.63041-1.0206 1.4933-1.0206 2.455 0 1.9251 1.5589 3.4682 3.4837 3.4682h6.9688c1.9251 0 3.484-1.5981 3.484-3.5232 0-1.9251-1.5589-3.5232-3.484-3.5232h-1.0834c-0.25294-1.6916-1.6986-2.9083-3.4463-2.9083-1.7995 0-3.2805 1.4153-3.465 3.1679"
/>
<path
class="cloud-front"
d="m4.1996 7.6995c-0.33902 0.10407-0.64276 0.28787-0.88794 0.5334-0.39017 0.38982-0.63147 0.92322-0.63147 1.5176 0 1.1896 0.96414 2.1431 2.1537 2.1431h4.3071c1.1896 0 2.153-0.98742 2.153-2.1777 0-1.1896-0.96344-2.1777-2.153-2.1777h-0.66992c-0.15593-1.0449-1.0499-1.7974-2.1297-1.7974-1.112 0-2.0274 0.87524-2.1417 1.9586"
/>
`
: ""
}
${
rainStates.has(state)
? svg`
<path
class="rain"
d="m5.2852 14.734c-0.22401 0.24765-0.57115 0.2988-0.77505 0.11395-0.20391-0.1845-0.18732-0.53481 0.036689-0.78281 0.14817-0.16298 0.59126-0.32914 0.87559-0.42369 0.12453-0.04092 0.22684 0.05186 0.19791 0.17956-0.065617 0.2921-0.18732 0.74965-0.33514 0.91299"
/>
<path
class="rain"
d="m11.257 14.163c-0.22437 0.24765-0.57115 0.2988-0.77505 0.11395-0.2039-0.1845-0.18768-0.53481 0.03669-0.78281 0.14817-0.16298 0.59126-0.32914 0.8756-0.42369 0.12453-0.04092 0.22684 0.05186 0.19791 0.17956-0.06562 0.2921-0.18732 0.74965-0.33514 0.91299"
/>
<path
class="rain"
d="m8.432 15.878c-0.15452 0.17039-0.3937 0.20567-0.53446 0.07867-0.14041-0.12735-0.12876-0.36865 0.025753-0.53975 0.10195-0.11218 0.40711-0.22684 0.60325-0.29175 0.085725-0.02858 0.15628 0.03563 0.13652 0.12382-0.045508 0.20108-0.12912 0.51647-0.23107 0.629"
/>
<path
class="rain"
d="m7.9991 14.118c-0.19226 0.21237-0.49001 0.25612-0.66499 0.09737-0.17462-0.15804-0.16051-0.45861 0.03175-0.67098 0.12665-0.14005 0.50729-0.28293 0.75071-0.36336 0.10689-0.03563 0.19473 0.0441 0.17004 0.15346-0.056092 0.25082-0.16051 0.64347-0.28751 0.78352"
/>
`
: ""
}
${
state === "pouring"
? svg`
<path
class="rain"
d="m10.648 16.448c-0.19226 0.21449-0.49001 0.25894-0.66499 0.09878-0.17498-0.16016-0.16087-0.4639 0.03175-0.67874 0.12665-0.14146 0.50694-0.2854 0.75071-0.36724 0.10689-0.03563 0.19473 0.0448 0.17004 0.15558-0.05645 0.25365-0.16051 0.65017-0.28751 0.79163"
/>
<path
class="rain"
d="m5.9383 16.658c-0.22437 0.25012-0.5715 0.30162-0.77505 0.11501-0.20391-0.18627-0.18768-0.54046 0.036689-0.79093 0.14817-0.1651 0.59126-0.33267 0.87559-0.42827 0.12418-0.04127 0.22648 0.05221 0.19791 0.18168-0.065617 0.29528-0.18732 0.75741-0.33514 0.92251"
/>
`
: ""
}
${
windyStates.has(state)
? svg`
<path
class="cloud-back"
d="m 13.59616,15.30968 c 0,0 -0.09137,-0.0071 -0.250472,-0.0187 -0.158045,-0.01235 -0.381353,-0.02893 -0.64382,-0.05715 -0.262466,-0.02716 -0.564444,-0.06385 -0.877358,-0.124531 -0.156986,-0.03034 -0.315383,-0.06844 -0.473781,-0.111478 -0.157691,-0.04551 -0.313266,-0.09842 -0.463902,-0.161219 l -0.267406,-0.0949 c -0.09984,-0.02646 -0.205669,-0.04904 -0.305153,-0.06738 -0.193322,-0.02716 -0.3838218,-0.03316 -0.5640912,-0.02011 -0.3626556,0.02611 -0.6847417,0.119239 -0.94615,0.226483 -0.2617611,0.108656 -0.4642556,0.230364 -0.600075,0.324203 -0.1358195,0.09419 -0.2049639,0.160514 -0.2049639,0.160514 0,0 0.089958,-0.01623 0.24765,-0.04445 0.1559278,-0.02575 0.3764139,-0.06174 0.6367639,-0.08714 0.2596444,-0.02646 0.5591527,-0.0441 0.8678333,-0.02328 0.076905,0.0035 0.1538111,0.01658 0.2321278,0.02293 0.077611,0.01058 0.1534581,0.02893 0.2314221,0.04022 0.07267,0.01834 0.1397,0.03986 0.213078,0.05644 l 0.238125,0.08925 c 0.09207,0.03281 0.183444,0.07055 0.275872,0.09878 0.09243,0.0261 0.185208,0.05327 0.277636,0.07161 0.184856,0.0388 0.367947,0.06174 0.543983,0.0702 0.353131,0.01905 0.678745,-0.01341 0.951442,-0.06456 0.27305,-0.05292 0.494595,-0.123119 0.646642,-0.181681 0.152047,-0.05785 0.234597,-0.104069 0.234597,-0.104069"
/>
<path
class="cloud-back"
d="m 4.7519154,13.905801 c 0,0 0.091369,-0.0032 0.2511778,-0.0092 0.1580444,-0.0064 0.3820583,-0.01446 0.6455833,-0.03281 0.2631722,-0.01729 0.5662083,-0.04269 0.8812389,-0.09137 0.1576916,-0.02434 0.3175,-0.05609 0.4776611,-0.09384 0.1591027,-0.03951 0.3167944,-0.08643 0.4699,-0.14358 l 0.2702277,-0.08467 c 0.1008945,-0.02222 0.2074334,-0.04127 0.3072695,-0.05574 0.1943805,-0.01976 0.3848805,-0.0187 0.5651499,0.0014 0.3608917,0.03951 0.67945,0.144639 0.936625,0.261761 0.2575278,0.118534 0.4554364,0.247297 0.5873754,0.346781 0.132291,0.09913 0.198966,0.168275 0.198966,0.168275 0,0 -0.08925,-0.01976 -0.245886,-0.05397 C 9.9423347,14.087088 9.7232597,14.042988 9.4639681,14.00736 9.2057347,13.97173 8.9072848,13.94245 8.5978986,13.95162 c -0.077258,7.06e-4 -0.1541638,0.01058 -0.2328333,0.01411 -0.077964,0.0078 -0.1545166,0.02328 -0.2331861,0.03175 -0.073025,0.01588 -0.1404055,0.03422 -0.2141361,0.04798 l -0.2420055,0.08008 c -0.093486,0.02963 -0.1859139,0.06421 -0.2794,0.0889 C 7.3028516,14.23666 7.2093653,14.2603 7.116232,14.27512 6.9303181,14.30722 6.7465209,14.3231 6.5697792,14.32486 6.2166487,14.33046 5.8924459,14.28605 5.6218654,14.224318 5.3505793,14.161565 5.1318571,14.082895 4.9822793,14.01869 4.8327015,13.95519 4.7519154,13.905801 4.7519154,13.905801"
/>
`
: ""
}
${
snowyStates.has(state)
? svg`
<path
class="rain"
d="m 8.4319893,15.348341 c 0,0.257881 -0.209197,0.467079 -0.467078,0.467079 -0.258586,0 -0.46743,-0.209198 -0.46743,-0.467079 0,-0.258233 0.208844,-0.467431 0.46743,-0.467431 0.257881,0 0.467078,0.209198 0.467078,0.467431"
/>
<path
class="rain"
d="m 11.263878,14.358553 c 0,0.364067 -0.295275,0.659694 -0.659695,0.659694 -0.364419,0 -0.6596937,-0.295627 -0.6596937,-0.659694 0,-0.364419 0.2952747,-0.659694 0.6596937,-0.659694 0.36442,0 0.659695,0.295275 0.659695,0.659694"
/>
<path
class="rain"
d="m 5.3252173,13.69847 c 0,0.364419 -0.295275,0.660047 -0.659695,0.660047 -0.364067,0 -0.659694,-0.295628 -0.659694,-0.660047 0,-0.364067 0.295627,-0.659694 0.659694,-0.659694 0.36442,0 0.659695,0.295627 0.659695,0.659694"
/>
`
: ""
}
${
lightningStates.has(state)
? svg`
<path
class="sun"
d="m 9.9252695,10.935875 -1.6483986,2.341014 1.1170184,0.05929 -1.2169864,2.02141 3.0450261,-2.616159 H 9.8864918 L 10.97937,11.294651 10.700323,10.79794 h -0.508706 l -0.2663475,0.137936"
/>
`
: ""
}
</svg>`;
};
export const getWeatherStateIcon = (
state: string,
element: HTMLElement
): TemplateResult | undefined => {
const userDefinedIcon = getComputedStyle(element).getPropertyValue(
`--weather-icon-${state}`
);
if (userDefinedIcon) {
return html`
<div
style="background-size: cover;${styleMap({
"background-image": userDefinedIcon,
})}"
></div>
`;
}
if (weatherSVGs.has(state)) {
return html`${getWeatherStateSVG(state)}`;
}
if (state in weatherIcons) {
return html`
<ha-icon class="weather-icon" .icon=${weatherIcons[state]}></ha-icon>
`;
}
return undefined;
};

View File

@@ -3,6 +3,7 @@ import { HomeAssistant } from "../types";
export interface ZHAEntityReference extends HassEntity { export interface ZHAEntityReference extends HassEntity {
name: string; name: string;
original_name?: string;
} }
export interface ZHADevice { export interface ZHADevice {
@@ -26,6 +27,12 @@ export interface ZHADevice {
signature: any; signature: any;
} }
export interface ZHADeviceEndpoint {
device: ZHADevice;
endpoint_id: number;
entities: ZHAEntityReference[];
}
export interface Attribute { export interface Attribute {
name: string; name: string;
id: number; id: number;
@@ -56,7 +63,12 @@ export interface ReadAttributeServiceData {
export interface ZHAGroup { export interface ZHAGroup {
name: string; name: string;
group_id: number; group_id: number;
members: ZHADevice[]; members: ZHADeviceEndpoint[];
}
export interface ZHAGroupMember {
ieee: string;
endpoint_id: string;
} }
export const reconfigureNode = ( export const reconfigureNode = (
@@ -213,7 +225,7 @@ export const fetchGroup = (
export const fetchGroupableDevices = ( export const fetchGroupableDevices = (
hass: HomeAssistant hass: HomeAssistant
): Promise<ZHADevice[]> => ): Promise<ZHADeviceEndpoint[]> =>
hass.callWS({ hass.callWS({
type: "zha/devices/groupable", type: "zha/devices/groupable",
}); });
@@ -221,7 +233,7 @@ export const fetchGroupableDevices = (
export const addMembersToGroup = ( export const addMembersToGroup = (
hass: HomeAssistant, hass: HomeAssistant,
groupId: number, groupId: number,
membersToAdd: string[] membersToAdd: ZHAGroupMember[]
): Promise<ZHAGroup> => ): Promise<ZHAGroup> =>
hass.callWS({ hass.callWS({
type: "zha/group/members/add", type: "zha/group/members/add",
@@ -232,7 +244,7 @@ export const addMembersToGroup = (
export const removeMembersFromGroup = ( export const removeMembersFromGroup = (
hass: HomeAssistant, hass: HomeAssistant,
groupId: number, groupId: number,
membersToRemove: string[] membersToRemove: ZHAGroupMember[]
): Promise<ZHAGroup> => ): Promise<ZHAGroup> =>
hass.callWS({ hass.callWS({
type: "zha/group/members/remove", type: "zha/group/members/remove",
@@ -243,7 +255,7 @@ export const removeMembersFromGroup = (
export const addGroup = ( export const addGroup = (
hass: HomeAssistant, hass: HomeAssistant,
groupName: string, groupName: string,
membersToAdd?: string[] membersToAdd?: ZHAGroupMember[]
): Promise<ZHAGroup> => ): Promise<ZHAGroup> =>
hass.callWS({ hass.callWS({
type: "zha/group/add", type: "zha/group/add",

View File

@@ -339,7 +339,6 @@ class DataEntryFlowDialog extends LitElement {
ha-icon-button { ha-icon-button {
display: inline-block; display: inline-block;
padding: 8px; padding: 8px;
margin: 16px 16px 0 0;
float: right; float: right;
} }
`, `,

View File

@@ -10,6 +10,7 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
import CoverEntity from "../../../util/cover-model"; import CoverEntity from "../../../util/cover-model";
const FEATURE_CLASS_NAMES = { const FEATURE_CLASS_NAMES = {
4: "has-set_position",
128: "has-set_tilt_position", 128: "has-set_tilt_position",
}; };
class MoreInfoCover extends LocalizeMixin(PolymerElement) { class MoreInfoCover extends LocalizeMixin(PolymerElement) {
@@ -23,6 +24,7 @@ class MoreInfoCover extends LocalizeMixin(PolymerElement) {
overflow: hidden; overflow: hidden;
} }
.has-set_position .current_position,
.has-current_position .current_position, .has-current_position .current_position,
.has-set_tilt_position .tilt, .has-set_tilt_position .tilt,
.has-current_tilt_position .tilt { .has-current_tilt_position .tilt {

View File

@@ -456,15 +456,15 @@ export class HaVoiceCommandDialog extends LitElement {
} }
.bouncer { .bouncer {
width: 40px; width: 48px;
height: 40px; height: 48px;
position: absolute; position: absolute;
top: 0; top: 0;
} }
.double-bounce1, .double-bounce1,
.double-bounce2 { .double-bounce2 {
width: 40px; width: 48px;
height: 40px; height: 48px;
border-radius: 50%; border-radius: 50%;
background-color: var(--primary-color); background-color: var(--primary-color);
opacity: 0.2; opacity: 0.2;

View File

@@ -104,6 +104,11 @@ window.hassConnection.then(({ conn }) => {
}); });
window.addEventListener("error", (e) => { window.addEventListener("error", (e) => {
if (!__DEV__ && e.message === "ResizeObserver loop limit exceeded") {
e.stopImmediatePropagation();
e.stopPropagation();
return;
}
const homeAssistant = document.querySelector("home-assistant") as any; const homeAssistant = document.querySelector("home-assistant") as any;
if ( if (
homeAssistant && homeAssistant &&

View File

@@ -18,4 +18,6 @@ export const demoConfig: HassConfig = {
whitelist_external_dirs: [], whitelist_external_dirs: [],
config_source: "storage", config_source: "storage",
safe_mode: false, safe_mode: false,
internal_url: "http://homeassistant.local:8123",
external_url: null,
}; };

View File

@@ -16,7 +16,9 @@ import { navigate } from "../common/navigate";
import "../components/ha-menu-button"; import "../components/ha-menu-button";
import "../components/ha-icon-button-arrow-prev"; import "../components/ha-icon-button-arrow-prev";
import { HomeAssistant, Route } from "../types"; import { HomeAssistant, Route } from "../types";
import "../components/ha-svg-icon";
import "../components/ha-icon"; import "../components/ha-icon";
import "../components/ha-tab";
export interface PageNavigation { export interface PageNavigation {
path: string; path: string;
@@ -26,6 +28,7 @@ export interface PageNavigation {
core?: boolean; core?: boolean;
advancedOnly?: boolean; advancedOnly?: boolean;
icon?: string; icon?: string;
iconPath?: string;
info?: any; info?: any;
} }
@@ -33,12 +36,12 @@ export interface PageNavigation {
class HassTabsSubpage extends LitElement { class HassTabsSubpage extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property({ type: Boolean }) public hassio = false;
@property({ type: String, attribute: "back-path" }) public backPath?: string; @property({ type: String, attribute: "back-path" }) public backPath?: string;
@property() public backCallback?: () => void; @property() public backCallback?: () => void;
@property({ type: Boolean }) public hassio = false;
@property({ type: Boolean, attribute: "main-page" }) public mainPage = false; @property({ type: Boolean, attribute: "main-page" }) public mainPage = false;
@property() public route!: Route; @property() public route!: Route;
@@ -69,27 +72,23 @@ class HassTabsSubpage extends LitElement {
return shownTabs.map( return shownTabs.map(
(page) => (page) =>
html` html`
<div <ha-tab
class="tab ${classMap({ .hass=${this.hass}
active: page === activeTab,
})}"
@click=${this._tabTapped} @click=${this._tabTapped}
.path=${page.path} .path=${page.path}
.active=${page === activeTab}
.narrow=${this.narrow}
.name=${page.translationKey
? this.hass.localize(page.translationKey)
: page.name}
> >
${this.narrow ${page.iconPath
? html` <ha-icon .icon=${page.icon}></ha-icon> ` ? html`<ha-svg-icon
: ""} slot="icon"
${!this.narrow || page === activeTab .path=${page.iconPath}
? html` ></ha-svg-icon>`
<span class="name" : html`<ha-icon slot="icon" .icon=${page.icon}></ha-icon>`}
>${page.translationKey </ha-tab>
? this.hass.localize(page.translationKey)
: page.name}</span
>
`
: ""}
<mwc-ripple></mwc-ripple>
</div>
` `
); );
} }
@@ -119,8 +118,8 @@ class HassTabsSubpage extends LitElement {
${this.mainPage ${this.mainPage
? html` ? html`
<ha-menu-button <ha-menu-button
.hass=${this.hass}
.hassio=${this.hassio} .hassio=${this.hassio}
.hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
></ha-menu-button> ></ha-menu-button>
` `
@@ -150,7 +149,7 @@ class HassTabsSubpage extends LitElement {
`; `;
} }
private _tabTapped(ev: MouseEvent): void { private _tabTapped(ev: Event): void {
navigate(this, (ev.currentTarget as any).path, true); navigate(this, (ev.currentTarget as any).path, true);
} }
@@ -174,6 +173,10 @@ class HassTabsSubpage extends LitElement {
background-color: var(--primary-background-color); background-color: var(--primary-background-color);
} }
ha-menu-button {
margin-right: 24px;
}
.toolbar { .toolbar {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -211,35 +214,6 @@ class HassTabsSubpage extends LitElement {
justify-content: center; justify-content: center;
} }
.tab {
padding: 0 32px;
display: flex;
flex-direction: column;
text-align: center;
align-items: center;
justify-content: center;
height: 64px;
cursor: pointer;
}
.name {
white-space: nowrap;
}
.tab.active {
color: var(--primary-color);
}
#tabbar:not(.bottom-bar) .tab.active {
border-bottom: 2px solid var(--primary-color);
}
.bottom-bar .tab {
padding: 0 16px;
width: 20%;
min-width: 0;
}
:host(:not([narrow])) #toolbar-icon { :host(:not([narrow])) #toolbar-icon {
min-width: 40px; min-width: 40px;
} }

View File

@@ -16,7 +16,7 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { import {
AreaRegistryEntry, AreaRegistryEntry,
createAreaRegistryEntry, createAreaRegistryEntry,
@@ -26,6 +26,8 @@ import {
devicesInArea, devicesInArea,
} from "../../../data/device_registry"; } from "../../../data/device_registry";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../components/ha-icon-button";
import "../../../components/ha-svg-icon";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
@@ -35,6 +37,7 @@ import {
loadAreaRegistryDetailDialog, loadAreaRegistryDetailDialog,
showAreaRegistryDetailDialog, showAreaRegistryDetailDialog,
} from "./show-dialog-area-registry-detail"; } from "./show-dialog-area-registry-detail";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-areas-dashboard") @customElement("ha-config-areas-dashboard")
export class HaConfigAreasDashboard extends LitElement { export class HaConfigAreasDashboard extends LitElement {
@@ -120,15 +123,16 @@ export class HaConfigAreasDashboard extends LitElement {
@click=${this._showHelp} @click=${this._showHelp}
></ha-icon-button> ></ha-icon-button>
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize( title="${this.hass.localize(
"ui.panel.config.areas.picker.create_area" "ui.panel.config.areas.picker.create_area"
)}" )}"
@click=${this._createArea} @click=${this._createArea}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -177,25 +181,25 @@ export class HaConfigAreasDashboard extends LitElement {
--app-header-background-color: var(--sidebar-background-color); --app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color); --app-header-text-color: var(--sidebar-text-color);
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab.rtl { mwc-fab.rtl {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[is-wide].rtl { mwc-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -14,7 +14,8 @@ import { classMap } from "lit-html/directives/class-map";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "../../../components/ha-svg-icon";
import "@material/mwc-fab";
import { import {
AutomationConfig, AutomationConfig,
AutomationEntity, AutomationEntity,
@@ -40,6 +41,7 @@ import { HaDeviceAction } from "./action/types/ha-automation-action-device_id";
import "./condition/ha-automation-condition"; import "./condition/ha-automation-condition";
import "./trigger/ha-automation-trigger"; import "./trigger/ha-automation-trigger";
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device"; import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
import { mdiContentSave } from "@mdi/js";
export class HaAutomationEditor extends LitElement { export class HaAutomationEditor extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@@ -244,11 +246,10 @@ export class HaAutomationEditor extends LitElement {
</div> </div>
` `
: ""} : ""}
<ha-fab <mwc-fab
?is-wide="${this.isWide}" ?is-wide="${this.isWide}"
?narrow="${this.narrow}" ?narrow="${this.narrow}"
?dirty="${this._dirty}" ?dirty="${this._dirty}"
icon="hass:content-save"
.title="${this.hass.localize( .title="${this.hass.localize(
"ui.panel.config.automation.editor.save" "ui.panel.config.automation.editor.save"
)}" )}"
@@ -256,7 +257,9 @@ export class HaAutomationEditor extends LitElement {
class="${classMap({ class="${classMap({
rtl: computeRTL(this.hass), rtl: computeRTL(this.hass),
})}" })}"
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
</mwc-fab>
</hass-tabs-subpage> </hass-tabs-subpage>
`; `;
} }
@@ -452,7 +455,7 @@ export class HaAutomationEditor extends LitElement {
ha-entity-toggle { ha-entity-toggle {
margin-right: 8px; margin-right: 8px;
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
@@ -461,24 +464,24 @@ export class HaAutomationEditor extends LitElement {
transition: margin-bottom 0.3s; transition: margin-bottom 0.3s;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
margin-bottom: -140px; margin-bottom: -140px;
} }
ha-fab[dirty] { mwc-fab[dirty] {
margin-bottom: 0; margin-bottom: 0;
} }
ha-fab.rtl { mwc-fab.rtl {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[is-wide].rtl { mwc-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -18,7 +18,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-entity-toggle"; import "../../../components/entity/ha-entity-toggle";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { import {
AutomationConfig, AutomationConfig,
AutomationEntity, AutomationEntity,
@@ -30,6 +30,8 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showThingtalkDialog } from "./show-dialog-thingtalk"; import { showThingtalkDialog } from "./show-dialog-thingtalk";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-automation-picker") @customElement("ha-automation-picker")
class HaAutomationPicker extends LitElement { class HaAutomationPicker extends LitElement {
@@ -168,17 +170,18 @@ class HaAutomationPicker extends LitElement {
hasFab hasFab
> >
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-fab <mwc-fab
slot="fab" slot="fab"
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title=${this.hass.localize( title=${this.hass.localize(
"ui.panel.config.automation.picker.add_automation" "ui.panel.config.automation.picker.add_automation"
)} )}
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
@click=${this._createNew} @click=${this._createNew}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -208,7 +211,7 @@ class HaAutomationPicker extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
@@ -216,19 +219,19 @@ class HaAutomationPicker extends LitElement {
cursor: pointer; cursor: pointer;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab[rtl] { mwc-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[rtl][is-wide] { mwc-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -11,6 +11,7 @@ import "../../../resources/ha-style";
import "../ha-config-section"; import "../ha-config-section";
import "./ha-config-core-form"; import "./ha-config-core-form";
import "./ha-config-name-form"; import "./ha-config-name-form";
import "./ha-config-url-form";
/* /*
* @appliesMixin LocalizeMixin * @appliesMixin LocalizeMixin
@@ -59,6 +60,7 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
<ha-config-name-form hass="[[hass]]"></ha-config-name-form> <ha-config-name-form hass="[[hass]]"></ha-config-name-form>
<ha-config-core-form hass="[[hass]]"></ha-config-core-form> <ha-config-core-form hass="[[hass]]"></ha-config-core-form>
<ha-config-url-form hass="[[hass]]"></ha-config-url-form>
</ha-config-section> </ha-config-section>
`; `;
} }

View File

@@ -0,0 +1,168 @@
import "@material/mwc-button/mwc-button";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../components/ha-card";
import { saveCoreConfig } from "../../../data/core";
import type { PolymerChangedEvent } from "../../../polymer-types";
import type { HomeAssistant } from "../../../types";
@customElement("ha-config-url-form")
class ConfigUrlForm extends LitElement {
@property() public hass!: HomeAssistant;
@property() private _error?: string;
@property() private _working = false;
@property() private _external_url?: string;
@property() private _internal_url?: string;
protected render(): TemplateResult {
const canEdit = ["storage", "default"].includes(
this.hass.config.config_source
);
const disabled = this._working || !canEdit;
if (!this.hass.userData?.showAdvanced) {
return html``;
}
return html`
<ha-card>
<div class="card-content">
${!canEdit
? html`
<p>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.edit_requires_storage"
)}
</p>
`
: ""}
${this._error ? html`<div class="error">${this._error}</div>` : ""}
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.external_url"
)}
</div>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.external_url"
)}
name="external_url"
type="url"
.disabled=${disabled}
.value=${this._externalUrlValue}
@value-changed=${this._handleChange}
>
</paper-input>
</div>
<div class="row">
<div class="flex">
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.internal_url"
)}
</div>
<paper-input
class="flex"
.label=${this.hass.localize(
"ui.panel.config.core.section.core.core_config.internal_url"
)}
name="internal_url"
type="url"
.disabled=${disabled}
.value=${this._internalUrlValue}
@value-changed=${this._handleChange}
>
</paper-input>
</div>
</div>
<div class="card-actions">
<mwc-button @click=${this._save} .disabled=${disabled}>
${this.hass.localize(
"ui.panel.config.core.section.core.core_config.save_button"
)}
</mwc-button>
</div>
</ha-card>
`;
}
private get _internalUrlValue() {
return this._internal_url !== undefined
? this._internal_url
: this.hass.config.internal_url;
}
private get _externalUrlValue() {
return this._external_url !== undefined
? this._external_url
: this.hass.config.external_url;
}
private _handleChange(ev: PolymerChangedEvent<string>) {
const target = ev.currentTarget as PaperInputElement;
this[`_${target.name}`] = target.value;
}
private async _save() {
this._working = true;
this._error = undefined;
try {
await saveCoreConfig(this.hass, {
external_url: this._external_url || null,
internal_url: this._internal_url || null,
});
} catch (err) {
this._error = err.message || err;
} finally {
this._working = false;
}
}
static get styles(): CSSResult {
return css`
.row {
display: flex;
flex-direction: row;
margin: 0 -8px;
align-items: center;
}
.secondary {
color: var(--secondary-text-color);
}
.flex {
flex: 1;
}
.row > * {
margin: 0 8px;
}
.error {
color: var(--error-color);
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-config-url-form": ConfigUrlForm;
}
}

View File

@@ -22,7 +22,7 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import "../../../components/ha-icon"; import "../../../components/ha-icon";
import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-loading-screen";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
@@ -31,6 +31,8 @@ import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { HELPER_DOMAINS } from "./const"; import { HELPER_DOMAINS } from "./const";
import { showHelperDetailDialog } from "./show-dialog-helper-detail"; import { showHelperDetailDialog } from "./show-dialog-helper-detail";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-helpers") @customElement("ha-config-helpers")
export class HaConfigHelpers extends LitElement { export class HaConfigHelpers extends LitElement {
@@ -154,15 +156,16 @@ export class HaConfigHelpers extends LitElement {
hasFab hasFab
> >
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize( title="${this.hass.localize(
"ui.panel.config.helpers.picker.add_helper" "ui.panel.config.helpers.picker.add_helper"
)}" )}"
@click=${this._createHelpler} @click=${this._createHelpler}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -209,17 +212,17 @@ export class HaConfigHelpers extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
`; `;

View File

@@ -20,7 +20,7 @@ import {
} from "../../../common/util/render-status"; } from "../../../common/util/render-status";
import "../../../components/entity/ha-state-icon"; import "../../../components/entity/ha-state-icon";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { import {
ConfigEntry, ConfigEntry,
deleteConfigEntry, deleteConfigEntry,
@@ -58,6 +58,9 @@ import type {
HaIntegrationCard, HaIntegrationCard,
} from "./ha-integration-card"; } from "./ha-integration-card";
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
import { LocalizeFunc } from "../../../common/translations/localize";
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress { interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
localized_title?: string; localized_title?: string;
@@ -119,7 +122,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
this._deviceRegistryEntries = entries; this._deviceRegistryEntries = entries;
}), }),
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => { subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
const translationsPromisses: Promise<void>[] = []; const translationsPromisses: Promise<LocalizeFunc>[] = [];
flowsInProgress.forEach((flow) => { flowsInProgress.forEach((flow) => {
// To render title placeholders // To render title placeholders
if (flow.context.title_placeholders) { if (flow.context.title_placeholders) {
@@ -452,15 +455,16 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
` `
: ""} : ""}
</div> </div>
<ha-fab <mwc-fab
icon="hass:plus"
aria-label=${this.hass.localize("ui.panel.config.integrations.new")} aria-label=${this.hass.localize("ui.panel.config.integrations.new")}
title=${this.hass.localize("ui.panel.config.integrations.new")} title=${this.hass.localize("ui.panel.config.integrations.new")}
@click=${this._createFlow} @click=${this._createFlow}
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
?rtl=${computeRTL(this.hass!)} ?rtl=${computeRTL(this.hass!)}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
</hass-tabs-subpage> </hass-tabs-subpage>
`; `;
} }
@@ -680,24 +684,24 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
h2 { h2 {
margin-top: 0; margin-top: 0;
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab[rtl] { mwc-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[is-wide].rtl { mwc-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
left: 24px; left: 24px;
right: auto; right: auto;

View File

@@ -148,7 +148,9 @@ export class HaIntegrationCard extends LitElement {
> >
` `
: ""} : ""}
${devices.length && entities.length ? "and" : ""} ${devices.length && entities.length
? this.hass.localize("ui.common.and")
: ""}
${entities.length ${entities.length
? html` ? html`
<a <a

View File

@@ -1,3 +1,4 @@
import "@material/mwc-fab";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import { import {
css, css,
@@ -16,7 +17,6 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../../components/data-table/ha-data-table"; } from "../../../../components/data-table/ha-data-table";
import "../../../../components/ha-fab";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import "../../../../components/ha-icon-button"; import "../../../../components/ha-icon-button";
import { import {
@@ -34,6 +34,8 @@ import "../../../../layouts/hass-tabs-subpage-data-table";
import { HomeAssistant, Route } from "../../../../types"; import { HomeAssistant, Route } from "../../../../types";
import { lovelaceTabs } from "../ha-config-lovelace"; import { lovelaceTabs } from "../ha-config-lovelace";
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail"; import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
import "../../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-lovelace-dashboards") @customElement("ha-config-lovelace-dashboards")
export class HaConfigLovelaceDashboards extends LitElement { export class HaConfigLovelaceDashboards extends LitElement {
@@ -222,15 +224,16 @@ export class HaConfigLovelaceDashboards extends LitElement {
hasFab hasFab
> >
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize( title="${this.hass.localize(
"ui.panel.config.lovelace.dashboards.picker.add_dashboard" "ui.panel.config.lovelace.dashboards.picker.add_dashboard"
)}" )}"
@click=${this._addDashboard} @click=${this._addDashboard}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -308,17 +311,17 @@ export class HaConfigLovelaceDashboards extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
`; `;

View File

@@ -3,6 +3,7 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-icon-item"; import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox"; import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-tooltip/paper-tooltip"; import "@polymer/paper-tooltip/paper-tooltip";
import "@material/mwc-fab";
import { import {
css, css,
CSSResult, CSSResult,
@@ -19,7 +20,6 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../../components/data-table/ha-data-table"; } from "../../../../components/data-table/ha-data-table";
import "../../../../components/ha-fab";
import "../../../../components/ha-icon"; import "../../../../components/ha-icon";
import { import {
createResource, createResource,
@@ -38,6 +38,8 @@ import { HomeAssistant, Route } from "../../../../types";
import { loadLovelaceResources } from "../../../lovelace/common/load-resources"; import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
import { lovelaceTabs } from "../ha-config-lovelace"; import { lovelaceTabs } from "../ha-config-lovelace";
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail"; import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
import "../../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-lovelace-resources") @customElement("ha-config-lovelace-resources")
export class HaConfigLovelaceRescources extends LitElement { export class HaConfigLovelaceRescources extends LitElement {
@@ -102,15 +104,16 @@ export class HaConfigLovelaceRescources extends LitElement {
hasFab hasFab
> >
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title=${this.hass.localize( title=${this.hass.localize(
"ui.panel.config.lovelace.resources.picker.add_resource" "ui.panel.config.lovelace.resources.picker.add_resource"
)} )}
@click=${this._addResource} @click=${this._addResource}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -199,17 +202,17 @@ export class HaConfigLovelaceRescources extends LitElement {
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
`; `;

View File

@@ -10,7 +10,7 @@ import {
} from "lit-element"; } from "lit-element";
import { compare } from "../../../common/string/compare"; import { compare } from "../../../common/string/compare";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { import {
createPerson, createPerson,
deletePerson, deletePerson,
@@ -29,6 +29,8 @@ import {
loadPersonDetailDialog, loadPersonDetailDialog,
showPersonDetailDialog, showPersonDetailDialog,
} from "./show-dialog-person-detail"; } from "./show-dialog-person-detail";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
class HaConfigPerson extends LitElement { class HaConfigPerson extends LitElement {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@@ -121,13 +123,14 @@ class HaConfigPerson extends LitElement {
</ha-config-section> </ha-config-section>
</hass-tabs-subpage> </hass-tabs-subpage>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title="${hass.localize("ui.panel.config.person.add_person")}" title="${hass.localize("ui.panel.config.person.add_person")}"
@click=${this._createPerson} @click=${this._createPerson}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -237,16 +240,16 @@ class HaConfigPerson extends LitElement {
ha-card.storage paper-item { ha-card.storage paper-item {
cursor: pointer; cursor: pointer;
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }

View File

@@ -15,7 +15,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { forwardHaptic } from "../../../data/haptics"; import { forwardHaptic } from "../../../data/haptics";
import { activateScene, SceneEntity } from "../../../data/scene"; import { activateScene, SceneEntity } from "../../../data/scene";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
@@ -24,6 +24,8 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-scene-dashboard") @customElement("ha-scene-dashboard")
class HaSceneDashboard extends LitElement { class HaSceneDashboard extends LitElement {
@@ -144,13 +146,14 @@ class HaSceneDashboard extends LitElement {
></ha-icon-button> ></ha-icon-button>
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<a href="/config/scene/edit/new"> <a href="/config/scene/edit/new">
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title=${this.hass.localize("ui.panel.config.scene.picker.add_scene")} title=${this.hass.localize("ui.panel.config.scene.picker.add_scene")}
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
</a> </a>
`; `;
} }
@@ -197,26 +200,26 @@ class HaSceneDashboard extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab[rtl] { mwc-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[rtl][is-wide] { mwc-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -23,7 +23,7 @@ import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/device/ha-device-picker"; import "../../../components/device/ha-device-picker";
import "../../../components/entity/ha-entities-picker"; import "../../../components/entity/ha-entities-picker";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { import {
computeDeviceName, computeDeviceName,
DeviceRegistryEntry, DeviceRegistryEntry,
@@ -54,6 +54,8 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../../../components/ha-svg-icon";
import { mdiContentSave } from "@mdi/js";
interface DeviceEntities { interface DeviceEntities {
id: string; id: string;
@@ -372,17 +374,18 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
` `
: ""} : ""}
</div> </div>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
?dirty=${this._dirty} ?dirty=${this._dirty}
icon="hass:content-save"
.title=${this.hass.localize("ui.panel.config.scene.editor.save")} .title=${this.hass.localize("ui.panel.config.scene.editor.save")}
@click=${this._saveScene} @click=${this._saveScene}
class=${classMap({ class=${classMap({
rtl: computeRTL(this.hass), rtl: computeRTL(this.hass),
})} })}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
</mwc-fab>
</hass-tabs-subpage> </hass-tabs-subpage>
`; `;
} }
@@ -728,7 +731,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
span[slot="introduction"] a { span[slot="introduction"] a {
color: var(--primary-color); color: var(--primary-color);
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
@@ -737,24 +740,24 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
transition: margin-bottom 0.3s; transition: margin-bottom 0.3s;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
margin-bottom: -140px; margin-bottom: -140px;
} }
ha-fab[dirty] { mwc-fab[dirty] {
margin-bottom: 0; margin-bottom: 0;
} }
ha-fab.rtl { mwc-fab.rtl {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[is-wide].rtl { mwc-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -15,7 +15,7 @@ import { computeObjectId } from "../../../common/entity/compute_object_id";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { import {
Action, Action,
deleteScript, deleteScript,
@@ -30,6 +30,8 @@ import "../automation/action/ha-automation-action";
import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id"; import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id";
import "../ha-config-section"; import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../../../components/ha-svg-icon";
import { mdiContentSave } from "@mdi/js";
export class HaScriptEditor extends LitElement { export class HaScriptEditor extends LitElement {
@property() public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@@ -139,17 +141,18 @@ export class HaScriptEditor extends LitElement {
: ""} : ""}
</div> </div>
</div> </div>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
?dirty=${this._dirty} ?dirty=${this._dirty}
icon="hass:content-save"
.title="${this.hass.localize("ui.common.save")}" .title="${this.hass.localize("ui.common.save")}"
@click=${this._saveScript} @click=${this._saveScript}
class="${classMap({ class="${classMap({
rtl: computeRTL(this.hass), rtl: computeRTL(this.hass),
})}" })}"
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
</mwc-fab>
</hass-tabs-subpage> </hass-tabs-subpage>
`; `;
} }
@@ -300,7 +303,7 @@ export class HaScriptEditor extends LitElement {
span[slot="introduction"] a { span[slot="introduction"] a {
color: var(--primary-color); color: var(--primary-color);
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
@@ -309,24 +312,24 @@ export class HaScriptEditor extends LitElement {
transition: margin-bottom 0.3s; transition: margin-bottom 0.3s;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
margin-bottom: -140px; margin-bottom: -140px;
} }
ha-fab[dirty] { mwc-fab[dirty] {
margin-bottom: 0; margin-bottom: 0;
} }
ha-fab.rtl { mwc-fab.rtl {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[is-wide].rtl { mwc-fab[is-wide].rtl {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -15,7 +15,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { computeRTL } from "../../../common/util/compute_rtl"; import { computeRTL } from "../../../common/util/compute_rtl";
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table"; import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { triggerScript } from "../../../data/script"; import { triggerScript } from "../../../data/script";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
@@ -23,6 +23,8 @@ import { haStyle } from "../../../resources/styles";
import { HomeAssistant, Route } from "../../../types"; import { HomeAssistant, Route } from "../../../types";
import { showToast } from "../../../util/toast"; import { showToast } from "../../../util/toast";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-script-picker") @customElement("ha-script-picker")
class HaScriptPicker extends LitElement { class HaScriptPicker extends LitElement {
@@ -139,15 +141,16 @@ class HaScriptPicker extends LitElement {
></ha-icon-button> ></ha-icon-button>
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<a href="/config/script/edit/new"> <a href="/config/script/edit/new">
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title="${this.hass.localize( title="${this.hass.localize(
"ui.panel.config.script.picker.add_script" "ui.panel.config.script.picker.add_script"
)}" )}"
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
</a> </a>
`; `;
} }
@@ -193,26 +196,26 @@ class HaScriptPicker extends LitElement {
return [ return [
haStyle, haStyle,
css` css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab[rtl] { mwc-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[rtl][is-wide] { mwc-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -13,7 +13,7 @@ import {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user"; import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import "../../../layouts/hass-tabs-subpage-data-table"; import "../../../layouts/hass-tabs-subpage-data-table";
@@ -21,6 +21,8 @@ import { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showAddUserDialog } from "./show-dialog-add-user"; import { showAddUserDialog } from "./show-dialog-add-user";
import { showUserDetailDialog } from "./show-dialog-user-detail"; import { showUserDetailDialog } from "./show-dialog-user-detail";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-users") @customElement("ha-config-users")
export class HaConfigUsers extends LitElement { export class HaConfigUsers extends LitElement {
@@ -97,14 +99,15 @@ export class HaConfigUsers extends LitElement {
hasFab hasFab
> >
</hass-tabs-subpage-data-table> </hass-tabs-subpage-data-table>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
.title=${this.hass.localize("ui.panel.config.users.picker.add_user")} .title=${this.hass.localize("ui.panel.config.users.picker.add_user")}
@click=${this._addUser} @click=${this._addUser}
?rtl=${computeRTL(this.hass)} ?rtl=${computeRTL(this.hass)}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -166,24 +169,24 @@ export class HaConfigUsers extends LitElement {
static get styles() { static get styles() {
return css` return css`
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[rtl] { mwc-fab[rtl] {
right: auto; right: auto;
left: 16px; left: 16px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
ha-fab[rtl][is-wide] { mwc-fab[rtl][is-wide] {
bottom: 24px; bottom: 24px;
right: auto; right: auto;
left: 24px; left: 24px;

View File

@@ -18,31 +18,31 @@ import type { SelectionChangedEvent } from "../../../components/data-table/ha-da
import { import {
addGroup, addGroup,
fetchGroupableDevices, fetchGroupableDevices,
ZHADevice,
ZHAGroup, ZHAGroup,
ZHADeviceEndpoint,
} from "../../../data/zha"; } from "../../../data/zha";
import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
import type { PolymerChangedEvent } from "../../../polymer-types"; import type { PolymerChangedEvent } from "../../../polymer-types";
import type { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import "./zha-devices-data-table"; import "./zha-device-endpoint-data-table";
import type { ZHADevicesDataTable } from "./zha-devices-data-table"; import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
@customElement("zha-add-group-page") @customElement("zha-add-group-page")
export class ZHAAddGroupPage extends LitElement { export class ZHAAddGroupPage extends LitElement {
@property() public hass!: HomeAssistant; @property({ type: Object }) public hass!: HomeAssistant;
@property() public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public devices: ZHADevice[] = []; @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@property() private _processingAdd = false; @property() private _processingAdd = false;
@property() private _groupName = ""; @property() private _groupName = "";
@query("zha-devices-data-table") @query("zha-device-endpoint-data-table")
private _zhaDevicesDataTable!: ZHADevicesDataTable; private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable;
private _firstUpdatedCalled = false; private _firstUpdatedCalled = false;
@@ -87,14 +87,14 @@ export class ZHAAddGroupPage extends LitElement {
${this.hass.localize("ui.panel.config.zha.groups.add_members")} ${this.hass.localize("ui.panel.config.zha.groups.add_members")}
</div> </div>
<zha-devices-data-table <zha-device-endpoint-data-table
.hass=${this.hass} .hass=${this.hass}
.devices=${this.devices} .deviceEndpoints=${this.deviceEndpoints}
.narrow=${this.narrow} .narrow=${this.narrow}
selectable selectable
@selection-changed=${this._handleAddSelectionChanged} @selection-changed=${this._handleAddSelectionChanged}
> >
</zha-devices-data-table> </zha-device-endpoint-data-table>
<div class="paper-dialog-buttons"> <div class="paper-dialog-buttons">
<mwc-button <mwc-button
@@ -121,7 +121,7 @@ export class ZHAAddGroupPage extends LitElement {
} }
private async _fetchData() { private async _fetchData() {
this.devices = await fetchGroupableDevices(this.hass!); this.deviceEndpoints = await fetchGroupableDevices(this.hass!);
} }
private _handleAddSelectionChanged( private _handleAddSelectionChanged(
@@ -132,11 +132,11 @@ export class ZHAAddGroupPage extends LitElement {
private async _createGroup(): Promise<void> { private async _createGroup(): Promise<void> {
this._processingAdd = true; this._processingAdd = true;
const group: ZHAGroup = await addGroup( const members = this._selectedDevicesToAdd.map((member) => {
this.hass, const memberParts = member.split("_");
this._groupName, return { ieee: memberParts[0], endpoint_id: memberParts[1] };
this._selectedDevicesToAdd });
); const group: ZHAGroup = await addGroup(this.hass, this._groupName, members);
this._selectedDevicesToAdd = []; this._selectedDevicesToAdd = [];
this._processingAdd = false; this._processingAdd = false;
this._groupName = ""; this._groupName = "";

View File

@@ -16,6 +16,7 @@ import "../../../components/data-table/ha-data-table";
import type { import type {
DataTableColumnContainer, DataTableColumnContainer,
RowClickedEvent, RowClickedEvent,
DataTableRowData,
} from "../../../components/data-table/ha-data-table"; } from "../../../components/data-table/ha-data-table";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
@@ -27,19 +28,19 @@ import type { HomeAssistant, Route } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { formatAsPaddedHex, sortZHADevices } from "./functions"; import { formatAsPaddedHex, sortZHADevices } from "./functions";
export interface DeviceRowData extends ZHADevice { export interface DeviceRowData extends DataTableRowData {
device?: DeviceRowData; device?: DeviceRowData;
} }
@customElement("zha-config-dashboard") @customElement("zha-config-dashboard")
class ZHAConfigDashboard extends LitElement { class ZHAConfigDashboard extends LitElement {
@property() public hass!: HomeAssistant; @property({ type: Object }) public hass!: HomeAssistant;
@property() public route!: Route; @property({ type: Object }) public route!: Route;
@property() public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public isWide!: boolean; @property({ type: Boolean }) public isWide!: boolean;
@property() private _devices: ZHADevice[] = []; @property() private _devices: ZHADevice[] = [];
@@ -91,7 +92,7 @@ class ZHAConfigDashboard extends LitElement {
title: "IEEE", title: "IEEE",
sortable: true, sortable: true,
filterable: true, filterable: true,
width: "25%", width: "30%",
}, },
} }
); );

View File

@@ -0,0 +1,182 @@
import {
customElement,
html,
LitElement,
property,
query,
TemplateResult,
css,
CSSResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
HaDataTable,
DataTableRowData,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-state-icon";
import type { ZHADeviceEndpoint, ZHAEntityReference } from "../../../data/zha";
import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info";
import type { HomeAssistant } from "../../../types";
export interface DeviceEndpointRowData extends DataTableRowData {
id: string;
name: string;
model: string;
manufacturer: string;
endpoint_id: number;
entities: ZHAEntityReference[];
}
@customElement("zha-device-endpoint-data-table")
export class ZHADeviceEndpointDataTable extends LitElement {
@property({ type: Object }) public hass!: HomeAssistant;
@property({ type: Boolean }) public narrow = false;
@property({ type: Boolean }) public selectable = false;
@property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@query("ha-data-table") private _dataTable!: HaDataTable;
private _deviceEndpoints = memoizeOne(
(deviceEndpoints: ZHADeviceEndpoint[]) => {
const outputDevices: DeviceEndpointRowData[] = [];
deviceEndpoints.forEach((deviceEndpoint) => {
outputDevices.push({
name:
deviceEndpoint.device.user_given_name || deviceEndpoint.device.name,
model: deviceEndpoint.device.model,
manufacturer: deviceEndpoint.device.manufacturer,
id: deviceEndpoint.device.ieee + "_" + deviceEndpoint.endpoint_id,
ieee: deviceEndpoint.device.ieee,
endpoint_id: deviceEndpoint.endpoint_id,
entities: deviceEndpoint.entities,
});
});
return outputDevices;
}
);
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: "Devices",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div
class="mdc-data-table__cell table-cell-text"
@click=${this._handleClicked}
style="cursor: pointer;"
>
${name}
</div>
`,
},
endpoint_id: {
title: "Endpoint",
sortable: true,
filterable: true,
},
}
: {
name: {
title: "Name",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div
class="mdc-data-table__cell table-cell-text"
@click=${this._handleClicked}
style="cursor: pointer;"
>
${name}
</div>
`,
},
endpoint_id: {
title: "Endpoint",
sortable: true,
filterable: true,
},
entities: {
title: "Associated Entities",
sortable: false,
filterable: false,
width: "50%",
template: (entities) => html`
${entities.length
? entities.length > 3
? html`${entities.slice(0, 2).map(
(entity) =>
html`<div
style="overflow: hidden; text-overflow: ellipsis;"
>
${entity.name || entity.original_name}
</div>`
)}
<div>And ${entities.length - 2} more...</div>`
: entities.map(
(entity) =>
html`<div
style="overflow: hidden; text-overflow: ellipsis;"
>
${entity.name || entity.original_name}
</div>`
)
: "This endpoint has no associated entities"}
`,
},
}
);
public clearSelection() {
this._dataTable.clearSelection();
}
protected render(): TemplateResult {
return html`
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._deviceEndpoints(this.deviceEndpoints)}
.selectable=${this.selectable}
auto-height
></ha-data-table>
`;
}
private async _handleClicked(ev: CustomEvent) {
const rowId = ((ev.target as HTMLElement).closest(
".mdc-data-table__row"
) as any).rowId;
const ieee = rowId.substring(0, rowId.indexOf("_"));
showZHADeviceInfoDialog(this, { ieee });
}
static get styles(): CSSResult[] {
return [
css`
.table-cell-text {
word-break: break-word;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-device-endpoint-data-table": ZHADeviceEndpointDataTable;
}
}

View File

@@ -1,124 +0,0 @@
import {
customElement,
html,
LitElement,
property,
query,
TemplateResult,
} from "lit-element";
import memoizeOne from "memoize-one";
import "../../../components/data-table/ha-data-table";
import type {
DataTableColumnContainer,
HaDataTable,
} from "../../../components/data-table/ha-data-table";
import "../../../components/entity/ha-state-icon";
import type { ZHADevice } from "../../../data/zha";
import { showZHADeviceInfoDialog } from "../../../dialogs/zha-device-info-dialog/show-dialog-zha-device-info";
import type { HomeAssistant } from "../../../types";
export interface DeviceRowData extends ZHADevice {
device?: DeviceRowData;
}
@customElement("zha-devices-data-table")
export class ZHADevicesDataTable extends LitElement {
@property() public hass!: HomeAssistant;
@property() public narrow = false;
@property({ type: Boolean }) public selectable = false;
@property() public devices: ZHADevice[] = [];
@query("ha-data-table") private _dataTable!: HaDataTable;
private _devices = memoizeOne((devices: ZHADevice[]) => {
let outputDevices: DeviceRowData[] = devices;
outputDevices = outputDevices.map((device) => {
return {
...device,
name: device.user_given_name || device.name,
model: device.model,
manufacturer: device.manufacturer,
id: device.ieee,
};
});
return outputDevices;
});
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer =>
narrow
? {
name: {
title: "Devices",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div @click=${this._handleClicked} style="cursor: pointer;">
${name}
</div>
`,
},
}
: {
name: {
title: "Name",
sortable: true,
filterable: true,
direction: "asc",
grows: true,
template: (name) => html`
<div @click=${this._handleClicked} style="cursor: pointer;">
${name}
</div>
`,
},
manufacturer: {
title: "Manufacturer",
sortable: true,
filterable: true,
width: "20%",
},
model: {
title: "Model",
sortable: true,
filterable: true,
width: "20%",
},
}
);
public clearSelection() {
this._dataTable.clearSelection();
}
protected render(): TemplateResult {
return html`
<ha-data-table
.columns=${this._columns(this.narrow)}
.data=${this._devices(this.devices)}
.selectable=${this.selectable}
auto-height
></ha-data-table>
`;
}
private async _handleClicked(ev: CustomEvent) {
const ieee = ((ev.target as HTMLElement).closest(
".mdc-data-table__row"
) as any).rowId;
showZHADeviceInfoDialog(this, { ieee });
}
}
declare global {
interface HTMLElementTagNameMap {
"zha-devices-data-table": ZHADevicesDataTable;
}
}

View File

@@ -9,8 +9,8 @@ import {
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
query,
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one";
import { HASSDomEvent } from "../../../common/dom/fire_event"; import { HASSDomEvent } from "../../../common/dom/fire_event";
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table"; import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
@@ -20,8 +20,8 @@ import {
fetchGroupableDevices, fetchGroupableDevices,
removeGroups, removeGroups,
removeMembersFromGroup, removeMembersFromGroup,
ZHADevice,
ZHAGroup, ZHAGroup,
ZHADeviceEndpoint,
} from "../../../data/zha"; } from "../../../data/zha";
import "../../../layouts/hass-error-screen"; import "../../../layouts/hass-error-screen";
import "../../../layouts/hass-subpage"; import "../../../layouts/hass-subpage";
@@ -29,37 +29,40 @@ import { HomeAssistant } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { formatAsPaddedHex } from "./functions"; import { formatAsPaddedHex } from "./functions";
import "./zha-device-card"; import "./zha-device-card";
import "./zha-devices-data-table"; import "./zha-device-endpoint-data-table";
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
@customElement("zha-group-page") @customElement("zha-group-page")
export class ZHAGroupPage extends LitElement { export class ZHAGroupPage extends LitElement {
@property() public hass!: HomeAssistant; @property({ type: Object }) public hass!: HomeAssistant;
@property() public group?: ZHAGroup; @property({ type: Object }) public group?: ZHAGroup;
@property() public groupId!: number; @property({ type: Number }) public groupId!: number;
@property() public narrow!: boolean; @property({ type: Boolean }) public narrow!: boolean;
@property() public isWide!: boolean; @property({ type: Boolean }) public isWide!: boolean;
@property() public devices: ZHADevice[] = []; @property({ type: Array }) public deviceEndpoints: ZHADeviceEndpoint[] = [];
@property() private _processingAdd = false; @property() private _processingAdd = false;
@property() private _processingRemove = false; @property() private _processingRemove = false;
@property() private _filteredDevices: ZHADevice[] = []; @property() private _filteredDeviceEndpoints: ZHADeviceEndpoint[] = [];
@property() private _selectedDevicesToAdd: string[] = []; @property() private _selectedDevicesToAdd: string[] = [];
@property() private _selectedDevicesToRemove: string[] = []; @property() private _selectedDevicesToRemove: string[] = [];
private _firstUpdatedCalled = false; @query("#addMembers")
private _zhaAddMembersDataTable!: ZHADeviceEndpointDataTable;
private _members = memoizeOne( @query("#removeMembers")
(group: ZHAGroup): ZHADevice[] => group.members private _zhaRemoveMembersDataTable!: ZHADeviceEndpointDataTable;
);
private _firstUpdatedCalled = false;
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
@@ -74,8 +77,8 @@ export class ZHAGroupPage extends LitElement {
this._processingRemove = false; this._processingRemove = false;
this._selectedDevicesToRemove = []; this._selectedDevicesToRemove = [];
this._selectedDevicesToAdd = []; this._selectedDevicesToAdd = [];
this.devices = []; this.deviceEndpoints = [];
this._filteredDevices = []; this._filteredDeviceEndpoints = [];
} }
protected firstUpdated(changedProperties: PropertyValues): void { protected firstUpdated(changedProperties: PropertyValues): void {
@@ -97,8 +100,6 @@ export class ZHAGroupPage extends LitElement {
`; `;
} }
const members = this._members(this.group);
return html` return html`
<hass-subpage .header=${this.group.name}> <hass-subpage .header=${this.group.name}>
<ha-icon-button <ha-icon-button
@@ -122,13 +123,13 @@ export class ZHAGroupPage extends LitElement {
${this.hass.localize("ui.panel.config.zha.groups.members")} ${this.hass.localize("ui.panel.config.zha.groups.members")}
</div> </div>
${members.length ${this.group.members.length
? members.map( ? this.group.members.map(
(member) => html` (member) => html`
<zha-device-card <zha-device-card
class="card" class="card"
.hass=${this.hass} .hass=${this.hass}
.device=${member} .device=${member.device}
.narrow=${this.narrow} .narrow=${this.narrow}
.showActions=${false} .showActions=${false}
.showEditableInfo=${false} .showEditableInfo=${false}
@@ -140,7 +141,7 @@ export class ZHAGroupPage extends LitElement {
This group has no members This group has no members
</p> </p>
`} `}
${members.length ${this.group.members.length
? html` ? html`
<div class="header"> <div class="header">
${this.hass.localize( ${this.hass.localize(
@@ -148,14 +149,15 @@ export class ZHAGroupPage extends LitElement {
)} )}
</div> </div>
<zha-devices-data-table <zha-device-endpoint-data-table
id="removeMembers"
.hass=${this.hass} .hass=${this.hass}
.devices=${members} .deviceEndpoints=${this.group.members}
.narrow=${this.narrow} .narrow=${this.narrow}
selectable selectable
@selection-changed=${this._handleRemoveSelectionChanged} @selection-changed=${this._handleRemoveSelectionChanged}
> >
</zha-devices-data-table> </zha-device-endpoint-data-table>
<div class="paper-dialog-buttons"> <div class="paper-dialog-buttons">
<mwc-button <mwc-button
@@ -182,14 +184,15 @@ export class ZHAGroupPage extends LitElement {
${this.hass.localize("ui.panel.config.zha.groups.add_members")} ${this.hass.localize("ui.panel.config.zha.groups.add_members")}
</div> </div>
<zha-devices-data-table <zha-device-endpoint-data-table
id="addMembers"
.hass=${this.hass} .hass=${this.hass}
.devices=${this._filteredDevices} .deviceEndpoints=${this._filteredDeviceEndpoints}
.narrow=${this.narrow} .narrow=${this.narrow}
selectable selectable
@selection-changed=${this._handleAddSelectionChanged} @selection-changed=${this._handleAddSelectionChanged}
> >
</zha-devices-data-table> </zha-device-endpoint-data-table>
<div class="paper-dialog-buttons"> <div class="paper-dialog-buttons">
<mwc-button <mwc-button
@@ -218,16 +221,22 @@ export class ZHAGroupPage extends LitElement {
if (this.groupId !== null && this.groupId !== undefined) { if (this.groupId !== null && this.groupId !== undefined) {
this.group = await fetchGroup(this.hass!, this.groupId); this.group = await fetchGroup(this.hass!, this.groupId);
} }
this.devices = await fetchGroupableDevices(this.hass!); this.deviceEndpoints = await fetchGroupableDevices(this.hass!);
// filter the groupable devices so we only show devices that aren't already in the group // filter the groupable devices so we only show devices that aren't already in the group
this._filterDevices(); this._filterDevices();
} }
private _filterDevices() { private _filterDevices() {
// filter the groupable devices so we only show devices that aren't already in the group // filter the groupable devices so we only show devices that aren't already in the group
this._filteredDevices = this.devices.filter((device) => { this._filteredDeviceEndpoints = this.deviceEndpoints.filter(
return !this.group!.members.some((member) => member.ieee === device.ieee); (deviceEndpoint) => {
}); return !this.group!.members.some(
(member) =>
member.device.ieee === deviceEndpoint.device.ieee &&
member.endpoint_id === deviceEndpoint.endpoint_id
);
}
);
} }
private _handleAddSelectionChanged( private _handleAddSelectionChanged(
@@ -244,25 +253,27 @@ export class ZHAGroupPage extends LitElement {
private async _addMembersToGroup(): Promise<void> { private async _addMembersToGroup(): Promise<void> {
this._processingAdd = true; this._processingAdd = true;
this.group = await addMembersToGroup( const members = this._selectedDevicesToAdd.map((member) => {
this.hass, const memberParts = member.split("_");
this.groupId, return { ieee: memberParts[0], endpoint_id: memberParts[1] };
this._selectedDevicesToAdd });
); this.group = await addMembersToGroup(this.hass, this.groupId, members);
this._filterDevices(); this._filterDevices();
this._selectedDevicesToAdd = []; this._selectedDevicesToAdd = [];
this._zhaAddMembersDataTable.clearSelection();
this._processingAdd = false; this._processingAdd = false;
} }
private async _removeMembersFromGroup(): Promise<void> { private async _removeMembersFromGroup(): Promise<void> {
this._processingRemove = true; this._processingRemove = true;
this.group = await removeMembersFromGroup( const members = this._selectedDevicesToRemove.map((member) => {
this.hass, const memberParts = member.split("_");
this.groupId, return { ieee: memberParts[0], endpoint_id: memberParts[1] };
this._selectedDevicesToRemove });
); this.group = await removeMembersFromGroup(this.hass, this.groupId, members);
this._filterDevices(); this._filterDevices();
this._selectedDevicesToRemove = []; this._selectedDevicesToRemove = [];
this._zhaRemoveMembersDataTable.clearSelection();
this._processingRemove = false; this._processingRemove = false;
} }

View File

@@ -20,7 +20,7 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
import { navigate } from "../../../common/navigate"; import { navigate } from "../../../common/navigate";
import { compare } from "../../../common/string/compare"; import { compare } from "../../../common/string/compare";
import "../../../components/ha-card"; import "../../../components/ha-card";
import "../../../components/ha-fab"; import "@material/mwc-fab";
import "../../../components/map/ha-locations-editor"; import "../../../components/map/ha-locations-editor";
import type { import type {
HaLocationsEditor, HaLocationsEditor,
@@ -47,6 +47,8 @@ import type { HomeAssistant, Route } from "../../../types";
import "../ha-config-section"; import "../ha-config-section";
import { configSections } from "../ha-panel-config"; import { configSections } from "../ha-panel-config";
import { showZoneDetailDialog } from "./show-dialog-zone-detail"; import { showZoneDetailDialog } from "./show-dialog-zone-detail";
import "../../../components/ha-svg-icon";
import { mdiPlus } from "@mdi/js";
@customElement("ha-config-zone") @customElement("ha-config-zone")
export class HaConfigZone extends SubscribeMixin(LitElement) { export class HaConfigZone extends SubscribeMixin(LitElement) {
@@ -244,13 +246,14 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
: ""} : ""}
</hass-tabs-subpage> </hass-tabs-subpage>
<ha-fab <mwc-fab
?is-wide=${this.isWide} ?is-wide=${this.isWide}
?narrow=${this.narrow} ?narrow=${this.narrow}
icon="hass:plus"
title="${hass.localize("ui.panel.config.zone.add_zone")}" title="${hass.localize("ui.panel.config.zone.add_zone")}"
@click=${this._createZone} @click=${this._createZone}
></ha-fab> >
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
</mwc-fab>
`; `;
} }
@@ -526,17 +529,17 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
ha-card paper-item { ha-card paper-item {
cursor: pointer; cursor: pointer;
} }
ha-fab { mwc-fab {
position: fixed; position: fixed;
bottom: 16px; bottom: 16px;
right: 16px; right: 16px;
z-index: 1; z-index: 1;
} }
ha-fab[is-wide] { mwc-fab[is-wide] {
bottom: 24px; bottom: 24px;
right: 24px; right: 24px;
} }
ha-fab[narrow] { mwc-fab[narrow] {
bottom: 84px; bottom: 84px;
} }
`; `;

View File

@@ -34,7 +34,11 @@ class IntegrationsCard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<ha-card header="Integrations"> <ha-card
.header=${this.hass.localize(
"ui.panel.developer-tools.tabs.info.integrations"
)}
>
<table class="card-content"> <table class="card-content">
<tbody> <tbody>
${this._sortedIntegrations(this.hass!.config.components).map( ${this._sortedIntegrations(this.hass!.config.components).map(
@@ -62,22 +66,29 @@ class IntegrationsCard extends LitElement {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Documentation ${this.hass.localize(
"ui.panel.developer-tools.tabs.info.documentation"
)}
</a> </a>
</td> </td>
${!manifest.is_built_in ${manifest.is_built_in || manifest.issue_tracker
? "" ? html`
: html`
<td> <td>
<a <a
href=${integrationIssuesUrl(domain)} href=${integrationIssuesUrl(
domain,
manifest
)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
Issues ${this.hass.localize(
"ui.panel.developer-tools.tabs.info.issues"
)}
</a> </a>
</td> </td>
`} `
: ""}
`} `}
</tr> </tr>
`; `;

View File

@@ -84,15 +84,19 @@ class DialogSystemLogDetail extends LitElement {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>documentation</a >documentation</a
>${!this._manifest.is_built_in >${this._manifest.is_built_in ||
? "" this._manifest.issue_tracker
: html`, ? html`,
<a <a
href=${integrationIssuesUrl(integration)} href=${integrationIssuesUrl(
integration,
this._manifest
)}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
>issues</a >issues</a
>`}) >`
: ""})
`} `}
` `
: ""} : ""}

View File

@@ -231,7 +231,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
stateLabel === "triggered" || stateLabel === "triggered" ||
!stateLabel !stateLabel
? "" ? ""
: stateLabel; : this._stateDisplay(state);
} }
private _actionDisplay(state: string): string { private _actionDisplay(state: string): string {

View File

@@ -1,13 +1,18 @@
import "@material/mwc-ripple"; import "@material/mwc-ripple";
import type { Ripple } from "@material/mwc-ripple";
import { RippleHandlers } from "@material/mwc-ripple/ripple-handlers";
import { HassEntity } from "home-assistant-js-websocket"; import { HassEntity } from "home-assistant-js-websocket";
import { import {
css, css,
CSSResult, CSSResult,
customElement, customElement,
eventOptions,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
queryAsync,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
@@ -69,6 +74,10 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
@property() private _config?: ButtonCardConfig; @property() private _config?: ButtonCardConfig;
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
@internalProperty() private _shouldRenderRipple = false;
public getCardSize(): number { public getCardSize(): number {
return 2; return 2;
} }
@@ -149,6 +158,13 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
return html` return html`
<ha-card <ha-card
@action=${this._handleAction} @action=${this._handleAction}
@focus="${this.handleRippleFocus}"
@blur="${this.handleRippleBlur}"
@mousedown="${this.handleRippleActivate}"
@mouseup="${this.handleRippleDeactivate}"
@touchstart="${this.handleRippleActivate}"
@touchend="${this.handleRippleDeactivate}"
@touchcancel="${this.handleRippleDeactivate}"
.actionHandler=${actionHandler({ .actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action), hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action), hasDoubleClick: hasAction(this._config!.double_tap_action),
@@ -189,7 +205,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
</span> </span>
` `
: ""} : ""}
<mwc-ripple></mwc-ripple> ${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
</ha-card> </ha-card>
`; `;
} }
@@ -214,6 +230,28 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
} }
} }
private _rippleHandlers: RippleHandlers = new RippleHandlers(() => {
this._shouldRenderRipple = true;
return this._ripple;
});
@eventOptions({ passive: true })
private handleRippleActivate(evt?: Event) {
this._rippleHandlers.startPress(evt);
}
private handleRippleDeactivate() {
this._rippleHandlers.endPress();
}
private handleRippleFocus() {
this._rippleHandlers.startFocus();
}
private handleRippleBlur() {
this._rippleHandlers.endFocus();
}
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-card { ha-card {
@@ -227,11 +265,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
justify-content: center; justify-content: center;
position: relative;
} }
ha-card:focus { ha-card:focus {
outline: none; outline: none;
background: var(--divider-color);
} }
ha-icon { ha-icon {

View File

@@ -10,17 +10,21 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { styleMap } from "lit-html/directives/style-map"; import { styleMap } from "lit-html/directives/style-map";
import "@thomasloven/round-slider";
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
import { fireEvent } from "../../../common/dom/fire_event"; import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name"; import { computeStateName } from "../../../common/entity/compute_state_name";
import { isValidEntityId } from "../../../common/entity/valid_entity_id"; import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import "../../../components/ha-card"; import "../../../components/ha-card";
import { HomeAssistant } from "../../../types"; import type { HomeAssistant } from "../../../types";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { GaugeCardConfig } from "./types"; import type { GaugeCardConfig } from "./types";
import { debounce } from "../../../common/util/debounce";
import { installResizeObserver } from "../common/install-resize-observer";
export const severityMap = { export const severityMap = {
red: "var(--label-badge-red)", red: "var(--label-badge-red)",
@@ -63,11 +67,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
@property() public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() private _baseUnit = "50px";
@property() private _config?: GaugeCardConfig; @property() private _config?: GaugeCardConfig;
private _updated?: boolean; private _resizeObserver?: ResizeObserver;
public connectedCallback(): void {
super.connectedCallback();
this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback(): void {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
}
public getCardSize(): number { public getCardSize(): number {
return 2; return 2;
@@ -83,11 +96,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
this._config = { min: 0, max: 100, ...config }; this._config = { min: 0, max: 100, ...config };
} }
public connectedCallback(): void {
super.connectedCallback();
this._setBaseUnit();
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._config || !this.hass) { if (!this._config || !this.hass) {
return html``; return html``;
@@ -121,33 +129,32 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
`; `;
} }
const sliderBarColor = this._computeSeverity(state);
return html` return html`
<ha-card <ha-card
@click="${this._handleClick}" @click=${this._handleClick}
tabindex="0" tabindex="0"
style=${styleMap({ style=${styleMap({
"--base-unit": this._baseUnit, "--round-slider-bar-color": sliderBarColor,
})} })}
> >
<div class="container"> <round-slider
<div class="gauge-a"></div> readonly
<div arcLength="180"
class="gauge-c" startAngle="180"
style=${styleMap({ .value=${state}
transform: `rotate(${this._translateTurn(state)}turn)`, .min=${this._config.min}
"background-color": this._computeSeverity(state), .max=${this._config.max}
})} ></round-slider>
></div>
<div class="gauge-b"></div>
</div>
<div class="gauge-data"> <div class="gauge-data">
<div id="percent"> <div class="percent">
${stateObj.state} ${stateObj.state}
${this._config.unit || ${this._config.unit ||
stateObj.attributes.unit_of_measurement || stateObj.attributes.unit_of_measurement ||
""} ""}
</div> </div>
<div id="name"> <div class="name">
${this._config.name || computeStateName(stateObj)} ${this._config.name || computeStateName(stateObj)}
</div> </div>
</div> </div>
@@ -160,10 +167,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._updated = true; this._attachObserver();
this._setBaseUnit();
// eslint-disable-next-line wc/no-self-class
this.classList.add("init");
} }
protected updated(changedProps: PropertyValues): void { protected updated(changedProps: PropertyValues): void {
@@ -187,16 +191,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
} }
} }
private _setBaseUnit(): void {
if (!this.isConnected || !this._updated) {
return;
}
const baseUnit = this._computeBaseUnit();
if (baseUnit !== "0px") {
this._baseUnit = baseUnit;
}
}
private _computeSeverity(numberValue: number): string { private _computeSeverity(numberValue: number): string {
const sections = this._config!.severity; const sections = this._config!.severity;
@@ -229,95 +223,122 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
return severityMap.normal; return severityMap.normal;
} }
private _translateTurn(value: number): number {
const { min, max } = this._config!;
const maxTurnValue = Math.min(Math.max(value, min!), max!);
return (5 * (maxTurnValue - min!)) / (max! - min!) / 10;
}
private _computeBaseUnit(): string {
return this.clientWidth < 200 ? this.clientWidth / 5 + "px" : "50px";
}
private _handleClick(): void { private _handleClick(): void {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }
private async _attachObserver(): Promise<void> {
await installResizeObserver();
this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false)
);
const card = this.shadowRoot!.querySelector("ha-card");
// If we show an error or warning there is no ha-card
if (!card) {
return;
}
this._resizeObserver.observe(card);
}
private _measureCard() {
if (this.offsetWidth < 200) {
this.setAttribute("narrow", "");
} else {
this.removeAttribute("narrow");
}
if (this.offsetWidth < 150) {
this.setAttribute("veryNarrow", "");
} else {
this.removeAttribute("veryNarrow");
}
}
static get styles(): CSSResult { static get styles(): CSSResult {
return css` return css`
ha-card { ha-card {
cursor: pointer; cursor: pointer;
padding: 16px 16px 0 16px;
height: 100%; height: 100%;
overflow: hidden;
padding: 16px 16px 0 16px;
display: flex; display: flex;
align-items: center;
justify-content: center;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
justify-content: center;
align-items: center;
} }
ha-card:focus { ha-card:focus {
outline: none; outline: none;
background: var(--divider-color); background: var(--divider-color);
} }
.container {
width: calc(var(--base-unit) * 4); round-slider {
height: calc(var(--base-unit) * 2); max-width: 200px;
overflow: hidden; --round-slider-path-width: 35px;
position: relative; --round-slider-path-color: var(--disabled-text-color);
} --round-slider-linecap: "butt";
.gauge-a {
position: absolute;
background-color: var(--primary-background-color);
width: calc(var(--base-unit) * 4);
height: calc(var(--base-unit) * 2);
top: 0%;
border-radius: calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5)
0px 0px;
}
.gauge-b {
position: absolute;
background-color: var(--paper-card-background-color);
width: calc(var(--base-unit) * 2.5);
height: calc(var(--base-unit) * 1.25);
top: calc(var(--base-unit) * 0.75);
margin-left: calc(var(--base-unit) * 0.75);
margin-right: auto;
border-radius: calc(var(--base-unit) * 2.5) calc(var(--base-unit) * 2.5)
0px 0px;
}
.gauge-c {
position: absolute;
background-color: var(--label-badge-blue);
width: calc(var(--base-unit) * 4);
height: calc(var(--base-unit) * 2);
top: calc(var(--base-unit) * 2);
margin-left: auto;
margin-right: auto;
border-radius: 0px 0px calc(var(--base-unit) * 2)
calc(var(--base-unit) * 2);
transform-origin: center top;
}
.init .gauge-c {
transition: all 1.3s ease-in-out;
} }
.gauge-data { .gauge-data {
line-height: 1;
text-align: center; text-align: center;
color: var(--primary-text-color);
line-height: calc(var(--base-unit) * 0.3);
width: 100%;
position: relative; position: relative;
top: calc(var(--base-unit) * -0.5); color: var(--primary-text-color);
margin-top: -28px;
margin-bottom: 14px;
} }
.init .gauge-data {
transition: all 1s ease-out; .gauge-data .percent {
font-size: 28px;
} }
.gauge-data #percent {
font-size: calc(var(--base-unit) * 0.55); .gauge-data .name {
line-height: calc(var(--base-unit) * 0.55); padding-top: 6px;
font-size: 14px;
} }
.gauge-data #name {
padding-top: calc(var(--base-unit) * 0.15); /* ============= NARROW ============= */
font-size: calc(var(--base-unit) * 0.3);
:host([narrow]) round-slider {
--round-slider-path-width: 22px;
}
:host([narrow]) .gauge-data {
margin-top: -24px;
margin-bottom: 12px;
}
:host([narrow]) .gauge-data .percent {
font-size: 24px;
}
:host([narrow]) .gauge-data .name {
font-size: 12px;
}
/* ============= VERY NARROW ============= */
:host([veryNarrow]) round-slider {
--round-slider-path-width: 15px;
}
:host([veryNarrow]) ha-card {
padding-bottom: 16px;
}
:host([veryNarrow]) .gauge-data {
margin-top: 0;
margin-bottom: 0;
}
:host([veryNarrow]) .gauge-data .percent {
font-size: 20px;
}
:host([veryNarrow]) .gauge-data .name {
font-size: 10px;
} }
`; `;
} }

View File

@@ -46,6 +46,7 @@ import "../components/hui-marquee";
import type { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import "../components/hui-warning"; import "../components/hui-warning";
import { MediaControlCardConfig } from "./types"; import { MediaControlCardConfig } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
function getContrastRatio( function getContrastRatio(
rgb1: [number, number, number], rgb1: [number, number, number],
@@ -223,7 +224,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._measureCard()); this.updateComplete.then(() => this._attachObserver());
if (!this.hass || !this._config) { if (!this.hass || !this._config) {
return; return;
@@ -252,6 +253,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
clearInterval(this._progressInterval); clearInterval(this._progressInterval);
this._progressInterval = undefined; this._progressInterval = undefined;
} }
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
} }
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -624,15 +628,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
this._cardHeight = card.offsetHeight; this._cardHeight = card.offsetHeight;
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
if (typeof ResizeObserver !== "function") { await installResizeObserver();
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver( this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false) debounce(() => this._measureCard(), 250, false)
); );

View File

@@ -21,16 +21,17 @@ import { UNAVAILABLE } from "../../../data/entity";
import { import {
getSecondaryWeatherAttribute, getSecondaryWeatherAttribute,
getWeatherUnit, getWeatherUnit,
weatherIcons, getWeatherStateIcon,
weatherImages, weatherSVGStyles,
} from "../../../data/weather"; } from "../../../data/weather";
import { HomeAssistant, WeatherEntity } from "../../../types"; import type { HomeAssistant, WeatherEntity } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive"; import { actionHandler } from "../common/directives/action-handler-directive";
import { findEntities } from "../common/find-entites"; import { findEntities } from "../common/find-entites";
import { hasConfigOrEntityChanged } from "../common/has-changed"; import { hasConfigOrEntityChanged } from "../common/has-changed";
import "../components/hui-warning"; import "../components/hui-warning";
import { LovelaceCard, LovelaceCardEditor } from "../types"; import type { LovelaceCard, LovelaceCardEditor } from "../types";
import { WeatherForecastCardConfig } from "./types"; import type { WeatherForecastCardConfig } from "./types";
import { installResizeObserver } from "../common/install-resize-observer";
const DAY_IN_MILLISECONDS = 86400000; const DAY_IN_MILLISECONDS = 86400000;
@@ -72,7 +73,13 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
this.updateComplete.then(() => this._measureCard()); this.updateComplete.then(() => this._attachObserver());
}
public disconnectedCallback(): void {
if (this._resizeObserver) {
this._resizeObserver.disconnect();
}
} }
public getCardSize(): number { public getCardSize(): number {
@@ -158,6 +165,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
hourly = timeDiff < DAY_IN_MILLISECONDS; hourly = timeDiff < DAY_IN_MILLISECONDS;
} }
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
return html` return html`
<ha-card <ha-card
@action=${this._handleAction} @action=${this._handleAction}
@@ -166,25 +175,16 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
> >
<div class="content"> <div class="content">
<div class="icon-image"> <div class="icon-image">
${stateObj.state in weatherImages ${weatherStateIcon ||
? html` html`
<img <ha-icon
class="weather-image" class="weather-icon"
src="${weatherImages[stateObj.state]}" .icon=${stateIcon(stateObj)}
/> ></ha-icon>
` `}
: html`
<ha-icon
class="weather-icon"
.icon=${weatherIcons[stateObj.state] || stateIcon(stateObj)}
></ha-icon>
`}
</div> </div>
<div class="info"> <div class="info">
<div class="name-state"> <div class="name-state">
<div class="name">
${this._config.name || computeStateName(stateObj)}
</div>
<div class="state"> <div class="state">
${computeStateDisplay( ${computeStateDisplay(
this.hass.localize, this.hass.localize,
@@ -192,6 +192,9 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
this.hass.language this.hass.language
)} )}
</div> </div>
<div class="name">
${this._config.name || computeStateName(stateObj)}
</div>
</div> </div>
<div class="temp-attribute"> <div class="temp-attribute">
<div class="temp"> <div class="temp">
@@ -200,7 +203,20 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
> >
</div> </div>
<div class="attribute"> <div class="attribute">
${getSecondaryWeatherAttribute(this.hass, stateObj)} ${this._config.secondary_info_attribute !== undefined
? html`
${this.hass!.localize(
`ui.card.weather.attributes.${this._config.secondary_info_attribute}`
)}
${stateObj.attributes[
this._config.secondary_info_attribute
]}
${getWeatherUnit(
this.hass,
this._config.secondary_info_attribute
)}
`
: getSecondaryWeatherAttribute(this.hass, stateObj)}
</div> </div>
</div> </div>
</div> </div>
@@ -231,21 +247,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
${item.condition !== undefined && item.condition !== null ${item.condition !== undefined && item.condition !== null
? html` ? html`
<div class="forecast-image-icon"> <div class="forecast-image-icon">
${item.condition in weatherImages ${getWeatherStateIcon(item.condition, this)}
? html`
<img
class="forecast-image"
src="${weatherImages[item.condition]}"
/>
`
: item.condition in weatherIcons
? html`
<ha-icon
class="forecast-icon"
.icon=${weatherIcons[item.condition]}
></ha-icon>
`
: ""}
</div> </div>
` `
: ""} : ""}
@@ -286,15 +288,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
fireEvent(this, "hass-more-info", { entityId: this._config!.entity }); fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
} }
private _attachObserver(): void { private async _attachObserver(): Promise<void> {
if (typeof ResizeObserver !== "function") { await installResizeObserver();
import("resize-observer").then((modules) => {
modules.install();
this._attachObserver();
});
return;
}
this._resizeObserver = new ResizeObserver( this._resizeObserver = new ResizeObserver(
debounce(() => this._measureCard(), 250, false) debounce(() => this._measureCard(), 250, false)
); );
@@ -321,201 +316,205 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
} }
} }
static get styles(): CSSResult { static get styles(): CSSResult[] {
return css` return [
:host { weatherSVGStyles,
display: block; css`
} :host {
display: block;
}
ha-card { ha-card {
cursor: pointer; cursor: pointer;
padding: 16px; padding: 16px;
} }
.content { .content {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.icon-image { .icon-image {
display: flex; display: flex;
align-items: center; align-items: center;
min-width: 64px; min-width: 64px;
margin-right: 16px; margin-right: 16px;
} }
.weather-image, .icon-image > * {
.weather-icon { flex: 0 0 64px;
flex: 0 0 64px; height: 64px;
} }
.weather-icon { .weather-icon {
--mdc-icon-size: 64px; --mdc-icon-size: 64px;
} }
.info { .info {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-grow: 1; flex-grow: 1;
overflow: hidden; overflow: hidden;
} }
.temp-attribute { .temp-attribute {
text-align: right; text-align: right;
} }
.temp-attribute .temp { .temp-attribute .temp {
position: relative; position: relative;
margin-right: 24px; margin-right: 24px;
} }
.temp-attribute .temp span { .temp-attribute .temp span {
position: absolute; position: absolute;
font-size: 24px; font-size: 24px;
top: 1px; top: 1px;
} }
.name, .state,
.temp-attribute .temp { .temp-attribute .temp {
font-size: 28px; font-size: 28px;
line-height: 1.2; line-height: 1.2;
} }
.state, .name,
.attribute { .attribute {
font-size: 14px; font-size: 14px;
line-height: 1; line-height: 1;
} }
.name-state { .name-state {
overflow: hidden; overflow: hidden;
padding-right: 12px; padding-right: 12px;
width: 100%; width: 100%;
} }
.name, .name,
.state { .state {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.attribute { .attribute {
white-space: nowrap; white-space: nowrap;
} }
.forecast { .forecast {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
padding-top: 16px; padding-top: 16px;
} }
.forecast > div { .forecast > div {
text-align: center; text-align: center;
} }
.forecast .icon, .forecast .icon,
.forecast .temp { .forecast .temp {
margin: 4px 0; margin: 4px 0;
} }
.forecast .temp { .forecast .temp {
font-size: 16px; font-size: 16px;
} }
.forecast-image-icon { .forecast-image-icon {
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
} display: flex;
}
.forecast-image { .forecast-image-icon > * {
width: 40px; width: 40px;
} }
.forecast-icon { .forecast-icon {
--mdc-icon-size: 40px; --mdc-icon-size: 40px;
} }
.attribute, .attribute,
.templow, .templow,
.state { .name {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
.unavailable { .unavailable {
height: 100px; height: 100px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 16px; font-size: 16px;
padding: 10px 20px; padding: 10px 20px;
text-align: center; text-align: center;
} }
/* ============= NARROW ============= */ /* ============= NARROW ============= */
:host([narrow]) .icon-image { :host([narrow]) .icon-image {
min-width: 52px; min-width: 52px;
} }
:host([narrow]) .weather-image { :host([narrow]) .weather-image {
flex: 0 0 52px; flex: 0 0 52px;
width: 52px; width: 52px;
} }
:host([narrow]) .weather-icon { :host([narrow]) .weather-icon {
--mdc-icon-size: 52px; --mdc-icon-size: 52px;
} }
:host([narrow]) .name, :host([narrow]) .state,
:host([narrow]) .temp-attribute .temp { :host([narrow]) .temp-attribute .temp {
font-size: 22px; font-size: 22px;
} }
:host([narrow]) .temp-attribute .temp { :host([narrow]) .temp-attribute .temp {
margin-right: 16px; margin-right: 16px;
} }
:host([narrow]) .temp span { :host([narrow]) .temp span {
top: 1px; top: 1px;
font-size: 16px; font-size: 16px;
} }
/* ============= VERY NARROW ============= */ /* ============= VERY NARROW ============= */
:host([veryNarrow]) .state, :host([veryNarrow]) .name,
:host([veryNarrow]) .attribute { :host([veryNarrow]) .attribute {
display: none; display: none;
} }
:host([veryNarrow]) .info { :host([veryNarrow]) .info {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
} }
:host([veryNarrow]) .name-state { :host([veryNarrow]) .name-state {
padding-right: 0; padding-right: 0;
} }
/* ============= VERY VERY NARROW ============= */ /* ============= VERY VERY NARROW ============= */
:host([veryVeryNarrow]) .info { :host([veryVeryNarrow]) .info {
padding-top: 4px; padding-top: 4px;
align-items: center; align-items: center;
} }
:host([veryVeryNarrow]) .content { :host([veryVeryNarrow]) .content {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
flex-direction: column; flex-direction: column;
} }
:host([veryVeryNarrow]) .icon-image { :host([veryVeryNarrow]) .icon-image {
margin-right: 0; margin-right: 0;
} }
`; `,
];
} }
} }

View File

@@ -34,7 +34,8 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
| "last-changed" | "last-changed"
| "last-triggered" | "last-triggered"
| "position" | "position"
| "tilt-position"; | "tilt-position"
| "brightness";
action_name?: string; action_name?: string;
service?: string; service?: string;
service_data?: object; service_data?: object;
@@ -275,4 +276,5 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig {
entity: string; entity: string;
name?: string; name?: string;
show_forecast?: boolean; show_forecast?: boolean;
secondary_info_attribute?: string;
} }

View File

@@ -170,12 +170,12 @@ class ActionHandler extends HTMLElement implements ActionHandler {
display: null, display: null,
}); });
this.ripple.disabled = false; this.ripple.disabled = false;
this.ripple.activate(); this.ripple.startPress();
this.ripple.unbounded = true; this.ripple.unbounded = true;
} }
private stopAnimation() { private stopAnimation() {
this.ripple.deactivate(); this.ripple.endPress();
this.ripple.disabled = true; this.ripple.disabled = true;
this.style.display = "none"; this.style.display = "none";
} }

View File

@@ -465,7 +465,8 @@ export const generateLovelaceConfigFromData = async (
}; };
export const generateLovelaceConfigFromHass = async ( export const generateLovelaceConfigFromHass = async (
hass: HomeAssistant hass: HomeAssistant,
localize?: LocalizeFunc
): Promise<LovelaceConfig> => { ): Promise<LovelaceConfig> => {
// We want to keep the registry subscriptions alive after generating the UI // We want to keep the registry subscriptions alive after generating the UI
// so that we don't serve up stale data after changing areas. // so that we don't serve up stale data after changing areas.
@@ -488,6 +489,6 @@ export const generateLovelaceConfigFromHass = async (
deviceEntries, deviceEntries,
entityEntries, entityEntries,
hass.states, hass.states,
hass.localize localize || hass.localize
); );
}; };

View File

@@ -0,0 +1,6 @@
export const installResizeObserver = async () => {
if (typeof ResizeObserver !== "function") {
const modules = await import("resize-observer");
modules.install();
}
};

View File

@@ -1,4 +1,3 @@
import "@material/mwc-ripple";
import { import {
css, css,
CSSResult, CSSResult,
@@ -78,7 +77,6 @@ export class HuiButtonsBase extends LitElement {
? entityConf.name || computeStateName(stateObj) ? entityConf.name || computeStateName(stateObj)
: ""} : ""}
</span> </span>
<mwc-ripple unbounded></mwc-ripple>
</div> </div>
`; `;
})} })}
@@ -108,6 +106,7 @@ export class HuiButtonsBase extends LitElement {
cursor: pointer; cursor: pointer;
align-items: center; align-items: center;
display: inline-flex; display: inline-flex;
outline: none;
} }
`; `;
} }

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