Compare commits
38 Commits
add-import
...
20200512.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
de1ffe67b1 | ||
![]() |
eb2b24d57c | ||
![]() |
825db8a56a | ||
![]() |
577a21fc5c | ||
![]() |
1b2841eef9 | ||
![]() |
845511e322 | ||
![]() |
96ab057853 | ||
![]() |
f85cf0a238 | ||
![]() |
84a2676a9c | ||
![]() |
466a1af902 | ||
![]() |
ebbe7e805f | ||
![]() |
c861ee025e | ||
![]() |
6d0823328d | ||
![]() |
60be14dc77 | ||
![]() |
2d627819d9 | ||
![]() |
cf575f83f5 | ||
![]() |
79935b2d4c | ||
![]() |
d1cceb2013 | ||
![]() |
f5da130d51 | ||
![]() |
8768304ec5 | ||
![]() |
f10a5dcdbe | ||
![]() |
a27428ebcd | ||
![]() |
d19acf17c2 | ||
![]() |
1a0bf861ee | ||
![]() |
db906ad4d0 | ||
![]() |
75ed0f2f99 | ||
![]() |
29ed1144d5 | ||
![]() |
fa445d4066 | ||
![]() |
d10be4ef2d | ||
![]() |
7f66d5b8e9 | ||
![]() |
0c8cd680c2 | ||
![]() |
a7ba1977b4 | ||
![]() |
a960b39235 | ||
![]() |
3febf059ec | ||
![]() |
20203f7bdb | ||
![]() |
a399d76d06 | ||
![]() |
1ca097c5a0 | ||
![]() |
ae6243b7bf |
@@ -9,25 +9,30 @@ const paths = require("../paths");
|
||||
gulp.task("compress-app", function compressApp() {
|
||||
const jsLatest = gulp
|
||||
.src(path.resolve(paths.output, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(paths.output));
|
||||
|
||||
const jsEs5 = gulp
|
||||
.src(path.resolve(paths.output_es5, "**/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(paths.output_es5));
|
||||
|
||||
const polyfills = gulp
|
||||
.src(path.resolve(paths.static, "polyfills/*.js"))
|
||||
.pipe(zopfli())
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.static, "polyfills")));
|
||||
|
||||
const translations = gulp
|
||||
.src(path.resolve(paths.static, "translations/*.json"))
|
||||
.pipe(zopfli())
|
||||
.src(path.resolve(paths.static, "translations/**/*.json"))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.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() {
|
||||
|
@@ -1,9 +1,14 @@
|
||||
const del = require("del");
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const mapStream = require("map-stream");
|
||||
|
||||
const inDir = "translations/frontend";
|
||||
const downloadDir = inDir + "/downloads";
|
||||
const inDirFrontend = "translations/frontend";
|
||||
const inDirBackend = "translations/backend";
|
||||
const downloadDir = "translations/downloads";
|
||||
const srcMeta = "src/translations/translationMetadata.json";
|
||||
|
||||
const encoding = "utf8";
|
||||
|
||||
const tasks = [];
|
||||
|
||||
@@ -53,9 +58,25 @@ gulp.task(taskName, function () {
|
||||
});
|
||||
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";
|
||||
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);
|
||||
|
||||
@@ -65,6 +86,7 @@ gulp.task(
|
||||
gulp.series(
|
||||
"check-translations-html",
|
||||
"move-downloaded-translations",
|
||||
"check-all-files-exist",
|
||||
"clean-downloaded-translations"
|
||||
)
|
||||
);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultArray,
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import memoizeOne from "memoize-one";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import "../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
@@ -66,7 +67,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
<div class="card-group">
|
||||
${addons.map(
|
||||
(addon) => html`
|
||||
<paper-card
|
||||
<ha-card
|
||||
.addon=${addon}
|
||||
class=${addon.available ? "" : "not_available"}
|
||||
@click=${this._addonTapped}
|
||||
@@ -78,8 +79,8 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
.description=${addon.description}
|
||||
.available=${addon.available}
|
||||
.icon=${addon.installed && addon.installed !== addon.version
|
||||
? "hassio:arrow-up-bold-circle"
|
||||
: "hassio:puzzle"}
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
.iconTitle=${addon.installed
|
||||
? addon.installed !== addon.version
|
||||
? "New version available"
|
||||
@@ -111,7 +112,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
: ""}
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
@@ -127,7 +128,7 @@ class HassioAddonRepositoryEl extends LitElement {
|
||||
return [
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
.not_available {
|
||||
|
@@ -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 {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -6,22 +9,21 @@ import {
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
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 {
|
||||
fetchHassioAddonsInfo,
|
||||
HassioAddonInfo,
|
||||
HassioAddonRepository,
|
||||
reloadHassioAddons,
|
||||
} 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/loading-screen";
|
||||
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 { supervisorTabs } from "../hassio-panel";
|
||||
import "./hassio-addon-repository";
|
||||
|
||||
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
if (a.slug === "local") {
|
||||
@@ -94,27 +96,17 @@ class HassioAddonStore extends LitElement {
|
||||
.tabs=${supervisorTabs}
|
||||
>
|
||||
<span slot="header">Add-on store</span>
|
||||
<paper-menu-button
|
||||
close-on-activate
|
||||
no-animations
|
||||
horizontal-align="right"
|
||||
horizontal-offset="-5"
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
<ha-icon-button
|
||||
icon="hassio:dots-vertical"
|
||||
slot="dropdown-trigger"
|
||||
alt="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>
|
||||
<ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
|
||||
<mwc-icon-button slot="trigger" alt="menu">
|
||||
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-list-item @tap=${this._manageRepositories}>
|
||||
Repositories
|
||||
</mwc-list-item>
|
||||
<mwc-list-item @tap=${this.refreshData}>
|
||||
Reload
|
||||
</mwc-list-item>
|
||||
</ha-button-menu>
|
||||
${repos.length === 0
|
||||
? html`<loading-screen></loading-screen>`
|
||||
: html`
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "web-animations-js/web-animations-next-lite.min";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
@@ -23,9 +23,9 @@ import {
|
||||
fetchHassioHardwareAudio,
|
||||
HassioHardwareAudioDevice,
|
||||
} from "../../../../src/data/hassio/hardware";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-audio")
|
||||
@@ -46,7 +46,7 @@ class HassioAddonAudio extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-card heading="Audio">
|
||||
<ha-card header="Audio">
|
||||
<div class="card-content">
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
|
||||
@@ -92,7 +92,7 @@ class HassioAddonAudio extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._saveSettings}>Save</mwc-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class HassioAddonAudio extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
:host,
|
||||
paper-card,
|
||||
ha-card,
|
||||
paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
|
@@ -8,12 +8,10 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-network";
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -13,6 +12,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||
import {
|
||||
@@ -23,9 +23,8 @@ import {
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-config")
|
||||
class HassioAddonConfig extends LitElement {
|
||||
@@ -46,7 +45,7 @@ class HassioAddonConfig extends LitElement {
|
||||
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<paper-card heading="Configuration">
|
||||
<ha-card header="Configuration">
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
@value-changed=${this._configChanged}
|
||||
@@ -65,7 +64,7 @@ class HassioAddonConfig extends LitElement {
|
||||
Save
|
||||
</mwc-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -77,7 +76,7 @@ class HassioAddonConfig extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
paper-card {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.card-actions {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@@ -11,15 +10,15 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
setHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
interface NetworkItem {
|
||||
@@ -53,7 +52,7 @@ class HassioAddonNetwork extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<paper-card heading="Network">
|
||||
<ha-card header="Network">
|
||||
<div class="card-content">
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
|
||||
@@ -90,7 +89,7 @@ class HassioAddonNetwork extends LitElement {
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._saveTapped}>Save</mwc-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ class HassioAddonNetwork extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
paper-card {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,16 +8,15 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import {
|
||||
HassioAddonDetails,
|
||||
fetchHassioAddonDocumentation,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import {
|
||||
fetchHassioAddonDocumentation,
|
||||
HassioAddonDetails,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-addon-documentation-tab")
|
||||
class HassioAddonDocumentationDashboard extends LitElement {
|
||||
@@ -41,14 +39,14 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
? html`<ha-markdown .content=${this._content}></ha-markdown>`
|
||||
: html`<loading-screen></loading-screen>`}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -58,7 +56,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.content {
|
||||
|
@@ -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 {
|
||||
css,
|
||||
@@ -14,18 +19,17 @@ import {
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
} 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 { HomeAssistant, Route } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./config/hassio-addon-audio";
|
||||
import "./config/hassio-addon-config";
|
||||
import "./config/hassio-addon-network";
|
||||
import "./hassio-addon-router";
|
||||
import "./info/hassio-addon-info";
|
||||
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")
|
||||
class HassioAddonDashboard extends LitElement {
|
||||
@@ -59,7 +63,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
{
|
||||
name: "Info",
|
||||
path: `/hassio/addon/${this.addon.slug}/info`,
|
||||
icon: "hassio:information-variant",
|
||||
iconPath: mdiInformationVariant,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -67,7 +71,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
addonTabs.push({
|
||||
name: "Documentation",
|
||||
path: `/hassio/addon/${this.addon.slug}/documentation`,
|
||||
icon: "hassio:file-document",
|
||||
iconPath: mdiFileDocument,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,12 +80,12 @@ class HassioAddonDashboard extends LitElement {
|
||||
{
|
||||
name: "Configuration",
|
||||
path: `/hassio/addon/${this.addon.slug}/config`,
|
||||
icon: "hassio:cogs",
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
{
|
||||
name: "Log",
|
||||
path: `/hassio/addon/${this.addon.slug}/logs`,
|
||||
icon: "hassio:math-log",
|
||||
iconPath: mdiMathLog,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -115,7 +119,6 @@ class HassioAddonDashboard extends LitElement {
|
||||
css`
|
||||
:host {
|
||||
color: var(--primary-text-color);
|
||||
--paper-card-header-color: var(--primary-text-color);
|
||||
}
|
||||
.content {
|
||||
padding: 24px 0 32px;
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import { customElement, property } from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../src/layouts/hass-router-page";
|
||||
import { customElement, property } from "lit-element";
|
||||
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
|
||||
import "./info/hassio-addon-info-tab";
|
||||
import "./config/hassio-addon-config-tab";
|
||||
import "./log/hassio-addon-log-tab";
|
||||
import "./documentation/hassio-addon-documentation-tab";
|
||||
import { HassioAddonDetails } from "../../../src/data/hassio/addon";
|
||||
|
||||
@customElement("hassio-addon-router")
|
||||
class HassioAddonRouter extends HassRouterPage {
|
||||
|
@@ -8,12 +8,10 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "./hassio-addon-info";
|
||||
|
||||
@customElement("hassio-addon-info-tab")
|
||||
|
@@ -1,5 +1,20 @@
|
||||
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 {
|
||||
css,
|
||||
@@ -16,10 +31,11 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import { navigate } from "../../../../src/common/navigate";
|
||||
import "../../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../../src/components/buttons/ha-progress-button";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-label-badge";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import "../../../../src/components/ha-icon";
|
||||
import {
|
||||
fetchHassioAddonChangelog,
|
||||
HassioAddonDetails,
|
||||
@@ -30,23 +46,23 @@ import {
|
||||
setHassioAddonSecurity,
|
||||
uninstallHassioAddon,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import "../../components/hassio-card-content";
|
||||
import { showHassioMarkdownDialog } from "../../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
const STAGE_ICON = {
|
||||
stable: "mdi:check-circle",
|
||||
experimental: "mdi:flask",
|
||||
deprecated: "mdi:exclamation-thick",
|
||||
stable: mdiCheckCircle,
|
||||
experimental: mdiFlask,
|
||||
deprecated: mdiExclamationThick,
|
||||
};
|
||||
|
||||
const PERMIS_DESC = {
|
||||
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: {
|
||||
title: "Add-on Security Rating",
|
||||
@@ -116,7 +132,7 @@ class HassioAddonInfo extends LitElement {
|
||||
return html`
|
||||
${this._computeUpdateAvailable
|
||||
? html`
|
||||
<paper-card heading="Update available! 🎉">
|
||||
<ha-card header="Update available! 🎉">
|
||||
<div class="card-content">
|
||||
<hassio-card-content
|
||||
.hass=${this.hass}
|
||||
@@ -124,7 +140,7 @@ class HassioAddonInfo extends LitElement {
|
||||
.version_latest} is available"
|
||||
.description="You are currently running version ${this.addon
|
||||
.version}"
|
||||
icon="hassio:arrow-up-bold-circle"
|
||||
icon=${mdiArrowUpBoldCircle}
|
||||
iconClass="update"
|
||||
></hassio-card-content>
|
||||
${!this.addon.available
|
||||
@@ -151,12 +167,13 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
${!this.addon.protected
|
||||
? 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">
|
||||
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>
|
||||
@@ -164,11 +181,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<mwc-button @click=${this._protectionToggled}>Enable Protection mode</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="addon-header">
|
||||
${!this.narrow ? this.addon.name : ""}
|
||||
@@ -177,18 +194,18 @@ class HassioAddonInfo extends LitElement {
|
||||
? html`
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<ha-icon
|
||||
<ha-svg-icon
|
||||
title="Add-on is running"
|
||||
class="running"
|
||||
icon="hassio:circle"
|
||||
></ha-icon>
|
||||
path=${mdiCircle}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<ha-icon
|
||||
<ha-svg-icon
|
||||
title="Add-on is stopped"
|
||||
class="stopped"
|
||||
icon="hassio:circle"
|
||||
></ha-icon>
|
||||
path=${mdiCircle}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
`
|
||||
: html` ${this.addon.version_latest} `}
|
||||
@@ -232,10 +249,11 @@ class HassioAddonInfo extends LitElement {
|
||||
})}
|
||||
@click=${this._showMoreInfo}
|
||||
id="stage"
|
||||
.icon=${STAGE_ICON[this.addon.stage]}
|
||||
label="stage"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
<ha-label-badge
|
||||
class=${classMap({
|
||||
green: [5, 6].includes(Number(this.addon.rating)),
|
||||
@@ -253,10 +271,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_network"
|
||||
icon="hassio:network"
|
||||
label="host"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiNetwork}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.full_access
|
||||
@@ -264,10 +283,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="full_access"
|
||||
icon="hassio:chip"
|
||||
label="hardware"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiChip}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.homeassistant_api
|
||||
@@ -275,10 +295,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="homeassistant_api"
|
||||
icon="hassio:home-assistant"
|
||||
label="hass"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this._computeHassioApi
|
||||
@@ -286,10 +307,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="hassio_api"
|
||||
icon="hassio:home-assistant"
|
||||
label="hassio"
|
||||
.description=${this.addon.hassio_role}
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiHomeAssistant}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.docker_api
|
||||
@@ -297,10 +319,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="docker_api"
|
||||
icon="hassio:docker"
|
||||
label="docker"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiDocker}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.host_pid
|
||||
@@ -308,10 +331,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="host_pid"
|
||||
icon="hassio:pound"
|
||||
label="host pid"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiPound}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.apparmor
|
||||
@@ -320,10 +344,11 @@ class HassioAddonInfo extends LitElement {
|
||||
@click=${this._showMoreInfo}
|
||||
class=${this._computeApparmorClassName}
|
||||
id="apparmor"
|
||||
icon="hassio:shield"
|
||||
label="apparmor"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiShield}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.auth_api
|
||||
@@ -331,10 +356,11 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="auth_api"
|
||||
icon="hassio:key"
|
||||
label="auth"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon path=${mdiKey}></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
${this.addon.ingress
|
||||
@@ -342,10 +368,13 @@ class HassioAddonInfo extends LitElement {
|
||||
<ha-label-badge
|
||||
@click=${this._showMoreInfo}
|
||||
id="ingress"
|
||||
icon="hassio:cursor-default-click-outline"
|
||||
label="ingress"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
>
|
||||
<ha-svg-icon
|
||||
path=${mdiCursorDefaultClickOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-label-badge>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -399,7 +428,7 @@ class HassioAddonInfo extends LitElement {
|
||||
<div>
|
||||
Protection mode
|
||||
<span>
|
||||
<ha-icon icon="hassio:information"></ha-icon>
|
||||
<ha-svg-icon path=${mdiInformation}></ha-svg-icon>
|
||||
<paper-tooltip>
|
||||
Grant the add-on elevated system access.
|
||||
</paper-tooltip>
|
||||
@@ -502,17 +531,17 @@ class HassioAddonInfo extends LitElement {
|
||||
</ha-progress-button>
|
||||
`}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
|
||||
${this.addon.long_description
|
||||
? html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<ha-markdown
|
||||
.content=${this.addon.long_description}
|
||||
></ha-markdown>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
@@ -526,16 +555,21 @@ class HassioAddonInfo extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
paper-card {
|
||||
ha-card {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
paper-card.warning {
|
||||
ha-card.warning {
|
||||
background-color: var(--google-red-500);
|
||||
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;
|
||||
}
|
||||
.warning {
|
||||
@@ -548,7 +582,7 @@ class HassioAddonInfo extends LitElement {
|
||||
.addon-header {
|
||||
padding-left: 8px;
|
||||
font-size: 24px;
|
||||
color: var(--paper-card-header-color, --primary-text-color);
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
}
|
||||
.addon-version {
|
||||
float: right;
|
||||
@@ -575,7 +609,7 @@ class HassioAddonInfo extends LitElement {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
}
|
||||
.state ha-icon {
|
||||
.state ha-svg-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--secondary-text-color);
|
||||
@@ -583,10 +617,10 @@ class HassioAddonInfo extends LitElement {
|
||||
ha-switch {
|
||||
display: flex;
|
||||
}
|
||||
ha-icon.running {
|
||||
ha-svg-icon.running {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
ha-icon.stopped {
|
||||
ha-svg-icon.stopped {
|
||||
color: var(--google-red-300);
|
||||
}
|
||||
ha-call-api-button {
|
||||
@@ -664,7 +698,7 @@ class HassioAddonInfo extends LitElement {
|
||||
}
|
||||
|
||||
private _showMoreInfo(ev): void {
|
||||
const id = ev.target.getAttribute("id");
|
||||
const id = ev.currentTarget.id;
|
||||
showHassioMarkdownDialog(this, {
|
||||
title: PERMIS_DESC[id].title,
|
||||
content: PERMIS_DESC[id].description,
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,12 +8,10 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "./hassio-addon-logs";
|
||||
|
||||
@customElement("hassio-addon-log-tab")
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/ha-card";
|
||||
import {
|
||||
fetchHassioAddonLogs,
|
||||
HassioAddonDetails,
|
||||
@@ -36,7 +36,7 @@ class HassioAddonLogs extends LitElement {
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<h1>${this.addon.name}</h1>
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
<div class="card-content">
|
||||
${this._content
|
||||
@@ -48,7 +48,7 @@ class HassioAddonLogs extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class HassioAddonLogs extends LitElement {
|
||||
hassioStyle,
|
||||
css`
|
||||
:host,
|
||||
paper-card {
|
||||
ha-card {
|
||||
display: block;
|
||||
}
|
||||
.errors {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -8,7 +9,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../src/components/ha-relative-time";
|
||||
import "../../../src/components/ha-icon";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
@customElement("hassio-card-content")
|
||||
@@ -31,7 +32,7 @@ class HassioCardContent extends LitElement {
|
||||
|
||||
@property() public iconClass?: string;
|
||||
|
||||
@property() public icon = "hass:help-circle";
|
||||
@property() public icon = mdiHelpCircle;
|
||||
|
||||
@property() public iconImage?: string;
|
||||
|
||||
@@ -48,11 +49,11 @@ class HassioCardContent extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-icon
|
||||
<ha-svg-icon
|
||||
class=${this.iconClass}
|
||||
.icon=${this.icon}
|
||||
.path=${this.icon}
|
||||
.title=${this.iconTitle}
|
||||
></ha-icon>
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
<div>
|
||||
<div class="title">
|
||||
@@ -78,25 +79,25 @@ class HassioCardContent extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-icon {
|
||||
ha-svg-icon {
|
||||
margin-right: 24px;
|
||||
margin-left: 8px;
|
||||
margin-top: 12px;
|
||||
float: left;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-icon.update {
|
||||
ha-svg-icon.update {
|
||||
color: var(--paper-orange-400);
|
||||
}
|
||||
ha-icon.running,
|
||||
ha-icon.installed {
|
||||
ha-svg-icon.running,
|
||||
ha-svg-icon.installed {
|
||||
color: var(--paper-green-400);
|
||||
}
|
||||
ha-icon.hassupdate,
|
||||
ha-icon.snapshot {
|
||||
ha-svg-icon.hassupdate,
|
||||
ha-svg-icon.snapshot {
|
||||
color: var(--paper-item-icon-color);
|
||||
}
|
||||
ha-icon.not_available {
|
||||
ha-svg-icon.not_available {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.title {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
import "../../../src/components/ha-card";
|
||||
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
@@ -29,19 +30,19 @@ class HassioAddons extends LitElement {
|
||||
<div class="card-group">
|
||||
${!this.addons
|
||||
? html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
You don't have any add-ons installed yet. Head over to
|
||||
<a href="#" @click=${this._openStore}>the add-on store</a>
|
||||
to get started!
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
: this.addons
|
||||
.sort((a, b) => (a.name > b.name ? 1 : -1))
|
||||
.map(
|
||||
(addon) => html`
|
||||
<paper-card .addon=${addon} @click=${this._addonTapped}>
|
||||
<ha-card .addon=${addon} @click=${this._addonTapped}>
|
||||
<div class="card-content">
|
||||
<hassio-card-content
|
||||
.hass=${this.hass}
|
||||
@@ -51,8 +52,8 @@ class HassioAddons extends LitElement {
|
||||
.showTopbar=${addon.installed !== addon.version}
|
||||
topbarClass="update"
|
||||
.icon=${addon.installed !== addon.version
|
||||
? "hassio:arrow-up-bold-circle"
|
||||
: "hassio:puzzle"}
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
.iconTitle=${addon.state !== "started"
|
||||
? "Add-on is stopped"
|
||||
: addon.installed !== addon.version
|
||||
@@ -75,7 +76,7 @@ class HassioAddons extends LitElement {
|
||||
: undefined}
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
@@ -88,7 +89,7 @@ class HassioAddons extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
|
@@ -12,14 +12,13 @@ import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
|
||||
@customElement("hassio-dashboard")
|
||||
class HassioDashboard extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -10,15 +10,15 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
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 {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import "../../../src/components/ha-icon";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
@customElement("hassio-update")
|
||||
@@ -72,7 +72,7 @@ export class HassioUpdate extends LitElement {
|
||||
`https://${
|
||||
this.hassInfo.version_latest.includes("b") ? "rc" : "www"
|
||||
}.home-assistant.io/latest-release-notes/`,
|
||||
"hassio:home-assistant"
|
||||
mdiHomeAssistant
|
||||
)}
|
||||
${this._renderUpdateCard(
|
||||
"Supervisor",
|
||||
@@ -107,12 +107,12 @@ export class HassioUpdate extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
${icon
|
||||
? html`
|
||||
<div class="icon">
|
||||
<ha-icon .icon=${icon}></ha-icon>
|
||||
<ha-svg-icon .path=${icon}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -133,7 +133,7 @@ export class HassioUpdate extends LitElement {
|
||||
Update
|
||||
</ha-call-api-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,46 +5,50 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../src/components/dialog/ha-paper-dialog";
|
||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||
import "../../../../src/components/ha-markdown";
|
||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
|
||||
|
||||
@customElement("dialog-hassio-markdown")
|
||||
class HassioMarkdownDialog extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public title!: string;
|
||||
|
||||
@property() public content!: string;
|
||||
|
||||
@query("#dialog") private _dialog!: PaperDialogElement;
|
||||
@property() private _opened = false;
|
||||
|
||||
public showDialog(params: HassioMarkdownDialogParams) {
|
||||
this.title = params.title;
|
||||
this.content = params.content;
|
||||
this._dialog.open();
|
||||
this._opened = true;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._opened) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-paper-dialog id="dialog" with-backdrop="">
|
||||
<app-toolbar>
|
||||
<ha-icon-button
|
||||
icon="hassio:close"
|
||||
dialog-dismiss=""
|
||||
></ha-icon-button>
|
||||
<div main-title="">${this.title}</div>
|
||||
</app-toolbar>
|
||||
<paper-dialog-scrollable>
|
||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||
</paper-dialog-scrollable>
|
||||
</ha-paper-dialog>
|
||||
<ha-dialog
|
||||
open
|
||||
@closing=${this._closeDialog}
|
||||
.heading=${createCloseHeading(this.hass, this.title)}
|
||||
>
|
||||
<ha-markdown .content=${this.content || ""}></ha-markdown>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._opened = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
|
@@ -1,9 +1,11 @@
|
||||
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-spinner/paper-spinner";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -16,16 +18,14 @@ import {
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
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 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";
|
||||
|
||||
@customElement("dialog-hassio-repositories")
|
||||
@@ -84,12 +84,13 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
<div secondary>${repo.maintainer}</div>
|
||||
<div secondary>${repo.url}</div>
|
||||
</paper-item-body>
|
||||
<ha-icon-button
|
||||
<mwc-icon-button
|
||||
.slug=${repo.slug}
|
||||
title="Remove"
|
||||
@click=${this._removeRepository}
|
||||
icon="hassio:delete"
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon path=${mdiDelete}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
</paper-item>
|
||||
`;
|
||||
})
|
||||
@@ -194,7 +195,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
}
|
||||
|
||||
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 repository = repositories.find((repo) => {
|
||||
return repo.slug === slug;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "./dialog-hassio-repositories";
|
||||
import { HassioAddonRepository } from "../../../../src/data/hassio/addon";
|
||||
import "./dialog-hassio-repositories";
|
||||
|
||||
export interface HassioRepositoryDialogParams {
|
||||
repos: HassioAddonRepository[];
|
||||
|
@@ -1,10 +1,6 @@
|
||||
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 { 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 {
|
||||
css,
|
||||
@@ -13,10 +9,10 @@ import {
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} 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 {
|
||||
fetchHassioSnapshotInfo,
|
||||
@@ -76,7 +72,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
@property() private _error?: string;
|
||||
|
||||
@property() private snapshot?: HassioSnapshotDetail;
|
||||
@property() private _snapshot?: HassioSnapshotDetail;
|
||||
|
||||
@property() private _folders!: FolderItem[];
|
||||
|
||||
@@ -88,49 +84,35 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
@property() private _restoreHass: boolean | null | undefined = true;
|
||||
|
||||
@query("#dialog") private _dialog!: PaperDialogElement;
|
||||
|
||||
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.snapshot.folders
|
||||
this._snapshot.folders
|
||||
).sort((a: FolderItem, b: FolderItem) => (a.name > b.name ? 1 : -1));
|
||||
this._addons = _computeAddons(
|
||||
this.snapshot.addons
|
||||
this._snapshot.addons
|
||||
).sort((a: AddonItem, b: AddonItem) => (a.name > b.name ? 1 : -1));
|
||||
|
||||
this._dialogParams = params;
|
||||
|
||||
try {
|
||||
this._dialog.open();
|
||||
} catch {
|
||||
await this.showDialog(params);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.snapshot) {
|
||||
if (!this._dialogParams || !this._snapshot) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<ha-paper-dialog
|
||||
id="dialog"
|
||||
with-backdrop=""
|
||||
.on-iron-overlay-closed=${this._dialogClosed}
|
||||
<ha-dialog
|
||||
open
|
||||
stacked
|
||||
@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">
|
||||
${this.snapshot.type === "full"
|
||||
${this._snapshot.type === "full"
|
||||
? "Full snapshot"
|
||||
: "Partial snapshot"}
|
||||
(${this._computeSize})<br />
|
||||
${this._formatDatetime(this.snapshot.date)}
|
||||
${this._formatDatetime(this._snapshot.date)}
|
||||
</div>
|
||||
<div>Home Assistant:</div>
|
||||
<paper-checkbox
|
||||
@@ -139,7 +121,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
this._restoreHass = (ev.target as PaperCheckboxElement).checked;
|
||||
}}"
|
||||
>
|
||||
Home Assistant ${this.snapshot.homeassistant}
|
||||
Home Assistant ${this._snapshot.homeassistant}
|
||||
</paper-checkbox>
|
||||
${this._folders.length
|
||||
? html`
|
||||
@@ -183,7 +165,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
</paper-dialog-scrollable>
|
||||
`
|
||||
: ""}
|
||||
${this.snapshot.protected
|
||||
${this._snapshot.protected
|
||||
? html`
|
||||
<paper-input
|
||||
autofocus=""
|
||||
@@ -197,37 +179,35 @@ class HassioSnapshotDialog extends LitElement {
|
||||
${this._error ? html` <p class="error">Error: ${this._error}</p> ` : ""}
|
||||
|
||||
<div>Actions:</div>
|
||||
<ul class="buttons">
|
||||
<li>
|
||||
<mwc-button @click=${this._downloadClicked}>
|
||||
<ha-icon icon="hassio:download" class="icon"></ha-icon>
|
||||
Download Snapshot
|
||||
</mwc-button>
|
||||
</li>
|
||||
<li>
|
||||
<mwc-button @click=${this._partialRestoreClicked}>
|
||||
<ha-icon icon="hassio:history" class="icon"> </ha-icon>
|
||||
Restore Selected
|
||||
</mwc-button>
|
||||
</li>
|
||||
${this.snapshot.type === "full"
|
||||
? html`
|
||||
<li>
|
||||
<mwc-button @click=${this._fullRestoreClicked}>
|
||||
<ha-icon icon="hassio:history" class="icon"> </ha-icon>
|
||||
Wipe & restore
|
||||
</mwc-button>
|
||||
</li>
|
||||
`
|
||||
: ""}
|
||||
<li>
|
||||
<mwc-button @click=${this._deleteClicked}>
|
||||
<ha-icon icon="hassio:delete" class="icon warning"> </ha-icon>
|
||||
<span class="warning">Delete Snapshot</span>
|
||||
</mwc-button>
|
||||
</li>
|
||||
</ul>
|
||||
</ha-paper-dialog>
|
||||
|
||||
<mwc-button @click=${this._downloadClicked} slot="primaryAction">
|
||||
<ha-svg-icon path=${mdiDownload} class="icon"></ha-svg-icon>
|
||||
Download Snapshot
|
||||
</mwc-button>
|
||||
|
||||
<mwc-button
|
||||
@click=${this._partialRestoreClicked}
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
|
||||
Restore Selected
|
||||
</mwc-button>
|
||||
${this._snapshot.type === "full"
|
||||
? html`
|
||||
<mwc-button
|
||||
@click=${this._fullRestoreClicked}
|
||||
slot="secondaryAction"
|
||||
>
|
||||
<ha-svg-icon path=${mdiHistory} class="icon"></ha-svg-icon>
|
||||
Wipe & restore
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}
|
||||
<mwc-button @click=${this._deleteClicked} slot="secondaryAction">
|
||||
<ha-svg-icon path=${mdiDelete} class="icon warning"></ha-svg-icon>
|
||||
<span class="warning">Delete Snapshot</span>
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -235,37 +215,10 @@ class HassioSnapshotDialog extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
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 {
|
||||
display: block;
|
||||
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 {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
@@ -336,7 +289,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
folders,
|
||||
};
|
||||
|
||||
if (this.snapshot!.protected) {
|
||||
if (this._snapshot!.protected) {
|
||||
data.password = this._snapshotPassword;
|
||||
}
|
||||
|
||||
@@ -344,13 +297,13 @@ class HassioSnapshotDialog extends LitElement {
|
||||
.callApi(
|
||||
"POST",
|
||||
|
||||
`hassio/snapshots/${this.snapshot!.slug}/restore/partial`,
|
||||
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
|
||||
data
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
alert("Snapshot restored!");
|
||||
this._dialog.close();
|
||||
this._closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
@@ -363,20 +316,20 @@ class HassioSnapshotDialog extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.snapshot!.protected
|
||||
const data = this._snapshot!.protected
|
||||
? { password: this._snapshotPassword }
|
||||
: undefined;
|
||||
|
||||
this.hass
|
||||
.callApi(
|
||||
"POST",
|
||||
`hassio/snapshots/${this.snapshot!.slug}/restore/full`,
|
||||
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
|
||||
data
|
||||
)
|
||||
.then(
|
||||
() => {
|
||||
alert("Snapshot restored!");
|
||||
this._dialog.close();
|
||||
this._closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
@@ -391,11 +344,11 @@ class HassioSnapshotDialog extends LitElement {
|
||||
|
||||
this.hass
|
||||
|
||||
.callApi("POST", `hassio/snapshots/${this.snapshot!.slug}/remove`)
|
||||
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
|
||||
.then(
|
||||
() => {
|
||||
this._dialog.close();
|
||||
this._dialogParams!.onDelete();
|
||||
this._closeDialog();
|
||||
},
|
||||
(error) => {
|
||||
this._error = error.body.message;
|
||||
@@ -408,7 +361,7 @@ class HassioSnapshotDialog extends LitElement {
|
||||
try {
|
||||
signedPath = await getSignedPath(
|
||||
this.hass,
|
||||
`/api/hassio/snapshots/${this.snapshot!.slug}/download`
|
||||
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
|
||||
);
|
||||
} catch (err) {
|
||||
alert(`Error: ${err.message}`);
|
||||
@@ -419,19 +372,19 @@ class HassioSnapshotDialog extends LitElement {
|
||||
const a = document.createElement("a");
|
||||
a.href = signedPath.path;
|
||||
a.download = `Hass_io_${name}.tar`;
|
||||
this._dialog.appendChild(a);
|
||||
this.shadowRoot!.appendChild(a);
|
||||
a.click();
|
||||
this._dialog.removeChild(a);
|
||||
this.shadowRoot!.removeChild(a);
|
||||
}
|
||||
|
||||
private get _computeName() {
|
||||
return this.snapshot
|
||||
? this.snapshot.name || this.snapshot.slug
|
||||
return this._snapshot
|
||||
? this._snapshot.name || this._snapshot.slug
|
||||
: "Unnamed snapshot";
|
||||
}
|
||||
|
||||
private get _computeSize() {
|
||||
return Math.ceil(this.snapshot!.size * 10) / 10 + " MB";
|
||||
return Math.ceil(this._snapshot!.size * 10) / 10 + " MB";
|
||||
}
|
||||
|
||||
private _formatDatetime(datetime) {
|
||||
@@ -445,9 +398,9 @@ class HassioSnapshotDialog extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
private _closeDialog() {
|
||||
this._dialogParams = undefined;
|
||||
this.snapshot = undefined;
|
||||
this._snapshot = undefined;
|
||||
this._snapshotPassword = "";
|
||||
this._folders = [];
|
||||
this._addons = [];
|
||||
|
@@ -3,11 +3,11 @@ import {
|
||||
HassioAddonDetails,
|
||||
restartHassioAddon,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
|
||||
export const suggestAddonRestart = async (
|
||||
element: LitElement,
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "../../src/components/ha-icon-button";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { customElement, property, PropertyValues } from "lit-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.
|
||||
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")
|
||||
class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
@@ -5,37 +6,36 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import "../../src/resources/ha-style";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
icon: "hassio:view-dashboard",
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
path: `/hassio/store`,
|
||||
icon: "hassio:store",
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
icon: "hassio:backup-restore",
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
path: `/hassio/system`,
|
||||
icon: "hassio:cogs",
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -86,9 +86,6 @@ class HassioIngressView extends LitElement {
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
ha-icon-button {
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
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 type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-svg-icon";
|
||||
import {
|
||||
createHassioFullSnapshot,
|
||||
createHassioPartialSnapshot,
|
||||
@@ -28,15 +31,14 @@ import {
|
||||
reloadHassioSnapshots,
|
||||
} from "../../../src/data/hassio/snapshot";
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { PolymerChangedEvent } from "../../../src/polymer-types";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../components/hassio-card-content";
|
||||
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface CheckboxItem {
|
||||
slug: string;
|
||||
@@ -98,12 +100,13 @@ class HassioSnapshots extends LitElement {
|
||||
>
|
||||
<span slot="header">Snapshots</span>
|
||||
|
||||
<ha-icon-button
|
||||
icon="hassio:reload"
|
||||
<mwc-icon-button
|
||||
slot="toolbar-icon"
|
||||
aria-label="Reload snapshots"
|
||||
@click=${this.refreshData}
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon path=${mdiReload}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
|
||||
<div class="content">
|
||||
<h1>
|
||||
@@ -114,7 +117,7 @@ class HassioSnapshots extends LitElement {
|
||||
Home Assistant instance.
|
||||
</p>
|
||||
<div class="card-group">
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
autofocus
|
||||
@@ -195,7 +198,7 @@ class HassioSnapshots extends LitElement {
|
||||
Create
|
||||
</mwc-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</div>
|
||||
|
||||
<h1>Available snapshots</h1>
|
||||
@@ -204,15 +207,15 @@ class HassioSnapshots extends LitElement {
|
||||
? undefined
|
||||
: this._snapshots.length === 0
|
||||
? html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
You don't have any snapshots yet.
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
: this._snapshots.map(
|
||||
(snapshot) => html`
|
||||
<paper-card
|
||||
<ha-card
|
||||
class="pointer"
|
||||
.snapshot=${snapshot}
|
||||
@click=${this._snapshotClicked}
|
||||
@@ -224,12 +227,12 @@ class HassioSnapshots extends LitElement {
|
||||
.description=${this._computeDetails(snapshot)}
|
||||
.datetime=${snapshot.date}
|
||||
.icon=${snapshot.type === "full"
|
||||
? "hassio:package-variant-closed"
|
||||
: "hassio:package-variant"}
|
||||
? mdiPackageVariantClosed
|
||||
: mdiPackageVariant}
|
||||
.icon-class="snapshot"
|
||||
></hassio-card-content>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -12,23 +11,23 @@ import {
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
|
||||
import {
|
||||
changeHostOptions,
|
||||
fetchHassioHostInfo,
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo as HassioHostInfoType,
|
||||
rebootHost,
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
changeHostOptions,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
@customElement("hassio-host-info")
|
||||
class HassioHostInfo extends LitElement {
|
||||
@@ -42,7 +41,7 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<h2>Host system</h2>
|
||||
<table class="info">
|
||||
@@ -113,7 +112,7 @@ class HassioHostInfo extends LitElement {
|
||||
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
|
||||
: ""}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -122,7 +121,7 @@ class HassioHostInfo extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
ha-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -11,15 +10,16 @@ import {
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/components/ha-card";
|
||||
import {
|
||||
HassioSupervisorInfo as HassioSupervisorInfoType,
|
||||
setSupervisorOption,
|
||||
SupervisorOptions,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||
|
||||
@customElement("hassio-supervisor-info")
|
||||
class HassioSupervisorInfo extends LitElement {
|
||||
@@ -31,7 +31,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<h2>Supervisor</h2>
|
||||
<table class="info">
|
||||
@@ -92,7 +92,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
ha-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import "@material/mwc-button";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -12,14 +11,13 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} 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 { HomeAssistant } from "../../../src/types";
|
||||
|
||||
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
|
||||
|
||||
import "../components/hassio-ansi-to-html";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "../../../src/layouts/loading-screen";
|
||||
|
||||
interface LogProvider {
|
||||
key: string;
|
||||
@@ -70,7 +68,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
@@ -105,7 +103,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._refresh}>Refresh</mwc-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -114,7 +112,7 @@ class HassioSupervisorLog extends LitElement {
|
||||
haStyle,
|
||||
hassioStyle,
|
||||
css`
|
||||
paper-card {
|
||||
ha-card {
|
||||
width: 100%;
|
||||
}
|
||||
pre {
|
||||
|
@@ -13,16 +13,15 @@ import {
|
||||
HassioHostInfo,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-host-info";
|
||||
import "./hassio-supervisor-info";
|
||||
import "./hassio-supervisor-log";
|
||||
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
|
||||
@customElement("hassio-system")
|
||||
class HassioSystem extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
63
package.json
@@ -24,19 +24,19 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@fullcalendar/core": "5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "5.0.0-beta.2",
|
||||
"@material/chips": "^6.0.0-canary.35a32aaea.0",
|
||||
"@material/mwc-button": "0.14.1",
|
||||
"@material/mwc-checkbox": "0.14.1",
|
||||
"@material/mwc-dialog": "0.14.1",
|
||||
"@material/mwc-fab": "0.14.1",
|
||||
"@material/mwc-formfield": "0.14.1",
|
||||
"@material/mwc-icon-button": "0.14.1",
|
||||
"@material/mwc-list": "0.14.1",
|
||||
"@material/mwc-menu": "0.14.1",
|
||||
"@material/mwc-ripple": "0.14.1",
|
||||
"@material/mwc-switch": "0.14.1",
|
||||
"@fullcalendar/core": "^5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
||||
"@material/chips": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/mwc-button": "^0.15.0",
|
||||
"@material/mwc-checkbox": "^0.15.0",
|
||||
"@material/mwc-dialog": "^0.15.0",
|
||||
"@material/mwc-fab": "^0.15.0",
|
||||
"@material/mwc-formfield": "^0.15.0",
|
||||
"@material/mwc-icon-button": "^0.15.0",
|
||||
"@material/mwc-list": "^0.15.0",
|
||||
"@material/mwc-menu": "^0.15.0",
|
||||
"@material/mwc-ripple": "^0.15.0",
|
||||
"@material/mwc-switch": "^0.15.0",
|
||||
"@mdi/js": "4.9.95",
|
||||
"@mdi/svg": "4.9.95",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
@@ -77,7 +77,7 @@
|
||||
"@polymer/paper-toast": "^3.0.1",
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@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-date-picker": "^4.0.7",
|
||||
"@webcomponents/shadycss": "^1.9.0",
|
||||
@@ -93,14 +93,14 @@
|
||||
"fuse.js": "^3.4.4",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "5.0.0",
|
||||
"home-assistant-js-websocket": "^5.1.2",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
"leaflet": "^1.4.0",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"lit-element": "^2.2.1",
|
||||
"lit-html": "^1.1.0",
|
||||
"lit-element": "^2.3.1",
|
||||
"lit-html": "^1.2.1",
|
||||
"lit-virtualizer": "^0.4.2",
|
||||
"marked": "^0.6.1",
|
||||
"mdn-polyfills": "^5.16.0",
|
||||
@@ -196,20 +196,21 @@
|
||||
"resolutions": {
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "^1.1.2",
|
||||
"@material/animation": "6.0.0",
|
||||
"@material/base": "6.0.0",
|
||||
"@material/checkbox": "6.0.0",
|
||||
"@material/density": "6.0.0",
|
||||
"@material/dom": "6.0.0",
|
||||
"@material/elevation": "6.0.0",
|
||||
"@material/feature-targeting": "6.0.0",
|
||||
"@material/ripple": "6.0.0",
|
||||
"@material/rtl": "6.0.0",
|
||||
"@material/shape": "6.0.0",
|
||||
"@material/theme": "6.0.0",
|
||||
"@material/touch-target": "6.0.0",
|
||||
"@material/typography": "6.0.0"
|
||||
"lit-html": "1.2.1",
|
||||
"lit-element": "2.3.1",
|
||||
"@material/animation": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/base": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/checkbox": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/density": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/dom": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/elevation": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/feature-targeting": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/ripple": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/rtl": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/shape": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/theme": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/touch-target": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/typography": "7.0.0-canary.d92d8c93e.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
Before Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 888 B |
Before Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 639 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 840 B |
Before Width: | Height: | Size: 798 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 487 B |
Before Width: | Height: | Size: 774 B |
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200505.0",
|
||||
version="20200512.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "../../components/ha-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@@ -9,8 +8,10 @@ import {
|
||||
} from "lit-element";
|
||||
import { html, TemplateResult } from "lit-html";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { fireEvent } from "../dom/fire_event";
|
||||
import { mdiMagnify, mdiClose } from "@mdi/js";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
|
||||
@customElement("search-input")
|
||||
class SearchInput extends LitElement {
|
||||
@@ -47,17 +48,22 @@ class SearchInput extends LitElement {
|
||||
@value-changed=${this._filterInputChanged}
|
||||
.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 &&
|
||||
html`
|
||||
<ha-icon-button
|
||||
<mwc-icon-button
|
||||
slot="suffix"
|
||||
class="suffix"
|
||||
@click=${this._clearSearch}
|
||||
icon="hass:close"
|
||||
alt="Clear"
|
||||
title="Clear"
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`}
|
||||
</paper-input>
|
||||
`;
|
||||
@@ -77,11 +83,14 @@ class SearchInput extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-icon,
|
||||
ha-icon-button {
|
||||
ha-svg-icon,
|
||||
mwc-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-icon {
|
||||
mwc-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
ha-svg-icon.prefix {
|
||||
margin: 8px;
|
||||
}
|
||||
`;
|
||||
|
@@ -6,18 +6,19 @@ import {
|
||||
CSSResult,
|
||||
css,
|
||||
query,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-menu";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { Menu } from "@material/mwc-menu";
|
||||
|
||||
import { haStyle } from "../resources/styles";
|
||||
import type { Menu, Corner } from "@material/mwc-menu";
|
||||
|
||||
import "./ha-icon-button";
|
||||
|
||||
@customElement("ha-button-menu")
|
||||
export class HaButtonMenu extends LitElement {
|
||||
@property() public corner: Corner = "TOP_START";
|
||||
|
||||
@query("mwc-menu") private _menu?: Menu;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -25,7 +26,7 @@ export class HaButtonMenu extends LitElement {
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger"></slot>
|
||||
</div>
|
||||
<mwc-menu>
|
||||
<mwc-menu .corner=${this.corner}>
|
||||
<slot></slot>
|
||||
</mwc-menu>
|
||||
`;
|
||||
@@ -36,15 +37,13 @@ export class HaButtonMenu extends LitElement {
|
||||
this._menu!.show();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
`,
|
||||
];
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,17 +4,19 @@ import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
||||
import "./ha-icon-button";
|
||||
import { css, CSSResult, customElement, html } from "lit-element";
|
||||
import type { Constructor, HomeAssistant } from "../types";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
|
||||
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||
|
||||
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
${title}
|
||||
<ha-icon-button
|
||||
<mwc-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
class="close_button"
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
`;
|
||||
|
||||
@customElement("ha-dialog")
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.icon =
|
||||
this._icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? "hass:arrow-left"
|
||||
: "hass:arrow-right";
|
||||
? mdiArrowLeft
|
||||
: mdiArrowRight;
|
||||
}, 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 {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import { get, set, clear, Store } from "idb-keyval";
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
@@ -11,84 +10,23 @@ import {
|
||||
CSSResult,
|
||||
} from "lit-element";
|
||||
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 { 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 MDI_PREFIXES = ["mdi", "hass", "hassio", "hademo"];
|
||||
|
||||
let toRead: Array<[string, (string) => void]> = [];
|
||||
checkCacheVersion();
|
||||
|
||||
// Queue up as many icon fetches in 1 transaction
|
||||
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);
|
||||
const debouncedWriteCache = debounce(() => writeCache(chunks), 2000);
|
||||
|
||||
@customElement("ha-icon")
|
||||
export class HaIcon extends LitElement {
|
||||
@@ -96,11 +34,14 @@ export class HaIcon extends LitElement {
|
||||
|
||||
@property() private _path?: string;
|
||||
|
||||
@property() private _noMdi = false;
|
||||
@property() private _viewBox?;
|
||||
|
||||
@property() private _legacy = false;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("icon")) {
|
||||
this._path = undefined;
|
||||
this._viewBox = undefined;
|
||||
this._loadIcon();
|
||||
}
|
||||
}
|
||||
@@ -109,25 +50,39 @@ export class HaIcon extends LitElement {
|
||||
if (!this.icon) {
|
||||
return html``;
|
||||
}
|
||||
if (this._noMdi) {
|
||||
if (this._legacy) {
|
||||
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() {
|
||||
if (!this.icon) {
|
||||
return;
|
||||
}
|
||||
const icon = this.icon.split(":", 2);
|
||||
if (!MDI_PREFIXES.includes(icon[0])) {
|
||||
this._noMdi = true;
|
||||
const [iconPrefix, iconName] = this.icon.split(":", 2);
|
||||
|
||||
if (!iconPrefix || !iconName) {
|
||||
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);
|
||||
if (cachedPath) {
|
||||
this._path = cachedPath;
|
||||
@@ -147,6 +102,12 @@ export class HaIcon extends LitElement {
|
||||
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) {
|
||||
const iconPack = await promise;
|
||||
this._path = iconPack[iconName];
|
||||
|
@@ -31,12 +31,14 @@ class HaLabelBadge extends LitElement {
|
||||
big: Boolean(this.value && this.value.length > 4),
|
||||
})}"
|
||||
>
|
||||
${this.icon && !this.value && !this.image
|
||||
? html` <ha-icon .icon="${this.icon}"></ha-icon> `
|
||||
: ""}
|
||||
${this.value && !this.image
|
||||
? html` <span>${this.value}</span> `
|
||||
: ""}
|
||||
<slot>
|
||||
${this.icon && !this.value && !this.image
|
||||
? html` <ha-icon .icon=${this.icon}></ha-icon> `
|
||||
: ""}
|
||||
${this.value && !this.image
|
||||
? html` <span>${this.value}</span> `
|
||||
: ""}
|
||||
</slot>
|
||||
</div>
|
||||
${this.label
|
||||
? html`
|
||||
|
@@ -13,6 +13,7 @@ import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-icon-button";
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
|
||||
@customElement("ha-menu-button")
|
||||
class HaMenuButton extends LitElement {
|
||||
@@ -55,11 +56,12 @@ class HaMenuButton extends LitElement {
|
||||
(entityId) => computeDomain(entityId) === "configurator"
|
||||
));
|
||||
return html`
|
||||
<ha-icon-button
|
||||
<mwc-icon-button
|
||||
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
icon="hass:menu"
|
||||
@click=${this._toggleMenu}
|
||||
></ha-icon-button>
|
||||
>
|
||||
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
${hasNotifications ? html` <div class="dot"></div> ` : ""}
|
||||
`;
|
||||
}
|
||||
@@ -133,8 +135,8 @@ class HaMenuButton extends LitElement {
|
||||
background-color: var(--accent-color);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: 5px;
|
||||
right: 2px;
|
||||
top: 9px;
|
||||
right: 7px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--app-header-background-color);
|
||||
}
|
||||
|
@@ -12,10 +12,12 @@ import {
|
||||
export class HaSvgIcon extends LitElement {
|
||||
@property() public path?: string;
|
||||
|
||||
@property() public viewBox?: string;
|
||||
|
||||
protected render(): SVGTemplateResult {
|
||||
return svg`
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
viewBox=${this.viewBox || "0 0 24 24"}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
focusable="false">
|
||||
<g>
|
||||
|
135
src/components/ha-tab.ts
Normal 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;
|
||||
}
|
||||
}
|
@@ -8,6 +8,8 @@ export interface ConfigUpdateValues {
|
||||
elevation: number;
|
||||
unit_system: "metric" | "imperial";
|
||||
time_zone: string;
|
||||
external_url?: string | null;
|
||||
internal_url?: string | null;
|
||||
}
|
||||
|
||||
export const saveCoreConfig = (
|
||||
|
16
src/data/custom_iconsets.ts
Normal 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
@@ -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)
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
@@ -7,6 +7,7 @@ export interface IntegrationManifest {
|
||||
name: string;
|
||||
config_flow: boolean;
|
||||
documentation: string;
|
||||
issue_tracker?: string;
|
||||
dependencies?: string[];
|
||||
after_dependencies?: string[];
|
||||
codeowners?: string[];
|
||||
@@ -17,7 +18,11 @@ export interface IntegrationManifest {
|
||||
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`;
|
||||
|
||||
export const domainToName = (localize: LocalizeFunc, domain: string) =>
|
||||
|
@@ -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 = {
|
||||
"clear-night": "/static/images/weather/night.png",
|
||||
cloudy: "/static/images/weather/cloudy.png",
|
||||
fog: "/static/images/weather/cloudy.png",
|
||||
lightning: "/static/images/weather/lightning.png",
|
||||
"lightning-rainy": "/static/images/weather/lightning-rainy.png",
|
||||
partlycloudy: "/static/images/weather/partly-cloudy.png",
|
||||
pouring: "/static/images/weather/pouring.png",
|
||||
rainy: "/static/images/weather/rainy.png",
|
||||
hail: "/static/images/weather/rainy.png",
|
||||
snowy: "/static/images/weather/snowy.png",
|
||||
"snowy-rainy": "/static/images/weather/snowy.png",
|
||||
sunny: "/static/images/weather/sunny.png",
|
||||
windy: "/static/images/weather/windy.png",
|
||||
"windy-variant": "/static/images/weather/windy.png",
|
||||
};
|
||||
import type { HomeAssistant, WeatherEntity } from "../types";
|
||||
|
||||
export const weatherSVGs = new Set<string>([
|
||||
"clear-night",
|
||||
"cloudy",
|
||||
"fog",
|
||||
"lightning",
|
||||
"lightning-rainy",
|
||||
"partlycloudy",
|
||||
"pouring",
|
||||
"rainy",
|
||||
"hail",
|
||||
"snowy",
|
||||
"snowy-rainy",
|
||||
"sunny",
|
||||
"windy",
|
||||
"windy-variant",
|
||||
]);
|
||||
|
||||
export const weatherIcons = {
|
||||
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 = [
|
||||
"N",
|
||||
"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;
|
||||
};
|
||||
|
@@ -3,6 +3,7 @@ import { HomeAssistant } from "../types";
|
||||
|
||||
export interface ZHAEntityReference extends HassEntity {
|
||||
name: string;
|
||||
original_name?: string;
|
||||
}
|
||||
|
||||
export interface ZHADevice {
|
||||
@@ -26,6 +27,12 @@ export interface ZHADevice {
|
||||
signature: any;
|
||||
}
|
||||
|
||||
export interface ZHADeviceEndpoint {
|
||||
device: ZHADevice;
|
||||
endpoint_id: number;
|
||||
entities: ZHAEntityReference[];
|
||||
}
|
||||
|
||||
export interface Attribute {
|
||||
name: string;
|
||||
id: number;
|
||||
@@ -56,7 +63,12 @@ export interface ReadAttributeServiceData {
|
||||
export interface ZHAGroup {
|
||||
name: string;
|
||||
group_id: number;
|
||||
members: ZHADevice[];
|
||||
members: ZHADeviceEndpoint[];
|
||||
}
|
||||
|
||||
export interface ZHAGroupMember {
|
||||
ieee: string;
|
||||
endpoint_id: string;
|
||||
}
|
||||
|
||||
export const reconfigureNode = (
|
||||
@@ -213,7 +225,7 @@ export const fetchGroup = (
|
||||
|
||||
export const fetchGroupableDevices = (
|
||||
hass: HomeAssistant
|
||||
): Promise<ZHADevice[]> =>
|
||||
): Promise<ZHADeviceEndpoint[]> =>
|
||||
hass.callWS({
|
||||
type: "zha/devices/groupable",
|
||||
});
|
||||
@@ -221,7 +233,7 @@ export const fetchGroupableDevices = (
|
||||
export const addMembersToGroup = (
|
||||
hass: HomeAssistant,
|
||||
groupId: number,
|
||||
membersToAdd: string[]
|
||||
membersToAdd: ZHAGroupMember[]
|
||||
): Promise<ZHAGroup> =>
|
||||
hass.callWS({
|
||||
type: "zha/group/members/add",
|
||||
@@ -232,7 +244,7 @@ export const addMembersToGroup = (
|
||||
export const removeMembersFromGroup = (
|
||||
hass: HomeAssistant,
|
||||
groupId: number,
|
||||
membersToRemove: string[]
|
||||
membersToRemove: ZHAGroupMember[]
|
||||
): Promise<ZHAGroup> =>
|
||||
hass.callWS({
|
||||
type: "zha/group/members/remove",
|
||||
@@ -243,7 +255,7 @@ export const removeMembersFromGroup = (
|
||||
export const addGroup = (
|
||||
hass: HomeAssistant,
|
||||
groupName: string,
|
||||
membersToAdd?: string[]
|
||||
membersToAdd?: ZHAGroupMember[]
|
||||
): Promise<ZHAGroup> =>
|
||||
hass.callWS({
|
||||
type: "zha/group/add",
|
||||
|
@@ -339,7 +339,6 @@ class DataEntryFlowDialog extends LitElement {
|
||||
ha-icon-button {
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
margin: 16px 16px 0 0;
|
||||
float: right;
|
||||
}
|
||||
`,
|
||||
|
@@ -10,6 +10,7 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import CoverEntity from "../../../util/cover-model";
|
||||
|
||||
const FEATURE_CLASS_NAMES = {
|
||||
4: "has-set_position",
|
||||
128: "has-set_tilt_position",
|
||||
};
|
||||
class MoreInfoCover extends LocalizeMixin(PolymerElement) {
|
||||
@@ -23,6 +24,7 @@ class MoreInfoCover extends LocalizeMixin(PolymerElement) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.has-set_position .current_position,
|
||||
.has-current_position .current_position,
|
||||
.has-set_tilt_position .tilt,
|
||||
.has-current_tilt_position .tilt {
|
||||
|
@@ -456,15 +456,15 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
}
|
||||
|
||||
.bouncer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
.double-bounce1,
|
||||
.double-bounce2 {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--primary-color);
|
||||
opacity: 0.2;
|
||||
|
@@ -104,6 +104,11 @@ window.hassConnection.then(({ conn }) => {
|
||||
});
|
||||
|
||||
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;
|
||||
if (
|
||||
homeAssistant &&
|
||||
|
@@ -18,4 +18,6 @@ export const demoConfig: HassConfig = {
|
||||
whitelist_external_dirs: [],
|
||||
config_source: "storage",
|
||||
safe_mode: false,
|
||||
internal_url: "http://homeassistant.local:8123",
|
||||
external_url: null,
|
||||
};
|
||||
|
@@ -16,7 +16,9 @@ import { navigate } from "../common/navigate";
|
||||
import "../components/ha-menu-button";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
import "../components/ha-svg-icon";
|
||||
import "../components/ha-icon";
|
||||
import "../components/ha-tab";
|
||||
|
||||
export interface PageNavigation {
|
||||
path: string;
|
||||
@@ -26,6 +28,7 @@ export interface PageNavigation {
|
||||
core?: boolean;
|
||||
advancedOnly?: boolean;
|
||||
icon?: string;
|
||||
iconPath?: string;
|
||||
info?: any;
|
||||
}
|
||||
|
||||
@@ -33,12 +36,12 @@ export interface PageNavigation {
|
||||
class HassTabsSubpage extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public hassio = false;
|
||||
|
||||
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||
|
||||
@property() public backCallback?: () => void;
|
||||
|
||||
@property({ type: Boolean }) public hassio = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "main-page" }) public mainPage = false;
|
||||
|
||||
@property() public route!: Route;
|
||||
@@ -69,27 +72,23 @@ class HassTabsSubpage extends LitElement {
|
||||
return shownTabs.map(
|
||||
(page) =>
|
||||
html`
|
||||
<div
|
||||
class="tab ${classMap({
|
||||
active: page === activeTab,
|
||||
})}"
|
||||
<ha-tab
|
||||
.hass=${this.hass}
|
||||
@click=${this._tabTapped}
|
||||
.path=${page.path}
|
||||
.active=${page === activeTab}
|
||||
.narrow=${this.narrow}
|
||||
.name=${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: page.name}
|
||||
>
|
||||
${this.narrow
|
||||
? html` <ha-icon .icon=${page.icon}></ha-icon> `
|
||||
: ""}
|
||||
${!this.narrow || page === activeTab
|
||||
? html`
|
||||
<span class="name"
|
||||
>${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: page.name}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
${page.iconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${page.iconPath}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="icon" .icon=${page.icon}></ha-icon>`}
|
||||
</ha-tab>
|
||||
`
|
||||
);
|
||||
}
|
||||
@@ -119,8 +118,8 @@ class HassTabsSubpage extends LitElement {
|
||||
${this.mainPage
|
||||
? html`
|
||||
<ha-menu-button
|
||||
.hass=${this.hass}
|
||||
.hassio=${this.hassio}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></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);
|
||||
}
|
||||
|
||||
@@ -174,6 +173,10 @@ class HassTabsSubpage extends LitElement {
|
||||
background-color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
ha-menu-button {
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -211,35 +214,6 @@ class HassTabsSubpage extends LitElement {
|
||||
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 {
|
||||
min-width: 40px;
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
createAreaRegistryEntry,
|
||||
@@ -26,6 +26,8 @@ import {
|
||||
devicesInArea,
|
||||
} from "../../../data/device_registry";
|
||||
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-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
@@ -35,6 +37,7 @@ import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-areas-dashboard")
|
||||
export class HaConfigAreasDashboard extends LitElement {
|
||||
@@ -120,15 +123,16 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
@click=${this._showHelp}
|
||||
></ha-icon-button>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.create_area"
|
||||
)}"
|
||||
@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-text-color: var(--sidebar-text-color);
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab.rtl {
|
||||
mwc-fab.rtl {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[is-wide].rtl {
|
||||
mwc-fab[is-wide].rtl {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -14,7 +14,8 @@ import { classMap } from "lit-html/directives/class-map";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -40,6 +41,7 @@ import { HaDeviceAction } from "./action/types/ha-automation-action-device_id";
|
||||
import "./condition/ha-automation-condition";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
|
||||
export class HaAutomationEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@@ -244,11 +246,10 @@ export class HaAutomationEditor extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide="${this.isWide}"
|
||||
?narrow="${this.narrow}"
|
||||
?dirty="${this._dirty}"
|
||||
icon="hass:content-save"
|
||||
.title="${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.save"
|
||||
)}"
|
||||
@@ -256,7 +257,9 @@ export class HaAutomationEditor extends LitElement {
|
||||
class="${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}"
|
||||
></ha-fab>
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -452,7 +455,7 @@ export class HaAutomationEditor extends LitElement {
|
||||
ha-entity-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
@@ -461,24 +464,24 @@ export class HaAutomationEditor extends LitElement {
|
||||
transition: margin-bottom 0.3s;
|
||||
}
|
||||
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
margin-bottom: -140px;
|
||||
}
|
||||
ha-fab[dirty] {
|
||||
mwc-fab[dirty] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ha-fab.rtl {
|
||||
mwc-fab.rtl {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[is-wide].rtl {
|
||||
mwc-fab[is-wide].rtl {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -18,7 +18,7 @@ import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/entity/ha-entity-toggle";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -30,6 +30,8 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-automation-picker")
|
||||
class HaAutomationPicker extends LitElement {
|
||||
@@ -168,17 +170,18 @@ class HaAutomationPicker extends LitElement {
|
||||
hasFab
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
slot="fab"
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.add_automation"
|
||||
)}
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
@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 [
|
||||
haStyle,
|
||||
css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
@@ -216,19 +219,19 @@ class HaAutomationPicker extends LitElement {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab[rtl] {
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[rtl][is-wide] {
|
||||
mwc-fab[rtl][is-wide] {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -11,6 +11,7 @@ import "../../../resources/ha-style";
|
||||
import "../ha-config-section";
|
||||
import "./ha-config-core-form";
|
||||
import "./ha-config-name-form";
|
||||
import "./ha-config-url-form";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
@@ -59,6 +60,7 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
||||
|
||||
<ha-config-name-form hass="[[hass]]"></ha-config-name-form>
|
||||
<ha-config-core-form hass="[[hass]]"></ha-config-core-form>
|
||||
<ha-config-url-form hass="[[hass]]"></ha-config-url-form>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
|
168
src/panels/config/core/ha-config-url-form.ts
Normal 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;
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
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 { HELPER_DOMAINS } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-helpers")
|
||||
export class HaConfigHelpers extends LitElement {
|
||||
@@ -154,15 +156,16 @@ export class HaConfigHelpers extends LitElement {
|
||||
hasFab
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.add_helper"
|
||||
)}"
|
||||
@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 {
|
||||
return css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
|
@@ -20,7 +20,7 @@ import {
|
||||
} from "../../../common/util/render-status";
|
||||
import "../../../components/entity/ha-state-icon";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
ConfigEntry,
|
||||
deleteConfigEntry,
|
||||
@@ -58,6 +58,9 @@ import type {
|
||||
HaIntegrationCard,
|
||||
} from "./ha-integration-card";
|
||||
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 {
|
||||
localized_title?: string;
|
||||
@@ -119,7 +122,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
this._deviceRegistryEntries = entries;
|
||||
}),
|
||||
subscribeConfigFlowInProgress(this.hass, async (flowsInProgress) => {
|
||||
const translationsPromisses: Promise<void>[] = [];
|
||||
const translationsPromisses: Promise<LocalizeFunc>[] = [];
|
||||
flowsInProgress.forEach((flow) => {
|
||||
// To render title placeholders
|
||||
if (flow.context.title_placeholders) {
|
||||
@@ -452,15 +455,16 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-fab
|
||||
icon="hass:plus"
|
||||
<mwc-fab
|
||||
aria-label=${this.hass.localize("ui.panel.config.integrations.new")}
|
||||
title=${this.hass.localize("ui.panel.config.integrations.new")}
|
||||
@click=${this._createFlow}
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
?rtl=${computeRTL(this.hass!)}
|
||||
></ha-fab>
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -680,24 +684,24 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab[rtl] {
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
ha-fab[is-wide].rtl {
|
||||
mwc-fab[is-wide].rtl {
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: auto;
|
||||
|
@@ -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
|
||||
? html`
|
||||
<a
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@material/mwc-fab";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-fab";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import {
|
||||
@@ -34,6 +34,8 @@ import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../types";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-lovelace-dashboards")
|
||||
export class HaConfigLovelaceDashboards extends LitElement {
|
||||
@@ -222,15 +224,16 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
||||
hasFab
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.add_dashboard"
|
||||
)}"
|
||||
@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 {
|
||||
return css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
|
@@ -3,6 +3,7 @@ import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -19,7 +20,6 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-fab";
|
||||
import "../../../../components/ha-icon";
|
||||
import {
|
||||
createResource,
|
||||
@@ -38,6 +38,8 @@ import { HomeAssistant, Route } from "../../../../types";
|
||||
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-lovelace-resources")
|
||||
export class HaConfigLovelaceRescources extends LitElement {
|
||||
@@ -102,15 +104,16 @@ export class HaConfigLovelaceRescources extends LitElement {
|
||||
hasFab
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.resources.picker.add_resource"
|
||||
)}
|
||||
@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 {
|
||||
return css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
|
@@ -10,7 +10,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
createPerson,
|
||||
deletePerson,
|
||||
@@ -29,6 +29,8 @@ import {
|
||||
loadPersonDetailDialog,
|
||||
showPersonDetailDialog,
|
||||
} from "./show-dialog-person-detail";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
class HaConfigPerson extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
@@ -121,13 +123,14 @@ class HaConfigPerson extends LitElement {
|
||||
</ha-config-section>
|
||||
</hass-tabs-subpage>
|
||||
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${hass.localize("ui.panel.config.person.add_person")}"
|
||||
@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 {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { activateScene, SceneEntity } from "../../../data/scene";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
@@ -24,6 +24,8 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-scene-dashboard")
|
||||
class HaSceneDashboard extends LitElement {
|
||||
@@ -144,13 +146,14 @@ class HaSceneDashboard extends LitElement {
|
||||
></ha-icon-button>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<a href="/config/scene/edit/new">
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title=${this.hass.localize("ui.panel.config.scene.picker.add_scene")}
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
></ha-fab>
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
@@ -197,26 +200,26 @@ class HaSceneDashboard extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab[rtl] {
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[rtl][is-wide] {
|
||||
mwc-fab[rtl][is-wide] {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -23,7 +23,7 @@ import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/device/ha-device-picker";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
computeDeviceName,
|
||||
DeviceRegistryEntry,
|
||||
@@ -54,6 +54,8 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
|
||||
interface DeviceEntities {
|
||||
id: string;
|
||||
@@ -372,17 +374,18 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
?dirty=${this._dirty}
|
||||
icon="hass:content-save"
|
||||
.title=${this.hass.localize("ui.panel.config.scene.editor.save")}
|
||||
@click=${this._saveScene}
|
||||
class=${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}
|
||||
></ha-fab>
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -728,7 +731,7 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
@@ -737,24 +740,24 @@ export class HaSceneEditor extends SubscribeMixin(LitElement) {
|
||||
transition: margin-bottom 0.3s;
|
||||
}
|
||||
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
margin-bottom: -140px;
|
||||
}
|
||||
ha-fab[dirty] {
|
||||
mwc-fab[dirty] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ha-fab.rtl {
|
||||
mwc-fab.rtl {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[is-wide].rtl {
|
||||
mwc-fab[is-wide].rtl {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -15,7 +15,7 @@ import { computeObjectId } from "../../../common/entity/compute_object_id";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import {
|
||||
Action,
|
||||
deleteScript,
|
||||
@@ -30,6 +30,8 @@ import "../automation/action/ha-automation-action";
|
||||
import { HaDeviceAction } from "../automation/action/types/ha-automation-action-device_id";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiContentSave } from "@mdi/js";
|
||||
|
||||
export class HaScriptEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@@ -139,17 +141,18 @@ export class HaScriptEditor extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
?dirty=${this._dirty}
|
||||
icon="hass:content-save"
|
||||
.title="${this.hass.localize("ui.common.save")}"
|
||||
@click=${this._saveScript}
|
||||
class="${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}"
|
||||
></ha-fab>
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiContentSave}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
@@ -300,7 +303,7 @@ export class HaScriptEditor extends LitElement {
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
@@ -309,24 +312,24 @@ export class HaScriptEditor extends LitElement {
|
||||
transition: margin-bottom 0.3s;
|
||||
}
|
||||
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
margin-bottom: -140px;
|
||||
}
|
||||
ha-fab[dirty] {
|
||||
mwc-fab[dirty] {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ha-fab.rtl {
|
||||
mwc-fab.rtl {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[is-wide].rtl {
|
||||
mwc-fab[is-wide].rtl {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -15,7 +15,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { DataTableColumnContainer } from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import { triggerScript } from "../../../data/script";
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@@ -23,6 +23,8 @@ import { haStyle } from "../../../resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-script-picker")
|
||||
class HaScriptPicker extends LitElement {
|
||||
@@ -139,15 +141,16 @@ class HaScriptPicker extends LitElement {
|
||||
></ha-icon-button>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<a href="/config/script/edit/new">
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.script.picker.add_script"
|
||||
)}"
|
||||
?rtl=${computeRTL(this.hass)}
|
||||
></ha-fab>
|
||||
>
|
||||
<ha-svg-icon slot="icon" path=${mdiPlus}></ha-svg-icon>
|
||||
</mwc-fab>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
@@ -193,26 +196,26 @@ class HaScriptPicker extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab[rtl] {
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
ha-fab[rtl][is-wide] {
|
||||
mwc-fab[rtl][is-wide] {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -13,7 +13,7 @@ import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import { deleteUser, fetchUsers, updateUser, User } from "../../../data/user";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
@@ -21,6 +21,8 @@ import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showAddUserDialog } from "./show-dialog-add-user";
|
||||
import { showUserDetailDialog } from "./show-dialog-user-detail";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-users")
|
||||
export class HaConfigUsers extends LitElement {
|
||||
@@ -97,14 +99,15 @@ export class HaConfigUsers extends LitElement {
|
||||
hasFab
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
.title=${this.hass.localize("ui.panel.config.users.picker.add_user")}
|
||||
@click=${this._addUser}
|
||||
?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() {
|
||||
return css`
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[rtl] {
|
||||
mwc-fab[rtl] {
|
||||
right: auto;
|
||||
left: 16px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
ha-fab[rtl][is-wide] {
|
||||
mwc-fab[rtl][is-wide] {
|
||||
bottom: 24px;
|
||||
right: auto;
|
||||
left: 24px;
|
||||
|
@@ -18,31 +18,31 @@ import type { SelectionChangedEvent } from "../../../components/data-table/ha-da
|
||||
import {
|
||||
addGroup,
|
||||
fetchGroupableDevices,
|
||||
ZHADevice,
|
||||
ZHAGroup,
|
||||
ZHADeviceEndpoint,
|
||||
} from "../../../data/zha";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import "./zha-devices-data-table";
|
||||
import type { ZHADevicesDataTable } from "./zha-devices-data-table";
|
||||
import "./zha-device-endpoint-data-table";
|
||||
import type { ZHADeviceEndpointDataTable } from "./zha-device-endpoint-data-table";
|
||||
|
||||
@customElement("zha-add-group-page")
|
||||
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 _groupName = "";
|
||||
|
||||
@query("zha-devices-data-table")
|
||||
private _zhaDevicesDataTable!: ZHADevicesDataTable;
|
||||
@query("zha-device-endpoint-data-table")
|
||||
private _zhaDevicesDataTable!: ZHADeviceEndpointDataTable;
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
@@ -87,14 +87,14 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
</div>
|
||||
|
||||
<zha-devices-data-table
|
||||
<zha-device-endpoint-data-table
|
||||
.hass=${this.hass}
|
||||
.devices=${this.devices}
|
||||
.deviceEndpoints=${this.deviceEndpoints}
|
||||
.narrow=${this.narrow}
|
||||
selectable
|
||||
@selection-changed=${this._handleAddSelectionChanged}
|
||||
>
|
||||
</zha-devices-data-table>
|
||||
</zha-device-endpoint-data-table>
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button
|
||||
@@ -121,7 +121,7 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
this.devices = await fetchGroupableDevices(this.hass!);
|
||||
this.deviceEndpoints = await fetchGroupableDevices(this.hass!);
|
||||
}
|
||||
|
||||
private _handleAddSelectionChanged(
|
||||
@@ -132,11 +132,11 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
|
||||
private async _createGroup(): Promise<void> {
|
||||
this._processingAdd = true;
|
||||
const group: ZHAGroup = await addGroup(
|
||||
this.hass,
|
||||
this._groupName,
|
||||
this._selectedDevicesToAdd
|
||||
);
|
||||
const members = this._selectedDevicesToAdd.map((member) => {
|
||||
const memberParts = member.split("_");
|
||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||
});
|
||||
const group: ZHAGroup = await addGroup(this.hass, this._groupName, members);
|
||||
this._selectedDevicesToAdd = [];
|
||||
this._processingAdd = false;
|
||||
this._groupName = "";
|
||||
|
@@ -16,6 +16,7 @@ import "../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
DataTableRowData,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
@@ -27,19 +28,19 @@ import type { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { formatAsPaddedHex, sortZHADevices } from "./functions";
|
||||
|
||||
export interface DeviceRowData extends ZHADevice {
|
||||
export interface DeviceRowData extends DataTableRowData {
|
||||
device?: DeviceRowData;
|
||||
}
|
||||
|
||||
@customElement("zha-config-dashboard")
|
||||
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[] = [];
|
||||
|
||||
@@ -91,7 +92,7 @@ class ZHAConfigDashboard extends LitElement {
|
||||
title: "IEEE",
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
width: "25%",
|
||||
width: "30%",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
182
src/panels/config/zha/zha-device-endpoint-data-table.ts
Normal 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;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -9,8 +9,8 @@ import {
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { SelectionChangedEvent } from "../../../components/data-table/ha-data-table";
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
fetchGroupableDevices,
|
||||
removeGroups,
|
||||
removeMembersFromGroup,
|
||||
ZHADevice,
|
||||
ZHAGroup,
|
||||
ZHADeviceEndpoint,
|
||||
} from "../../../data/zha";
|
||||
import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
@@ -29,37 +29,40 @@ import { HomeAssistant } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
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")
|
||||
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 _processingRemove = false;
|
||||
|
||||
@property() private _filteredDevices: ZHADevice[] = [];
|
||||
@property() private _filteredDeviceEndpoints: ZHADeviceEndpoint[] = [];
|
||||
|
||||
@property() private _selectedDevicesToAdd: string[] = [];
|
||||
|
||||
@property() private _selectedDevicesToRemove: string[] = [];
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
@query("#addMembers")
|
||||
private _zhaAddMembersDataTable!: ZHADeviceEndpointDataTable;
|
||||
|
||||
private _members = memoizeOne(
|
||||
(group: ZHAGroup): ZHADevice[] => group.members
|
||||
);
|
||||
@query("#removeMembers")
|
||||
private _zhaRemoveMembersDataTable!: ZHADeviceEndpointDataTable;
|
||||
|
||||
private _firstUpdatedCalled = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -74,8 +77,8 @@ export class ZHAGroupPage extends LitElement {
|
||||
this._processingRemove = false;
|
||||
this._selectedDevicesToRemove = [];
|
||||
this._selectedDevicesToAdd = [];
|
||||
this.devices = [];
|
||||
this._filteredDevices = [];
|
||||
this.deviceEndpoints = [];
|
||||
this._filteredDeviceEndpoints = [];
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
@@ -97,8 +100,6 @@ export class ZHAGroupPage extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
const members = this._members(this.group);
|
||||
|
||||
return html`
|
||||
<hass-subpage .header=${this.group.name}>
|
||||
<ha-icon-button
|
||||
@@ -122,13 +123,13 @@ export class ZHAGroupPage extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.zha.groups.members")}
|
||||
</div>
|
||||
|
||||
${members.length
|
||||
? members.map(
|
||||
${this.group.members.length
|
||||
? this.group.members.map(
|
||||
(member) => html`
|
||||
<zha-device-card
|
||||
class="card"
|
||||
.hass=${this.hass}
|
||||
.device=${member}
|
||||
.device=${member.device}
|
||||
.narrow=${this.narrow}
|
||||
.showActions=${false}
|
||||
.showEditableInfo=${false}
|
||||
@@ -140,7 +141,7 @@ export class ZHAGroupPage extends LitElement {
|
||||
This group has no members
|
||||
</p>
|
||||
`}
|
||||
${members.length
|
||||
${this.group.members.length
|
||||
? html`
|
||||
<div class="header">
|
||||
${this.hass.localize(
|
||||
@@ -148,14 +149,15 @@ export class ZHAGroupPage extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<zha-devices-data-table
|
||||
<zha-device-endpoint-data-table
|
||||
id="removeMembers"
|
||||
.hass=${this.hass}
|
||||
.devices=${members}
|
||||
.deviceEndpoints=${this.group.members}
|
||||
.narrow=${this.narrow}
|
||||
selectable
|
||||
@selection-changed=${this._handleRemoveSelectionChanged}
|
||||
>
|
||||
</zha-devices-data-table>
|
||||
</zha-device-endpoint-data-table>
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button
|
||||
@@ -182,14 +184,15 @@ export class ZHAGroupPage extends LitElement {
|
||||
${this.hass.localize("ui.panel.config.zha.groups.add_members")}
|
||||
</div>
|
||||
|
||||
<zha-devices-data-table
|
||||
<zha-device-endpoint-data-table
|
||||
id="addMembers"
|
||||
.hass=${this.hass}
|
||||
.devices=${this._filteredDevices}
|
||||
.deviceEndpoints=${this._filteredDeviceEndpoints}
|
||||
.narrow=${this.narrow}
|
||||
selectable
|
||||
@selection-changed=${this._handleAddSelectionChanged}
|
||||
>
|
||||
</zha-devices-data-table>
|
||||
</zha-device-endpoint-data-table>
|
||||
|
||||
<div class="paper-dialog-buttons">
|
||||
<mwc-button
|
||||
@@ -218,16 +221,22 @@ export class ZHAGroupPage extends LitElement {
|
||||
if (this.groupId !== null && this.groupId !== undefined) {
|
||||
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
|
||||
this._filterDevices();
|
||||
}
|
||||
|
||||
private _filterDevices() {
|
||||
// filter the groupable devices so we only show devices that aren't already in the group
|
||||
this._filteredDevices = this.devices.filter((device) => {
|
||||
return !this.group!.members.some((member) => member.ieee === device.ieee);
|
||||
});
|
||||
this._filteredDeviceEndpoints = this.deviceEndpoints.filter(
|
||||
(deviceEndpoint) => {
|
||||
return !this.group!.members.some(
|
||||
(member) =>
|
||||
member.device.ieee === deviceEndpoint.device.ieee &&
|
||||
member.endpoint_id === deviceEndpoint.endpoint_id
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private _handleAddSelectionChanged(
|
||||
@@ -244,25 +253,27 @@ export class ZHAGroupPage extends LitElement {
|
||||
|
||||
private async _addMembersToGroup(): Promise<void> {
|
||||
this._processingAdd = true;
|
||||
this.group = await addMembersToGroup(
|
||||
this.hass,
|
||||
this.groupId,
|
||||
this._selectedDevicesToAdd
|
||||
);
|
||||
const members = this._selectedDevicesToAdd.map((member) => {
|
||||
const memberParts = member.split("_");
|
||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||
});
|
||||
this.group = await addMembersToGroup(this.hass, this.groupId, members);
|
||||
this._filterDevices();
|
||||
this._selectedDevicesToAdd = [];
|
||||
this._zhaAddMembersDataTable.clearSelection();
|
||||
this._processingAdd = false;
|
||||
}
|
||||
|
||||
private async _removeMembersFromGroup(): Promise<void> {
|
||||
this._processingRemove = true;
|
||||
this.group = await removeMembersFromGroup(
|
||||
this.hass,
|
||||
this.groupId,
|
||||
this._selectedDevicesToRemove
|
||||
);
|
||||
const members = this._selectedDevicesToRemove.map((member) => {
|
||||
const memberParts = member.split("_");
|
||||
return { ieee: memberParts[0], endpoint_id: memberParts[1] };
|
||||
});
|
||||
this.group = await removeMembersFromGroup(this.hass, this.groupId, members);
|
||||
this._filterDevices();
|
||||
this._selectedDevicesToRemove = [];
|
||||
this._zhaRemoveMembersDataTable.clearSelection();
|
||||
this._processingRemove = false;
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ import { computeStateDomain } from "../../../common/entity/compute_state_domain"
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "@material/mwc-fab";
|
||||
import "../../../components/map/ha-locations-editor";
|
||||
import type {
|
||||
HaLocationsEditor,
|
||||
@@ -47,6 +47,8 @@ import type { HomeAssistant, Route } from "../../../types";
|
||||
import "../ha-config-section";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showZoneDetailDialog } from "./show-dialog-zone-detail";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
|
||||
@customElement("ha-config-zone")
|
||||
export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
@@ -244,13 +246,14 @@ export class HaConfigZone extends SubscribeMixin(LitElement) {
|
||||
: ""}
|
||||
</hass-tabs-subpage>
|
||||
|
||||
<ha-fab
|
||||
<mwc-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${hass.localize("ui.panel.config.zone.add_zone")}"
|
||||
@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 {
|
||||
cursor: pointer;
|
||||
}
|
||||
ha-fab {
|
||||
mwc-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
mwc-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
mwc-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
|
@@ -34,7 +34,11 @@ class IntegrationsCard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-card header="Integrations">
|
||||
<ha-card
|
||||
.header=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.info.integrations"
|
||||
)}
|
||||
>
|
||||
<table class="card-content">
|
||||
<tbody>
|
||||
${this._sortedIntegrations(this.hass!.config.components).map(
|
||||
@@ -62,22 +66,29 @@ class IntegrationsCard extends LitElement {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.info.documentation"
|
||||
)}
|
||||
</a>
|
||||
</td>
|
||||
${!manifest.is_built_in
|
||||
? ""
|
||||
: html`
|
||||
${manifest.is_built_in || manifest.issue_tracker
|
||||
? html`
|
||||
<td>
|
||||
<a
|
||||
href=${integrationIssuesUrl(domain)}
|
||||
href=${integrationIssuesUrl(
|
||||
domain,
|
||||
manifest
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Issues
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.info.issues"
|
||||
)}
|
||||
</a>
|
||||
</td>
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
`}
|
||||
</tr>
|
||||
`;
|
||||
|
@@ -84,15 +84,19 @@ class DialogSystemLogDetail extends LitElement {
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>documentation</a
|
||||
>${!this._manifest.is_built_in
|
||||
? ""
|
||||
: html`,
|
||||
>${this._manifest.is_built_in ||
|
||||
this._manifest.issue_tracker
|
||||
? html`,
|
||||
<a
|
||||
href=${integrationIssuesUrl(integration)}
|
||||
href=${integrationIssuesUrl(
|
||||
integration,
|
||||
this._manifest
|
||||
)}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>issues</a
|
||||
>`})
|
||||
>`
|
||||
: ""})
|
||||
`}
|
||||
`
|
||||
: ""}
|
||||
|
@@ -231,7 +231,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
|
||||
stateLabel === "triggered" ||
|
||||
!stateLabel
|
||||
? ""
|
||||
: stateLabel;
|
||||
: this._stateDisplay(state);
|
||||
}
|
||||
|
||||
private _actionDisplay(state: string): string {
|
||||
|
@@ -1,13 +1,18 @@
|
||||
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 {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
eventOptions,
|
||||
html,
|
||||
internalProperty,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
queryAsync,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { ifDefined } from "lit-html/directives/if-defined";
|
||||
@@ -69,6 +74,10 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@property() private _config?: ButtonCardConfig;
|
||||
|
||||
@queryAsync("mwc-ripple") private _ripple!: Promise<Ripple | null>;
|
||||
|
||||
@internalProperty() private _shouldRenderRipple = false;
|
||||
|
||||
public getCardSize(): number {
|
||||
return 2;
|
||||
}
|
||||
@@ -149,6 +158,13 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
return html`
|
||||
<ha-card
|
||||
@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({
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
@@ -189,7 +205,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
</span>
|
||||
`
|
||||
: ""}
|
||||
<mwc-ripple></mwc-ripple>
|
||||
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : ""}
|
||||
</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 {
|
||||
return css`
|
||||
ha-card {
|
||||
@@ -227,11 +265,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ha-card:focus {
|
||||
outline: none;
|
||||
background: var(--divider-color);
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
|
@@ -10,17 +10,21 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import "@thomasloven/round-slider";
|
||||
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||
import "../../../components/ha-card";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { GaugeCardConfig } from "./types";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { GaugeCardConfig } from "./types";
|
||||
import { debounce } from "../../../common/util/debounce";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
|
||||
export const severityMap = {
|
||||
red: "var(--label-badge-red)",
|
||||
@@ -63,11 +67,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() private _baseUnit = "50px";
|
||||
|
||||
@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 {
|
||||
return 2;
|
||||
@@ -83,11 +96,6 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
this._config = { min: 0, max: 100, ...config };
|
||||
}
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._setBaseUnit();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config || !this.hass) {
|
||||
return html``;
|
||||
@@ -121,33 +129,32 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
const sliderBarColor = this._computeSeverity(state);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
@click="${this._handleClick}"
|
||||
@click=${this._handleClick}
|
||||
tabindex="0"
|
||||
style=${styleMap({
|
||||
"--base-unit": this._baseUnit,
|
||||
"--round-slider-bar-color": sliderBarColor,
|
||||
})}
|
||||
>
|
||||
<div class="container">
|
||||
<div class="gauge-a"></div>
|
||||
<div
|
||||
class="gauge-c"
|
||||
style=${styleMap({
|
||||
transform: `rotate(${this._translateTurn(state)}turn)`,
|
||||
"background-color": this._computeSeverity(state),
|
||||
})}
|
||||
></div>
|
||||
<div class="gauge-b"></div>
|
||||
</div>
|
||||
<round-slider
|
||||
readonly
|
||||
arcLength="180"
|
||||
startAngle="180"
|
||||
.value=${state}
|
||||
.min=${this._config.min}
|
||||
.max=${this._config.max}
|
||||
></round-slider>
|
||||
<div class="gauge-data">
|
||||
<div id="percent">
|
||||
<div class="percent">
|
||||
${stateObj.state}
|
||||
${this._config.unit ||
|
||||
stateObj.attributes.unit_of_measurement ||
|
||||
""}
|
||||
</div>
|
||||
<div id="name">
|
||||
<div class="name">
|
||||
${this._config.name || computeStateName(stateObj)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,10 +167,7 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._updated = true;
|
||||
this._setBaseUnit();
|
||||
// eslint-disable-next-line wc/no-self-class
|
||||
this.classList.add("init");
|
||||
this._attachObserver();
|
||||
}
|
||||
|
||||
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 {
|
||||
const sections = this._config!.severity;
|
||||
|
||||
@@ -229,95 +223,122 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
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 {
|
||||
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 {
|
||||
return css`
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
padding: 16px 16px 0 16px;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 16px 16px 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
ha-card:focus {
|
||||
outline: none;
|
||||
background: var(--divider-color);
|
||||
}
|
||||
.container {
|
||||
width: calc(var(--base-unit) * 4);
|
||||
height: calc(var(--base-unit) * 2);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.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;
|
||||
|
||||
round-slider {
|
||||
max-width: 200px;
|
||||
--round-slider-path-width: 35px;
|
||||
--round-slider-path-color: var(--disabled-text-color);
|
||||
--round-slider-linecap: "butt";
|
||||
}
|
||||
|
||||
.gauge-data {
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
color: var(--primary-text-color);
|
||||
line-height: calc(var(--base-unit) * 0.3);
|
||||
width: 100%;
|
||||
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);
|
||||
line-height: calc(var(--base-unit) * 0.55);
|
||||
|
||||
.gauge-data .name {
|
||||
padding-top: 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.gauge-data #name {
|
||||
padding-top: calc(var(--base-unit) * 0.15);
|
||||
font-size: calc(var(--base-unit) * 0.3);
|
||||
|
||||
/* ============= NARROW ============= */
|
||||
|
||||
: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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ import "../components/hui-marquee";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import "../components/hui-warning";
|
||||
import { MediaControlCardConfig } from "./types";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
|
||||
function getContrastRatio(
|
||||
rgb1: [number, number, number],
|
||||
@@ -223,7 +224,7 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.updateComplete.then(() => this._measureCard());
|
||||
this.updateComplete.then(() => this._attachObserver());
|
||||
|
||||
if (!this.hass || !this._config) {
|
||||
return;
|
||||
@@ -252,6 +253,9 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
clearInterval(this._progressInterval);
|
||||
this._progressInterval = undefined;
|
||||
}
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -624,15 +628,8 @@ export class HuiMediaControlCard extends LitElement implements LovelaceCard {
|
||||
this._cardHeight = card.offsetHeight;
|
||||
}
|
||||
|
||||
private _attachObserver(): void {
|
||||
if (typeof ResizeObserver !== "function") {
|
||||
import("resize-observer").then((modules) => {
|
||||
modules.install();
|
||||
this._attachObserver();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
private async _attachObserver(): Promise<void> {
|
||||
await installResizeObserver();
|
||||
this._resizeObserver = new ResizeObserver(
|
||||
debounce(() => this._measureCard(), 250, false)
|
||||
);
|
||||
|
@@ -21,16 +21,17 @@ import { UNAVAILABLE } from "../../../data/entity";
|
||||
import {
|
||||
getSecondaryWeatherAttribute,
|
||||
getWeatherUnit,
|
||||
weatherIcons,
|
||||
weatherImages,
|
||||
getWeatherStateIcon,
|
||||
weatherSVGStyles,
|
||||
} from "../../../data/weather";
|
||||
import { HomeAssistant, WeatherEntity } from "../../../types";
|
||||
import type { HomeAssistant, WeatherEntity } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entites";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-warning";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { WeatherForecastCardConfig } from "./types";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import type { WeatherForecastCardConfig } from "./types";
|
||||
import { installResizeObserver } from "../common/install-resize-observer";
|
||||
|
||||
const DAY_IN_MILLISECONDS = 86400000;
|
||||
|
||||
@@ -72,7 +73,13 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this.updateComplete.then(() => this._measureCard());
|
||||
this.updateComplete.then(() => this._attachObserver());
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public getCardSize(): number {
|
||||
@@ -158,6 +165,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
hourly = timeDiff < DAY_IN_MILLISECONDS;
|
||||
}
|
||||
|
||||
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
|
||||
|
||||
return html`
|
||||
<ha-card
|
||||
@action=${this._handleAction}
|
||||
@@ -166,25 +175,16 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
>
|
||||
<div class="content">
|
||||
<div class="icon-image">
|
||||
${stateObj.state in weatherImages
|
||||
? html`
|
||||
<img
|
||||
class="weather-image"
|
||||
src="${weatherImages[stateObj.state]}"
|
||||
/>
|
||||
`
|
||||
: html`
|
||||
<ha-icon
|
||||
class="weather-icon"
|
||||
.icon=${weatherIcons[stateObj.state] || stateIcon(stateObj)}
|
||||
></ha-icon>
|
||||
`}
|
||||
${weatherStateIcon ||
|
||||
html`
|
||||
<ha-icon
|
||||
class="weather-icon"
|
||||
.icon=${stateIcon(stateObj)}
|
||||
></ha-icon>
|
||||
`}
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name-state">
|
||||
<div class="name">
|
||||
${this._config.name || computeStateName(stateObj)}
|
||||
</div>
|
||||
<div class="state">
|
||||
${computeStateDisplay(
|
||||
this.hass.localize,
|
||||
@@ -192,6 +192,9 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
this.hass.language
|
||||
)}
|
||||
</div>
|
||||
<div class="name">
|
||||
${this._config.name || computeStateName(stateObj)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="temp-attribute">
|
||||
<div class="temp">
|
||||
@@ -200,7 +203,20 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
>
|
||||
</div>
|
||||
<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>
|
||||
@@ -231,21 +247,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
${item.condition !== undefined && item.condition !== null
|
||||
? html`
|
||||
<div class="forecast-image-icon">
|
||||
${item.condition in weatherImages
|
||||
? 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>
|
||||
`
|
||||
: ""}
|
||||
${getWeatherStateIcon(item.condition, this)}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -286,15 +288,8 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||
}
|
||||
|
||||
private _attachObserver(): void {
|
||||
if (typeof ResizeObserver !== "function") {
|
||||
import("resize-observer").then((modules) => {
|
||||
modules.install();
|
||||
this._attachObserver();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
private async _attachObserver(): Promise<void> {
|
||||
await installResizeObserver();
|
||||
this._resizeObserver = new ResizeObserver(
|
||||
debounce(() => this._measureCard(), 250, false)
|
||||
);
|
||||
@@ -321,201 +316,205 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
weatherSVGStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
padding: 16px;
|
||||
}
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 64px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.icon-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 64px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.weather-image,
|
||||
.weather-icon {
|
||||
flex: 0 0 64px;
|
||||
}
|
||||
.icon-image > * {
|
||||
flex: 0 0 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.weather-icon {
|
||||
--mdc-icon-size: 64px;
|
||||
}
|
||||
.weather-icon {
|
||||
--mdc-icon-size: 64px;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.temp-attribute {
|
||||
text-align: right;
|
||||
}
|
||||
.temp-attribute {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.temp-attribute .temp {
|
||||
position: relative;
|
||||
margin-right: 24px;
|
||||
}
|
||||
.temp-attribute .temp {
|
||||
position: relative;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.temp-attribute .temp span {
|
||||
position: absolute;
|
||||
font-size: 24px;
|
||||
top: 1px;
|
||||
}
|
||||
.temp-attribute .temp span {
|
||||
position: absolute;
|
||||
font-size: 24px;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.name,
|
||||
.temp-attribute .temp {
|
||||
font-size: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.state,
|
||||
.temp-attribute .temp {
|
||||
font-size: 28px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.state,
|
||||
.attribute {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
.name,
|
||||
.attribute {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.name-state {
|
||||
overflow: hidden;
|
||||
padding-right: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
.name-state {
|
||||
overflow: hidden;
|
||||
padding-right: 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.name,
|
||||
.state {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.name,
|
||||
.state {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.attribute {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.attribute {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.forecast {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: 16px;
|
||||
}
|
||||
.forecast {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.forecast > div {
|
||||
text-align: center;
|
||||
}
|
||||
.forecast > div {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forecast .icon,
|
||||
.forecast .temp {
|
||||
margin: 4px 0;
|
||||
}
|
||||
.forecast .icon,
|
||||
.forecast .temp {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.forecast .temp {
|
||||
font-size: 16px;
|
||||
}
|
||||
.forecast .temp {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.forecast-image-icon {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
.forecast-image-icon {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.forecast-image {
|
||||
width: 40px;
|
||||
}
|
||||
.forecast-image-icon > * {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.forecast-icon {
|
||||
--mdc-icon-size: 40px;
|
||||
}
|
||||
.forecast-icon {
|
||||
--mdc-icon-size: 40px;
|
||||
}
|
||||
|
||||
.attribute,
|
||||
.templow,
|
||||
.state {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.attribute,
|
||||
.templow,
|
||||
.name {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.unavailable {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.unavailable {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ============= NARROW ============= */
|
||||
/* ============= NARROW ============= */
|
||||
|
||||
:host([narrow]) .icon-image {
|
||||
min-width: 52px;
|
||||
}
|
||||
:host([narrow]) .icon-image {
|
||||
min-width: 52px;
|
||||
}
|
||||
|
||||
:host([narrow]) .weather-image {
|
||||
flex: 0 0 52px;
|
||||
width: 52px;
|
||||
}
|
||||
:host([narrow]) .weather-image {
|
||||
flex: 0 0 52px;
|
||||
width: 52px;
|
||||
}
|
||||
|
||||
:host([narrow]) .weather-icon {
|
||||
--mdc-icon-size: 52px;
|
||||
}
|
||||
:host([narrow]) .weather-icon {
|
||||
--mdc-icon-size: 52px;
|
||||
}
|
||||
|
||||
:host([narrow]) .name,
|
||||
:host([narrow]) .temp-attribute .temp {
|
||||
font-size: 22px;
|
||||
}
|
||||
:host([narrow]) .state,
|
||||
:host([narrow]) .temp-attribute .temp {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
:host([narrow]) .temp-attribute .temp {
|
||||
margin-right: 16px;
|
||||
}
|
||||
:host([narrow]) .temp-attribute .temp {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
:host([narrow]) .temp span {
|
||||
top: 1px;
|
||||
font-size: 16px;
|
||||
}
|
||||
:host([narrow]) .temp span {
|
||||
top: 1px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* ============= VERY NARROW ============= */
|
||||
/* ============= VERY NARROW ============= */
|
||||
|
||||
:host([veryNarrow]) .state,
|
||||
:host([veryNarrow]) .attribute {
|
||||
display: none;
|
||||
}
|
||||
:host([veryNarrow]) .name,
|
||||
:host([veryNarrow]) .attribute {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host([veryNarrow]) .info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
:host([veryNarrow]) .info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
:host([veryNarrow]) .name-state {
|
||||
padding-right: 0;
|
||||
}
|
||||
:host([veryNarrow]) .name-state {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
/* ============= VERY VERY NARROW ============= */
|
||||
/* ============= VERY VERY NARROW ============= */
|
||||
|
||||
:host([veryVeryNarrow]) .info {
|
||||
padding-top: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
:host([veryVeryNarrow]) .info {
|
||||
padding-top: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host([veryVeryNarrow]) .content {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([veryVeryNarrow]) .content {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:host([veryVeryNarrow]) .icon-image {
|
||||
margin-right: 0;
|
||||
}
|
||||
`;
|
||||
:host([veryVeryNarrow]) .icon-image {
|
||||
margin-right: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -34,7 +34,8 @@ export interface EntitiesCardEntityConfig extends EntityConfig {
|
||||
| "last-changed"
|
||||
| "last-triggered"
|
||||
| "position"
|
||||
| "tilt-position";
|
||||
| "tilt-position"
|
||||
| "brightness";
|
||||
action_name?: string;
|
||||
service?: string;
|
||||
service_data?: object;
|
||||
@@ -275,4 +276,5 @@ export interface WeatherForecastCardConfig extends LovelaceCardConfig {
|
||||
entity: string;
|
||||
name?: string;
|
||||
show_forecast?: boolean;
|
||||
secondary_info_attribute?: string;
|
||||
}
|
||||
|
@@ -170,12 +170,12 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
display: null,
|
||||
});
|
||||
this.ripple.disabled = false;
|
||||
this.ripple.activate();
|
||||
this.ripple.startPress();
|
||||
this.ripple.unbounded = true;
|
||||
}
|
||||
|
||||
private stopAnimation() {
|
||||
this.ripple.deactivate();
|
||||
this.ripple.endPress();
|
||||
this.ripple.disabled = true;
|
||||
this.style.display = "none";
|
||||
}
|
||||
|
@@ -465,7 +465,8 @@ export const generateLovelaceConfigFromData = async (
|
||||
};
|
||||
|
||||
export const generateLovelaceConfigFromHass = async (
|
||||
hass: HomeAssistant
|
||||
hass: HomeAssistant,
|
||||
localize?: LocalizeFunc
|
||||
): Promise<LovelaceConfig> => {
|
||||
// We want to keep the registry subscriptions alive after generating the UI
|
||||
// so that we don't serve up stale data after changing areas.
|
||||
@@ -488,6 +489,6 @@ export const generateLovelaceConfigFromHass = async (
|
||||
deviceEntries,
|
||||
entityEntries,
|
||||
hass.states,
|
||||
hass.localize
|
||||
localize || hass.localize
|
||||
);
|
||||
};
|
||||
|
6
src/panels/lovelace/common/install-resize-observer.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const installResizeObserver = async () => {
|
||||
if (typeof ResizeObserver !== "function") {
|
||||
const modules = await import("resize-observer");
|
||||
modules.install();
|
||||
}
|
||||
};
|
@@ -1,4 +1,3 @@
|
||||
import "@material/mwc-ripple";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -78,7 +77,6 @@ export class HuiButtonsBase extends LitElement {
|
||||
? entityConf.name || computeStateName(stateObj)
|
||||
: ""}
|
||||
</span>
|
||||
<mwc-ripple unbounded></mwc-ripple>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
@@ -108,6 +106,7 @@ export class HuiButtonsBase extends LitElement {
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|