mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-02 14:07:55 +00:00
Merge pull request #5862 from home-assistant/dev
This commit is contained in:
commit
6853db693a
@ -19,6 +19,8 @@ gulp.task("gen-service-worker-app-dev", (done) => {
|
|||||||
console.debug('Service worker disabled in development');
|
console.debug('Service worker disabled in development');
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
|
// This will activate the dev service worker,
|
||||||
|
// removing any prod service worker the dev might have running
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
@ -27,6 +29,28 @@ self.addEventListener('install', (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gulp.task("gen-service-worker-app-prod", async () => {
|
gulp.task("gen-service-worker-app-prod", async () => {
|
||||||
|
// Read bundled source file
|
||||||
|
const bundleManifestLatest = require(path.resolve(
|
||||||
|
paths.output,
|
||||||
|
"manifest.json"
|
||||||
|
));
|
||||||
|
let serviceWorkerContent = fs.readFileSync(
|
||||||
|
paths.root + bundleManifestLatest["service_worker.js"],
|
||||||
|
"utf-8"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete old file from frontend_latest so manifest won't pick it up
|
||||||
|
fs.removeSync(paths.root + bundleManifestLatest["service_worker.js"]);
|
||||||
|
fs.removeSync(paths.root + bundleManifestLatest["service_worker.js.map"]);
|
||||||
|
|
||||||
|
// Remove ES5
|
||||||
|
const bundleManifestES5 = require(path.resolve(
|
||||||
|
paths.output_es5,
|
||||||
|
"manifest.json"
|
||||||
|
));
|
||||||
|
fs.removeSync(paths.root + bundleManifestES5["service_worker.js"]);
|
||||||
|
fs.removeSync(paths.root + bundleManifestES5["service_worker.js.map"]);
|
||||||
|
|
||||||
const workboxManifest = await workboxBuild.getManifest({
|
const workboxManifest = await workboxBuild.getManifest({
|
||||||
// Files that mach this pattern will be considered unique and skip revision check
|
// Files that mach this pattern will be considered unique and skip revision check
|
||||||
// ignore JS files + translation files
|
// ignore JS files + translation files
|
||||||
@ -37,7 +61,8 @@ gulp.task("gen-service-worker-app-prod", async () => {
|
|||||||
"frontend_latest/*.js",
|
"frontend_latest/*.js",
|
||||||
// Cache all English translations because we catch them as fallback
|
// Cache all English translations because we catch them as fallback
|
||||||
// Using pattern to match hash instead of * to avoid caching en-GB
|
// Using pattern to match hash instead of * to avoid caching en-GB
|
||||||
"static/translations/**/en-+([a-f0-9]).json",
|
// 'v' added as valid hash letter because in dev we hash with 'dev'
|
||||||
|
"static/translations/**/en-+([a-fv0-9]).json",
|
||||||
// Icon shown on splash screen
|
// Icon shown on splash screen
|
||||||
"static/icons/favicon-192x192.png",
|
"static/icons/favicon-192x192.png",
|
||||||
"static/icons/favicon.ico",
|
"static/icons/favicon.ico",
|
||||||
@ -53,20 +78,6 @@ gulp.task("gen-service-worker-app-prod", async () => {
|
|||||||
console.warn(warning);
|
console.warn(warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace `null` with 0 for better compression
|
|
||||||
for (const entry of workboxManifest.manifestEntries) {
|
|
||||||
if (entry.revision === null) {
|
|
||||||
entry.revision = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const manifest = require(path.resolve(paths.output, "manifest.json"));
|
|
||||||
|
|
||||||
// Write bundled source file
|
|
||||||
let serviceWorkerContent = fs.readFileSync(
|
|
||||||
paths.root + manifest["service_worker.js"],
|
|
||||||
"utf-8"
|
|
||||||
);
|
|
||||||
// remove source map and add WB manifest
|
// remove source map and add WB manifest
|
||||||
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
|
serviceWorkerContent = sourceMapUrl.removeFrom(serviceWorkerContent);
|
||||||
serviceWorkerContent = serviceWorkerContent.replace(
|
serviceWorkerContent = serviceWorkerContent.replace(
|
||||||
@ -76,8 +87,4 @@ gulp.task("gen-service-worker-app-prod", async () => {
|
|||||||
|
|
||||||
// Write new file to root
|
// Write new file to root
|
||||||
fs.writeFileSync(swDest, serviceWorkerContent);
|
fs.writeFileSync(swDest, serviceWorkerContent);
|
||||||
|
|
||||||
// Delete old file from frontend_latest
|
|
||||||
fs.removeSync(paths.root + manifest["service_worker.js"]);
|
|
||||||
fs.removeSync(paths.root + manifest["service_worker.js.map"]);
|
|
||||||
});
|
});
|
||||||
|
@ -20,7 +20,9 @@ const createWebpackConfig = ({
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
mode: isProdBuild ? "production" : "development",
|
mode: isProdBuild ? "production" : "development",
|
||||||
devtool: isProdBuild ? "source-map" : "inline-cheap-module-source-map",
|
devtool: isProdBuild
|
||||||
|
? "cheap-module-source-map"
|
||||||
|
: "eval-cheap-module-source-map",
|
||||||
entry,
|
entry,
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -74,6 +76,10 @@ const createWebpackConfig = ({
|
|||||||
/@polymer\/font-roboto\/roboto\.js$/,
|
/@polymer\/font-roboto\/roboto\.js$/,
|
||||||
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||||
),
|
),
|
||||||
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
|
/@vaadin\/vaadin-material-styles\/font-roboto\.js$/,
|
||||||
|
path.resolve(paths.polymer_dir, "src/util/empty.js")
|
||||||
|
),
|
||||||
// Ignore mwc icons pointing at CDN.
|
// Ignore mwc icons pointing at CDN.
|
||||||
new webpack.NormalModuleReplacementPlugin(
|
new webpack.NormalModuleReplacementPlugin(
|
||||||
/@material\/mwc-icon\/mwc-icon-font\.js$/,
|
/@material\/mwc-icon\/mwc-icon-font\.js$/,
|
||||||
|
@ -184,7 +184,7 @@ export class HcConnect extends LitElement {
|
|||||||
this.castManager = null;
|
this.castManager = null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
registerServiceWorker(false);
|
registerServiceWorker(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleDemo() {
|
private async _handleDemo() {
|
||||||
|
@ -7,5 +7,5 @@ set -e
|
|||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
STATS=1 NODE_ENV=production ../node_modules/.bin/webpack --profile --json > compilation-stats.json
|
STATS=1 NODE_ENV=production ../node_modules/.bin/webpack --profile --json > compilation-stats.json
|
||||||
npx webpack-bundle-analyzer compilation-stats.json dist
|
npx webpack-bundle-analyzer compilation-stats.json dist/frontend_latest
|
||||||
rm compilation-stats.json
|
rm compilation-stats.json
|
||||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20200513.0",
|
version="20200514.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -121,7 +121,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
const tempA = document.createElement("a");
|
const tempA = document.createElement("a");
|
||||||
tempA.href = this.redirectUri!;
|
tempA.href = this.redirectUri!;
|
||||||
if (tempA.host === location.host) {
|
if (tempA.host === location.host) {
|
||||||
registerServiceWorker(false);
|
registerServiceWorker(this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class HaCoverControls extends PolymerElement {
|
|||||||
icon="hass:stop"
|
icon="hass:stop"
|
||||||
on-click="onStopTap"
|
on-click="onStopTap"
|
||||||
invisible$="[[!entityObj.supportsStop]]"
|
invisible$="[[!entityObj.supportsStop]]"
|
||||||
disabled="[[computStopDisabled(stateObj)]]"
|
disabled="[[computeStopDisabled(stateObj)]]"
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
aria-label="Close cover"
|
aria-label="Close cover"
|
||||||
|
@ -31,7 +31,7 @@ class HaCoverTiltControls extends PolymerElement {
|
|||||||
icon="hass:stop"
|
icon="hass:stop"
|
||||||
on-click="onStopTiltTap"
|
on-click="onStopTiltTap"
|
||||||
invisible$="[[!entityObj.supportsStopTilt]]"
|
invisible$="[[!entityObj.supportsStopTilt]]"
|
||||||
disabled="[[computStopDisabled(stateObj)]]"
|
disabled="[[computeStopDisabled(stateObj)]]"
|
||||||
title="Stop tilt"
|
title="Stop tilt"
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
|
@ -164,10 +164,18 @@ self.addEventListener("install", (event) => {
|
|||||||
event.waitUntil(caches.delete(cacheName));
|
event.waitUntil(caches.delete(cacheName));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.addEventListener("activate", () => {
|
||||||
|
// Attach the service worker to any page of the app
|
||||||
|
// that didn't have a service worker loaded.
|
||||||
|
// Happens the first time they open the app without any
|
||||||
|
// service worker registered.
|
||||||
|
// This will serve code splitted bundles from SW.
|
||||||
|
clients.claim();
|
||||||
|
});
|
||||||
|
|
||||||
self.addEventListener("message", (message) => {
|
self.addEventListener("message", (message) => {
|
||||||
if (message.data.type === "skipWaiting") {
|
if (message.data.type === "skipWaiting") {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
clients.claim();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export class HomeAssistantAppEl extends HassElement {
|
|||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._initialize();
|
this._initialize();
|
||||||
setTimeout(registerServiceWorker, 1000);
|
setTimeout(() => registerServiceWorker(this), 1000);
|
||||||
/* polyfill for paper-dropdown */
|
/* polyfill for paper-dropdown */
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
|
/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min"
|
||||||
|
@ -96,7 +96,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
|||||||
import(
|
import(
|
||||||
/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config"
|
/* webpackChunkName: "onboarding-core-config" */ "./onboarding-core-config"
|
||||||
);
|
);
|
||||||
registerServiceWorker(false);
|
registerServiceWorker(this, false);
|
||||||
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,10 +14,7 @@ import memoizeOne from "memoize-one";
|
|||||||
import * as Fuse from "fuse.js";
|
import * as Fuse from "fuse.js";
|
||||||
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
import { caseInsensitiveCompare } from "../../../common/string/compare";
|
||||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||||
import {
|
import { nextRender } from "../../../common/util/render-status";
|
||||||
afterNextRender,
|
|
||||||
nextRender,
|
|
||||||
} from "../../../common/util/render-status";
|
|
||||||
import "../../../components/entity/ha-state-icon";
|
import "../../../components/entity/ha-state-icon";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "@material/mwc-fab";
|
import "@material/mwc-fab";
|
||||||
@ -46,6 +43,7 @@ import { domainToName } from "../../../data/integration";
|
|||||||
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../layouts/hass-tabs-subpage";
|
import "../../../layouts/hass-tabs-subpage";
|
||||||
|
import "../../../layouts/hass-loading-screen";
|
||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
@ -96,7 +94,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
@property() public route!: Route;
|
@property() public route!: Route;
|
||||||
|
|
||||||
@property() private _configEntries: ConfigEntryExtended[] = [];
|
@property() private _configEntries?: ConfigEntryExtended[];
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
private _configEntriesInProgress: DataEntryFlowProgressExtended[] = [];
|
private _configEntriesInProgress: DataEntryFlowProgressExtended[] = [];
|
||||||
@ -217,32 +215,17 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
if (
|
if (
|
||||||
this._searchParms.has("config_entry") &&
|
this._searchParms.has("config_entry") &&
|
||||||
changed.has("_configEntries") &&
|
changed.has("_configEntries") &&
|
||||||
!(changed.get("_configEntries") as ConfigEntry[]).length &&
|
!changed.get("_configEntries") &&
|
||||||
this._configEntries.length
|
this._configEntries
|
||||||
) {
|
) {
|
||||||
afterNextRender(() => {
|
this._highlightEntry();
|
||||||
const entryId = this._searchParms.get("config_entry")!;
|
|
||||||
const configEntry = this._configEntries.find(
|
|
||||||
(entry) => entry.entry_id === entryId
|
|
||||||
);
|
|
||||||
if (!configEntry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const card: HaIntegrationCard = this.shadowRoot!.querySelector(
|
|
||||||
`[data-domain=${configEntry?.domain}]`
|
|
||||||
) as HaIntegrationCard;
|
|
||||||
if (card) {
|
|
||||||
card.scrollIntoView({
|
|
||||||
block: "center",
|
|
||||||
});
|
|
||||||
card.classList.add("highlight");
|
|
||||||
card.selectedConfigEntryId = entryId;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
if (!this._configEntries) {
|
||||||
|
return html`<hass-loading-screen></hass-loading-screen>`;
|
||||||
|
}
|
||||||
const [
|
const [
|
||||||
groupedConfigEntries,
|
groupedConfigEntries,
|
||||||
ignoredConfigEntries,
|
ignoredConfigEntries,
|
||||||
@ -428,7 +411,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
</p>
|
</p>
|
||||||
<mwc-button @click=${this._createFlow} unelevated
|
<mwc-button @click=${this._createFlow} unelevated
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.panel.config.integrations.add"
|
"ui.panel.config.integrations.add_integration"
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -491,7 +474,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleRemoved(ev: HASSDomEvent<ConfigEntryRemovedEvent>) {
|
private _handleRemoved(ev: HASSDomEvent<ConfigEntryRemovedEvent>) {
|
||||||
this._configEntries = this._configEntries.filter(
|
this._configEntries = this._configEntries!.filter(
|
||||||
(entry) => entry.entry_id !== ev.detail.entryId
|
(entry) => entry.entry_id !== ev.detail.entryId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -594,6 +577,27 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
ev.target.style.visibility = "hidden";
|
ev.target.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _highlightEntry() {
|
||||||
|
await nextRender();
|
||||||
|
const entryId = this._searchParms.get("config_entry")!;
|
||||||
|
const configEntry = this._configEntries!.find(
|
||||||
|
(entry) => entry.entry_id === entryId
|
||||||
|
);
|
||||||
|
if (!configEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const card: HaIntegrationCard = this.shadowRoot!.querySelector(
|
||||||
|
`[data-domain=${configEntry?.domain}]`
|
||||||
|
) as HaIntegrationCard;
|
||||||
|
if (card) {
|
||||||
|
card.scrollIntoView({
|
||||||
|
block: "center",
|
||||||
|
});
|
||||||
|
card.classList.add("highlight");
|
||||||
|
card.selectedConfigEntryId = entryId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@ -93,7 +93,11 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
html`<paper-item
|
html`<paper-item
|
||||||
.entryId=${item.entry_id}
|
.entryId=${item.entry_id}
|
||||||
@click=${this._selectConfigEntry}
|
@click=${this._selectConfigEntry}
|
||||||
><paper-item-body>${item.title}</paper-item-body
|
><paper-item-body
|
||||||
|
>${item.title ||
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.integrations.config_entry.unnamed_entry"
|
||||||
|
)}</paper-item-body
|
||||||
><ha-icon-next></ha-icon-next
|
><ha-icon-next></ha-icon-next
|
||||||
></paper-item>`
|
></paper-item>`
|
||||||
)}
|
)}
|
||||||
|
@ -1,50 +1,406 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "@polymer/paper-spinner/paper-spinner";
|
||||||
|
import "@polymer/paper-tabs/paper-tab";
|
||||||
|
import "@polymer/paper-tabs/paper-tabs";
|
||||||
import {
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { navigate } from "../../../../common/navigate";
|
||||||
import "./hui-edit-view";
|
import "../../../../components/dialog/ha-paper-dialog";
|
||||||
|
import type { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
|
||||||
|
import type {
|
||||||
|
LovelaceBadgeConfig,
|
||||||
|
LovelaceCardConfig,
|
||||||
|
LovelaceViewConfig,
|
||||||
|
} from "../../../../data/lovelace";
|
||||||
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyleDialog } from "../../../../resources/styles";
|
||||||
|
import type { HomeAssistant } from "../../../../types";
|
||||||
|
import "../../components/hui-entity-editor";
|
||||||
|
import { addView, deleteView, replaceView } from "../config-util";
|
||||||
|
import "../hui-badge-preview";
|
||||||
|
import { processEditorEntities } from "../process-editor-entities";
|
||||||
|
import {
|
||||||
|
EntitiesEditorEvent,
|
||||||
|
ViewEditEvent,
|
||||||
|
ViewVisibilityChangeEvent,
|
||||||
|
} from "../types";
|
||||||
|
import "./hui-view-editor";
|
||||||
|
import "./hui-view-visibility-editor";
|
||||||
import { EditViewDialogParams } from "./show-edit-view-dialog";
|
import { EditViewDialogParams } from "./show-edit-view-dialog";
|
||||||
|
|
||||||
declare global {
|
|
||||||
// for fire event
|
|
||||||
interface HASSDomEvents {
|
|
||||||
"reload-lovelace": undefined;
|
|
||||||
}
|
|
||||||
// for add event listener
|
|
||||||
interface HTMLElementEventMap {
|
|
||||||
"reload-lovelace": HASSDomEvent<undefined>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("hui-dialog-edit-view")
|
@customElement("hui-dialog-edit-view")
|
||||||
export class HuiDialogEditView extends LitElement {
|
export class HuiDialogEditView extends LitElement {
|
||||||
@property() protected hass?: HomeAssistant;
|
@property() public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() private _params?: EditViewDialogParams;
|
@property() private _params?: EditViewDialogParams;
|
||||||
|
|
||||||
|
@property() private _config?: LovelaceViewConfig;
|
||||||
|
|
||||||
|
@property() private _badges?: LovelaceBadgeConfig[];
|
||||||
|
|
||||||
|
@property() private _cards?: LovelaceCardConfig[];
|
||||||
|
|
||||||
|
@property() private _saving = false;
|
||||||
|
|
||||||
|
@property() private _curTab?: string;
|
||||||
|
|
||||||
|
private _curTabIndex = 0;
|
||||||
|
|
||||||
public async showDialog(params: EditViewDialogParams): Promise<void> {
|
public async showDialog(params: EditViewDialogParams): Promise<void> {
|
||||||
|
// Wait till dialog is rendered.
|
||||||
this._params = params;
|
this._params = params;
|
||||||
|
|
||||||
|
if (this._dialog == null) {
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
(this.shadowRoot!.children[0] as any).showDialog();
|
}
|
||||||
|
|
||||||
|
if (this._params.viewIndex === undefined) {
|
||||||
|
this._config = {};
|
||||||
|
this._badges = [];
|
||||||
|
this._cards = [];
|
||||||
|
} else {
|
||||||
|
const {
|
||||||
|
cards,
|
||||||
|
badges,
|
||||||
|
...viewConfig
|
||||||
|
} = this._params.lovelace!.config.views[this._params.viewIndex];
|
||||||
|
this._config = viewConfig;
|
||||||
|
this._badges = badges ? processEditorEntities(badges) : [];
|
||||||
|
this._cards = cards;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dialog.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _dialog(): HaPaperDialog {
|
||||||
|
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _viewConfigTitle(): string {
|
||||||
|
if (!this._config || !this._config.title) {
|
||||||
|
return this.hass!.localize("ui.panel.lovelace.editor.edit_view.header");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_view.header_name",
|
||||||
|
"name",
|
||||||
|
this._config.title
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._params) {
|
if (!this._params) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
|
||||||
<hui-edit-view
|
let content;
|
||||||
|
switch (this._curTab) {
|
||||||
|
case "tab-settings":
|
||||||
|
content = html`
|
||||||
|
<hui-view-editor
|
||||||
|
.isNew=${this._params.viewIndex === undefined}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.lovelace="${this._params.lovelace}"
|
.config="${this._config}"
|
||||||
.viewIndex="${this._params.viewIndex}"
|
@view-config-changed="${this._viewConfigChanged}"
|
||||||
>
|
></hui-view-editor>
|
||||||
</hui-edit-view>
|
|
||||||
`;
|
`;
|
||||||
|
break;
|
||||||
|
case "tab-badges":
|
||||||
|
content = html`
|
||||||
|
${this._badges?.length
|
||||||
|
? html`
|
||||||
|
<div class="preview-badges">
|
||||||
|
${this._badges.map((badgeConfig) => {
|
||||||
|
return html`
|
||||||
|
<hui-badge-preview
|
||||||
|
.hass=${this.hass}
|
||||||
|
.config=${badgeConfig}
|
||||||
|
></hui-badge-preview>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<hui-entity-editor
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entities="${this._badges}"
|
||||||
|
@entities-changed="${this._badgesChanged}"
|
||||||
|
></hui-entity-editor>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case "tab-visibility":
|
||||||
|
content = html`
|
||||||
|
<hui-view-visibility-editor
|
||||||
|
.hass="${this.hass}"
|
||||||
|
.config="${this._config}"
|
||||||
|
@view-visibility-changed="${this._viewVisibilityChanged}"
|
||||||
|
></hui-view-visibility-editor>
|
||||||
|
`;
|
||||||
|
break;
|
||||||
|
case "tab-cards":
|
||||||
|
content = html` Cards `;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<ha-paper-dialog with-backdrop modal>
|
||||||
|
<h2>
|
||||||
|
${this._viewConfigTitle}
|
||||||
|
</h2>
|
||||||
|
<paper-tabs
|
||||||
|
scrollable
|
||||||
|
hide-scroll-buttons
|
||||||
|
.selected="${this._curTabIndex}"
|
||||||
|
@selected-item-changed="${this._handleTabSelected}"
|
||||||
|
>
|
||||||
|
<paper-tab id="tab-settings"
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_view.tab_settings"
|
||||||
|
)}</paper-tab
|
||||||
|
>
|
||||||
|
<paper-tab id="tab-badges"
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_view.tab_badges"
|
||||||
|
)}</paper-tab
|
||||||
|
>
|
||||||
|
<paper-tab id="tab-visibility"
|
||||||
|
>${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_view.tab_visibility"
|
||||||
|
)}</paper-tab
|
||||||
|
>
|
||||||
|
</paper-tabs>
|
||||||
|
<paper-dialog-scrollable> ${content} </paper-dialog-scrollable>
|
||||||
|
<div class="paper-dialog-buttons">
|
||||||
|
${this._params.viewIndex !== undefined
|
||||||
|
? html`
|
||||||
|
<mwc-button class="warning" @click="${this._deleteConfirm}">
|
||||||
|
${this.hass!.localize(
|
||||||
|
"ui.panel.lovelace.editor.edit_view.delete"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<mwc-button @click="${this._closeDialog}"
|
||||||
|
>${this.hass!.localize("ui.common.cancel")}</mwc-button
|
||||||
|
>
|
||||||
|
<mwc-button
|
||||||
|
?disabled="${!this._config || this._saving}"
|
||||||
|
@click="${this._save}"
|
||||||
|
>
|
||||||
|
<paper-spinner
|
||||||
|
?active="${this._saving}"
|
||||||
|
alt="Saving"
|
||||||
|
></paper-spinner>
|
||||||
|
${this.hass!.localize("ui.common.save")}</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</ha-paper-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _delete(): Promise<void> {
|
||||||
|
if (!this._params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this._params.lovelace!.saveConfig(
|
||||||
|
deleteView(this._params.lovelace!.config, this._params.viewIndex!)
|
||||||
|
);
|
||||||
|
this._closeDialog();
|
||||||
|
navigate(this, `/${window.location.pathname.split("/")[1]}`);
|
||||||
|
} catch (err) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: `Deleting failed: ${err.message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _deleteConfirm(): void {
|
||||||
|
showConfirmationDialog(this, {
|
||||||
|
title: this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.views.confirm_delete${
|
||||||
|
this._cards?.length ? `_existing_cards` : ""
|
||||||
|
}`
|
||||||
|
),
|
||||||
|
text: this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.views.confirm_delete${
|
||||||
|
this._cards?.length ? `_existing_cards` : ""
|
||||||
|
}_text`,
|
||||||
|
"name",
|
||||||
|
this._config?.title || "Unnamed view",
|
||||||
|
"number",
|
||||||
|
this._cards?.length || 0
|
||||||
|
),
|
||||||
|
confirm: () => this._delete(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _resizeDialog(): Promise<void> {
|
||||||
|
await this.updateComplete;
|
||||||
|
fireEvent(this._dialog as HTMLElement, "iron-resize");
|
||||||
|
}
|
||||||
|
|
||||||
|
private _closeDialog(): void {
|
||||||
|
this._curTabIndex = 0;
|
||||||
|
this._params = undefined;
|
||||||
|
this._config = {};
|
||||||
|
this._badges = [];
|
||||||
|
this._dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleTabSelected(ev: CustomEvent): void {
|
||||||
|
if (!ev.detail.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._curTab = ev.detail.value.id;
|
||||||
|
this._resizeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save(): Promise<void> {
|
||||||
|
if (!this._params || !this._config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this._isConfigChanged()) {
|
||||||
|
this._closeDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._saving = true;
|
||||||
|
|
||||||
|
const viewConf: LovelaceViewConfig = {
|
||||||
|
...this._config,
|
||||||
|
badges: this._badges,
|
||||||
|
cards: this._cards,
|
||||||
|
};
|
||||||
|
|
||||||
|
const lovelace = this._params.lovelace!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await lovelace.saveConfig(
|
||||||
|
this._creatingView
|
||||||
|
? addView(lovelace.config, viewConf)
|
||||||
|
: replaceView(lovelace.config, this._params.viewIndex!, viewConf)
|
||||||
|
);
|
||||||
|
if (this._params.saveCallback) {
|
||||||
|
this._params.saveCallback(
|
||||||
|
this._params.viewIndex || lovelace.config.views.length,
|
||||||
|
viewConf
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this._closeDialog();
|
||||||
|
} catch (err) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: `Saving failed: ${err.message}`,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this._saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _viewConfigChanged(ev: ViewEditEvent): void {
|
||||||
|
if (ev.detail && ev.detail.config) {
|
||||||
|
this._config = ev.detail.config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _viewVisibilityChanged(
|
||||||
|
ev: HASSDomEvent<ViewVisibilityChangeEvent>
|
||||||
|
): void {
|
||||||
|
if (ev.detail.visible && this._config) {
|
||||||
|
this._config.visible = ev.detail.visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _badgesChanged(ev: EntitiesEditorEvent): void {
|
||||||
|
if (!this._badges || !this.hass || !ev.detail || !ev.detail.entities) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._badges = processEditorEntities(ev.detail.entities);
|
||||||
|
this._resizeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isConfigChanged(): boolean {
|
||||||
|
return (
|
||||||
|
this._creatingView ||
|
||||||
|
JSON.stringify(this._config) !==
|
||||||
|
JSON.stringify(
|
||||||
|
this._params!.lovelace!.config.views[this._params!.viewIndex!]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _creatingView(): boolean {
|
||||||
|
return this._params!.viewIndex === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
|
/* overrule the ha-style-dialog max-height on small screens */
|
||||||
|
ha-paper-dialog {
|
||||||
|
max-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media all and (min-width: 660px) {
|
||||||
|
ha-paper-dialog {
|
||||||
|
width: 650px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ha-paper-dialog {
|
||||||
|
max-width: 650px;
|
||||||
|
}
|
||||||
|
paper-tabs {
|
||||||
|
--paper-tabs-selection-bar-color: var(--primary-color);
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
mwc-button paper-spinner {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
mwc-button.warning {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
paper-spinner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
paper-spinner[active] {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
paper-dialog-scrollable {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: var(--error-color);
|
||||||
|
border-bottom: 1px solid var(--error-color);
|
||||||
|
}
|
||||||
|
.preview-badges {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 12px 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,400 +0,0 @@
|
|||||||
import "@material/mwc-button";
|
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import "../../../../components/ha-icon-button";
|
|
||||||
import "@polymer/paper-spinner/paper-spinner";
|
|
||||||
import "@polymer/paper-tabs/paper-tab";
|
|
||||||
import "@polymer/paper-tabs/paper-tabs";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
CSSResult,
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit-element";
|
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
|
||||||
import { navigate } from "../../../../common/navigate";
|
|
||||||
import "../../../../components/dialog/ha-paper-dialog";
|
|
||||||
import type { HaPaperDialog } from "../../../../components/dialog/ha-paper-dialog";
|
|
||||||
import type {
|
|
||||||
LovelaceBadgeConfig,
|
|
||||||
LovelaceCardConfig,
|
|
||||||
LovelaceViewConfig,
|
|
||||||
} from "../../../../data/lovelace";
|
|
||||||
import {
|
|
||||||
showAlertDialog,
|
|
||||||
showConfirmationDialog,
|
|
||||||
} from "../../../../dialogs/generic/show-dialog-box";
|
|
||||||
import { haStyleDialog } from "../../../../resources/styles";
|
|
||||||
import type { HomeAssistant } from "../../../../types";
|
|
||||||
import "../../components/hui-entity-editor";
|
|
||||||
import type { Lovelace } from "../../types";
|
|
||||||
import { addView, deleteView, replaceView } from "../config-util";
|
|
||||||
import "../hui-badge-preview";
|
|
||||||
import { processEditorEntities } from "../process-editor-entities";
|
|
||||||
import {
|
|
||||||
EntitiesEditorEvent,
|
|
||||||
ViewEditEvent,
|
|
||||||
ViewVisibilityChangeEvent,
|
|
||||||
} from "../types";
|
|
||||||
import "./hui-view-editor";
|
|
||||||
import "./hui-view-visibility-editor";
|
|
||||||
|
|
||||||
@customElement("hui-edit-view")
|
|
||||||
export class HuiEditView extends LitElement {
|
|
||||||
@property() public lovelace?: Lovelace;
|
|
||||||
|
|
||||||
@property() public viewIndex?: number;
|
|
||||||
|
|
||||||
@property() public hass?: HomeAssistant;
|
|
||||||
|
|
||||||
@property() private _config?: LovelaceViewConfig;
|
|
||||||
|
|
||||||
@property() private _badges?: LovelaceBadgeConfig[];
|
|
||||||
|
|
||||||
@property() private _cards?: LovelaceCardConfig[];
|
|
||||||
|
|
||||||
@property() private _saving: boolean;
|
|
||||||
|
|
||||||
@property() private _curTab?: string;
|
|
||||||
|
|
||||||
private _curTabIndex: number;
|
|
||||||
|
|
||||||
public constructor() {
|
|
||||||
super();
|
|
||||||
this._saving = false;
|
|
||||||
this._curTabIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async showDialog(): Promise<void> {
|
|
||||||
// Wait till dialog is rendered.
|
|
||||||
if (this._dialog == null) {
|
|
||||||
await this.updateComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.viewIndex === undefined) {
|
|
||||||
this._config = {};
|
|
||||||
this._badges = [];
|
|
||||||
this._cards = [];
|
|
||||||
} else {
|
|
||||||
const { cards, badges, ...viewConfig } = this.lovelace!.config.views[
|
|
||||||
this.viewIndex
|
|
||||||
];
|
|
||||||
this._config = viewConfig;
|
|
||||||
this._badges = badges ? processEditorEntities(badges) : [];
|
|
||||||
this._cards = cards;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._dialog.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _dialog(): HaPaperDialog {
|
|
||||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _viewConfigTitle(): string {
|
|
||||||
if (!this._config || !this._config.title) {
|
|
||||||
return this.hass!.localize("ui.panel.lovelace.editor.edit_view.header");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_view.header_name",
|
|
||||||
"name",
|
|
||||||
this._config.title
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
|
||||||
let content;
|
|
||||||
switch (this._curTab) {
|
|
||||||
case "tab-settings":
|
|
||||||
content = html`
|
|
||||||
<hui-view-editor
|
|
||||||
.isNew=${this.viewIndex === undefined}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.config="${this._config}"
|
|
||||||
@view-config-changed="${this._viewConfigChanged}"
|
|
||||||
></hui-view-editor>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case "tab-badges":
|
|
||||||
content = html`
|
|
||||||
${this._badges?.length
|
|
||||||
? html`
|
|
||||||
<div class="preview-badges">
|
|
||||||
${this._badges.map((badgeConfig) => {
|
|
||||||
return html`
|
|
||||||
<hui-badge-preview
|
|
||||||
.hass=${this.hass}
|
|
||||||
.config=${badgeConfig}
|
|
||||||
></hui-badge-preview>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<hui-entity-editor
|
|
||||||
.hass=${this.hass}
|
|
||||||
.entities="${this._badges}"
|
|
||||||
@entities-changed="${this._badgesChanged}"
|
|
||||||
></hui-entity-editor>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case "tab-visibility":
|
|
||||||
content = html`
|
|
||||||
<hui-view-visibility-editor
|
|
||||||
.hass="${this.hass}"
|
|
||||||
.config="${this._config}"
|
|
||||||
@view-visibility-changed="${this._viewVisibilityChanged}"
|
|
||||||
></hui-view-visibility-editor>
|
|
||||||
`;
|
|
||||||
break;
|
|
||||||
case "tab-cards":
|
|
||||||
content = html` Cards `;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return html`
|
|
||||||
<ha-paper-dialog with-backdrop modal>
|
|
||||||
<h2>
|
|
||||||
${this._viewConfigTitle}
|
|
||||||
</h2>
|
|
||||||
<paper-tabs
|
|
||||||
scrollable
|
|
||||||
hide-scroll-buttons
|
|
||||||
.selected="${this._curTabIndex}"
|
|
||||||
@selected-item-changed="${this._handleTabSelected}"
|
|
||||||
>
|
|
||||||
<paper-tab id="tab-settings"
|
|
||||||
>${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_view.tab_settings"
|
|
||||||
)}</paper-tab
|
|
||||||
>
|
|
||||||
<paper-tab id="tab-badges"
|
|
||||||
>${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_view.tab_badges"
|
|
||||||
)}</paper-tab
|
|
||||||
>
|
|
||||||
<paper-tab id="tab-visibility"
|
|
||||||
>${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_view.tab_visibility"
|
|
||||||
)}</paper-tab
|
|
||||||
>
|
|
||||||
</paper-tabs>
|
|
||||||
<paper-dialog-scrollable> ${content} </paper-dialog-scrollable>
|
|
||||||
<div class="paper-dialog-buttons">
|
|
||||||
${this.viewIndex !== undefined
|
|
||||||
? html`
|
|
||||||
<mwc-button class="warning" @click="${this._deleteConfirm}">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.lovelace.editor.edit_view.delete"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
<mwc-button @click="${this._closeDialog}"
|
|
||||||
>${this.hass!.localize("ui.common.cancel")}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button
|
|
||||||
?disabled="${!this._config || this._saving}"
|
|
||||||
@click="${this._save}"
|
|
||||||
>
|
|
||||||
<paper-spinner
|
|
||||||
?active="${this._saving}"
|
|
||||||
alt="Saving"
|
|
||||||
></paper-spinner>
|
|
||||||
${this.hass!.localize("ui.common.save")}</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-paper-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _delete(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.lovelace!.saveConfig(
|
|
||||||
deleteView(this.lovelace!.config, this.viewIndex!)
|
|
||||||
);
|
|
||||||
this._closeDialog();
|
|
||||||
navigate(this, `/${window.location.pathname.split("/")[1]}`);
|
|
||||||
} catch (err) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: `Deleting failed: ${err.message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _deleteConfirm(): void {
|
|
||||||
showConfirmationDialog(this, {
|
|
||||||
title: this.hass!.localize(
|
|
||||||
`ui.panel.lovelace.views.confirm_delete${
|
|
||||||
this._cards?.length ? `_existing_cards` : ""
|
|
||||||
}`
|
|
||||||
),
|
|
||||||
text: this.hass!.localize(
|
|
||||||
`ui.panel.lovelace.views.confirm_delete${
|
|
||||||
this._cards?.length ? `_existing_cards` : ""
|
|
||||||
}_text`,
|
|
||||||
"name",
|
|
||||||
this._config?.title || "Unnamed view",
|
|
||||||
"number",
|
|
||||||
this._cards?.length || 0
|
|
||||||
),
|
|
||||||
confirm: () => this._delete(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _resizeDialog(): Promise<void> {
|
|
||||||
await this.updateComplete;
|
|
||||||
fireEvent(this._dialog as HTMLElement, "iron-resize");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _closeDialog(): void {
|
|
||||||
this._curTabIndex = 0;
|
|
||||||
this.lovelace = undefined;
|
|
||||||
this._config = {};
|
|
||||||
this._badges = [];
|
|
||||||
this._dialog.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleTabSelected(ev: CustomEvent): void {
|
|
||||||
if (!ev.detail.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._curTab = ev.detail.value.id;
|
|
||||||
this._resizeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _save(): Promise<void> {
|
|
||||||
if (!this._config) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this._isConfigChanged()) {
|
|
||||||
this._closeDialog();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._saving = true;
|
|
||||||
|
|
||||||
const viewConf: LovelaceViewConfig = {
|
|
||||||
...this._config,
|
|
||||||
badges: this._badges,
|
|
||||||
cards: this._cards,
|
|
||||||
};
|
|
||||||
|
|
||||||
const lovelace = this.lovelace!;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await lovelace.saveConfig(
|
|
||||||
this._creatingView
|
|
||||||
? addView(lovelace.config, viewConf)
|
|
||||||
: replaceView(lovelace.config, this.viewIndex!, viewConf)
|
|
||||||
);
|
|
||||||
this._closeDialog();
|
|
||||||
} catch (err) {
|
|
||||||
showAlertDialog(this, {
|
|
||||||
text: `Saving failed: ${err.message}`,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
this._saving = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _viewConfigChanged(ev: ViewEditEvent): void {
|
|
||||||
if (ev.detail && ev.detail.config) {
|
|
||||||
this._config = ev.detail.config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _viewVisibilityChanged(
|
|
||||||
ev: HASSDomEvent<ViewVisibilityChangeEvent>
|
|
||||||
): void {
|
|
||||||
if (ev.detail.visible && this._config) {
|
|
||||||
this._config.visible = ev.detail.visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _badgesChanged(ev: EntitiesEditorEvent): void {
|
|
||||||
if (!this._badges || !this.hass || !ev.detail || !ev.detail.entities) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._badges = processEditorEntities(ev.detail.entities);
|
|
||||||
this._resizeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _isConfigChanged(): boolean {
|
|
||||||
return (
|
|
||||||
this._creatingView ||
|
|
||||||
JSON.stringify(this._config) !==
|
|
||||||
JSON.stringify(this.lovelace!.config.views[this.viewIndex!])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _creatingView(): boolean {
|
|
||||||
return this.viewIndex === undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
|
||||||
/* overrule the ha-style-dialog max-height on small screens */
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-height: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@media all and (min-width: 660px) {
|
|
||||||
ha-paper-dialog {
|
|
||||||
width: 650px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ha-paper-dialog {
|
|
||||||
max-width: 650px;
|
|
||||||
}
|
|
||||||
paper-tabs {
|
|
||||||
--paper-tabs-selection-bar-color: var(--primary-color);
|
|
||||||
text-transform: uppercase;
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
mwc-button paper-spinner {
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
mwc-button.warning {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
paper-spinner {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
paper-spinner[active] {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
paper-dialog-scrollable {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
color: var(--error-color);
|
|
||||||
border-bottom: 1px solid var(--error-color);
|
|
||||||
}
|
|
||||||
.preview-badges {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 12px 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"hui-edit-view": HuiEditView;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import { Lovelace } from "../../types";
|
import { Lovelace } from "../../types";
|
||||||
|
import { LovelaceViewConfig } from "../../../../data/lovelace";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -20,6 +21,7 @@ const dialogTag = "hui-dialog-edit-view";
|
|||||||
export interface EditViewDialogParams {
|
export interface EditViewDialogParams {
|
||||||
lovelace: Lovelace;
|
lovelace: Lovelace;
|
||||||
viewIndex?: number;
|
viewIndex?: number;
|
||||||
|
saveCallback?: (viewIndex: number, viewConfig: LovelaceViewConfig) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerEditViewDialog = (element: HTMLElement): Event =>
|
const registerEditViewDialog = (element: HTMLElement): Event =>
|
||||||
|
@ -15,9 +15,14 @@ import { hasConfigOrEntityChanged } from "../common/has-changed";
|
|||||||
import "../components/hui-generic-entity-row";
|
import "../components/hui-generic-entity-row";
|
||||||
import "../components/hui-timestamp-display";
|
import "../components/hui-timestamp-display";
|
||||||
import "../components/hui-warning";
|
import "../components/hui-warning";
|
||||||
import { EntityConfig, LovelaceRow } from "./types";
|
import { LovelaceRow } from "./types";
|
||||||
|
import { EntitiesCardEntityConfig } from "../cards/types";
|
||||||
|
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||||
|
import { hasAction } from "../common/has-action";
|
||||||
|
import { ActionHandlerEvent } from "../../../data/lovelace";
|
||||||
|
import { handleAction } from "../common/handle-action";
|
||||||
|
|
||||||
interface SensorEntityConfig extends EntityConfig {
|
interface SensorEntityConfig extends EntitiesCardEntityConfig {
|
||||||
format?: "relative" | "date" | "time" | "datetime";
|
format?: "relative" | "date" | "time" | "datetime";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +64,14 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
<hui-generic-entity-row .hass=${this.hass} .config=${this._config}>
|
||||||
<div class="text-content">
|
<div
|
||||||
|
class="text-content"
|
||||||
|
@action=${this._handleAction}
|
||||||
|
.actionHandler=${actionHandler({
|
||||||
|
hasHold: hasAction(this._config.hold_action),
|
||||||
|
hasDoubleClick: hasAction(this._config.double_tap_action),
|
||||||
|
})}
|
||||||
|
>
|
||||||
${stateObj.attributes.device_class ===
|
${stateObj.attributes.device_class ===
|
||||||
SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
SENSOR_DEVICE_CLASS_TIMESTAMP &&
|
||||||
stateObj.state !== "unavailable" &&
|
stateObj.state !== "unavailable" &&
|
||||||
@ -81,6 +93,10 @@ class HuiSensorEntityRow extends LitElement implements LovelaceRow {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _handleAction(ev: ActionHandlerEvent) {
|
||||||
|
handleAction(this, this.hass!, this._config!, ev.detail.action);
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
div {
|
div {
|
||||||
|
@ -31,7 +31,11 @@ import "../../components/ha-icon";
|
|||||||
import "../../components/ha-menu-button";
|
import "../../components/ha-menu-button";
|
||||||
import "../../components/ha-icon-button-arrow-next";
|
import "../../components/ha-icon-button-arrow-next";
|
||||||
import "../../components/ha-icon-button-arrow-prev";
|
import "../../components/ha-icon-button-arrow-prev";
|
||||||
import type { LovelaceConfig, LovelacePanelConfig } from "../../data/lovelace";
|
import type {
|
||||||
|
LovelaceConfig,
|
||||||
|
LovelacePanelConfig,
|
||||||
|
LovelaceViewConfig,
|
||||||
|
} from "../../data/lovelace";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
@ -424,18 +428,24 @@ class HUIRoot extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
if (!oldLovelace || oldLovelace.editMode !== this.lovelace!.editMode) {
|
||||||
|
const views = this.config && this.config.views;
|
||||||
|
|
||||||
|
// Adjust for higher header
|
||||||
|
if (!views || views.length < 2) {
|
||||||
|
fireEvent(this, "iron-resize");
|
||||||
|
}
|
||||||
|
|
||||||
// Leave unused entities when leaving edit mode
|
// Leave unused entities when leaving edit mode
|
||||||
if (
|
if (
|
||||||
this.lovelace!.mode === "storage" &&
|
this.lovelace!.mode === "storage" &&
|
||||||
viewPath === "hass-unused-entities"
|
viewPath === "hass-unused-entities"
|
||||||
) {
|
) {
|
||||||
const views = this.config && this.config.views;
|
|
||||||
navigate(this, `${this.route?.prefix}/${views[0]?.path || 0}`);
|
navigate(this, `${this.route?.prefix}/${views[0]?.path || 0}`);
|
||||||
newSelectView = 0;
|
newSelectView = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!force) {
|
if (!force && huiView) {
|
||||||
huiView.lovelace = this.lovelace;
|
huiView.lovelace = this.lovelace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -552,6 +562,10 @@ class HUIRoot extends LitElement {
|
|||||||
private _addView() {
|
private _addView() {
|
||||||
showEditViewDialog(this, {
|
showEditViewDialog(this, {
|
||||||
lovelace: this.lovelace!,
|
lovelace: this.lovelace!,
|
||||||
|
saveCallback: (viewIndex: number, viewConfig: LovelaceViewConfig) => {
|
||||||
|
const path = viewConfig.path || viewIndex;
|
||||||
|
navigate(this, `${this.route?.prefix}/${path}`);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@ export interface LovelaceCard extends HTMLElement {
|
|||||||
hass?: HomeAssistant;
|
hass?: HomeAssistant;
|
||||||
isPanel?: boolean;
|
isPanel?: boolean;
|
||||||
editMode?: boolean;
|
editMode?: boolean;
|
||||||
index?: number;
|
|
||||||
getCardSize(): number;
|
getCardSize(): number;
|
||||||
setConfig(config: LovelaceCardConfig): void;
|
setConfig(config: LovelaceCardConfig): void;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import { showEditCardDialog } from "../editor/card-editor/show-edit-card-dialog"
|
|||||||
import { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
|
import { Lovelace, LovelaceBadge, LovelaceCard } from "../types";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { mdiPlus } from "@mdi/js";
|
import { mdiPlus } from "@mdi/js";
|
||||||
|
import { nextRender } from "../../../common/util/render-status";
|
||||||
|
|
||||||
let editCodeLoaded = false;
|
let editCodeLoaded = false;
|
||||||
|
|
||||||
@ -60,6 +61,13 @@ export class HUIView extends LitElement {
|
|||||||
|
|
||||||
@property() private _badges: LovelaceBadge[] = [];
|
@property() private _badges: LovelaceBadge[] = [];
|
||||||
|
|
||||||
|
private _createColumnsIteration = 0;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super();
|
||||||
|
this.addEventListener("iron-resize", (ev) => ev.stopPropagation());
|
||||||
|
}
|
||||||
|
|
||||||
// Public to make demo happy
|
// Public to make demo happy
|
||||||
public createCardElement(cardConfig: LovelaceCardConfig) {
|
public createCardElement(cardConfig: LovelaceCardConfig) {
|
||||||
const element = createCardElement(cardConfig) as LovelaceCard;
|
const element = createCardElement(cardConfig) as LovelaceCard;
|
||||||
@ -148,9 +156,7 @@ export class HUIView extends LitElement {
|
|||||||
|
|
||||||
if (configChanged) {
|
if (configChanged) {
|
||||||
this._createCards(lovelace.config.views[this.index!]);
|
this._createCards(lovelace.config.views[this.index!]);
|
||||||
} else if (editModeChanged) {
|
} else if (editModeChanged || changedProperties.has("columns")) {
|
||||||
this._switchEditMode();
|
|
||||||
} else if (changedProperties.has("columns")) {
|
|
||||||
this._recreateColumns();
|
this._recreateColumns();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,62 +217,75 @@ export class HUIView extends LitElement {
|
|||||||
root.style.display = elements.length > 0 ? "block" : "none";
|
root.style.display = elements.length > 0 ? "block" : "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _switchEditMode() {
|
private async _recreateColumns() {
|
||||||
if (this.lovelace!.editMode) {
|
this._createColumns();
|
||||||
const wrappedCards = this._cards.map((element) => {
|
|
||||||
const wrapper = document.createElement("hui-card-options");
|
|
||||||
wrapper.hass = this.hass;
|
|
||||||
wrapper.lovelace = this.lovelace;
|
|
||||||
wrapper.path = [this.index!, (element as LovelaceCard).index!];
|
|
||||||
(element as LovelaceCard).editMode = true;
|
|
||||||
wrapper.appendChild(element);
|
|
||||||
return wrapper;
|
|
||||||
});
|
|
||||||
this._createColumns(wrappedCards);
|
|
||||||
} else {
|
|
||||||
this._cards.forEach((card) => {
|
|
||||||
(card as LovelaceCard).editMode = false;
|
|
||||||
});
|
|
||||||
this._createColumns(this._cards);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _recreateColumns() {
|
private _createColumns() {
|
||||||
this._createColumns(this._cards);
|
this._createColumnsIteration++;
|
||||||
}
|
const iteration = this._createColumnsIteration;
|
||||||
|
|
||||||
private _createColumns(elements: HTMLElement[]) {
|
|
||||||
const root = this.shadowRoot!.getElementById("columns")!;
|
const root = this.shadowRoot!.getElementById("columns")!;
|
||||||
|
|
||||||
while (root.lastChild) {
|
while (root.lastChild) {
|
||||||
root.removeChild(root.lastChild);
|
root.removeChild(root.lastChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
let columns: HTMLElement[][] = [];
|
let columns: [number, number][][] = [];
|
||||||
const columnEntityCount: number[] = [];
|
const columnEntityCount: number[] = [];
|
||||||
for (let i = 0; i < this.columns!; i++) {
|
for (let i = 0; i < this.columns!; i++) {
|
||||||
columns.push([]);
|
columns.push([]);
|
||||||
columnEntityCount.push(0);
|
columnEntityCount.push(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
elements.forEach((el) => {
|
this._cards.forEach((el, index) => {
|
||||||
const cardSize = computeCardSize(
|
const cardSize = computeCardSize(
|
||||||
(el.tagName === "HUI-CARD-OPTIONS" ? el.firstChild : el) as LovelaceCard
|
(el.tagName === "HUI-CARD-OPTIONS" ? el.firstChild : el) as LovelaceCard
|
||||||
);
|
);
|
||||||
columns[getColumnIndex(columnEntityCount, cardSize)].push(el);
|
columns[getColumnIndex(columnEntityCount, cardSize)].push([
|
||||||
|
index,
|
||||||
|
cardSize,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove empty columns
|
// Remove empty columns
|
||||||
columns = columns.filter((val) => val.length > 0);
|
columns = columns.filter((val) => val.length > 0);
|
||||||
|
|
||||||
columns.forEach((column) => {
|
columns.forEach((indexes) => {
|
||||||
const columnEl = document.createElement("div");
|
const columnEl = document.createElement("div");
|
||||||
columnEl.classList.add("column");
|
columnEl.classList.add("column");
|
||||||
column.forEach((el) => columnEl.appendChild(el));
|
this._addToColumn(columnEl, indexes, this.lovelace!.editMode, iteration);
|
||||||
root.appendChild(columnEl);
|
root.appendChild(columnEl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _addToColumn(columnEl, indexes, editMode, iteration) {
|
||||||
|
let i = 0;
|
||||||
|
for (const [index, cardSize] of indexes) {
|
||||||
|
const card: LovelaceCard = this._cards[index];
|
||||||
|
if (!editMode) {
|
||||||
|
card.editMode = false;
|
||||||
|
columnEl.appendChild(card);
|
||||||
|
} else {
|
||||||
|
const wrapper = document.createElement("hui-card-options");
|
||||||
|
wrapper.hass = this.hass;
|
||||||
|
wrapper.lovelace = this.lovelace;
|
||||||
|
wrapper.path = [this.index!, index];
|
||||||
|
card.editMode = true;
|
||||||
|
wrapper.appendChild(card);
|
||||||
|
columnEl.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
i += cardSize;
|
||||||
|
if (i > 5) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await nextRender();
|
||||||
|
if (iteration !== this._createColumnsIteration) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _createCards(config: LovelaceViewConfig): void {
|
private _createCards(config: LovelaceViewConfig): void {
|
||||||
if (!config || !config.cards || !Array.isArray(config.cards)) {
|
if (!config || !config.cards || !Array.isArray(config.cards)) {
|
||||||
this._cards = [];
|
this._cards = [];
|
||||||
@ -274,19 +293,14 @@ export class HUIView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const elements: LovelaceCard[] = [];
|
const elements: LovelaceCard[] = [];
|
||||||
config.cards.forEach((cardConfig, index) => {
|
config.cards.forEach((cardConfig) => {
|
||||||
const element = this.createCardElement(cardConfig);
|
const element = this.createCardElement(cardConfig);
|
||||||
element.index = index;
|
|
||||||
elements.push(element);
|
elements.push(element);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._cards = elements;
|
this._cards = elements;
|
||||||
|
|
||||||
if (this.lovelace!.editMode) {
|
this._createColumns();
|
||||||
this._switchEditMode();
|
|
||||||
} else {
|
|
||||||
this._createColumns(this._cards);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _rebuildCard(
|
private _rebuildCard(
|
||||||
@ -294,7 +308,9 @@ export class HUIView extends LitElement {
|
|||||||
config: LovelaceCardConfig
|
config: LovelaceCardConfig
|
||||||
): void {
|
): void {
|
||||||
const newCardEl = this.createCardElement(config);
|
const newCardEl = this.createCardElement(config);
|
||||||
|
if (cardElToReplace.parentElement) {
|
||||||
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
|
cardElToReplace.parentElement!.replaceChild(newCardEl, cardElToReplace);
|
||||||
|
}
|
||||||
this._cards = this._cards!.map((curCardEl) =>
|
this._cards = this._cards!.map((curCardEl) =>
|
||||||
curCardEl === cardElToReplace ? newCardEl : curCardEl
|
curCardEl === cardElToReplace ? newCardEl : curCardEl
|
||||||
);
|
);
|
||||||
|
@ -1391,6 +1391,7 @@
|
|||||||
"manuf": "by {manufacturer}",
|
"manuf": "by {manufacturer}",
|
||||||
"hub": "Connected via",
|
"hub": "Connected via",
|
||||||
"firmware": "Firmware: {version}",
|
"firmware": "Firmware: {version}",
|
||||||
|
"unnamed_entry": "Unnamed entry",
|
||||||
"device_unavailable": "device unavailable",
|
"device_unavailable": "device unavailable",
|
||||||
"entity_unavailable": "entity unavailable",
|
"entity_unavailable": "entity unavailable",
|
||||||
"area": "In {area}",
|
"area": "In {area}",
|
||||||
|
@ -1,49 +1,57 @@
|
|||||||
import { HassElement } from "../state/hass-element";
|
|
||||||
import { showToast } from "./toast";
|
import { showToast } from "./toast";
|
||||||
|
|
||||||
export const supportsServiceWorker = () =>
|
export const supportsServiceWorker = () =>
|
||||||
"serviceWorker" in navigator &&
|
"serviceWorker" in navigator &&
|
||||||
(location.protocol === "https:" || location.hostname === "localhost");
|
(location.protocol === "https:" || location.hostname === "localhost");
|
||||||
|
|
||||||
export const registerServiceWorker = (notifyUpdate = true) => {
|
export const registerServiceWorker = async (
|
||||||
|
rootEl: HTMLElement,
|
||||||
|
notifyUpdate = true
|
||||||
|
) => {
|
||||||
if (!supportsServiceWorker()) {
|
if (!supportsServiceWorker()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
navigator.serviceWorker.register("/service_worker.js").then((reg) => {
|
|
||||||
reg.addEventListener("updatefound", () => {
|
|
||||||
const installingWorker = reg.installing;
|
|
||||||
if (!installingWorker || !notifyUpdate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
installingWorker.addEventListener("statechange", () => {
|
|
||||||
if (
|
|
||||||
installingWorker.state === "installed" &&
|
|
||||||
navigator.serviceWorker.controller &&
|
|
||||||
!__DEV__ &&
|
|
||||||
!__DEMO__
|
|
||||||
) {
|
|
||||||
// Notify users here of a new frontend being available.
|
|
||||||
const haElement = window.document.querySelector(
|
|
||||||
"home-assistant, ha-onboarding"
|
|
||||||
)! as HassElement;
|
|
||||||
showToast(haElement, {
|
|
||||||
message: "A new version of the frontend is available.",
|
|
||||||
action: {
|
|
||||||
action: () =>
|
|
||||||
installingWorker.postMessage({ type: "skipWaiting" }),
|
|
||||||
text: "reload",
|
|
||||||
},
|
|
||||||
duration: 0,
|
|
||||||
dismissable: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the active service worker changes, refresh the page because the cache has changed
|
// If the active service worker changes, refresh the page because the cache has changed
|
||||||
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
navigator.serviceWorker.addEventListener("controllerchange", () => {
|
||||||
location.reload();
|
location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const reg = await navigator.serviceWorker.register("/service_worker.js");
|
||||||
|
|
||||||
|
if (!notifyUpdate || __DEV__ || __DEMO__) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reg.addEventListener("updatefound", () => {
|
||||||
|
const installingWorker = reg.installing;
|
||||||
|
|
||||||
|
if (!installingWorker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
installingWorker.addEventListener("statechange", () => {
|
||||||
|
if (
|
||||||
|
installingWorker.state !== "installed" ||
|
||||||
|
!navigator.serviceWorker.controller
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify users a new frontend is available.
|
||||||
|
// When
|
||||||
|
showToast(rootEl, {
|
||||||
|
message: "A new version of the frontend is available.",
|
||||||
|
action: {
|
||||||
|
// We tell the service worker to call skipWaiting, which activates
|
||||||
|
// the new service worker. Above we listen for `controllerchange`
|
||||||
|
// so we reload the page once a new servic worker activates.
|
||||||
|
action: () => installingWorker.postMessage({ type: "skipWaiting" }),
|
||||||
|
text: "reload",
|
||||||
|
},
|
||||||
|
duration: 0,
|
||||||
|
dismissable: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user