mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 04:46:34 +00:00
* Use MWC components for ha-form (#10120) * Dont create icon for supervisor (#10191) * Fix import (#10206) * Add "gas" device_class to customize (and sort existing ones) (#10196) * Make zone names readable on map in dark mode (#10195) * Tweak ha-form (#10194) * Extract black/white row into component (#10212) * Extract black/white row into component * Remove unused import * Fix dirty check/leaving automation editor (#10211) * Add selector demo to gallery (#10213) * Fix icon overlay for person badges (#10201) * Convert iframe panel to Lit (#10216) * Allow disabling an ha-form (#10218) * Fix alarm panel badge (#10221) * Add missing validation text (#10225) * Apply flat polyfill globally (#10222) * Add ha-bar to gallery (#10242) * Handle text overflow for tabs (#10239) * Remove "battery" device class from fixed icon list (#10246) * Add ha-chip to gallery (#10252) * Add netlify build script for gallery (#10253) * Add ha-label-badge to gallery (#10248) * Use correct build url (#10258) * Remove "Hass.io" from translation (#10257) * Update demo template (#10256) * Add WebRTC stream player (#10193) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Add tamper device class for binary sensor (#10268) * Fix missing translatable energy texts (#10230) * Consolidate all icon button logic into `<ha-icon-button>` + ensure tooltip (#9230) * Fix sizing / positioning error for trace graph node with subsequent branches (#10049) * Initial support for entity category (#10266) * Add support for device configuration URL (#10251) * Add support for device configuration URL * Lint * Tweak text * Bump mdc/mwc to 0.25.2 (#10271) * Bumped version to 20211014.0 * Warn if iframe won't be able to load the website (#10217) * Disable ha-form while submitting entry flow (#10290) * Convert all warning classes to ha-alert (#10289) * ABC automation types + use MWC (#10287) * Add "capitalize" option to `hui-timestamp-display` (#10280) * Add additional binary device classes to inversion list (#10152) * Fix energy onboarding `add_solar_production` button (#10275) (#10286) * Unify default dashboard name (#10254) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Fix icon buttons in Safari (#10293) * Only render badge value if there is no icon and no image (#10310) * Update MDI to v6.3.95 (#10313) * Rename `stream_type` to `frontend_stream_type` (#10298) * Fix translation key energy distribution solar (#10316) * Prevent mwc-list-item from opening up quick-bar (#10317) * Remove element resize hook (#10300) * Improve WebRTC stream error handling and cleanup (#10302) * Fix formatting of weather extrema temperatures (#10306) * Ensure current active dark modes gets used for manually set themes (#10307) * Add views dropdown and footer actions to the "move to view" dialog (#10172) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Icon Picker (#10161) * Use maxLiveSyncPlaybackRate in ha-hls-player (#10323) * Revise grid neutrality energy dashboard card, modify energy dashboard presentation to match (#10054) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Fix `ha-icon-button` in `ha-file-upload` (#10328) * Use error for protection mode alert (#10315) * Change unsupported reason container to software (#10325) * Migrate all paper checkbox elements to mwc (#10329) * Migrate all paper-radio elements to mwc-radio (#10327) * Correct grid neutrality card tooltip, make consistent with new colors (#10326) * Fix select options for add-on config (#10330) * Migrate all paper dialogs to mwc (#10333) * Stack gas and solar sources (#10244) * Set default value when enabling optional value (#10247) * Fix overflow icon color in backup dialog (#10331) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Convert default state icons (#10223) * Convert default state icons * update * Update cast/src/launcher/layout/hc-cast.ts Co-authored-by: Philip Allgaier <mail@spacegaier.de> * Update ha-config-core.js * Update * Finish * Add siren icon * FIx * Add curtain icons Co-authored-by: Philip Allgaier <mail@spacegaier.de> * Use secondary-text-color for trailing icon (#10340) * Use svg icons for default panels (#10342) * Tweak icon picker a bit (#10319) * Add support for `no-state` and `entity-no-longer-available` statistic… (#10345) * Change dark mode input fill color (#10341) * Replace paper progress with mwc-linear-progess (#10339) * Bumped version to 20211020.0 * Add auto slider/box mode to number entity (#10272) Co-authored-by: Paulus Schoutsen <balloob@gmail.com> * Correct automation editor event action translation (#10355) * Convert cloud account config to Lit (#10350) * Restore proper state badge image behavior (#10369) * Add to do list support to markdown (#10129) * Catch error if input_datetime state is incorrect (#10237) * Update MDI to v6.4.95 (#10389) * Remove deprecated icons that where replaced (#10371) * Make all automation type pickers use natural width to be able to show… (#10391) * Trim device name from entities on device page (#10285) * Update markdown card to allow word to be broken (#10387) * Fix Full Calendar Background color (#10373) * Add additional properties to zwave_js device info panel (#10132) * Fix various `slugify()` issues + add tests (#10383) * Add stopPropagation to move click handlers (#10379) * Use ha-chip for alarm control panel card (#10393) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Fix timezone issues with date formatting for ES5 (#10370) * Add automation editor to gallery (#10392) * Use ha-chip instead of ha-label-badge for add-on capabilities (#10398) * Do not close edit dialog when more info is escaped (#10249) * Ensure Sortable is recreated when card editors are reopened (#10382) * Ensure explicit `false` values from customize form get stored (#10381) * Add running device class to binary sensor (#10400) * Ensure consistent card look on device config page (#10386) * Add "Keep me logged in" checkbox within login flow (#10226) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Update delay label (#10284) * Introduced ha-icon-overflow-menu component (#10352) Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Use ha-alert to warn about logs from custom integrations (#10396) * Add support for hiding current weather in forecast card (#10267) * Allow configuration_url to point to an internal panel (#10395) * Bump Lit (#10409) * Bump format js (#10405) * Bump codemirror (#10404) * Bump and patch material elements (#10406) * Add blueprint scripts (#9504) * Make device classes in logbook translatable (#10376) * Improve device info add to Lovelace (#10413) * Add navigation option from more-info to history (#9717) * Move entities to center column on device page (#10412) * Bumped version to 20211026.0 * Shrink new section titles in more-info dialog a bit (#10414) Co-authored-by: Bram Kragten <mail@bramkragten.nl> Co-authored-by: Joakim Sørensen <joasoe@gmail.com> Co-authored-by: Philip Allgaier <mail@spacegaier.de> Co-authored-by: Jack Wilsdon <jack.wilsdon@gmail.com> Co-authored-by: Josh McCarty <josh@joshmccarty.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Allen Porter <allen.porter@gmail.com> Co-authored-by: Franck Nijhof <git@frenck.dev> Co-authored-by: chriss158 <edgi@arcor.de> Co-authored-by: Kyle Niewiada <aav7fl@users.noreply.github.com> Co-authored-by: MartinT <44962077+MartinTuroci@users.noreply.github.com> Co-authored-by: Michael Irigoyen <michael@irigoyen.dev> Co-authored-by: Allen Porter <allen@thebends.org> Co-authored-by: Paul Bottein <paul.bottein@gmail.com> Co-authored-by: uvjustin <46082645+uvjustin@users.noreply.github.com> Co-authored-by: Will Adler <will@wtadler.com> Co-authored-by: Rogério Ribeiro <zroger499@gmail.com> Co-authored-by: Zack Barett <arnett.zackary@gmail.com> Co-authored-by: Raman Gupta <7243222+raman325@users.noreply.github.com> Co-authored-by: Nathan Orick <cnathanorick@gmail.com> Co-authored-by: Tobias Kündig <tobias@offline.ch> Co-authored-by: Marc Hörsken <mback2k@users.noreply.github.com>
This commit is contained in:
commit
2c9223ed80
12
.yarn/patches/@material/mwc-icon-button/remove-icon.patch
Normal file
12
.yarn/patches/@material/mwc-icon-button/remove-icon.patch
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
diff --git a/mwc-icon-button-base.js b/mwc-icon-button-base.js
|
||||||
|
index 45cdaab93ccc0a6daaaaabc01266dcdc32e46bfd..b3ea5b541597308d85f86ce6c23fd00785fda835 100644
|
||||||
|
--- a/mwc-icon-button-base.js
|
||||||
|
+++ b/mwc-icon-button-base.js
|
||||||
|
@@ -63,7 +63,6 @@ export class IconButtonBase extends LitElement {
|
||||||
|
@touchend="${this.handleRippleDeactivate}"
|
||||||
|
@touchcancel="${this.handleRippleDeactivate}"
|
||||||
|
>${this.renderRipple()}
|
||||||
|
- <i class="material-icons">${this.icon}</i>
|
||||||
|
<span
|
||||||
|
><slot></slot
|
||||||
|
></span>
|
@ -210,6 +210,9 @@ module.exports.config = {
|
|||||||
publicPath: publicPath(latestBuild),
|
publicPath: publicPath(latestBuild),
|
||||||
isProdBuild,
|
isProdBuild,
|
||||||
latestBuild,
|
latestBuild,
|
||||||
|
defineOverlay: {
|
||||||
|
__DEMO__: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -22,17 +22,38 @@ const getMeta = () => {
|
|||||||
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
|
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
|
||||||
encoding,
|
encoding,
|
||||||
});
|
});
|
||||||
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
|
return {
|
||||||
|
path: svg.match(/ d="([^"]+)"/)[1],
|
||||||
|
name: icon.name,
|
||||||
|
tags: icon.tags,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const addRemovedMeta = (meta) => {
|
const addRemovedMeta = (meta) => {
|
||||||
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
|
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
|
||||||
const removed = JSON.parse(file);
|
const removed = JSON.parse(file);
|
||||||
const combinedMeta = [...meta, ...removed];
|
const removedMeta = removed.map((removeIcon) => ({
|
||||||
|
path: removeIcon.path,
|
||||||
|
name: removeIcon.name,
|
||||||
|
tags: [],
|
||||||
|
}));
|
||||||
|
const combinedMeta = [...meta, ...removedMeta];
|
||||||
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const homeAutomationTag = "Home Automation";
|
||||||
|
|
||||||
|
const orderMeta = (meta) => {
|
||||||
|
const homeAutomationMeta = meta.filter((icon) =>
|
||||||
|
icon.tags.includes(homeAutomationTag)
|
||||||
|
);
|
||||||
|
const otherMeta = meta.filter(
|
||||||
|
(icon) => !icon.tags.includes(homeAutomationTag)
|
||||||
|
);
|
||||||
|
return [...homeAutomationMeta, ...otherMeta];
|
||||||
|
};
|
||||||
|
|
||||||
const splitBySize = (meta) => {
|
const splitBySize = (meta) => {
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
const CHUNK_SIZE = 50000;
|
const CHUNK_SIZE = 50000;
|
||||||
@ -77,8 +98,10 @@ const findDifferentiator = (curString, prevString) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
gulp.task("gen-icons-json", (done) => {
|
gulp.task("gen-icons-json", (done) => {
|
||||||
const meta = addRemovedMeta(getMeta());
|
const meta = getMeta();
|
||||||
const split = splitBySize(meta);
|
|
||||||
|
const metaAndRemoved = addRemovedMeta(meta);
|
||||||
|
const split = splitBySize(metaAndRemoved);
|
||||||
|
|
||||||
if (!fs.existsSync(OUTPUT_DIR)) {
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
@ -116,5 +139,10 @@ gulp.task("gen-icons-json", (done) => {
|
|||||||
JSON.stringify({ version: package.version, parts })
|
JSON.stringify({ version: package.version, parts })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(OUTPUT_DIR, "iconList.json"),
|
||||||
|
JSON.stringify(orderMeta(meta).map((icon) => icon.name))
|
||||||
|
);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,6 @@ gulp.task(
|
|||||||
process.env.NODE_ENV = "development";
|
process.env.NODE_ENV = "development";
|
||||||
},
|
},
|
||||||
"clean-hassio",
|
"clean-hassio",
|
||||||
"gen-icons-json",
|
|
||||||
"gen-index-hassio-dev",
|
"gen-index-hassio-dev",
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
@ -34,7 +33,6 @@ gulp.task(
|
|||||||
process.env.NODE_ENV = "production";
|
process.env.NODE_ENV = "production";
|
||||||
},
|
},
|
||||||
"clean-hassio",
|
"clean-hassio",
|
||||||
"gen-icons-json",
|
|
||||||
"build-supervisor-translations",
|
"build-supervisor-translations",
|
||||||
"copy-translations-supervisor",
|
"copy-translations-supervisor",
|
||||||
"build-locale-data",
|
"build-locale-data",
|
||||||
|
@ -173,6 +173,7 @@ gulp.task("webpack-dev-server-gallery", () =>
|
|||||||
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
|
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
|
||||||
contentBase: paths.gallery_output_root,
|
contentBase: paths.gallery_output_root,
|
||||||
port: 8100,
|
port: 8100,
|
||||||
|
listenHost: "0.0.0.0",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import { mdiCast, mdiCastConnected } from "@mdi/js";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
import { Auth, Connection } from "home-assistant-js-websocket";
|
import { Auth, Connection } from "home-assistant-js-websocket";
|
||||||
@ -17,6 +18,7 @@ import {
|
|||||||
import { atLeastVersion } from "../../../../src/common/config/version";
|
import { atLeastVersion } from "../../../../src/common/config/version";
|
||||||
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
|
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
|
||||||
import "../../../../src/components/ha-icon";
|
import "../../../../src/components/ha-icon";
|
||||||
|
import "../../../../src/components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
getLegacyLovelaceCollection,
|
getLegacyLovelaceCollection,
|
||||||
getLovelaceCollection,
|
getLovelaceCollection,
|
||||||
@ -73,7 +75,7 @@ class HcCast extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<p class="center-item">
|
<p class="center-item">
|
||||||
<mwc-button raised @click=${this._handleLaunch}>
|
<mwc-button raised @click=${this._handleLaunch}>
|
||||||
<ha-icon icon="hass:cast"></ha-icon>
|
<ha-svg-icon .path=${mdiCast}></ha-svg-icon>
|
||||||
Start Casting
|
Start Casting
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</p>
|
</p>
|
||||||
@ -111,7 +113,7 @@ class HcCast extends LitElement {
|
|||||||
${this.castManager.status
|
${this.castManager.status
|
||||||
? html`
|
? html`
|
||||||
<mwc-button @click=${this._handleLaunch}>
|
<mwc-button @click=${this._handleLaunch}>
|
||||||
<ha-icon icon="hass:cast-connected"></ha-icon>
|
<ha-svg-icon .path=${mdiCastConnected}></ha-svg-icon>
|
||||||
Manage
|
Manage
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
@ -233,7 +235,7 @@ class HcCast extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-button ha-icon {
|
mwc-button ha-svg-icon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "@material/mwc-button";
|
import "@material/mwc-button";
|
||||||
|
import { mdiCastConnected, mdiCast } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import {
|
import {
|
||||||
Auth,
|
Auth,
|
||||||
@ -19,7 +20,7 @@ import {
|
|||||||
loadTokens,
|
loadTokens,
|
||||||
saveTokens,
|
saveTokens,
|
||||||
} from "../../../../src/common/auth/token_storage";
|
} from "../../../../src/common/auth/token_storage";
|
||||||
import "../../../../src/components/ha-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
import "../../../../src/layouts/hass-loading-screen";
|
import "../../../../src/layouts/hass-loading-screen";
|
||||||
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
|
||||||
import "./hc-layout";
|
import "./hc-layout";
|
||||||
@ -127,11 +128,11 @@ export class HcConnect extends LitElement {
|
|||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<mwc-button @click=${this._handleDemo}>
|
<mwc-button @click=${this._handleDemo}>
|
||||||
Show Demo
|
Show Demo
|
||||||
<ha-icon
|
<ha-svg-icon
|
||||||
.icon=${this.castManager.castState === "CONNECTED"
|
.path=${this.castManager.castState === "CONNECTED"
|
||||||
? "hass:cast-connected"
|
? mdiCastConnected
|
||||||
: "hass:cast"}
|
: mdiCast}
|
||||||
></ha-icon>
|
></ha-svg-icon>
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
|
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
|
||||||
@ -307,7 +308,7 @@ export class HcConnect extends LitElement {
|
|||||||
color: darkred;
|
color: darkred;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-button ha-icon {
|
mwc-button ha-svg-icon {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiTelevision } from "@mdi/js";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, state } from "lit/decorators";
|
import { customElement, state } from "lit/decorators";
|
||||||
import { CastManager } from "../../../src/cast/cast_manager";
|
import { CastManager } from "../../../src/cast/cast_manager";
|
||||||
@ -27,7 +28,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<ha-icon icon="hademo:television"></ha-icon>
|
<ha-svg-icon .path=${mdiTelevision}></ha-svg-icon>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="name">Show Chromecast interface</div>
|
<div class="name">Show Chromecast interface</div>
|
||||||
<google-cast-launcher></google-cast-launcher>
|
<google-cast-launcher></google-cast-launcher>
|
||||||
@ -72,7 +73,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
ha-icon {
|
ha-svg-icon {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
color: var(--paper-item-icon-color);
|
color: var(--paper-item-icon-color);
|
||||||
}
|
}
|
||||||
|
7
demo/src/stubs/area_registry.ts
Normal file
7
demo/src/stubs/area_registry.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { AreaRegistryEntry } from "../../../src/data/area_registry";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockAreaRegistry = (
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
data: AreaRegistryEntry[] = []
|
||||||
|
) => hass.mockWS("config/area_registry/list", () => data);
|
7
demo/src/stubs/device_registry.ts
Normal file
7
demo/src/stubs/device_registry.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockDeviceRegistry = (
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
data: DeviceRegistryEntry[] = []
|
||||||
|
) => hass.mockWS("config/device_registry/list", () => data);
|
7
demo/src/stubs/entity_registry.ts
Normal file
7
demo/src/stubs/entity_registry.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockEntityRegistry = (
|
||||||
|
hass: MockHomeAssistant,
|
||||||
|
data: EntityRegistryEntry[] = []
|
||||||
|
) => hass.mockWS("config/entity_registry/list", () => data);
|
59
demo/src/stubs/hassio_supervisor.ts
Normal file
59
demo/src/stubs/hassio_supervisor.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||||
|
import type { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||||
|
|
||||||
|
export const mockHassioSupervisor = (hass: MockHomeAssistant) => {
|
||||||
|
hass.config.components.push("hassio");
|
||||||
|
hass.mockWS("supervisor/api", (msg) => {
|
||||||
|
if (msg.endpoint === "/supervisor/info") {
|
||||||
|
const data: HassioSupervisorInfo = {
|
||||||
|
version: "2021.10.dev0805",
|
||||||
|
version_latest: "2021.10.dev0806",
|
||||||
|
update_available: true,
|
||||||
|
channel: "dev",
|
||||||
|
arch: "aarch64",
|
||||||
|
supported: true,
|
||||||
|
healthy: true,
|
||||||
|
ip_address: "172.30.32.2",
|
||||||
|
wait_boot: 5,
|
||||||
|
timezone: "America/Los_Angeles",
|
||||||
|
logging: "info",
|
||||||
|
debug: false,
|
||||||
|
debug_block: false,
|
||||||
|
diagnostics: true,
|
||||||
|
addons: [
|
||||||
|
{
|
||||||
|
name: "Visual Studio Code",
|
||||||
|
slug: "a0d7b954_vscode",
|
||||||
|
description:
|
||||||
|
"Fully featured VSCode experience, to edit your HA config in the browser, including auto-completion!",
|
||||||
|
state: "started",
|
||||||
|
version: "3.6.2",
|
||||||
|
version_latest: "3.6.2",
|
||||||
|
update_available: false,
|
||||||
|
repository: "a0d7b954",
|
||||||
|
icon: true,
|
||||||
|
logo: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Z-Wave JS",
|
||||||
|
slug: "core_zwave_js",
|
||||||
|
description:
|
||||||
|
"Control a ZWave network with Home Assistant Z-Wave JS",
|
||||||
|
state: "started",
|
||||||
|
version: "0.1.45",
|
||||||
|
version_latest: "0.1.45",
|
||||||
|
update_available: false,
|
||||||
|
repository: "core",
|
||||||
|
icon: true,
|
||||||
|
logo: true,
|
||||||
|
},
|
||||||
|
] as any,
|
||||||
|
addons_repositories: [
|
||||||
|
"https://github.com/hassio-addons/repository",
|
||||||
|
] as any,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return Promise.reject(`${msg.method} ${msg.endpoint} is not implemented`);
|
||||||
|
});
|
||||||
|
};
|
35
gallery/script/netlify_build_gallery
Executable file
35
gallery/script/netlify_build_gallery
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
TARGET_LABEL="Needs gallery preview"
|
||||||
|
|
||||||
|
if [[ "$NETLIFY" != "true" ]]; then
|
||||||
|
echo "This script can only be run on Netlify"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
function createStatus() {
|
||||||
|
state="$1"
|
||||||
|
description="$2"
|
||||||
|
target_url="$3"
|
||||||
|
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/home-assistant/frontend/statuses/$COMMIT_REF" \
|
||||||
|
-d '{"state": "'"${state}"'", "context": "Netlify/Gallery Preview Build", "description": "'"$description"'", "target_url": "'"$target_url"'"}'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "${PULL_REQUEST}" == "false" ]]; then
|
||||||
|
gulp build-gallery
|
||||||
|
else
|
||||||
|
if [[ "$(curl -sSLf -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $GITHUB_TOKEN" \
|
||||||
|
"https://api.github.com/repos/home-assistant/frontend/pulls/${REVIEW_ID}" | jq '.labels[].name' -r)" =~ "$TARGET_LABEL" ]]; then
|
||||||
|
createStatus "pending" "Building gallery preview" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||||
|
gulp build-gallery
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
createStatus "success" "Build complete" "$DEPLOY_URL"
|
||||||
|
else
|
||||||
|
createStatus "error" "Build failed" "https://app.netlify.com/sites/home-assistant-gallery/deploys/$BUILD_ID"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
createStatus "success" "Build was not requested by PR label"
|
||||||
|
fi
|
||||||
|
fi
|
143
gallery/src/components/demo-black-white-row.ts
Normal file
143
gallery/src/components/demo-black-white-row.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { Button } from "@material/mwc-button";
|
||||||
|
import { html, LitElement, css, TemplateResult } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
||||||
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
|
|
||||||
|
@customElement("demo-black-white-row")
|
||||||
|
class DemoBlackWhiteRow extends LitElement {
|
||||||
|
@property() title!: string;
|
||||||
|
|
||||||
|
@property() value!: any;
|
||||||
|
|
||||||
|
@property() disabled = false;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<div class="row">
|
||||||
|
<div class="content light">
|
||||||
|
<ha-card .header=${this.title}>
|
||||||
|
<div class="card-content">
|
||||||
|
<slot name="light"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@click=${this.handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
</div>
|
||||||
|
<div class="content dark">
|
||||||
|
<ha-card .header=${this.title}>
|
||||||
|
<div class="card-content">
|
||||||
|
<slot name="dark"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="card-actions">
|
||||||
|
<mwc-button
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@click=${this.handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</mwc-button>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<pre>${JSON.stringify(this.value, undefined, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
applyThemesOnElement(
|
||||||
|
this.shadowRoot!.querySelector(".dark"),
|
||||||
|
{
|
||||||
|
default_theme: "default",
|
||||||
|
default_dark_theme: "default",
|
||||||
|
themes: {},
|
||||||
|
darkMode: false,
|
||||||
|
},
|
||||||
|
"default",
|
||||||
|
{ dark: true }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit(ev) {
|
||||||
|
const content = (ev.target as Button).closest(".content")!;
|
||||||
|
fireEvent(this, "submitted" as any, {
|
||||||
|
slot: content.classList.contains("light") ? "light" : "dark",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 50px 0;
|
||||||
|
background-color: var(--primary-background-color);
|
||||||
|
}
|
||||||
|
.light {
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 50px;
|
||||||
|
padding-right: 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.light ha-card {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
padding-left: 50px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
width: 400px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
width: 300px;
|
||||||
|
margin: 0 16px 0;
|
||||||
|
overflow: auto;
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
}
|
||||||
|
.card-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 1500px) {
|
||||||
|
.light {
|
||||||
|
flex: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media only screen and (max-width: 1000px) {
|
||||||
|
.light,
|
||||||
|
.dark {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.row,
|
||||||
|
.dark {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
ha-card {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin: 16px auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-black-white-row": DemoBlackWhiteRow;
|
||||||
|
}
|
||||||
|
}
|
91
gallery/src/demos/demo-automation-editor-action.ts
Normal file
91
gallery/src/demos/demo-automation-editor-action.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
|
import { customElement, state } from "lit/decorators";
|
||||||
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
|
import type { HomeAssistant } from "../../../src/types";
|
||||||
|
import "../components/demo-black-white-row";
|
||||||
|
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
|
||||||
|
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import "../../../src/panels/config/automation/action/ha-automation-action";
|
||||||
|
import { HaChooseAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-choose";
|
||||||
|
import { HaDelayAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-delay";
|
||||||
|
import { HaDeviceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-device_id";
|
||||||
|
import { HaEventAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||||
|
import { HaRepeatAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||||
|
import { HaSceneAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-scene";
|
||||||
|
import { HaServiceAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||||
|
import { HaWaitForTriggerAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_for_trigger";
|
||||||
|
import { HaWaitAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-wait_template";
|
||||||
|
import { Action } from "../../../src/data/script";
|
||||||
|
import { HaConditionAction } from "../../../src/panels/config/automation/action/types/ha-automation-action-condition";
|
||||||
|
|
||||||
|
const SCHEMAS: { name: string; actions: Action[] }[] = [
|
||||||
|
{ name: "Event", actions: [HaEventAction.defaultConfig] },
|
||||||
|
{ name: "Device", actions: [HaDeviceAction.defaultConfig] },
|
||||||
|
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
|
||||||
|
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
|
||||||
|
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
|
||||||
|
{ name: "Scene", actions: [HaSceneAction.defaultConfig] },
|
||||||
|
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
|
||||||
|
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
|
||||||
|
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
|
||||||
|
{ name: "Choose", actions: [HaChooseAction.defaultConfig] },
|
||||||
|
{ name: "Variables", actions: [{ variables: { hello: "1" } }] },
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-automation-editor-action")
|
||||||
|
class DemoHaAutomationEditorAction extends LitElement {
|
||||||
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
|
private data: any = SCHEMAS.map((info) => info.actions);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const hass = provideHass(this);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
|
mockEntityRegistry(hass);
|
||||||
|
mockDeviceRegistry(hass);
|
||||||
|
mockAreaRegistry(hass);
|
||||||
|
mockHassioSupervisor(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const valueChanged = (ev) => {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
};
|
||||||
|
return html`
|
||||||
|
${SCHEMAS.map(
|
||||||
|
(info, sampleIdx) => html`
|
||||||
|
<demo-black-white-row
|
||||||
|
.title=${info.name}
|
||||||
|
.value=${this.data[sampleIdx]}
|
||||||
|
>
|
||||||
|
${["light", "dark"].map(
|
||||||
|
(slot) =>
|
||||||
|
html`
|
||||||
|
<ha-automation-action
|
||||||
|
slot=${slot}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.actions=${this.data[sampleIdx]}
|
||||||
|
.sampleIdx=${sampleIdx}
|
||||||
|
@value-changed=${valueChanged}
|
||||||
|
></ha-automation-action>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</demo-black-white-row>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-automation-editor-action": DemoHaAutomationEditorAction;
|
||||||
|
}
|
||||||
|
}
|
127
gallery/src/demos/demo-automation-editor-condition.ts
Normal file
127
gallery/src/demos/demo-automation-editor-condition.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
|
import { customElement, state } from "lit/decorators";
|
||||||
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
|
import type { HomeAssistant } from "../../../src/types";
|
||||||
|
import "../components/demo-black-white-row";
|
||||||
|
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
|
||||||
|
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import type { Condition } from "../../../src/data/automation";
|
||||||
|
import "../../../src/panels/config/automation/condition/ha-automation-condition";
|
||||||
|
import { HaDeviceCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-device";
|
||||||
|
import { HaLogicalCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-logical";
|
||||||
|
import HaNumericStateCondition from "../../../src/panels/config/automation/condition/types/ha-automation-condition-numeric_state";
|
||||||
|
import { HaStateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-state";
|
||||||
|
import { HaSunCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-sun";
|
||||||
|
import { HaTemplateCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-template";
|
||||||
|
import { HaTimeCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-time";
|
||||||
|
import { HaTriggerCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-trigger";
|
||||||
|
import { HaZoneCondition } from "../../../src/panels/config/automation/condition/types/ha-automation-condition-zone";
|
||||||
|
|
||||||
|
const SCHEMAS: { name: string; conditions: Condition[] }[] = [
|
||||||
|
{
|
||||||
|
name: "State",
|
||||||
|
conditions: [{ condition: "state", ...HaStateCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Numeric State",
|
||||||
|
conditions: [
|
||||||
|
{ condition: "numeric_state", ...HaNumericStateCondition.defaultConfig },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Sun",
|
||||||
|
conditions: [{ condition: "sun", ...HaSunCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Zone",
|
||||||
|
conditions: [{ condition: "zone", ...HaZoneCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
conditions: [{ condition: "time", ...HaTimeCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Template",
|
||||||
|
conditions: [
|
||||||
|
{ condition: "template", ...HaTemplateCondition.defaultConfig },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Device",
|
||||||
|
conditions: [{ condition: "device", ...HaDeviceCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "And",
|
||||||
|
conditions: [{ condition: "and", ...HaLogicalCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Or",
|
||||||
|
conditions: [{ condition: "or", ...HaLogicalCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Not",
|
||||||
|
conditions: [{ condition: "not", ...HaLogicalCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Trigger",
|
||||||
|
conditions: [{ condition: "trigger", ...HaTriggerCondition.defaultConfig }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-automation-editor-condition")
|
||||||
|
class DemoHaAutomationEditorCondition extends LitElement {
|
||||||
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
|
private data: any = SCHEMAS.map((info) => info.conditions);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const hass = provideHass(this);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
|
mockEntityRegistry(hass);
|
||||||
|
mockDeviceRegistry(hass);
|
||||||
|
mockAreaRegistry(hass);
|
||||||
|
mockHassioSupervisor(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const valueChanged = (ev) => {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
};
|
||||||
|
return html`
|
||||||
|
${SCHEMAS.map(
|
||||||
|
(info, sampleIdx) => html`
|
||||||
|
<demo-black-white-row
|
||||||
|
.title=${info.name}
|
||||||
|
.value=${this.data[sampleIdx]}
|
||||||
|
>
|
||||||
|
${["light", "dark"].map(
|
||||||
|
(slot) =>
|
||||||
|
html`
|
||||||
|
<ha-automation-condition
|
||||||
|
slot=${slot}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.conditions=${this.data[sampleIdx]}
|
||||||
|
.sampleIdx=${sampleIdx}
|
||||||
|
@value-changed=${valueChanged}
|
||||||
|
></ha-automation-condition>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</demo-black-white-row>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-automation-editor-condition": DemoHaAutomationEditorCondition;
|
||||||
|
}
|
||||||
|
}
|
159
gallery/src/demos/demo-automation-editor-trigger.ts
Normal file
159
gallery/src/demos/demo-automation-editor-trigger.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
|
import { customElement, state } from "lit/decorators";
|
||||||
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
|
import type { HomeAssistant } from "../../../src/types";
|
||||||
|
import "../components/demo-black-white-row";
|
||||||
|
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
|
||||||
|
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
import type { Trigger } from "../../../src/data/automation";
|
||||||
|
import { HaGeolocationTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-geo_location";
|
||||||
|
import { HaEventTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-event";
|
||||||
|
import { HaHassTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-homeassistant";
|
||||||
|
import { HaNumericStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-numeric_state";
|
||||||
|
import { HaSunTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-sun";
|
||||||
|
import { HaTagTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-tag";
|
||||||
|
import { HaTemplateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-template";
|
||||||
|
import { HaTimeTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time";
|
||||||
|
import { HaTimePatternTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-time_pattern";
|
||||||
|
import { HaWebhookTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-webhook";
|
||||||
|
import { HaZoneTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-zone";
|
||||||
|
import { HaDeviceTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-device";
|
||||||
|
import { HaStateTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-state";
|
||||||
|
import { HaMQTTTrigger } from "../../../src/panels/config/automation/trigger/types/ha-automation-trigger-mqtt";
|
||||||
|
import "../../../src/panels/config/automation/trigger/ha-automation-trigger";
|
||||||
|
|
||||||
|
const SCHEMAS: { name: string; triggers: Trigger[] }[] = [
|
||||||
|
{
|
||||||
|
name: "State",
|
||||||
|
triggers: [{ platform: "state", ...HaStateTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "MQTT",
|
||||||
|
triggers: [{ platform: "mqtt", ...HaMQTTTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "GeoLocation",
|
||||||
|
triggers: [
|
||||||
|
{ platform: "geo_location", ...HaGeolocationTrigger.defaultConfig },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Home Assistant",
|
||||||
|
triggers: [{ platform: "homeassistant", ...HaHassTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Numeric State",
|
||||||
|
triggers: [
|
||||||
|
{ platform: "numeric_state", ...HaNumericStateTrigger.defaultConfig },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Sun",
|
||||||
|
triggers: [{ platform: "sun", ...HaSunTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Time Pattern",
|
||||||
|
triggers: [
|
||||||
|
{ platform: "time_pattern", ...HaTimePatternTrigger.defaultConfig },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Webhook",
|
||||||
|
triggers: [{ platform: "webhook", ...HaWebhookTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Zone",
|
||||||
|
triggers: [{ platform: "zone", ...HaZoneTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Tag",
|
||||||
|
triggers: [{ platform: "tag", ...HaTagTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
triggers: [{ platform: "time", ...HaTimeTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Template",
|
||||||
|
triggers: [{ platform: "template", ...HaTemplateTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Event",
|
||||||
|
triggers: [{ platform: "event", ...HaEventTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "Device Trigger",
|
||||||
|
triggers: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-automation-editor-trigger")
|
||||||
|
class DemoHaAutomationEditorTrigger extends LitElement {
|
||||||
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
|
private data: any = SCHEMAS.map((info) => info.triggers);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const hass = provideHass(this);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
|
mockEntityRegistry(hass);
|
||||||
|
mockDeviceRegistry(hass);
|
||||||
|
mockAreaRegistry(hass);
|
||||||
|
mockHassioSupervisor(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const valueChanged = (ev) => {
|
||||||
|
const sampleIdx = ev.target.sampleIdx;
|
||||||
|
this.data[sampleIdx] = ev.detail.value;
|
||||||
|
this.requestUpdate();
|
||||||
|
};
|
||||||
|
return html`
|
||||||
|
${SCHEMAS.map(
|
||||||
|
(info, sampleIdx) => html`
|
||||||
|
<demo-black-white-row
|
||||||
|
.title=${info.name}
|
||||||
|
.value=${this.data[sampleIdx]}
|
||||||
|
>
|
||||||
|
${["light", "dark"].map(
|
||||||
|
(slot) =>
|
||||||
|
html`
|
||||||
|
<ha-automation-trigger
|
||||||
|
slot=${slot}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.triggers=${this.data[sampleIdx]}
|
||||||
|
.sampleIdx=${sampleIdx}
|
||||||
|
@value-changed=${valueChanged}
|
||||||
|
></ha-automation-trigger>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</demo-black-white-row>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-automation-editor-trigger": DemoHaAutomationEditorTrigger;
|
||||||
|
}
|
||||||
|
}
|
85
gallery/src/demos/demo-ha-bar.ts
Normal file
85
gallery/src/demos/demo-ha-bar.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import "../../../src/components/ha-bar";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
|
||||||
|
const bars: {
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
value: number;
|
||||||
|
warning?: number;
|
||||||
|
error?: number;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
value: 33,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
min: -10,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 200,
|
||||||
|
max: 13,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 4,
|
||||||
|
min: 13,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-ha-bar")
|
||||||
|
export class DemoHaBar extends LitElement {
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${bars
|
||||||
|
.map((bar) => ({ min: 0, max: 100, warning: 70, error: 90, ...bar }))
|
||||||
|
.map(
|
||||||
|
(bar) => html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
<pre>Config: ${JSON.stringify(bar)}</pre>
|
||||||
|
<ha-bar
|
||||||
|
class=${classMap({
|
||||||
|
warning: bar.value > bar.warning,
|
||||||
|
error: bar.value > bar.error,
|
||||||
|
})}
|
||||||
|
.min=${bar.min}
|
||||||
|
.max=${bar.max}
|
||||||
|
.value=${bar.value}
|
||||||
|
>
|
||||||
|
</ha-bar>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
ha-card {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 24px auto;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
--ha-bar-primary-color: var(--warning-color);
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
--ha-bar-primary-color: var(--error-color);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-bar": DemoHaBar;
|
||||||
|
}
|
||||||
|
}
|
61
gallery/src/demos/demo-ha-chip.ts
Normal file
61
gallery/src/demos/demo-ha-chip.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { mdiHomeAssistant } from "@mdi/js";
|
||||||
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-chip";
|
||||||
|
import "../../../src/components/ha-svg-icon";
|
||||||
|
|
||||||
|
const chips: {
|
||||||
|
icon?: string;
|
||||||
|
content?: string;
|
||||||
|
}[] = [
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
icon: mdiHomeAssistant,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
content: "Content",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: mdiHomeAssistant,
|
||||||
|
content: "Content",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-ha-chip")
|
||||||
|
export class DemoHaChip extends LitElement {
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-card header="ha-chip demo">
|
||||||
|
<div class="card-content">
|
||||||
|
${chips.map(
|
||||||
|
(chip) => html`
|
||||||
|
<ha-chip .hasIcon=${chip.icon !== undefined}>
|
||||||
|
${chip.icon
|
||||||
|
? html`<ha-svg-icon slot="icon" .path=${chip.icon}>
|
||||||
|
</ha-svg-icon>`
|
||||||
|
: ""}
|
||||||
|
${chip.content}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
ha-card {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 24px auto;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-chip": DemoHaChip;
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,25 @@
|
|||||||
/* eslint-disable lit/no-template-arrow */
|
/* eslint-disable lit/no-template-arrow */
|
||||||
import { LitElement, TemplateResult, css, html } from "lit";
|
import "@material/mwc-button";
|
||||||
|
import { LitElement, TemplateResult, html } from "lit";
|
||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
|
import { computeInitialHaFormData } from "../../../src/components/ha-form/compute-initial-ha-form-data";
|
||||||
|
import type { HaFormSchema } from "../../../src/components/ha-form/types";
|
||||||
import "../../../src/components/ha-form/ha-form";
|
import "../../../src/components/ha-form/ha-form";
|
||||||
import "../../../src/components/ha-card";
|
import "../components/demo-black-white-row";
|
||||||
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
|
|
||||||
import type { HaFormSchema } from "../../../src/components/ha-form/ha-form";
|
|
||||||
|
|
||||||
const SCHEMAS: {
|
const SCHEMAS: {
|
||||||
title: string;
|
title: string;
|
||||||
translations?: Record<string, string>;
|
translations?: Record<string, string>;
|
||||||
error?: Record<string, string>;
|
error?: Record<string, string>;
|
||||||
schema: HaFormSchema[];
|
schema: HaFormSchema[];
|
||||||
|
data?: Record<string, any>;
|
||||||
}[] = [
|
}[] = [
|
||||||
{
|
{
|
||||||
title: "Authentication",
|
title: "Authentication",
|
||||||
translations: {
|
translations: {
|
||||||
username: "Username",
|
username: "Username",
|
||||||
password: "Password",
|
password: "Password",
|
||||||
invalid_login: "Invalid login",
|
invalid_login: "Invalid username or password",
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
base: "invalid_login",
|
base: "invalid_login",
|
||||||
@ -57,6 +59,11 @@ const SCHEMAS: {
|
|||||||
optional: true,
|
optional: true,
|
||||||
default: 10,
|
default: 10,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "float",
|
||||||
|
name: "float",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "string",
|
type: "string",
|
||||||
name: "string",
|
name: "string",
|
||||||
@ -83,6 +90,80 @@ const SCHEMAS: {
|
|||||||
optional: true,
|
optional: true,
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "positive_time_period_dict",
|
||||||
|
name: "time",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Numbers",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
name: "int",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
name: "int with default",
|
||||||
|
optional: true,
|
||||||
|
default: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
name: "int range required",
|
||||||
|
required: true,
|
||||||
|
default: 5,
|
||||||
|
valueMin: 0,
|
||||||
|
valueMax: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "integer",
|
||||||
|
name: "int range optional",
|
||||||
|
optional: true,
|
||||||
|
valueMin: 0,
|
||||||
|
valueMax: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "select",
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
["default", "Default"],
|
||||||
|
["other", "Other"],
|
||||||
|
],
|
||||||
|
name: "select",
|
||||||
|
required: true,
|
||||||
|
default: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
["default", "Default"],
|
||||||
|
["other", "Other"],
|
||||||
|
],
|
||||||
|
name: "select optional",
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
options: [
|
||||||
|
["default", "Default"],
|
||||||
|
["other", "Other"],
|
||||||
|
["uno", "mas"],
|
||||||
|
["one", "more"],
|
||||||
|
["and", "another_one"],
|
||||||
|
["option", "1000"],
|
||||||
|
],
|
||||||
|
name: "select many otions",
|
||||||
|
optional: true,
|
||||||
|
default: "default",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -95,7 +176,7 @@ const SCHEMAS: {
|
|||||||
other: "Other",
|
other: "Other",
|
||||||
},
|
},
|
||||||
name: "multi",
|
name: "multi",
|
||||||
optional: true,
|
required: true,
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,101 +189,90 @@ const SCHEMAS: {
|
|||||||
and: "another_one",
|
and: "another_one",
|
||||||
option: "1000",
|
option: "1000",
|
||||||
},
|
},
|
||||||
name: "multi",
|
name: "multi many otions",
|
||||||
optional: true,
|
optional: true,
|
||||||
default: ["default"],
|
default: ["default"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Field specific error",
|
||||||
|
data: {
|
||||||
|
new_password: "hello",
|
||||||
|
new_password_2: "bye",
|
||||||
|
},
|
||||||
|
translations: {
|
||||||
|
new_password: "New Password",
|
||||||
|
new_password_2: "Re-type Password",
|
||||||
|
not_match: "The passwords do not match",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
new_password_2: "not_match",
|
||||||
|
},
|
||||||
|
schema: [
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
name: "new_password",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "string",
|
||||||
|
name: "new_password_2",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@customElement("demo-ha-form")
|
@customElement("demo-ha-form")
|
||||||
class DemoHaForm extends LitElement {
|
class DemoHaForm extends LitElement {
|
||||||
private lightModeData: any = [];
|
private data = SCHEMAS.map(
|
||||||
|
({ schema, data }) => data || computeInitialHaFormData(schema)
|
||||||
|
);
|
||||||
|
|
||||||
private darkModeData: any = [];
|
private disabled = SCHEMAS.map(() => false);
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${SCHEMAS.map((info, idx) => {
|
${SCHEMAS.map((info, idx) => {
|
||||||
const translations = info.translations || {};
|
const translations = info.translations || {};
|
||||||
const computeLabel = (schema) =>
|
return html`
|
||||||
translations[schema.name] || schema.name;
|
<demo-black-white-row
|
||||||
const computeError = (error) => translations[error] || error;
|
.title=${info.title}
|
||||||
|
.value=${this.data[idx]}
|
||||||
return [
|
.disabled=${this.disabled[idx]}
|
||||||
[this.lightModeData, "light"],
|
@submitted=${() => {
|
||||||
[this.darkModeData, "dark"],
|
this.disabled[idx] = true;
|
||||||
].map(
|
this.requestUpdate();
|
||||||
([data, type]) => html`
|
setTimeout(() => {
|
||||||
<div class="row" data-type=${type}>
|
this.disabled[idx] = false;
|
||||||
<ha-card .header=${info.title}>
|
this.requestUpdate();
|
||||||
<div class="card-content">
|
}, 2000);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${["light", "dark"].map(
|
||||||
|
(slot) => html`
|
||||||
<ha-form
|
<ha-form
|
||||||
.data=${data[idx]}
|
slot=${slot}
|
||||||
|
.data=${this.data[idx]}
|
||||||
.schema=${info.schema}
|
.schema=${info.schema}
|
||||||
.error=${info.error}
|
.error=${info.error}
|
||||||
.computeError=${computeError}
|
.disabled=${this.disabled[idx]}
|
||||||
.computeLabel=${computeLabel}
|
.computeError=${(error) => translations[error] || error}
|
||||||
|
.computeLabel=${(schema) =>
|
||||||
|
translations[schema.name] || schema.name}
|
||||||
@value-changed=${(e) => {
|
@value-changed=${(e) => {
|
||||||
data[idx] = e.detail.value;
|
this.data[idx] = e.detail.value;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}}
|
}}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
</div>
|
|
||||||
</ha-card>
|
|
||||||
<pre>${JSON.stringify(data[idx], undefined, 2)}</pre>
|
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
);
|
)}
|
||||||
|
</demo-black-white-row>
|
||||||
|
`;
|
||||||
})}
|
})}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated(changedProps) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
this.shadowRoot!.querySelectorAll("[data-type=dark]").forEach((el) => {
|
|
||||||
applyThemesOnElement(
|
|
||||||
el,
|
|
||||||
{
|
|
||||||
default_theme: "default",
|
|
||||||
default_dark_theme: "default",
|
|
||||||
themes: {},
|
|
||||||
darkMode: false,
|
|
||||||
},
|
|
||||||
"default",
|
|
||||||
{ dark: true }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
|
||||||
.row {
|
|
||||||
margin: 0 auto;
|
|
||||||
max-width: 800px;
|
|
||||||
display: flex;
|
|
||||||
padding: 50px;
|
|
||||||
background-color: var(--primary-background-color);
|
|
||||||
}
|
|
||||||
ha-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 384px;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
width: 400px;
|
|
||||||
margin: 0 16px;
|
|
||||||
overflow: auto;
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
@media only screen and (max-width: 800px) {
|
|
||||||
.row {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
pre {
|
|
||||||
margin: 16px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
122
gallery/src/demos/demo-ha-label-badge.ts
Normal file
122
gallery/src/demos/demo-ha-label-badge.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import { html, css, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement } from "lit/decorators";
|
||||||
|
import "../../../src/components/ha-label-badge";
|
||||||
|
import "../../../src/components/ha-card";
|
||||||
|
|
||||||
|
const colors = ["#03a9f4", "#ffa600", "#43a047"];
|
||||||
|
|
||||||
|
const badges: {
|
||||||
|
label?: string;
|
||||||
|
description?: string;
|
||||||
|
image?: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: "label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "label",
|
||||||
|
description: "Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "label",
|
||||||
|
description: "Description",
|
||||||
|
image: "/images/living_room.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Description",
|
||||||
|
image: "/images/living_room.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "label",
|
||||||
|
image: "/images/living_room.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: "/images/living_room.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "big label",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "big label",
|
||||||
|
description: "Description",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "big label",
|
||||||
|
description: "Description",
|
||||||
|
image: "/images/living_room.png",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-ha-label-badge")
|
||||||
|
export class DemoHaLabelBadge extends LitElement {
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
${badges.map(
|
||||||
|
(badge) => html`
|
||||||
|
<ha-label-badge
|
||||||
|
style="--ha-label-badge-color: ${colors[
|
||||||
|
Math.floor(Math.random() * colors.length)
|
||||||
|
]}"
|
||||||
|
.label=${badge.label}
|
||||||
|
.description=${badge.description}
|
||||||
|
.image=${badge.image}
|
||||||
|
>
|
||||||
|
</ha-label-badge>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
<ha-card>
|
||||||
|
<div class="card-content">
|
||||||
|
${badges.map(
|
||||||
|
(badge) => html`
|
||||||
|
<div class="badge">
|
||||||
|
<ha-label-badge
|
||||||
|
style="--ha-label-badge-color: ${colors[
|
||||||
|
Math.floor(Math.random() * colors.length)
|
||||||
|
]}"
|
||||||
|
.label=${badge.label}
|
||||||
|
.description=${badge.description}
|
||||||
|
.image=${badge.image}
|
||||||
|
>
|
||||||
|
</ha-label-badge>
|
||||||
|
<pre>${JSON.stringify(badge, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
ha-card {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 24px auto;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
margin-left: 16px;
|
||||||
|
background-color: var(--markdown-code-background-color);
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-label-badge": DemoHaLabelBadge;
|
||||||
|
}
|
||||||
|
}
|
131
gallery/src/demos/demo-ha-selector.ts
Normal file
131
gallery/src/demos/demo-ha-selector.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/* eslint-disable lit/no-template-arrow */
|
||||||
|
import "@material/mwc-button";
|
||||||
|
import { LitElement, TemplateResult, css, html } from "lit";
|
||||||
|
import { customElement, state } from "lit/decorators";
|
||||||
|
import "../../../src/components/ha-selector/ha-selector";
|
||||||
|
import "../../../src/components/ha-settings-row";
|
||||||
|
import { provideHass } from "../../../src/fake_data/provide_hass";
|
||||||
|
import type { HomeAssistant } from "../../../src/types";
|
||||||
|
import "../components/demo-black-white-row";
|
||||||
|
import { BlueprintInput } from "../../../src/data/blueprint";
|
||||||
|
import { mockEntityRegistry } from "../../../demo/src/stubs/entity_registry";
|
||||||
|
import { mockDeviceRegistry } from "../../../demo/src/stubs/device_registry";
|
||||||
|
import { mockAreaRegistry } from "../../../demo/src/stubs/area_registry";
|
||||||
|
import { mockHassioSupervisor } from "../../../demo/src/stubs/hassio_supervisor";
|
||||||
|
|
||||||
|
const SCHEMAS: {
|
||||||
|
name: string;
|
||||||
|
input: Record<string, BlueprintInput | null>;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
name: "One of each",
|
||||||
|
input: {
|
||||||
|
entity: { name: "Entity", selector: { entity: {} } },
|
||||||
|
device: { name: "Device", selector: { device: {} } },
|
||||||
|
addon: { name: "Addon", selector: { addon: {} } },
|
||||||
|
area: { name: "Area", selector: { area: {} } },
|
||||||
|
target: { name: "Target", selector: { target: {} } },
|
||||||
|
number_box: {
|
||||||
|
name: "Number Box",
|
||||||
|
selector: {
|
||||||
|
number: {
|
||||||
|
min: 0,
|
||||||
|
max: 10,
|
||||||
|
mode: "box",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
number_slider: {
|
||||||
|
name: "Number Slider",
|
||||||
|
selector: {
|
||||||
|
number: {
|
||||||
|
min: 0,
|
||||||
|
max: 10,
|
||||||
|
mode: "slider",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
boolean: { name: "Boolean", selector: { boolean: {} } },
|
||||||
|
time: { name: "Time", selector: { time: {} } },
|
||||||
|
action: { name: "Action", selector: { action: {} } },
|
||||||
|
text: { name: "Text", selector: { text: { multiline: false } } },
|
||||||
|
text_multiline: {
|
||||||
|
name: "Text multiline",
|
||||||
|
selector: { text: { multiline: true } },
|
||||||
|
},
|
||||||
|
object: { name: "Object", selector: { object: {} } },
|
||||||
|
select: {
|
||||||
|
name: "Select",
|
||||||
|
selector: { select: { options: ["Option 1", "Option 2"] } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@customElement("demo-ha-selector")
|
||||||
|
class DemoHaSelector extends LitElement {
|
||||||
|
@state() private hass!: HomeAssistant;
|
||||||
|
|
||||||
|
private data = SCHEMAS.map(() => ({}));
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
const hass = provideHass(this);
|
||||||
|
hass.updateTranslations(null, "en");
|
||||||
|
hass.updateTranslations("config", "en");
|
||||||
|
mockEntityRegistry(hass);
|
||||||
|
mockDeviceRegistry(hass);
|
||||||
|
mockAreaRegistry(hass);
|
||||||
|
mockHassioSupervisor(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
${SCHEMAS.map((info, idx) => {
|
||||||
|
const data = this.data[idx];
|
||||||
|
const valueChanged = (ev) => {
|
||||||
|
this.data[idx] = {
|
||||||
|
...data,
|
||||||
|
[ev.target.key]: ev.detail.value,
|
||||||
|
};
|
||||||
|
this.requestUpdate();
|
||||||
|
};
|
||||||
|
return html`
|
||||||
|
<demo-black-white-row .title=${info.name} .value=${this.data[idx]}>
|
||||||
|
${["light", "dark"].map((slot) =>
|
||||||
|
Object.entries(info.input).map(
|
||||||
|
([key, value]) =>
|
||||||
|
html`
|
||||||
|
<ha-settings-row narrow slot=${slot}>
|
||||||
|
<span slot="heading">${value?.name || key}</span>
|
||||||
|
<span slot="description">${value?.description}</span>
|
||||||
|
<ha-selector
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${value!.selector}
|
||||||
|
.key=${key}
|
||||||
|
.value=${data[key] ?? value!.default}
|
||||||
|
@value-changed=${valueChanged}
|
||||||
|
></ha-selector>
|
||||||
|
</ha-settings-row>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</demo-black-white-row>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
paper-input,
|
||||||
|
ha-selector {
|
||||||
|
width: 60;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"demo-ha-selector": DemoHaSelector;
|
||||||
|
}
|
||||||
|
}
|
@ -187,6 +187,7 @@ const createEntityRegistryEntries = (
|
|||||||
device_id: "mock-device-id",
|
device_id: "mock-device-id",
|
||||||
area_id: null,
|
area_id: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
entity_category: null,
|
||||||
entity_id: "binary_sensor.updater",
|
entity_id: "binary_sensor.updater",
|
||||||
name: null,
|
name: null,
|
||||||
icon: null,
|
icon: null,
|
||||||
@ -211,6 +212,7 @@ const createDeviceRegistryEntries = (
|
|||||||
area_id: null,
|
area_id: null,
|
||||||
name_by_user: null,
|
name_by_user: null,
|
||||||
disabled_by: null,
|
disabled_by: null,
|
||||||
|
configuration_url: null,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -65,10 +65,11 @@ class HaGallery extends PolymerElement {
|
|||||||
<app-header slot="header" fixed>
|
<app-header slot="header" fixed>
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
icon="hass:arrow-left"
|
|
||||||
on-click="_backTapped"
|
on-click="_backTapped"
|
||||||
class$="[[_computeHeaderButtonClass(_demo)]]"
|
class$="[[_computeHeaderButtonClass(_demo)]]"
|
||||||
></ha-icon-button>
|
>
|
||||||
|
<ha-icon icon="hass:arrow-left"></ha-icon>
|
||||||
|
</ha-icon-button>
|
||||||
<div main-title>
|
<div main-title>
|
||||||
[[_withDefault(_demo, "Home Assistant Gallery")]]
|
[[_withDefault(_demo, "Home Assistant Gallery")]]
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
@ -18,7 +17,7 @@ import { navigate } from "../../../src/common/navigate";
|
|||||||
import "../../../src/common/search/search-input";
|
import "../../../src/common/search/search-input";
|
||||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-svg-icon";
|
import "../../../src/components/ha-icon-button";
|
||||||
import {
|
import {
|
||||||
HassioAddonInfo,
|
HassioAddonInfo,
|
||||||
HassioAddonRepository,
|
HassioAddonRepository,
|
||||||
@ -92,9 +91,11 @@ class HassioAddonStore extends LitElement {
|
|||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
>
|
>
|
||||||
<mwc-icon-button slot="trigger" alt="menu">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
.label=${this.supervisor.localize("common.menu")}
|
||||||
</mwc-icon-button>
|
.path=${mdiDotsVertical}
|
||||||
|
slot="trigger"
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-list-item>
|
<mwc-list-item>
|
||||||
${this.supervisor.localize("store.repositories")}
|
${this.supervisor.localize("store.repositories")}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
@ -113,6 +114,7 @@ class HassioAddonStore extends LitElement {
|
|||||||
: html`
|
: html`
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<search-input
|
<search-input
|
||||||
|
.hass=${this.hass}
|
||||||
no-label-float
|
no-label-float
|
||||||
no-underline
|
no-underline
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
|
@ -15,12 +15,13 @@ import { customElement, property, query, state } from "lit/decorators";
|
|||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-button-menu";
|
import "../../../../src/components/ha-button-menu";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-alert";
|
|
||||||
import "../../../../src/components/ha-form/ha-form";
|
import "../../../../src/components/ha-form/ha-form";
|
||||||
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
|
import type { HaFormSchema } from "../../../../src/components/ha-form/types";
|
||||||
import "../../../../src/components/ha-formfield";
|
import "../../../../src/components/ha-formfield";
|
||||||
|
import "../../../../src/components/ha-icon-button";
|
||||||
import "../../../../src/components/ha-switch";
|
import "../../../../src/components/ha-switch";
|
||||||
import "../../../../src/components/ha-yaml-editor";
|
import "../../../../src/components/ha-yaml-editor";
|
||||||
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
|
||||||
@ -77,6 +78,18 @@ class HassioAddonConfig extends LitElement {
|
|||||||
this.addon.translations.en?.configuration?.[entry.name].name ||
|
this.addon.translations.en?.configuration?.[entry.name].name ||
|
||||||
entry.name;
|
entry.name;
|
||||||
|
|
||||||
|
private _schema = memoizeOne((schema: HaFormSchema[]): HaFormSchema[] =>
|
||||||
|
// @ts-expect-error supervisor does not implement [string, string] for select.options[]
|
||||||
|
schema.map((entry) =>
|
||||||
|
entry.type === "select"
|
||||||
|
? {
|
||||||
|
...entry,
|
||||||
|
options: entry.options.map((option) => [option, option]),
|
||||||
|
}
|
||||||
|
: entry
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
private _filteredShchema = memoizeOne(
|
private _filteredShchema = memoizeOne(
|
||||||
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
|
(options: Record<string, unknown>, schema: HaFormSchema[]) =>
|
||||||
schema.filter((entry) => entry.name in options || entry.required)
|
schema.filter((entry) => entry.name in options || entry.required)
|
||||||
@ -100,9 +113,11 @@ class HassioAddonConfig extends LitElement {
|
|||||||
</h2>
|
</h2>
|
||||||
<div class="card-menu">
|
<div class="card-menu">
|
||||||
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
<ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
|
||||||
<mwc-icon-button slot="trigger">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
.label=${this.hass.localize("common.menu")}
|
||||||
</mwc-icon-button>
|
.path=${mdiDotsVertical}
|
||||||
|
slot="trigger"
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-list-item .disabled=${!this._canShowSchema}>
|
<mwc-list-item .disabled=${!this._canShowSchema}>
|
||||||
${this._yamlMode
|
${this._yamlMode
|
||||||
? this.supervisor.localize(
|
? this.supervisor.localize(
|
||||||
@ -125,11 +140,13 @@ class HassioAddonConfig extends LitElement {
|
|||||||
.data=${this._options!}
|
.data=${this._options!}
|
||||||
@value-changed=${this._configChanged}
|
@value-changed=${this._configChanged}
|
||||||
.computeLabel=${this.computeLabel}
|
.computeLabel=${this.computeLabel}
|
||||||
.schema=${this._showOptional
|
.schema=${this._schema(
|
||||||
|
this._showOptional
|
||||||
? this.addon.schema!
|
? this.addon.schema!
|
||||||
: this._filteredShchema(
|
: this._filteredShchema(
|
||||||
this.addon.options,
|
this.addon.options,
|
||||||
this.addon.schema!
|
this.addon.schema!
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
></ha-form>`
|
></ha-form>`
|
||||||
: html` <ha-yaml-editor
|
: html` <ha-yaml-editor
|
||||||
|
@ -11,6 +11,12 @@ import {
|
|||||||
mdiHomeAssistant,
|
mdiHomeAssistant,
|
||||||
mdiKey,
|
mdiKey,
|
||||||
mdiNetwork,
|
mdiNetwork,
|
||||||
|
mdiNumeric1,
|
||||||
|
mdiNumeric2,
|
||||||
|
mdiNumeric3,
|
||||||
|
mdiNumeric4,
|
||||||
|
mdiNumeric5,
|
||||||
|
mdiNumeric6,
|
||||||
mdiPound,
|
mdiPound,
|
||||||
mdiShield,
|
mdiShield,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
@ -25,7 +31,7 @@ import "../../../../src/components/buttons/ha-call-api-button";
|
|||||||
import "../../../../src/components/buttons/ha-progress-button";
|
import "../../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-card";
|
import "../../../../src/components/ha-card";
|
||||||
import "../../../../src/components/ha-label-badge";
|
import "../../../../src/components/ha-chip";
|
||||||
import "../../../../src/components/ha-markdown";
|
import "../../../../src/components/ha-markdown";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-svg-icon";
|
||||||
@ -73,6 +79,15 @@ const STAGE_ICON = {
|
|||||||
deprecated: mdiExclamationThick,
|
deprecated: mdiExclamationThick,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const RATING_ICON = {
|
||||||
|
1: mdiNumeric1,
|
||||||
|
2: mdiNumeric2,
|
||||||
|
3: mdiNumeric3,
|
||||||
|
4: mdiNumeric4,
|
||||||
|
5: mdiNumeric5,
|
||||||
|
6: mdiNumeric6,
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hassio-addon-info")
|
@customElement("hassio-addon-info")
|
||||||
class HassioAddonInfo extends LitElement {
|
class HassioAddonInfo extends LitElement {
|
||||||
@property({ type: Boolean }) public narrow!: boolean;
|
@property({ type: Boolean }) public narrow!: boolean;
|
||||||
@ -180,23 +195,20 @@ class HassioAddonInfo extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
${!this.addon.protected
|
${!this.addon.protected
|
||||||
? html`
|
? html`
|
||||||
<ha-card class="warning">
|
<ha-alert
|
||||||
<h1 class="card-header">${this.supervisor.localize(
|
alert-type="error"
|
||||||
|
.title=${this.supervisor.localize(
|
||||||
"addon.dashboard.protection_mode.title"
|
"addon.dashboard.protection_mode.title"
|
||||||
)}
|
)}
|
||||||
</h1>
|
.actionText=${this.supervisor.localize(
|
||||||
<div class="card-content">
|
|
||||||
${this.supervisor.localize("addon.dashboard.protection_mode.content")}
|
|
||||||
</div>
|
|
||||||
<div class="card-actions protection-enable">
|
|
||||||
<mwc-button @click=${this._protectionToggled}>
|
|
||||||
${this.supervisor.localize(
|
|
||||||
"addon.dashboard.protection_mode.enable"
|
"addon.dashboard.protection_mode.enable"
|
||||||
)}
|
)}
|
||||||
</mwc-button>
|
@alert-action-clicked=${this._protectionToggled}
|
||||||
</div>
|
>
|
||||||
</div>
|
${this.supervisor.localize(
|
||||||
</ha-card>
|
"addon.dashboard.protection_mode.content"
|
||||||
|
)}
|
||||||
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
@ -249,6 +261,163 @@ class HassioAddonInfo extends LitElement {
|
|||||||
>`}
|
>`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="capabilities">
|
||||||
|
${this.addon.stage !== "stable"
|
||||||
|
? html` <ha-chip
|
||||||
|
hasIcon
|
||||||
|
class=${classMap({
|
||||||
|
yellow: this.addon.stage === "experimental",
|
||||||
|
red: this.addon.stage === "deprecated",
|
||||||
|
})}
|
||||||
|
@click=${this._showMoreInfo}
|
||||||
|
id="stage"
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${STAGE_ICON[this.addon.stage]}
|
||||||
|
>
|
||||||
|
</ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
`addon.dashboard.capability.stages.${this.addon.stage}`
|
||||||
|
)}
|
||||||
|
</ha-chip>`
|
||||||
|
: ""}
|
||||||
|
|
||||||
|
<ha-chip
|
||||||
|
hasIcon
|
||||||
|
class=${classMap({
|
||||||
|
green: [5, 6].includes(Number(this.addon.rating)),
|
||||||
|
yellow: [3, 4].includes(Number(this.addon.rating)),
|
||||||
|
red: [1, 2].includes(Number(this.addon.rating)),
|
||||||
|
})}
|
||||||
|
@click=${this._showMoreInfo}
|
||||||
|
id="rating"
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${RATING_ICON[this.addon.rating]}>
|
||||||
|
</ha-svg-icon>
|
||||||
|
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.rating"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
${this.addon.host_network
|
||||||
|
? html`
|
||||||
|
<ha-chip
|
||||||
|
hasIcon
|
||||||
|
@click=${this._showMoreInfo}
|
||||||
|
id="host_network"
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiNetwork}> </ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.host"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.full_access
|
||||||
|
? html`
|
||||||
|
<ha-chip
|
||||||
|
hasIcon
|
||||||
|
@click=${this._showMoreInfo}
|
||||||
|
id="full_access"
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiChip}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.hardware"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.homeassistant_api
|
||||||
|
? html`
|
||||||
|
<ha-chip
|
||||||
|
hasIcon
|
||||||
|
@click=${this._showMoreInfo}
|
||||||
|
id="homeassistant_api"
|
||||||
|
>
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${mdiHomeAssistant}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.core"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this._computeHassioApi
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="hassio_api">
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${mdiHomeAssistant}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
`addon.dashboard.capability.role.${this.addon.hassio_role}`
|
||||||
|
) || this.addon.hassio_role}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.docker_api
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="docker_api">
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiDocker}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.docker"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.host_pid
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="host_pid">
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiPound}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.host_pid"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.apparmor !== "default"
|
||||||
|
? html`
|
||||||
|
<ha-chip
|
||||||
|
hasIcon
|
||||||
|
@click=${this._showMoreInfo}
|
||||||
|
class=${this._computeApparmorClassName}
|
||||||
|
id="apparmor"
|
||||||
|
>
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiShield}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.apparmor"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.auth_api
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="auth_api">
|
||||||
|
<ha-svg-icon slot="icon" .path=${mdiKey}></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.auth"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${this.addon.ingress
|
||||||
|
? html`
|
||||||
|
<ha-chip hasIcon @click=${this._showMoreInfo} id="ingress">
|
||||||
|
<ha-svg-icon
|
||||||
|
slot="icon"
|
||||||
|
.path=${mdiCursorDefaultClickOutline}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${this.supervisor.localize(
|
||||||
|
"addon.dashboard.capability.label.ingress"
|
||||||
|
)}
|
||||||
|
</ha-chip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="description light-color">
|
<div class="description light-color">
|
||||||
${this.addon.description}.<br />
|
${this.addon.description}.<br />
|
||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
@ -269,172 +438,6 @@ class HassioAddonInfo extends LitElement {
|
|||||||
/>
|
/>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="security">
|
|
||||||
${this.addon.stage !== "stable"
|
|
||||||
? html` <ha-label-badge
|
|
||||||
class=${classMap({
|
|
||||||
yellow: this.addon.stage === "experimental",
|
|
||||||
red: this.addon.stage === "deprecated",
|
|
||||||
})}
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="stage"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.stage"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<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)),
|
|
||||||
yellow: [3, 4].includes(Number(this.addon.rating)),
|
|
||||||
red: [1, 2].includes(Number(this.addon.rating)),
|
|
||||||
})}
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="rating"
|
|
||||||
label="rating"
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
${this.addon.rating}
|
|
||||||
</ha-label-badge>
|
|
||||||
${this.addon.host_network
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="host_network"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.host"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiNetwork}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.full_access
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="full_access"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.hardware"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiChip}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.homeassistant_api
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="homeassistant_api"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.hass"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this._computeHassioApi
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="hassio_api"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.hassio"
|
|
||||||
)}
|
|
||||||
.description=${this.supervisor.localize(
|
|
||||||
`addon.dashboard.capability.role.${this.addon.hassio_role}`
|
|
||||||
) || this.addon.hassio_role}
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiHomeAssistant}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.docker_api
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="docker_api"
|
|
||||||
.label=".${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.docker"
|
|
||||||
)}"
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiDocker}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.host_pid
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="host_pid"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.host_pid"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiPound}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.apparmor
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
class=${this._computeApparmorClassName}
|
|
||||||
id="apparmor"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.apparmor"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiShield}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.auth_api
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="auth_api"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.auth"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiKey}></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
${this.addon.ingress
|
|
||||||
? html`
|
|
||||||
<ha-label-badge
|
|
||||||
@click=${this._showMoreInfo}
|
|
||||||
id="ingress"
|
|
||||||
.label=${this.supervisor.localize(
|
|
||||||
"addon.dashboard.capability.label.ingress"
|
|
||||||
)}
|
|
||||||
description=""
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${mdiCursorDefaultClickOutline}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</ha-label-badge>
|
|
||||||
`
|
|
||||||
: ""}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
${this.addon.version
|
${this.addon.version
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
@ -1178,34 +1181,31 @@ class HassioAddonInfo extends LitElement {
|
|||||||
.description a {
|
.description a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
ha-chip {
|
||||||
|
text-transform: capitalize;
|
||||||
|
--ha-chip-text-color: var(--text-primary-color);
|
||||||
|
--ha-chip-background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
.red {
|
.red {
|
||||||
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
|
--ha-chip-background-color: var(--label-badge-red, #df4c1e);
|
||||||
}
|
}
|
||||||
.blue {
|
.blue {
|
||||||
--ha-label-badge-color: var(--label-badge-blue, #039be5);
|
--ha-chip-background-color: var(--label-badge-blue, #039be5);
|
||||||
}
|
}
|
||||||
.green {
|
.green {
|
||||||
--ha-label-badge-color: var(--label-badge-green, #0da035);
|
--ha-chip-background-color: var(--label-badge-green, #0da035);
|
||||||
}
|
}
|
||||||
.yellow {
|
.yellow {
|
||||||
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
|
--ha-chip-background-color: var(--label-badge-yellow, #f4b400);
|
||||||
}
|
}
|
||||||
.security {
|
.capabilities {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.card-actions {
|
.card-actions {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.security h3 {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
.security ha-label-badge {
|
|
||||||
cursor: pointer;
|
|
||||||
margin-right: 4px;
|
|
||||||
--ha-label-badge-padding: 8px 0 0 0;
|
|
||||||
}
|
|
||||||
.changelog {
|
.changelog {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
@ -1245,6 +1245,9 @@ class HassioAddonInfo extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
ha-chip {
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
.addon-options {
|
.addon-options {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,8 @@ import {
|
|||||||
} from "../../../src/components/data-table/ha-data-table";
|
} from "../../../src/components/data-table/ha-data-table";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-fab";
|
import "../../../src/components/ha-fab";
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
import "../../../src/components/ha-icon-button";
|
||||||
|
import "../../../src/components/ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
fetchHassioBackups,
|
fetchHassioBackups,
|
||||||
friendlyFolderName,
|
friendlyFolderName,
|
||||||
@ -31,6 +32,7 @@ import {
|
|||||||
reloadHassioBackups,
|
reloadHassioBackups,
|
||||||
removeBackup,
|
removeBackup,
|
||||||
} from "../../../src/data/hassio/backup";
|
} from "../../../src/data/hassio/backup";
|
||||||
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||||
import {
|
import {
|
||||||
showAlertDialog,
|
showAlertDialog,
|
||||||
@ -40,9 +42,9 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
|
|||||||
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
|
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
|
||||||
import { haStyle } from "../../../src/resources/styles";
|
import { haStyle } from "../../../src/resources/styles";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
|
|
||||||
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
|
|
||||||
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
|
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
|
||||||
|
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
|
||||||
|
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
|
||||||
import { supervisorTabs } from "../hassio-tabs";
|
import { supervisorTabs } from "../hassio-tabs";
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
@ -179,9 +181,11 @@ export class HassioBackups extends LitElement {
|
|||||||
slot="toolbar-icon"
|
slot="toolbar-icon"
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
>
|
>
|
||||||
<mwc-icon-button slot="trigger" alt="menu">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
.label=${this.hass.localize("common.menu")}
|
||||||
</mwc-icon-button>
|
.path=${mdiDotsVertical}
|
||||||
|
slot="trigger"
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-list-item>
|
<mwc-list-item>
|
||||||
${this.supervisor?.localize("common.reload")}
|
${this.supervisor?.localize("common.reload")}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
@ -216,13 +220,15 @@ export class HassioBackups extends LitElement {
|
|||||||
</mwc-button>
|
</mwc-button>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
|
.label=${this.supervisor.localize(
|
||||||
|
"snapshot.delete_selected"
|
||||||
|
)}
|
||||||
|
.path=${mdiDelete}
|
||||||
id="delete-btn"
|
id="delete-btn"
|
||||||
class="warning"
|
class="warning"
|
||||||
@click=${this._deleteSelected}
|
@click=${this._deleteSelected}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
<paper-tooltip animation-delay="0" for="delete-btn">
|
<paper-tooltip animation-delay="0" for="delete-btn">
|
||||||
${this.supervisor.localize("backup.delete_selected")}
|
${this.supervisor.localize("backup.delete_selected")}
|
||||||
</paper-tooltip>
|
</paper-tooltip>
|
||||||
@ -368,7 +374,7 @@ export class HassioBackups extends LitElement {
|
|||||||
margin-right: -12px;
|
margin-right: -12px;
|
||||||
}
|
}
|
||||||
.header-btns > mwc-button,
|
.header-btns > mwc-button,
|
||||||
.header-btns > mwc-icon-button {
|
.header-btns > ha-icon-button {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiFolderUpload } from "@mdi/js";
|
import { mdiFolderUpload } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input-container";
|
import "@polymer/paper-input/paper-input-container";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
@ -6,9 +5,8 @@ import { customElement, state } from "lit/decorators";
|
|||||||
import { fireEvent } from "../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../src/common/dom/fire_event";
|
||||||
import "../../../src/components/ha-circular-progress";
|
import "../../../src/components/ha-circular-progress";
|
||||||
import "../../../src/components/ha-file-upload";
|
import "../../../src/components/ha-file-upload";
|
||||||
import "../../../src/components/ha-svg-icon";
|
|
||||||
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
|
||||||
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
|
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
|
||||||
|
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
|
||||||
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
|
||||||
import { HomeAssistant } from "../../../src/types";
|
import { HomeAssistant } from "../../../src/types";
|
||||||
|
|
||||||
@ -31,6 +29,7 @@ export class HassioUploadBackup extends LitElement {
|
|||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<ha-file-upload
|
<ha-file-upload
|
||||||
|
.hass=${this.hass}
|
||||||
.uploading=${this._uploading}
|
.uploading=${this._uploading}
|
||||||
.icon=${mdiFolderUpload}
|
.icon=${mdiFolderUpload}
|
||||||
accept="application/x-tar"
|
accept="application/x-tar"
|
||||||
|
@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
import { fireEvent } from "../../../../src/common/dom/fire_event";
|
||||||
import "../../../../src/components/ha-header-bar";
|
import "../../../../src/components/ha-header-bar";
|
||||||
|
import "../../../../src/components/ha-icon-button";
|
||||||
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||||
import { haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyleDialog } from "../../../../src/resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../src/types";
|
import type { HomeAssistant } from "../../../../src/types";
|
||||||
@ -52,9 +53,12 @@ export class DialogHassioBackupUpload
|
|||||||
<div slot="heading">
|
<div slot="heading">
|
||||||
<ha-header-bar>
|
<ha-header-bar>
|
||||||
<span slot="title"> Upload backup </span>
|
<span slot="title"> Upload backup </span>
|
||||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
.label=${this.hass.localize("common.close")}
|
||||||
</mwc-icon-button>
|
.path=${mdiClose}
|
||||||
|
slot="actionItems"
|
||||||
|
dialogAction="cancel"
|
||||||
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
</div>
|
</div>
|
||||||
<hassio-upload-backup
|
<hassio-upload-backup
|
||||||
|
@ -9,7 +9,7 @@ import "../../../../src/components/buttons/ha-progress-button";
|
|||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-button-menu";
|
import "../../../../src/components/ha-button-menu";
|
||||||
import "../../../../src/components/ha-header-bar";
|
import "../../../../src/components/ha-header-bar";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-icon-button";
|
||||||
import { getSignedPath } from "../../../../src/data/auth";
|
import { getSignedPath } from "../../../../src/data/auth";
|
||||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
import {
|
import {
|
||||||
@ -76,9 +76,12 @@ class HassioBackupDialog
|
|||||||
<div slot="heading">
|
<div slot="heading">
|
||||||
<ha-header-bar>
|
<ha-header-bar>
|
||||||
<span slot="title">${this._backup.name}</span>
|
<span slot="title">${this._backup.name}</span>
|
||||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
.label=${this.hass.localize("common.close")}
|
||||||
</mwc-icon-button>
|
.path=${mdiClose}
|
||||||
|
slot="actionItems"
|
||||||
|
dialogAction="cancel"
|
||||||
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
</div>
|
</div>
|
||||||
${this._restoringBackup
|
${this._restoringBackup
|
||||||
@ -110,9 +113,11 @@ class HassioBackupDialog
|
|||||||
@action=${this._handleMenuAction}
|
@action=${this._handleMenuAction}
|
||||||
@closed=${stopPropagation}
|
@closed=${stopPropagation}
|
||||||
>
|
>
|
||||||
<mwc-icon-button slot="trigger" alt="menu">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
.label=${this.hass.localize("common.menu")}
|
||||||
</mwc-icon-button>
|
.path=${mdiDotsVertical}
|
||||||
|
slot="trigger"
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-list-item>Download Backup</mwc-list-item>
|
<mwc-list-item>Download Backup</mwc-list-item>
|
||||||
<mwc-list-item class="error">Delete Backup</mwc-list-item>
|
<mwc-list-item class="error">Delete Backup</mwc-list-item>
|
||||||
</ha-button-menu>`
|
</ha-button-menu>`
|
||||||
@ -126,9 +131,6 @@ class HassioBackupDialog
|
|||||||
haStyle,
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-svg-icon {
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
}
|
|
||||||
ha-circular-progress {
|
ha-circular-progress {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -139,6 +141,9 @@ class HassioBackupDialog
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import "../../../../src/common/search/search-input";
|
|||||||
import { stringCompare } from "../../../../src/common/string/compare";
|
import { stringCompare } from "../../../../src/common/string/compare";
|
||||||
import "../../../../src/components/ha-dialog";
|
import "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-expansion-panel";
|
import "../../../../src/components/ha-expansion-panel";
|
||||||
|
import "../../../../src/components/ha-icon-button";
|
||||||
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
|
||||||
import { dump } from "../../../../src/resources/js-yaml-dump";
|
import { dump } from "../../../../src/resources/js-yaml-dump";
|
||||||
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
|
||||||
@ -70,10 +71,13 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
<h2>
|
<h2>
|
||||||
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
|
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
|
||||||
</h2>
|
</h2>
|
||||||
<mwc-icon-button dialogAction="close">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
.label=${this.hass.localize("common.close")}
|
||||||
</mwc-icon-button>
|
.path=${mdiClose}
|
||||||
|
dialogAction="close"
|
||||||
|
></ha-icon-button>
|
||||||
<search-input
|
<search-input
|
||||||
|
.hass=${this.hass}
|
||||||
autofocus
|
autofocus
|
||||||
no-label-float
|
no-label-float
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@ -141,7 +145,7 @@ class HassioHardwareDialog extends LitElement {
|
|||||||
haStyle,
|
haStyle,
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
|
@ -47,11 +47,6 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
hassioStyle,
|
hassioStyle,
|
||||||
css`
|
css`
|
||||||
ha-paper-dialog {
|
|
||||||
min-width: 350px;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
app-toolbar {
|
app-toolbar {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
@ -62,19 +57,6 @@ class HassioMarkdownDialog extends LitElement {
|
|||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||||
ha-paper-dialog {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
ha-paper-dialog::before {
|
|
||||||
content: "";
|
|
||||||
position: fixed;
|
|
||||||
z-index: -1;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
background-color: inherit;
|
|
||||||
}
|
|
||||||
app-toolbar {
|
app-toolbar {
|
||||||
color: var(--text-primary-color);
|
color: var(--text-primary-color);
|
||||||
background-color: var(--primary-color);
|
background-color: var(--primary-color);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-icon-button";
|
|
||||||
import "@material/mwc-list/mwc-list";
|
import "@material/mwc-list/mwc-list";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@material/mwc-tab";
|
import "@material/mwc-tab";
|
||||||
@ -16,9 +15,9 @@ import "../../../../src/components/ha-dialog";
|
|||||||
import "../../../../src/components/ha-expansion-panel";
|
import "../../../../src/components/ha-expansion-panel";
|
||||||
import "../../../../src/components/ha-formfield";
|
import "../../../../src/components/ha-formfield";
|
||||||
import "../../../../src/components/ha-header-bar";
|
import "../../../../src/components/ha-header-bar";
|
||||||
|
import "../../../../src/components/ha-icon-button";
|
||||||
import "../../../../src/components/ha-radio";
|
import "../../../../src/components/ha-radio";
|
||||||
import "../../../../src/components/ha-related-items";
|
import "../../../../src/components/ha-related-items";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
|
||||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
import {
|
import {
|
||||||
AccessPoints,
|
AccessPoints,
|
||||||
@ -104,9 +103,12 @@ export class DialogHassioNetwork
|
|||||||
<span slot="title">
|
<span slot="title">
|
||||||
${this.supervisor.localize("dialog.network.title")}
|
${this.supervisor.localize("dialog.network.title")}
|
||||||
</span>
|
</span>
|
||||||
<mwc-icon-button slot="actionItems" dialogAction="cancel">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
.label=${this.hass.localize("common.close")}
|
||||||
</mwc-icon-button>
|
.path=${mdiClose}
|
||||||
|
slot="actionItems"
|
||||||
|
dialogAction="cancel"
|
||||||
|
></ha-icon-button>
|
||||||
</ha-header-bar>
|
</ha-header-bar>
|
||||||
${this._interfaces.length > 1
|
${this._interfaces.length > 1
|
||||||
? html`<mwc-tab-bar
|
? html`<mwc-tab-bar
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiDelete } from "@mdi/js";
|
import { mdiDelete } from "@mdi/js";
|
||||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
@ -7,7 +6,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-icon-button";
|
||||||
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
|
||||||
import {
|
import {
|
||||||
addHassioDockerRegistry,
|
addHassioDockerRegistry,
|
||||||
@ -110,16 +109,15 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
)}:
|
)}:
|
||||||
${entry.username}</span
|
${entry.username}</span
|
||||||
>
|
>
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.entry=${entry}
|
.entry=${entry}
|
||||||
.title=${this.supervisor.localize(
|
.label=${this.supervisor.localize(
|
||||||
"dialog.registries.remove"
|
"dialog.registries.remove"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiDelete}
|
||||||
slot="meta"
|
slot="meta"
|
||||||
@click=${this._removeRegistry}
|
@click=${this._removeRegistry}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@ -234,7 +232,7 @@ class HassioRegistriesDialog extends LitElement {
|
|||||||
mwc-button {
|
mwc-button {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
color: var(--error-color);
|
color: var(--error-color);
|
||||||
margin: -10px;
|
margin: -10px;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiDelete } from "@mdi/js";
|
import { mdiDelete } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
@ -13,7 +12,7 @@ import { caseInsensitiveStringCompare } from "../../../../src/common/string/comp
|
|||||||
import "../../../../src/components/ha-alert";
|
import "../../../../src/components/ha-alert";
|
||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
import { createCloseHeading } from "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
import "../../../../src/components/ha-icon-button";
|
||||||
import {
|
import {
|
||||||
fetchHassioAddonsInfo,
|
fetchHassioAddonsInfo,
|
||||||
HassioAddonRepository,
|
HassioAddonRepository,
|
||||||
@ -90,15 +89,14 @@ class HassioRepositoriesDialog extends LitElement {
|
|||||||
<div secondary>${repo.maintainer}</div>
|
<div secondary>${repo.maintainer}</div>
|
||||||
<div secondary>${repo.url}</div>
|
<div secondary>${repo.url}</div>
|
||||||
</paper-item-body>
|
</paper-item-body>
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.slug=${repo.slug}
|
.slug=${repo.slug}
|
||||||
.title=${this._dialogParams!.supervisor.localize(
|
.label=${this._dialogParams!.supervisor.localize(
|
||||||
"dialog.repositories.remove"
|
"dialog.repositories.remove"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiDelete}
|
||||||
@click=${this._removeRepository}
|
@click=${this._removeRepository}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</paper-item>
|
</paper-item>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,6 @@ import "../../../../src/components/ha-alert";
|
|||||||
import "../../../../src/components/ha-circular-progress";
|
import "../../../../src/components/ha-circular-progress";
|
||||||
import "../../../../src/components/ha-dialog";
|
import "../../../../src/components/ha-dialog";
|
||||||
import "../../../../src/components/ha-settings-row";
|
import "../../../../src/components/ha-settings-row";
|
||||||
import "../../../../src/components/ha-svg-icon";
|
|
||||||
import "../../../../src/components/ha-switch";
|
import "../../../../src/components/ha-switch";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
|
@ -113,12 +113,6 @@ export class HassioMain extends SupervisorBaseElement {
|
|||||||
: this.hass.themes.default_theme);
|
: this.hass.themes.default_theme);
|
||||||
|
|
||||||
themeSettings = this.hass.selectedTheme;
|
themeSettings = this.hass.selectedTheme;
|
||||||
if (themeSettings?.dark === undefined) {
|
|
||||||
themeSettings = {
|
|
||||||
...this.hass.selectedTheme,
|
|
||||||
dark: this.hass.themes.darkMode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
themeName =
|
themeName =
|
||||||
(this.hass.selectedTheme as unknown as string) ||
|
(this.hass.selectedTheme as unknown as string) ||
|
||||||
|
@ -12,6 +12,7 @@ import { fireEvent } from "../../../src/common/dom/fire_event";
|
|||||||
import { navigate } from "../../../src/common/navigate";
|
import { navigate } from "../../../src/common/navigate";
|
||||||
import { extractSearchParam } from "../../../src/common/url/search-params";
|
import { extractSearchParam } from "../../../src/common/url/search-params";
|
||||||
import { nextRender } from "../../../src/common/util/render-status";
|
import { nextRender } from "../../../src/common/util/render-status";
|
||||||
|
import "../../../src/components/ha-icon-button";
|
||||||
import {
|
import {
|
||||||
fetchHassioAddonInfo,
|
fetchHassioAddonInfo,
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
@ -72,12 +73,11 @@ class HassioIngressView extends LitElement {
|
|||||||
|
|
||||||
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
|
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
|
||||||
? html`<div class="header">
|
? html`<div class="header">
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
.label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
|
||||||
|
.path=${mdiMenu}
|
||||||
@click=${this._toggleMenu}
|
@click=${this._toggleMenu}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiMenu}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
<div class="main-title">${this._addon.name}</div>
|
<div class="main-title">${this._addon.name}</div>
|
||||||
</div>
|
</div>
|
||||||
${iframe}`
|
${iframe}`
|
||||||
@ -241,7 +241,7 @@ class HassioIngressView extends LitElement {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { fireEvent } from "../../../src/common/dom/fire_event";
|
|||||||
import "../../../src/components/buttons/ha-progress-button";
|
import "../../../src/components/buttons/ha-progress-button";
|
||||||
import "../../../src/components/ha-button-menu";
|
import "../../../src/components/ha-button-menu";
|
||||||
import "../../../src/components/ha-card";
|
import "../../../src/components/ha-card";
|
||||||
|
import "../../../src/components/ha-icon-button";
|
||||||
import "../../../src/components/ha-settings-row";
|
import "../../../src/components/ha-settings-row";
|
||||||
import {
|
import {
|
||||||
extractApiErrorMessage,
|
extractApiErrorMessage,
|
||||||
@ -181,9 +182,11 @@ class HassioHostInfo extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<ha-button-menu corner="BOTTOM_START">
|
<ha-button-menu corner="BOTTOM_START">
|
||||||
<mwc-icon-button slot="trigger">
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
|
.label=${this.hass.localize("common.menu")}
|
||||||
</mwc-icon-button>
|
.path=${mdiDotsVertical}
|
||||||
|
slot="trigger"
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-list-item
|
<mwc-list-item
|
||||||
.action=${"hardware"}
|
.action=${"hardware"}
|
||||||
@click=${this._handleMenuAction}
|
@click=${this._handleMenuAction}
|
||||||
|
@ -31,29 +31,9 @@ import { documentationUrl } from "../../../src/util/documentation-url";
|
|||||||
import "../components/supervisor-metric";
|
import "../components/supervisor-metric";
|
||||||
import { hassioStyle } from "../resources/hassio-style";
|
import { hassioStyle } from "../resources/hassio-style";
|
||||||
|
|
||||||
const UNSUPPORTED_REASON_URL = {
|
const UNSUPPORTED_REASON_URL = {};
|
||||||
apparmor: "/more-info/unsupported/apparmor",
|
|
||||||
container: "/more-info/unsupported/container",
|
|
||||||
content_trust: "/more-info/unsupported/content_trust",
|
|
||||||
dbus: "/more-info/unsupported/dbus",
|
|
||||||
docker_configuration: "/more-info/unsupported/docker_configuration",
|
|
||||||
docker_version: "/more-info/unsupported/docker_version",
|
|
||||||
job_conditions: "/more-info/unsupported/job_conditions",
|
|
||||||
lxc: "/more-info/unsupported/lxc",
|
|
||||||
network_manager: "/more-info/unsupported/network_manager",
|
|
||||||
os_agent: "/more-info/unsupported/os_agent",
|
|
||||||
os: "/more-info/unsupported/os",
|
|
||||||
privileged: "/more-info/unsupported/privileged",
|
|
||||||
source_mods: "/more-info/unsupported/source_mods",
|
|
||||||
systemd: "/more-info/unsupported/systemd",
|
|
||||||
};
|
|
||||||
|
|
||||||
const UNHEALTHY_REASON_URL = {
|
const UNHEALTHY_REASON_URL = {
|
||||||
privileged: "/more-info/unsupported/privileged",
|
privileged: "/more-info/unsupported/privileged",
|
||||||
supervisor: "/more-info/unhealthy/supervisor",
|
|
||||||
setup: "/more-info/unhealthy/setup",
|
|
||||||
docker: "/more-info/unhealthy/docker",
|
|
||||||
untrusted: "/more-info/unhealthy/untrusted",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("hassio-supervisor-info")
|
@customElement("hassio-supervisor-info")
|
||||||
@ -425,11 +405,11 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
${this.supervisor.resolution.unsupported.map(
|
${this.supervisor.resolution.unsupported.map(
|
||||||
(reason) => html`
|
(reason) => html`
|
||||||
<li>
|
<li>
|
||||||
${UNSUPPORTED_REASON_URL[reason]
|
<a
|
||||||
? html`<a
|
|
||||||
href=${documentationUrl(
|
href=${documentationUrl(
|
||||||
this.hass,
|
this.hass,
|
||||||
UNSUPPORTED_REASON_URL[reason]
|
UNSUPPORTED_REASON_URL[reason] ||
|
||||||
|
`/more-info/unsupported/${reason}`
|
||||||
)}
|
)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
@ -437,8 +417,7 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
`system.supervisor.unsupported_reason.${reason}`
|
`system.supervisor.unsupported_reason.${reason}`
|
||||||
) || reason}
|
) || reason}
|
||||||
</a>`
|
</a>
|
||||||
: reason}
|
|
||||||
</li>
|
</li>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
@ -456,11 +435,11 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
${this.supervisor.resolution.unhealthy.map(
|
${this.supervisor.resolution.unhealthy.map(
|
||||||
(reason) => html`
|
(reason) => html`
|
||||||
<li>
|
<li>
|
||||||
${UNHEALTHY_REASON_URL[reason]
|
<a
|
||||||
? html`<a
|
|
||||||
href=${documentationUrl(
|
href=${documentationUrl(
|
||||||
this.hass,
|
this.hass,
|
||||||
UNHEALTHY_REASON_URL[reason]
|
UNHEALTHY_REASON_URL[reason] ||
|
||||||
|
`/more-info/unhealthy/${reason}`
|
||||||
)}
|
)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
@ -468,8 +447,7 @@ class HassioSupervisorInfo extends LitElement {
|
|||||||
${this.supervisor.localize(
|
${this.supervisor.localize(
|
||||||
`system.supervisor.unhealthy_reason.${reason}`
|
`system.supervisor.unhealthy_reason.${reason}`
|
||||||
) || reason}
|
) || reason}
|
||||||
</a>`
|
</a>
|
||||||
: reason}
|
|
||||||
</li>
|
</li>
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
|
95
package.json
95
package.json
@ -22,23 +22,23 @@
|
|||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^5.0.2",
|
"@braintree/sanitize-url": "^5.0.2",
|
||||||
"@codemirror/commands": "^0.19.2",
|
"@codemirror/commands": "^0.19.5",
|
||||||
"@codemirror/gutter": "^0.19.1",
|
"@codemirror/gutter": "^0.19.3",
|
||||||
"@codemirror/highlight": "^0.19.2",
|
"@codemirror/highlight": "^0.19.6",
|
||||||
"@codemirror/history": "^0.19.0",
|
"@codemirror/history": "^0.19.0",
|
||||||
"@codemirror/legacy-modes": "^0.19.0",
|
"@codemirror/legacy-modes": "^0.19.0",
|
||||||
"@codemirror/rectangular-selection": "^0.19.0",
|
"@codemirror/rectangular-selection": "^0.19.1",
|
||||||
"@codemirror/search": "^0.19.0",
|
"@codemirror/search": "^0.19.2",
|
||||||
"@codemirror/state": "^0.19.1",
|
"@codemirror/state": "^0.19.2",
|
||||||
"@codemirror/stream-parser": "^0.19.1",
|
"@codemirror/stream-parser": "^0.19.2",
|
||||||
"@codemirror/text": "^0.19.2",
|
"@codemirror/text": "^0.19.4",
|
||||||
"@codemirror/view": "^0.19.4",
|
"@codemirror/view": "^0.19.9",
|
||||||
"@formatjs/intl-datetimeformat": "^4.2.4",
|
"@formatjs/intl-datetimeformat": "^4.2.5",
|
||||||
"@formatjs/intl-getcanonicallocales": "^1.7.3",
|
"@formatjs/intl-getcanonicallocales": "^1.8.0",
|
||||||
"@formatjs/intl-locale": "^2.4.38",
|
"@formatjs/intl-locale": "^2.4.40",
|
||||||
"@formatjs/intl-numberformat": "^7.2.4",
|
"@formatjs/intl-numberformat": "^7.2.5",
|
||||||
"@formatjs/intl-pluralrules": "^4.1.4",
|
"@formatjs/intl-pluralrules": "^4.1.5",
|
||||||
"@formatjs/intl-relativetimeformat": "^9.3.1",
|
"@formatjs/intl-relativetimeformat": "^9.3.2",
|
||||||
"@formatjs/intl-utils": "^3.8.4",
|
"@formatjs/intl-utils": "^3.8.4",
|
||||||
"@fullcalendar/common": "5.9.0",
|
"@fullcalendar/common": "5.9.0",
|
||||||
"@fullcalendar/core": "5.9.0",
|
"@fullcalendar/core": "5.9.0",
|
||||||
@ -46,45 +46,38 @@
|
|||||||
"@fullcalendar/interaction": "5.9.0",
|
"@fullcalendar/interaction": "5.9.0",
|
||||||
"@fullcalendar/list": "5.9.0",
|
"@fullcalendar/list": "5.9.0",
|
||||||
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
|
||||||
"@material/chips": "13.0.0-canary.65125b3a6.0",
|
"@material/chips": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/data-table": "13.0.0-canary.65125b3a6.0",
|
"@material/data-table": "14.0.0-canary.261f2db59.0",
|
||||||
"@material/mwc-button": "0.25.1",
|
"@material/mwc-button": "0.25.3",
|
||||||
"@material/mwc-checkbox": "0.25.1",
|
"@material/mwc-checkbox": "0.25.3",
|
||||||
"@material/mwc-circular-progress": "0.25.1",
|
"@material/mwc-circular-progress": "0.25.3",
|
||||||
"@material/mwc-dialog": "0.25.1",
|
"@material/mwc-dialog": "0.25.3",
|
||||||
"@material/mwc-fab": "0.25.1",
|
"@material/mwc-fab": "0.25.3",
|
||||||
"@material/mwc-formfield": "0.25.1",
|
"@material/mwc-formfield": "0.25.3",
|
||||||
"@material/mwc-icon-button": "0.25.1",
|
"@material/mwc-icon-button": "patch:@material/mwc-icon-button@0.25.3#./.yarn/patches/@material/mwc-icon-button/remove-icon.patch",
|
||||||
"@material/mwc-linear-progress": "0.25.1",
|
"@material/mwc-linear-progress": "0.25.3",
|
||||||
"@material/mwc-list": "0.25.1",
|
"@material/mwc-list": "0.25.3",
|
||||||
"@material/mwc-menu": "0.25.1",
|
"@material/mwc-menu": "0.25.3",
|
||||||
"@material/mwc-radio": "0.25.1",
|
"@material/mwc-radio": "0.25.3",
|
||||||
"@material/mwc-ripple": "0.25.1",
|
"@material/mwc-ripple": "0.25.3",
|
||||||
"@material/mwc-switch": "0.25.1",
|
"@material/mwc-select": "0.25.3",
|
||||||
"@material/mwc-tab": "0.25.1",
|
"@material/mwc-slider": "0.25.3",
|
||||||
"@material/mwc-tab-bar": "0.25.1",
|
"@material/mwc-switch": "0.25.3",
|
||||||
"@material/top-app-bar": "13.0.0-canary.65125b3a6.0",
|
"@material/mwc-tab": "0.25.3",
|
||||||
"@mdi/js": "6.2.95",
|
"@material/mwc-tab-bar": "0.25.3",
|
||||||
"@mdi/svg": "6.2.95",
|
"@material/mwc-textfield": "0.25.3",
|
||||||
|
"@material/top-app-bar": "14.0.0-canary.261f2db59.0",
|
||||||
|
"@mdi/js": "6.4.95",
|
||||||
|
"@mdi/svg": "6.4.95",
|
||||||
"@polymer/app-layout": "^3.1.0",
|
"@polymer/app-layout": "^3.1.0",
|
||||||
"@polymer/iron-flex-layout": "^3.0.1",
|
"@polymer/iron-flex-layout": "^3.0.1",
|
||||||
"@polymer/iron-icon": "^3.0.1",
|
"@polymer/iron-icon": "^3.0.1",
|
||||||
"@polymer/iron-input": "^3.0.1",
|
"@polymer/iron-input": "^3.0.1",
|
||||||
"@polymer/iron-overlay-behavior": "^3.0.3",
|
|
||||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||||
"@polymer/paper-checkbox": "^3.1.0",
|
|
||||||
"@polymer/paper-dialog": "^3.0.1",
|
|
||||||
"@polymer/paper-dialog-behavior": "^3.0.1",
|
|
||||||
"@polymer/paper-dialog-scrollable": "^3.0.1",
|
|
||||||
"@polymer/paper-dropdown-menu": "^3.2.0",
|
"@polymer/paper-dropdown-menu": "^3.2.0",
|
||||||
"@polymer/paper-input": "^3.2.1",
|
"@polymer/paper-input": "^3.2.1",
|
||||||
"@polymer/paper-item": "^3.0.1",
|
"@polymer/paper-item": "^3.0.1",
|
||||||
"@polymer/paper-listbox": "^3.0.1",
|
"@polymer/paper-listbox": "^3.0.1",
|
||||||
"@polymer/paper-menu-button": "^3.1.0",
|
|
||||||
"@polymer/paper-progress": "^3.0.1",
|
|
||||||
"@polymer/paper-radio-button": "^3.0.1",
|
|
||||||
"@polymer/paper-radio-group": "^3.0.1",
|
|
||||||
"@polymer/paper-ripple": "^3.0.2",
|
|
||||||
"@polymer/paper-slider": "^3.0.1",
|
"@polymer/paper-slider": "^3.0.1",
|
||||||
"@polymer/paper-styles": "^3.0.1",
|
"@polymer/paper-styles": "^3.0.1",
|
||||||
"@polymer/paper-tabs": "^3.1.0",
|
"@polymer/paper-tabs": "^3.1.0",
|
||||||
@ -115,7 +108,7 @@
|
|||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"leaflet": "^1.7.1",
|
"leaflet": "^1.7.1",
|
||||||
"leaflet-draw": "^1.0.4",
|
"leaflet-draw": "^1.0.4",
|
||||||
"lit": "^2.0.0",
|
"lit": "^2.0.2",
|
||||||
"lit-vaadin-helpers": "^0.2.1",
|
"lit-vaadin-helpers": "^0.2.1",
|
||||||
"marked": "^3.0.2",
|
"marked": "^3.0.2",
|
||||||
"memoize-one": "^5.2.1",
|
"memoize-one": "^5.2.1",
|
||||||
@ -187,7 +180,7 @@
|
|||||||
"eslint-import-resolver-webpack": "^0.13.1",
|
"eslint-import-resolver-webpack": "^0.13.1",
|
||||||
"eslint-plugin-disable": "^2.0.1",
|
"eslint-plugin-disable": "^2.0.1",
|
||||||
"eslint-plugin-import": "^2.24.2",
|
"eslint-plugin-import": "^2.24.2",
|
||||||
"eslint-plugin-lit": "^1.5.1",
|
"eslint-plugin-lit": "^1.6.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-unused-imports": "^1.1.5",
|
"eslint-plugin-unused-imports": "^1.1.5",
|
||||||
"eslint-plugin-wc": "^1.3.2",
|
"eslint-plugin-wc": "^1.3.2",
|
||||||
@ -237,10 +230,10 @@
|
|||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||||
"lit": "^2.0.0",
|
"lit": "^2.0.2",
|
||||||
"lit-html": "2.0.0",
|
"lit-html": "2.0.1",
|
||||||
"lit-element": "3.0.0",
|
"lit-element": "3.0.1",
|
||||||
"@lit/reactive-element": "1.0.0"
|
"@lit/reactive-element": "1.0.1"
|
||||||
},
|
},
|
||||||
"main": "src/home-assistant.js",
|
"main": "src/home-assistant.js",
|
||||||
"husky": {
|
"husky": {
|
||||||
|
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="20211007.1",
|
version="20211026.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/frontend",
|
url="https://github.com/home-assistant/frontend",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
@ -7,16 +7,20 @@ import {
|
|||||||
PropertyValues,
|
PropertyValues,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import "./ha-password-manager-polyfill";
|
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
|
import "../components/ha-checkbox";
|
||||||
import "../components/ha-form/ha-form";
|
import "../components/ha-form/ha-form";
|
||||||
|
import "../components/ha-formfield";
|
||||||
import "../components/ha-markdown";
|
import "../components/ha-markdown";
|
||||||
|
import "../components/ha-alert";
|
||||||
import { AuthProvider } from "../data/auth";
|
import { AuthProvider } from "../data/auth";
|
||||||
import {
|
import {
|
||||||
DataEntryFlowStep,
|
DataEntryFlowStep,
|
||||||
DataEntryFlowStepForm,
|
DataEntryFlowStepForm,
|
||||||
} from "../data/data_entry_flow";
|
} from "../data/data_entry_flow";
|
||||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||||
|
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||||
|
import "./ha-password-manager-polyfill";
|
||||||
|
|
||||||
type State = "loading" | "error" | "step";
|
type State = "loading" | "error" | "step";
|
||||||
|
|
||||||
@ -31,12 +35,44 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _state: State = "loading";
|
@state() private _state: State = "loading";
|
||||||
|
|
||||||
@state() private _stepData: any = {};
|
@state() private _stepData?: Record<string, any>;
|
||||||
|
|
||||||
@state() private _step?: DataEntryFlowStep;
|
@state() private _step?: DataEntryFlowStep;
|
||||||
|
|
||||||
@state() private _errorMessage?: string;
|
@state() private _errorMessage?: string;
|
||||||
|
|
||||||
|
@state() private _submitting = false;
|
||||||
|
|
||||||
|
@state() private _storeToken = false;
|
||||||
|
|
||||||
|
willUpdate(changedProps: PropertyValues) {
|
||||||
|
super.willUpdate(changedProps);
|
||||||
|
|
||||||
|
if (!changedProps.has("_step")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._step) {
|
||||||
|
this._stepData = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldStep = changedProps.get("_step") as HaAuthFlow["_step"];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!oldStep ||
|
||||||
|
this._step.flow_id !== oldStep.flow_id ||
|
||||||
|
(this._step.type === "form" &&
|
||||||
|
oldStep.type === "form" &&
|
||||||
|
this._step.step_id !== oldStep.step_id)
|
||||||
|
) {
|
||||||
|
this._stepData =
|
||||||
|
this._step.type === "form"
|
||||||
|
? computeInitialHaFormData(this._step.data_schema)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<form>${this._renderForm()}</form>
|
<form>${this._renderForm()}</form>
|
||||||
@ -76,6 +112,24 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
if (changedProps.has("authProvider")) {
|
if (changedProps.has("authProvider")) {
|
||||||
this._providerChanged(this.authProvider);
|
this._providerChanged(this.authProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!changedProps.has("_step") || this._step?.type !== "form") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 100ms to give all the form elements time to initialize.
|
||||||
|
setTimeout(() => {
|
||||||
|
const form = this.renderRoot.querySelector("ha-form");
|
||||||
|
if (form) {
|
||||||
|
(form as any).focus();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.renderRoot.querySelector(
|
||||||
|
"ha-password-manager-polyfill"
|
||||||
|
)!.boundingRect = this.getBoundingClientRect();
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderForm(): TemplateResult {
|
private _renderForm(): TemplateResult {
|
||||||
@ -87,27 +141,33 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
return html`
|
return html`
|
||||||
${this._renderStep(this._step)}
|
${this._renderStep(this._step)}
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<mwc-button raised @click=${this._handleSubmit}
|
<mwc-button
|
||||||
>${this._step.type === "form"
|
raised
|
||||||
? this.localize("ui.panel.page-authorize.form.next")
|
@click=${this._handleSubmit}
|
||||||
: this.localize(
|
.disabled=${this._submitting}
|
||||||
"ui.panel.page-authorize.form.start_over"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
>
|
||||||
|
${this._step.type === "form"
|
||||||
|
? this.localize("ui.panel.page-authorize.form.next")
|
||||||
|
: this.localize("ui.panel.page-authorize.form.start_over")}
|
||||||
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
case "error":
|
case "error":
|
||||||
return html`
|
return html`
|
||||||
<div class="error">
|
<ha-alert alert-type="error">
|
||||||
${this.localize(
|
${this.localize(
|
||||||
"ui.panel.page-authorize.form.error",
|
"ui.panel.page-authorize.form.error",
|
||||||
"error",
|
"error",
|
||||||
this._errorMessage
|
this._errorMessage
|
||||||
)}
|
)}
|
||||||
</div>
|
</ha-alert>
|
||||||
`;
|
`;
|
||||||
case "loading":
|
case "loading":
|
||||||
return html` ${this.localize("ui.panel.page-authorize.form.working")} `;
|
return html`
|
||||||
|
<ha-alert alert-type="info">
|
||||||
|
${this.localize("ui.panel.page-authorize.form.working")}
|
||||||
|
</ha-alert>
|
||||||
|
`;
|
||||||
default:
|
default:
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
@ -140,16 +200,34 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
.data=${this._stepData}
|
.data=${this._stepData}
|
||||||
.schema=${step.data_schema}
|
.schema=${step.data_schema}
|
||||||
.error=${step.errors}
|
.error=${step.errors}
|
||||||
|
.disabled=${this._submitting}
|
||||||
.computeLabel=${this._computeLabelCallback(step)}
|
.computeLabel=${this._computeLabelCallback(step)}
|
||||||
.computeError=${this._computeErrorCallback(step)}
|
.computeError=${this._computeErrorCallback(step)}
|
||||||
@value-changed=${this._stepDataChanged}
|
@value-changed=${this._stepDataChanged}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
|
${this.clientId === window.location.origin && step.step_id !== "mfa"
|
||||||
|
? html`
|
||||||
|
<ha-formfield
|
||||||
|
class="store-token"
|
||||||
|
.label=${this.localize("ui.panel.page-authorize.store_token")}
|
||||||
|
>
|
||||||
|
<ha-checkbox
|
||||||
|
.checked=${this._storeToken}
|
||||||
|
@change=${this._storeTokenChanged}
|
||||||
|
></ha-checkbox>
|
||||||
|
</ha-formfield>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
`;
|
`;
|
||||||
default:
|
default:
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _storeTokenChanged(e: CustomEvent<HTMLInputElement>) {
|
||||||
|
this._storeToken = (e.currentTarget as HTMLInputElement).checked;
|
||||||
|
}
|
||||||
|
|
||||||
private async _providerChanged(newProvider?: AuthProvider) {
|
private async _providerChanged(newProvider?: AuthProvider) {
|
||||||
if (this._step && this._step.type === "form") {
|
if (this._step && this._step.type === "form") {
|
||||||
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||||
@ -189,7 +267,8 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._updateStep(data);
|
this._step = data;
|
||||||
|
this._state = "step";
|
||||||
} else {
|
} else {
|
||||||
this._state = "error";
|
this._state = "error";
|
||||||
this._errorMessage = data.message;
|
this._errorMessage = data.message;
|
||||||
@ -216,43 +295,13 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
if (this.oauth2State) {
|
if (this.oauth2State) {
|
||||||
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
url += `&state=${encodeURIComponent(this.oauth2State)}`;
|
||||||
}
|
}
|
||||||
|
if (this._storeToken) {
|
||||||
|
url += `&storeToken=true`;
|
||||||
|
}
|
||||||
|
|
||||||
document.location.assign(url);
|
document.location.assign(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _updateStep(step: DataEntryFlowStep) {
|
|
||||||
let stepData: any = null;
|
|
||||||
if (
|
|
||||||
this._step &&
|
|
||||||
(step.flow_id !== this._step.flow_id ||
|
|
||||||
(step.type === "form" &&
|
|
||||||
this._step.type === "form" &&
|
|
||||||
step.step_id !== this._step.step_id))
|
|
||||||
) {
|
|
||||||
stepData = {};
|
|
||||||
}
|
|
||||||
this._step = step;
|
|
||||||
this._state = "step";
|
|
||||||
if (stepData != null) {
|
|
||||||
this._stepData = stepData;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.updateComplete;
|
|
||||||
// 100ms to give all the form elements time to initialize.
|
|
||||||
setTimeout(() => {
|
|
||||||
const form = this.renderRoot.querySelector("ha-form");
|
|
||||||
if (form) {
|
|
||||||
(form as any).focus();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.renderRoot.querySelector(
|
|
||||||
"ha-password-manager-polyfill"
|
|
||||||
)!.boundingRect = this.getBoundingClientRect();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _stepDataChanged(ev: CustomEvent) {
|
private _stepDataChanged(ev: CustomEvent) {
|
||||||
this._stepData = ev.detail.value;
|
this._stepData = ev.detail.value;
|
||||||
}
|
}
|
||||||
@ -297,9 +346,7 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
this._providerChanged(this.authProvider);
|
this._providerChanged(this.authProvider);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._state = "loading";
|
this._submitting = true;
|
||||||
// To avoid a jumping UI.
|
|
||||||
this.style.setProperty("min-height", `${this.offsetHeight}px`);
|
|
||||||
|
|
||||||
const postData = { ...this._stepData, client_id: this.clientId };
|
const postData = { ...this._stepData, client_id: this.clientId };
|
||||||
|
|
||||||
@ -316,29 +363,28 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
|
|||||||
this._redirect(newStep.result);
|
this._redirect(newStep.result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this._updateStep(newStep);
|
this._step = newStep;
|
||||||
|
this._state = "step";
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error("Error submitting step", err);
|
console.error("Error submitting step", err);
|
||||||
this._state = "error";
|
this._state = "error";
|
||||||
this._errorMessage = this._unknownError();
|
this._errorMessage = this._unknownError();
|
||||||
} finally {
|
} finally {
|
||||||
this.style.setProperty("min-height", "");
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
|
||||||
/* So we can set min-height to avoid jumping during loading */
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.action {
|
.action {
|
||||||
margin: 24px 0 8px;
|
margin: 24px 0 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.error {
|
/* Align with the rest of the form. */
|
||||||
color: red;
|
.store-token {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: -16px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,10 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
|||||||
display: block;
|
display: block;
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
}
|
}
|
||||||
|
ha-auth-flow {
|
||||||
|
display: block;
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { HaFormSchema } from "../components/ha-form/ha-form";
|
import type { HaFormSchema } from "../components/ha-form/types";
|
||||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
import type { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@ -30,6 +30,14 @@ export function askWrite() {
|
|||||||
|
|
||||||
export function saveTokens(tokens: AuthData | null) {
|
export function saveTokens(tokens: AuthData | null) {
|
||||||
tokenCache.tokens = tokens;
|
tokenCache.tokens = tokens;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!tokenCache.writeEnabled &&
|
||||||
|
new URLSearchParams(window.location.search).get("storeToken") === "true"
|
||||||
|
) {
|
||||||
|
tokenCache.writeEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (tokenCache.writeEnabled) {
|
if (tokenCache.writeEnabled) {
|
||||||
try {
|
try {
|
||||||
storage.hassTokens = JSON.stringify(tokens);
|
storage.hassTokens = JSON.stringify(tokens);
|
||||||
@ -45,7 +53,6 @@ export function enableWrite() {
|
|||||||
saveTokens(tokenCache.tokens);
|
saveTokens(tokenCache.tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadTokens() {
|
export function loadTokens() {
|
||||||
if (tokenCache.tokens === undefined) {
|
if (tokenCache.tokens === undefined) {
|
||||||
try {
|
try {
|
||||||
|
@ -4,6 +4,10 @@ export const atLeastVersion = (
|
|||||||
minor: number,
|
minor: number,
|
||||||
patch?: number
|
patch?: number
|
||||||
): boolean => {
|
): boolean => {
|
||||||
|
if (__DEMO__) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const [haMajor, haMinor, haPatch] = version.split(".", 3);
|
const [haMajor, haMinor, haPatch] = version.split(".", 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,88 +1,146 @@
|
|||||||
/** Constants to be used in the frontend. */
|
/** Constants to be used in the frontend. */
|
||||||
|
|
||||||
|
import {
|
||||||
|
mdiAccount,
|
||||||
|
mdiAirFilter,
|
||||||
|
mdiAlert,
|
||||||
|
mdiAngleAcute,
|
||||||
|
mdiAppleSafari,
|
||||||
|
mdiBell,
|
||||||
|
mdiBookmark,
|
||||||
|
mdiBrightness5,
|
||||||
|
mdiBullhorn,
|
||||||
|
mdiCalendar,
|
||||||
|
mdiCalendarClock,
|
||||||
|
mdiCash,
|
||||||
|
mdiClock,
|
||||||
|
mdiCloudUpload,
|
||||||
|
mdiCog,
|
||||||
|
mdiCommentAlert,
|
||||||
|
mdiCounter,
|
||||||
|
mdiCurrentAc,
|
||||||
|
mdiEye,
|
||||||
|
mdiFan,
|
||||||
|
mdiFlash,
|
||||||
|
mdiFlower,
|
||||||
|
mdiFormatListBulleted,
|
||||||
|
mdiFormTextbox,
|
||||||
|
mdiGasCylinder,
|
||||||
|
mdiGauge,
|
||||||
|
mdiGoogleAssistant,
|
||||||
|
mdiGoogleCirclesCommunities,
|
||||||
|
mdiHomeAssistant,
|
||||||
|
mdiHomeAutomation,
|
||||||
|
mdiImageFilterFrames,
|
||||||
|
mdiLightbulb,
|
||||||
|
mdiLightningBolt,
|
||||||
|
mdiMailbox,
|
||||||
|
mdiMapMarkerRadius,
|
||||||
|
mdiMolecule,
|
||||||
|
mdiMoleculeCo,
|
||||||
|
mdiMoleculeCo2,
|
||||||
|
mdiPalette,
|
||||||
|
mdiRayVertex,
|
||||||
|
mdiRemote,
|
||||||
|
mdiRobot,
|
||||||
|
mdiRobotVacuum,
|
||||||
|
mdiScriptText,
|
||||||
|
mdiSineWave,
|
||||||
|
mdiTextToSpeech,
|
||||||
|
mdiThermometer,
|
||||||
|
mdiThermostat,
|
||||||
|
mdiTimerOutline,
|
||||||
|
mdiToggleSwitchOutline,
|
||||||
|
mdiVideo,
|
||||||
|
mdiWaterPercent,
|
||||||
|
mdiWeatherCloudy,
|
||||||
|
mdiWhiteBalanceSunny,
|
||||||
|
mdiWifi,
|
||||||
|
} from "@mdi/js";
|
||||||
|
|
||||||
// Constants should be alphabetically sorted by name.
|
// Constants should be alphabetically sorted by name.
|
||||||
// Arrays with values should be alphabetically sorted if order doesn't matter.
|
// Arrays with values should be alphabetically sorted if order doesn't matter.
|
||||||
// Each constant should have a description what it is supposed to be used for.
|
// Each constant should have a description what it is supposed to be used for.
|
||||||
|
|
||||||
/** Icon to use when no icon specified for domain. */
|
/** Icon to use when no icon specified for domain. */
|
||||||
export const DEFAULT_DOMAIN_ICON = "hass:bookmark";
|
export const DEFAULT_DOMAIN_ICON = mdiBookmark;
|
||||||
|
|
||||||
/** Icons for each domain */
|
/** Icons for each domain */
|
||||||
export const FIXED_DOMAIN_ICONS = {
|
export const FIXED_DOMAIN_ICONS = {
|
||||||
alert: "hass:alert",
|
alert: mdiAlert,
|
||||||
alexa: "hass:amazon-alexa",
|
air_quality: mdiAirFilter,
|
||||||
air_quality: "hass:air-filter",
|
automation: mdiRobot,
|
||||||
automation: "hass:robot",
|
calendar: mdiCalendar,
|
||||||
calendar: "hass:calendar",
|
camera: mdiVideo,
|
||||||
camera: "hass:video",
|
climate: mdiThermostat,
|
||||||
climate: "hass:thermostat",
|
configurator: mdiCog,
|
||||||
configurator: "hass:cog",
|
conversation: mdiTextToSpeech,
|
||||||
conversation: "hass:text-to-speech",
|
counter: mdiCounter,
|
||||||
counter: "hass:counter",
|
device_tracker: mdiAccount,
|
||||||
device_tracker: "hass:account",
|
fan: mdiFan,
|
||||||
fan: "hass:fan",
|
google_assistant: mdiGoogleAssistant,
|
||||||
google_assistant: "hass:google-assistant",
|
group: mdiGoogleCirclesCommunities,
|
||||||
group: "hass:google-circles-communities",
|
homeassistant: mdiHomeAssistant,
|
||||||
homeassistant: "hass:home-assistant",
|
homekit: mdiHomeAutomation,
|
||||||
homekit: "hass:home-automation",
|
image_processing: mdiImageFilterFrames,
|
||||||
image_processing: "hass:image-filter-frames",
|
input_boolean: mdiToggleSwitchOutline,
|
||||||
input_boolean: "hass:toggle-switch-outline",
|
input_datetime: mdiCalendarClock,
|
||||||
input_datetime: "hass:calendar-clock",
|
input_number: mdiRayVertex,
|
||||||
input_number: "hass:ray-vertex",
|
input_select: mdiFormatListBulleted,
|
||||||
input_select: "hass:format-list-bulleted",
|
input_text: mdiFormTextbox,
|
||||||
input_text: "hass:form-textbox",
|
light: mdiLightbulb,
|
||||||
light: "hass:lightbulb",
|
mailbox: mdiMailbox,
|
||||||
mailbox: "hass:mailbox",
|
notify: mdiCommentAlert,
|
||||||
notify: "hass:comment-alert",
|
number: mdiRayVertex,
|
||||||
number: "hass:ray-vertex",
|
persistent_notification: mdiBell,
|
||||||
persistent_notification: "hass:bell",
|
person: mdiAccount,
|
||||||
person: "hass:account",
|
plant: mdiFlower,
|
||||||
plant: "hass:flower",
|
proximity: mdiAppleSafari,
|
||||||
proximity: "hass:apple-safari",
|
remote: mdiRemote,
|
||||||
remote: "hass:remote",
|
scene: mdiPalette,
|
||||||
scene: "hass:palette",
|
script: mdiScriptText,
|
||||||
script: "hass:script-text",
|
select: mdiFormatListBulleted,
|
||||||
select: "hass:format-list-bulleted",
|
sensor: mdiEye,
|
||||||
sensor: "hass:eye",
|
siren: mdiBullhorn,
|
||||||
simple_alarm: "hass:bell",
|
simple_alarm: mdiBell,
|
||||||
sun: "hass:white-balance-sunny",
|
sun: mdiWhiteBalanceSunny,
|
||||||
switch: "hass:flash",
|
switch: mdiFlash,
|
||||||
timer: "hass:timer-outline",
|
timer: mdiTimerOutline,
|
||||||
updater: "hass:cloud-upload",
|
updater: mdiCloudUpload,
|
||||||
vacuum: "hass:robot-vacuum",
|
vacuum: mdiRobotVacuum,
|
||||||
water_heater: "hass:thermometer",
|
water_heater: mdiThermometer,
|
||||||
weather: "hass:weather-cloudy",
|
weather: mdiWeatherCloudy,
|
||||||
zone: "hass:map-marker-radius",
|
zone: mdiMapMarkerRadius,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FIXED_DEVICE_CLASS_ICONS = {
|
export const FIXED_DEVICE_CLASS_ICONS = {
|
||||||
aqi: "hass:air-filter",
|
aqi: mdiAirFilter,
|
||||||
battery: "hass:battery",
|
// battery: mdiBattery, => not included by design since `sensorIcon()` will dynamically determine the icon
|
||||||
carbon_dioxide: "mdi:molecule-co2",
|
carbon_dioxide: mdiMoleculeCo2,
|
||||||
carbon_monoxide: "mdi:molecule-co",
|
carbon_monoxide: mdiMoleculeCo,
|
||||||
current: "hass:current-ac",
|
current: mdiCurrentAc,
|
||||||
date: "hass:calendar",
|
date: mdiCalendar,
|
||||||
energy: "hass:lightning-bolt",
|
energy: mdiLightningBolt,
|
||||||
gas: "hass:gas-cylinder",
|
gas: mdiGasCylinder,
|
||||||
humidity: "hass:water-percent",
|
humidity: mdiWaterPercent,
|
||||||
illuminance: "hass:brightness-5",
|
illuminance: mdiBrightness5,
|
||||||
monetary: "mdi:cash",
|
monetary: mdiCash,
|
||||||
nitrogen_dioxide: "mdi:molecule",
|
nitrogen_dioxide: mdiMolecule,
|
||||||
nitrogen_monoxide: "mdi:molecule",
|
nitrogen_monoxide: mdiMolecule,
|
||||||
nitrous_oxide: "mdi:molecule",
|
nitrous_oxide: mdiMolecule,
|
||||||
ozone: "mdi:molecule",
|
ozone: mdiMolecule,
|
||||||
pm1: "mdi:molecule",
|
pm1: mdiMolecule,
|
||||||
pm10: "mdi:molecule",
|
pm10: mdiMolecule,
|
||||||
pm25: "mdi:molecule",
|
pm25: mdiMolecule,
|
||||||
power: "hass:flash",
|
power: mdiFlash,
|
||||||
power_factor: "hass:angle-acute",
|
power_factor: mdiAngleAcute,
|
||||||
pressure: "hass:gauge",
|
pressure: mdiGauge,
|
||||||
signal_strength: "hass:wifi",
|
signal_strength: mdiWifi,
|
||||||
sulphur_dioxide: "mdi:molecule",
|
sulphur_dioxide: mdiMolecule,
|
||||||
temperature: "hass:thermometer",
|
temperature: mdiThermometer,
|
||||||
timestamp: "hass:clock",
|
timestamp: mdiClock,
|
||||||
volatile_organic_compounds: "mdi:molecule",
|
volatile_organic_compounds: mdiMolecule,
|
||||||
voltage: "hass:sine-wave",
|
voltage: mdiSineWave,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Domains that have a state card. */
|
/** Domains that have a state card. */
|
||||||
|
@ -36,7 +36,15 @@ export const applyThemesOnElement = (
|
|||||||
let cacheKey = selectedTheme;
|
let cacheKey = selectedTheme;
|
||||||
let themeRules: Partial<ThemeVars> = {};
|
let themeRules: Partial<ThemeVars> = {};
|
||||||
|
|
||||||
if (themeSettings) {
|
// If there is no explicitly desired dark mode provided, we automatically
|
||||||
|
// use the active one from hass.themes.
|
||||||
|
if (!themeSettings || themeSettings?.dark === undefined) {
|
||||||
|
themeSettings = {
|
||||||
|
...themeSettings,
|
||||||
|
dark: themes.darkMode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (themeSettings.dark) {
|
if (themeSettings.dark) {
|
||||||
cacheKey = `${cacheKey}__dark`;
|
cacheKey = `${cacheKey}__dark`;
|
||||||
themeRules = { ...darkStyles };
|
themeRules = { ...darkStyles };
|
||||||
@ -86,7 +94,6 @@ export const applyThemesOnElement = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Custom theme logic (not relevant for default theme, since it would override
|
// Custom theme logic (not relevant for default theme, since it would override
|
||||||
// the derived calculations from above)
|
// the derived calculations from above)
|
||||||
|
@ -1,24 +1,36 @@
|
|||||||
/** Return an icon representing a alarm panel state. */
|
/** Return an icon representing a alarm panel state. */
|
||||||
|
|
||||||
|
import {
|
||||||
|
mdiShieldLock,
|
||||||
|
mdiShieldAirplane,
|
||||||
|
mdiShieldHome,
|
||||||
|
mdiShieldMoon,
|
||||||
|
mdiSecurity,
|
||||||
|
mdiShieldOutline,
|
||||||
|
mdiBellRing,
|
||||||
|
mdiShieldOff,
|
||||||
|
mdiShield,
|
||||||
|
} from "@mdi/js";
|
||||||
|
|
||||||
export const alarmPanelIcon = (state?: string) => {
|
export const alarmPanelIcon = (state?: string) => {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "armed_away":
|
case "armed_away":
|
||||||
return "hass:shield-lock";
|
return mdiShieldLock;
|
||||||
case "armed_vacation":
|
case "armed_vacation":
|
||||||
return "hass:shield-airplane";
|
return mdiShieldAirplane;
|
||||||
case "armed_home":
|
case "armed_home":
|
||||||
return "hass:shield-home";
|
return mdiShieldHome;
|
||||||
case "armed_night":
|
case "armed_night":
|
||||||
return "hass:shield-moon";
|
return mdiShieldMoon;
|
||||||
case "armed_custom_bypass":
|
case "armed_custom_bypass":
|
||||||
return "hass:security";
|
return mdiSecurity;
|
||||||
case "pending":
|
case "pending":
|
||||||
return "hass:shield-outline";
|
return mdiShieldOutline;
|
||||||
case "triggered":
|
case "triggered":
|
||||||
return "hass:bell-ring";
|
return mdiBellRing;
|
||||||
case "disarmed":
|
case "disarmed":
|
||||||
return "hass:shield-off";
|
return mdiShieldOff;
|
||||||
default:
|
default:
|
||||||
return "hass:shield";
|
return mdiShield;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,35 +1,92 @@
|
|||||||
/** Return an icon representing a battery state. */
|
/** Return an icon representing a battery state. */
|
||||||
|
import {
|
||||||
|
mdiBattery,
|
||||||
|
mdiBattery10,
|
||||||
|
mdiBattery20,
|
||||||
|
mdiBattery30,
|
||||||
|
mdiBattery40,
|
||||||
|
mdiBattery50,
|
||||||
|
mdiBattery60,
|
||||||
|
mdiBattery70,
|
||||||
|
mdiBattery80,
|
||||||
|
mdiBattery90,
|
||||||
|
mdiBatteryAlert,
|
||||||
|
mdiBatteryAlertVariantOutline,
|
||||||
|
mdiBatteryCharging,
|
||||||
|
mdiBatteryCharging10,
|
||||||
|
mdiBatteryCharging20,
|
||||||
|
mdiBatteryCharging30,
|
||||||
|
mdiBatteryCharging40,
|
||||||
|
mdiBatteryCharging50,
|
||||||
|
mdiBatteryCharging60,
|
||||||
|
mdiBatteryCharging70,
|
||||||
|
mdiBatteryCharging80,
|
||||||
|
mdiBatteryCharging90,
|
||||||
|
mdiBatteryChargingOutline,
|
||||||
|
mdiBatteryUnknown,
|
||||||
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
export const batteryIcon = (
|
const BATTERY_ICONS = {
|
||||||
|
10: mdiBattery10,
|
||||||
|
20: mdiBattery20,
|
||||||
|
30: mdiBattery30,
|
||||||
|
40: mdiBattery40,
|
||||||
|
50: mdiBattery50,
|
||||||
|
60: mdiBattery60,
|
||||||
|
70: mdiBattery70,
|
||||||
|
80: mdiBattery80,
|
||||||
|
90: mdiBattery90,
|
||||||
|
100: mdiBattery,
|
||||||
|
};
|
||||||
|
const BATTERY_CHARGING_ICONS = {
|
||||||
|
10: mdiBatteryCharging10,
|
||||||
|
20: mdiBatteryCharging20,
|
||||||
|
30: mdiBatteryCharging30,
|
||||||
|
40: mdiBatteryCharging40,
|
||||||
|
50: mdiBatteryCharging50,
|
||||||
|
60: mdiBatteryCharging60,
|
||||||
|
70: mdiBatteryCharging70,
|
||||||
|
80: mdiBatteryCharging80,
|
||||||
|
90: mdiBatteryCharging90,
|
||||||
|
100: mdiBatteryCharging,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const batteryStateIcon = (
|
||||||
batteryState: HassEntity,
|
batteryState: HassEntity,
|
||||||
batteryChargingState?: HassEntity
|
batteryChargingState?: HassEntity
|
||||||
) => {
|
) => {
|
||||||
const battery = Number(batteryState.state);
|
const battery = batteryState.state;
|
||||||
const battery_charging =
|
const batteryCharging =
|
||||||
batteryChargingState && batteryChargingState.state === "on";
|
batteryChargingState && batteryChargingState.state === "on";
|
||||||
let icon = "hass:battery";
|
|
||||||
|
|
||||||
if (isNaN(battery)) {
|
return batteryIcon(battery, batteryCharging);
|
||||||
if (batteryState.state === "off") {
|
};
|
||||||
icon += "-full";
|
|
||||||
} else if (batteryState.state === "on") {
|
export const batteryIcon = (
|
||||||
icon += "-alert";
|
batteryState: number | string,
|
||||||
} else {
|
batteryCharging?: boolean
|
||||||
icon += "-unknown";
|
) => {
|
||||||
}
|
const batteryValue = Number(batteryState);
|
||||||
return icon;
|
if (isNaN(batteryValue)) {
|
||||||
}
|
if (batteryState === "off") {
|
||||||
|
return mdiBattery;
|
||||||
const batteryRound = Math.round(battery / 10) * 10;
|
}
|
||||||
if (battery_charging && battery > 10) {
|
if (batteryState === "on") {
|
||||||
icon += `-charging-${batteryRound}`;
|
return mdiBatteryAlert;
|
||||||
} else if (battery_charging) {
|
}
|
||||||
icon += "-outline";
|
return mdiBatteryUnknown;
|
||||||
} else if (battery <= 5) {
|
}
|
||||||
icon += "-alert";
|
|
||||||
} else if (battery > 5 && battery < 95) {
|
const batteryRound = Math.round(batteryValue / 10) * 10;
|
||||||
icon += `-${batteryRound}`;
|
if (batteryCharging && batteryValue >= 10) {
|
||||||
}
|
return BATTERY_CHARGING_ICONS[batteryRound];
|
||||||
return icon;
|
}
|
||||||
|
if (batteryCharging) {
|
||||||
|
return mdiBatteryChargingOutline;
|
||||||
|
}
|
||||||
|
if (batteryValue <= 5) {
|
||||||
|
return mdiBatteryAlertVariantOutline;
|
||||||
|
}
|
||||||
|
return BATTERY_ICONS[batteryRound];
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,46 @@
|
|||||||
|
import {
|
||||||
|
mdiAlertCircle,
|
||||||
|
mdiBattery,
|
||||||
|
mdiBatteryCharging,
|
||||||
|
mdiBatteryOutline,
|
||||||
|
mdiBrightness5,
|
||||||
|
mdiBrightness7,
|
||||||
|
mdiCheckboxMarkedCircle,
|
||||||
|
mdiCheckCircle,
|
||||||
|
mdiCropPortrait,
|
||||||
|
mdiDoorClosed,
|
||||||
|
mdiDoorOpen,
|
||||||
|
mdiFire,
|
||||||
|
mdiGarage,
|
||||||
|
mdiGarageOpen,
|
||||||
|
mdiHome,
|
||||||
|
mdiHomeOutline,
|
||||||
|
mdiLock,
|
||||||
|
mdiLockOpen,
|
||||||
|
mdiMusicNote,
|
||||||
|
mdiMusicNoteOff,
|
||||||
|
mdiPackage,
|
||||||
|
mdiPackageUp,
|
||||||
|
mdiPlay,
|
||||||
|
mdiPowerPlug,
|
||||||
|
mdiPowerPlugOff,
|
||||||
|
mdiRadioboxBlank,
|
||||||
|
mdiRun,
|
||||||
|
mdiServerNetwork,
|
||||||
|
mdiServerNetworkOff,
|
||||||
|
mdiSmoke,
|
||||||
|
mdiSnowflake,
|
||||||
|
mdiSquare,
|
||||||
|
mdiSquareOutline,
|
||||||
|
mdiStop,
|
||||||
|
mdiThermometer,
|
||||||
|
mdiVibrate,
|
||||||
|
mdiWalk,
|
||||||
|
mdiWater,
|
||||||
|
mdiWaterOff,
|
||||||
|
mdiWindowClosed,
|
||||||
|
mdiWindowOpen,
|
||||||
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
/** Return an icon representing a binary sensor state. */
|
/** Return an icon representing a binary sensor state. */
|
||||||
@ -6,52 +49,55 @@ export const binarySensorIcon = (state?: string, stateObj?: HassEntity) => {
|
|||||||
const is_off = state === "off";
|
const is_off = state === "off";
|
||||||
switch (stateObj?.attributes.device_class) {
|
switch (stateObj?.attributes.device_class) {
|
||||||
case "battery":
|
case "battery":
|
||||||
return is_off ? "hass:battery" : "hass:battery-outline";
|
return is_off ? mdiBattery : mdiBatteryOutline;
|
||||||
case "battery_charging":
|
case "battery_charging":
|
||||||
return is_off ? "hass:battery" : "hass:battery-charging";
|
return is_off ? mdiBattery : mdiBatteryCharging;
|
||||||
case "cold":
|
case "cold":
|
||||||
return is_off ? "hass:thermometer" : "hass:snowflake";
|
return is_off ? mdiThermometer : mdiSnowflake;
|
||||||
case "connectivity":
|
case "connectivity":
|
||||||
return is_off ? "hass:server-network-off" : "hass:server-network";
|
return is_off ? mdiServerNetworkOff : mdiServerNetwork;
|
||||||
case "door":
|
case "door":
|
||||||
return is_off ? "hass:door-closed" : "hass:door-open";
|
return is_off ? mdiDoorClosed : mdiDoorOpen;
|
||||||
case "garage_door":
|
case "garage_door":
|
||||||
return is_off ? "hass:garage" : "hass:garage-open";
|
return is_off ? mdiGarage : mdiGarageOpen;
|
||||||
case "power":
|
case "power":
|
||||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
|
||||||
case "gas":
|
case "gas":
|
||||||
case "problem":
|
case "problem":
|
||||||
case "safety":
|
case "safety":
|
||||||
return is_off ? "hass:check-circle" : "hass:alert-circle";
|
case "tamper":
|
||||||
|
return is_off ? mdiCheckCircle : mdiAlertCircle;
|
||||||
case "smoke":
|
case "smoke":
|
||||||
return is_off ? "hass:check-circle" : "hass:smoke";
|
return is_off ? mdiCheckCircle : mdiSmoke;
|
||||||
case "heat":
|
case "heat":
|
||||||
return is_off ? "hass:thermometer" : "hass:fire";
|
return is_off ? mdiThermometer : mdiFire;
|
||||||
case "light":
|
case "light":
|
||||||
return is_off ? "hass:brightness-5" : "hass:brightness-7";
|
return is_off ? mdiBrightness5 : mdiBrightness7;
|
||||||
case "lock":
|
case "lock":
|
||||||
return is_off ? "hass:lock" : "hass:lock-open";
|
return is_off ? mdiLock : mdiLockOpen;
|
||||||
case "moisture":
|
case "moisture":
|
||||||
return is_off ? "hass:water-off" : "hass:water";
|
return is_off ? mdiWaterOff : mdiWater;
|
||||||
case "motion":
|
case "motion":
|
||||||
return is_off ? "hass:walk" : "hass:run";
|
return is_off ? mdiWalk : mdiRun;
|
||||||
case "occupancy":
|
case "occupancy":
|
||||||
return is_off ? "hass:home-outline" : "hass:home";
|
return is_off ? mdiHomeOutline : mdiHome;
|
||||||
case "opening":
|
case "opening":
|
||||||
return is_off ? "hass:square" : "hass:square-outline";
|
return is_off ? mdiSquare : mdiSquareOutline;
|
||||||
case "plug":
|
case "plug":
|
||||||
return is_off ? "hass:power-plug-off" : "hass:power-plug";
|
return is_off ? mdiPowerPlugOff : mdiPowerPlug;
|
||||||
case "presence":
|
case "presence":
|
||||||
return is_off ? "hass:home-outline" : "hass:home";
|
return is_off ? mdiHomeOutline : mdiHome;
|
||||||
|
case "running":
|
||||||
|
return is_off ? mdiStop : mdiPlay;
|
||||||
case "sound":
|
case "sound":
|
||||||
return is_off ? "hass:music-note-off" : "hass:music-note";
|
return is_off ? mdiMusicNoteOff : mdiMusicNote;
|
||||||
case "update":
|
case "update":
|
||||||
return is_off ? "mdi:package" : "mdi:package-up";
|
return is_off ? mdiPackage : mdiPackageUp;
|
||||||
case "vibration":
|
case "vibration":
|
||||||
return is_off ? "hass:crop-portrait" : "hass:vibrate";
|
return is_off ? mdiCropPortrait : mdiVibrate;
|
||||||
case "window":
|
case "window":
|
||||||
return is_off ? "hass:window-closed" : "hass:window-open";
|
return is_off ? mdiWindowClosed : mdiWindowOpen;
|
||||||
default:
|
default:
|
||||||
return is_off ? "hass:radiobox-blank" : "hass:checkbox-marked-circle";
|
return is_off ? mdiRadioboxBlank : mdiCheckboxMarkedCircle;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -39,7 +39,7 @@ export const computeStateDisplay = (
|
|||||||
const domain = computeStateDomain(stateObj);
|
const domain = computeStateDomain(stateObj);
|
||||||
|
|
||||||
if (domain === "input_datetime") {
|
if (domain === "input_datetime") {
|
||||||
if (state) {
|
if (state !== undefined) {
|
||||||
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
// If trying to display an explicit state, need to parse the explict state to `Date` then format.
|
||||||
// Attributes aren't available, we have to use `state`.
|
// Attributes aren't available, we have to use `state`.
|
||||||
try {
|
try {
|
||||||
@ -63,7 +63,7 @@ export const computeStateDisplay = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
} catch {
|
} catch (_e) {
|
||||||
// Formatting methods may throw error if date parsing doesn't go well,
|
// Formatting methods may throw error if date parsing doesn't go well,
|
||||||
// just return the state string in that case.
|
// just return the state string in that case.
|
||||||
return state;
|
return state;
|
||||||
@ -71,20 +71,7 @@ export const computeStateDisplay = (
|
|||||||
} else {
|
} else {
|
||||||
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
// If not trying to display an explicit state, create `Date` object from `stateObj`'s attributes then format.
|
||||||
let date: Date;
|
let date: Date;
|
||||||
if (!stateObj.attributes.has_time) {
|
if (stateObj.attributes.has_date && stateObj.attributes.has_time) {
|
||||||
date = new Date(
|
|
||||||
stateObj.attributes.year,
|
|
||||||
stateObj.attributes.month - 1,
|
|
||||||
stateObj.attributes.day
|
|
||||||
);
|
|
||||||
return formatDate(date, locale);
|
|
||||||
}
|
|
||||||
if (!stateObj.attributes.has_date) {
|
|
||||||
date = new Date();
|
|
||||||
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
|
||||||
return formatTime(date, locale);
|
|
||||||
}
|
|
||||||
|
|
||||||
date = new Date(
|
date = new Date(
|
||||||
stateObj.attributes.year,
|
stateObj.attributes.year,
|
||||||
stateObj.attributes.month - 1,
|
stateObj.attributes.month - 1,
|
||||||
@ -94,6 +81,21 @@ export const computeStateDisplay = (
|
|||||||
);
|
);
|
||||||
return formatDateTime(date, locale);
|
return formatDateTime(date, locale);
|
||||||
}
|
}
|
||||||
|
if (stateObj.attributes.has_date) {
|
||||||
|
date = new Date(
|
||||||
|
stateObj.attributes.year,
|
||||||
|
stateObj.attributes.month - 1,
|
||||||
|
stateObj.attributes.day
|
||||||
|
);
|
||||||
|
return formatDate(date, locale);
|
||||||
|
}
|
||||||
|
if (stateObj.attributes.has_time) {
|
||||||
|
date = new Date();
|
||||||
|
date.setHours(stateObj.attributes.hour, stateObj.attributes.minute);
|
||||||
|
return formatTime(date, locale);
|
||||||
|
}
|
||||||
|
return stateObj.state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain === "humidifier") {
|
if (domain === "humidifier") {
|
||||||
|
@ -1,4 +1,30 @@
|
|||||||
/** Return an icon representing a cover state. */
|
/** Return an icon representing a cover state. */
|
||||||
|
import {
|
||||||
|
mdiArrowUpBox,
|
||||||
|
mdiArrowDownBox,
|
||||||
|
mdiGarage,
|
||||||
|
mdiGarageOpen,
|
||||||
|
mdiGateArrowRight,
|
||||||
|
mdiGate,
|
||||||
|
mdiGateOpen,
|
||||||
|
mdiDoorOpen,
|
||||||
|
mdiDoorClosed,
|
||||||
|
mdiCircle,
|
||||||
|
mdiWindowShutter,
|
||||||
|
mdiWindowShutterOpen,
|
||||||
|
mdiBlinds,
|
||||||
|
mdiBlindsOpen,
|
||||||
|
mdiWindowClosed,
|
||||||
|
mdiWindowOpen,
|
||||||
|
mdiArrowExpandHorizontal,
|
||||||
|
mdiArrowUp,
|
||||||
|
mdiArrowCollapseHorizontal,
|
||||||
|
mdiArrowDown,
|
||||||
|
mdiCircleSlice8,
|
||||||
|
mdiArrowSplitVertical,
|
||||||
|
mdiCurtains,
|
||||||
|
mdiCurtainsClosed,
|
||||||
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
||||||
@ -8,74 +34,84 @@ export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
|
|||||||
case "garage":
|
case "garage":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
return "hass:arrow-up-box";
|
return mdiArrowUpBox;
|
||||||
case "closing":
|
case "closing":
|
||||||
return "hass:arrow-down-box";
|
return mdiArrowDownBox;
|
||||||
case "closed":
|
case "closed":
|
||||||
return "hass:garage";
|
return mdiGarage;
|
||||||
default:
|
default:
|
||||||
return "hass:garage-open";
|
return mdiGarageOpen;
|
||||||
}
|
}
|
||||||
case "gate":
|
case "gate":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
case "closing":
|
case "closing":
|
||||||
return "hass:gate-arrow-right";
|
return mdiGateArrowRight;
|
||||||
case "closed":
|
case "closed":
|
||||||
return "hass:gate";
|
return mdiGate;
|
||||||
default:
|
default:
|
||||||
return "hass:gate-open";
|
return mdiGateOpen;
|
||||||
}
|
}
|
||||||
case "door":
|
case "door":
|
||||||
return open ? "hass:door-open" : "hass:door-closed";
|
return open ? mdiDoorOpen : mdiDoorClosed;
|
||||||
case "damper":
|
case "damper":
|
||||||
return open ? "hass:circle" : "hass:circle-slice-8";
|
return open ? mdiCircle : mdiCircleSlice8;
|
||||||
case "shutter":
|
case "shutter":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
return "hass:arrow-up-box";
|
return mdiArrowUpBox;
|
||||||
case "closing":
|
case "closing":
|
||||||
return "hass:arrow-down-box";
|
return mdiArrowDownBox;
|
||||||
case "closed":
|
case "closed":
|
||||||
return "hass:window-shutter";
|
return mdiWindowShutter;
|
||||||
default:
|
default:
|
||||||
return "hass:window-shutter-open";
|
return mdiWindowShutterOpen;
|
||||||
|
}
|
||||||
|
case "curtain":
|
||||||
|
switch (state) {
|
||||||
|
case "opening":
|
||||||
|
return mdiArrowSplitVertical;
|
||||||
|
case "closing":
|
||||||
|
return mdiArrowCollapseHorizontal;
|
||||||
|
case "closed":
|
||||||
|
return mdiCurtainsClosed;
|
||||||
|
default:
|
||||||
|
return mdiCurtains;
|
||||||
}
|
}
|
||||||
case "blind":
|
case "blind":
|
||||||
case "curtain":
|
|
||||||
case "shade":
|
case "shade":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
return "hass:arrow-up-box";
|
return mdiArrowUpBox;
|
||||||
case "closing":
|
case "closing":
|
||||||
return "hass:arrow-down-box";
|
return mdiArrowDownBox;
|
||||||
case "closed":
|
case "closed":
|
||||||
return "hass:blinds";
|
return mdiBlinds;
|
||||||
default:
|
default:
|
||||||
return "hass:blinds-open";
|
return mdiBlindsOpen;
|
||||||
}
|
}
|
||||||
case "window":
|
case "window":
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
return "hass:arrow-up-box";
|
return mdiArrowUpBox;
|
||||||
case "closing":
|
case "closing":
|
||||||
return "hass:arrow-down-box";
|
return mdiArrowDownBox;
|
||||||
case "closed":
|
case "closed":
|
||||||
return "hass:window-closed";
|
return mdiWindowClosed;
|
||||||
default:
|
default:
|
||||||
return "hass:window-open";
|
return mdiWindowOpen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "opening":
|
case "opening":
|
||||||
return "hass:arrow-up-box";
|
return mdiArrowUpBox;
|
||||||
case "closing":
|
case "closing":
|
||||||
return "hass:arrow-down-box";
|
return mdiArrowDownBox;
|
||||||
case "closed":
|
case "closed":
|
||||||
return "hass:window-closed";
|
return mdiWindowClosed;
|
||||||
default:
|
default:
|
||||||
return "hass:window-open";
|
return mdiWindowOpen;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -84,9 +120,9 @@ export const computeOpenIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
return "hass:arrow-expand-horizontal";
|
return mdiArrowExpandHorizontal;
|
||||||
default:
|
default:
|
||||||
return "hass:arrow-up";
|
return mdiArrowUp;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -95,8 +131,8 @@ export const computeCloseIcon = (stateObj: HassEntity): string => {
|
|||||||
case "awning":
|
case "awning":
|
||||||
case "door":
|
case "door":
|
||||||
case "gate":
|
case "gate":
|
||||||
return "hass:arrow-collapse-horizontal";
|
return mdiArrowCollapseHorizontal;
|
||||||
default:
|
default:
|
||||||
return "hass:arrow-down";
|
return mdiArrowDown;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
|
import {
|
||||||
|
mdiAirHumidifierOff,
|
||||||
|
mdiAirHumidifier,
|
||||||
|
mdiLockOpen,
|
||||||
|
mdiLockAlert,
|
||||||
|
mdiLockClock,
|
||||||
|
mdiLock,
|
||||||
|
mdiCastConnected,
|
||||||
|
mdiCast,
|
||||||
|
mdiEmoticonDead,
|
||||||
|
mdiSleep,
|
||||||
|
mdiTimerSand,
|
||||||
|
mdiZWave,
|
||||||
|
mdiClock,
|
||||||
|
mdiCalendar,
|
||||||
|
mdiWeatherNight,
|
||||||
|
} from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
/**
|
/**
|
||||||
* Return the icon to be used for a domain.
|
* Return the icon to be used for a domain.
|
||||||
@ -28,36 +45,34 @@ export const domainIcon = (
|
|||||||
return coverIcon(compareState, stateObj);
|
return coverIcon(compareState, stateObj);
|
||||||
|
|
||||||
case "humidifier":
|
case "humidifier":
|
||||||
return state && state === "off"
|
return state && state === "off" ? mdiAirHumidifierOff : mdiAirHumidifier;
|
||||||
? "hass:air-humidifier-off"
|
|
||||||
: "hass:air-humidifier";
|
|
||||||
|
|
||||||
case "lock":
|
case "lock":
|
||||||
switch (compareState) {
|
switch (compareState) {
|
||||||
case "unlocked":
|
case "unlocked":
|
||||||
return "hass:lock-open";
|
return mdiLockOpen;
|
||||||
case "jammed":
|
case "jammed":
|
||||||
return "hass:lock-alert";
|
return mdiLockAlert;
|
||||||
case "locking":
|
case "locking":
|
||||||
case "unlocking":
|
case "unlocking":
|
||||||
return "hass:lock-clock";
|
return mdiLockClock;
|
||||||
default:
|
default:
|
||||||
return "hass:lock";
|
return mdiLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "media_player":
|
case "media_player":
|
||||||
return compareState === "playing" ? "hass:cast-connected" : "hass:cast";
|
return compareState === "playing" ? mdiCastConnected : mdiCast;
|
||||||
|
|
||||||
case "zwave":
|
case "zwave":
|
||||||
switch (compareState) {
|
switch (compareState) {
|
||||||
case "dead":
|
case "dead":
|
||||||
return "hass:emoticon-dead";
|
return mdiEmoticonDead;
|
||||||
case "sleeping":
|
case "sleeping":
|
||||||
return "hass:sleep";
|
return mdiSleep;
|
||||||
case "initializing":
|
case "initializing":
|
||||||
return "hass:timer-sand";
|
return mdiTimerSand;
|
||||||
default:
|
default:
|
||||||
return "hass:z-wave";
|
return mdiZWave;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "sensor": {
|
case "sensor": {
|
||||||
@ -71,17 +86,17 @@ export const domainIcon = (
|
|||||||
|
|
||||||
case "input_datetime":
|
case "input_datetime":
|
||||||
if (!stateObj?.attributes.has_date) {
|
if (!stateObj?.attributes.has_date) {
|
||||||
return "hass:clock";
|
return mdiClock;
|
||||||
}
|
}
|
||||||
if (!stateObj.attributes.has_time) {
|
if (!stateObj.attributes.has_time) {
|
||||||
return "hass:calendar";
|
return mdiCalendar;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "sun":
|
case "sun":
|
||||||
return stateObj?.state === "above_horizon"
|
return stateObj?.state === "above_horizon"
|
||||||
? FIXED_DOMAIN_ICONS[domain]
|
? FIXED_DOMAIN_ICONS[domain]
|
||||||
: "hass:weather-night";
|
: mdiWeatherNight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (domain in FIXED_DOMAIN_ICONS) {
|
if (domain in FIXED_DOMAIN_ICONS) {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
/** Return an icon representing a sensor state. */
|
/** Return an icon representing a sensor state. */
|
||||||
|
import { mdiBattery, mdiThermometer } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
|
|
||||||
import { batteryIcon } from "./battery_icon";
|
|
||||||
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
|
import { SENSOR_DEVICE_CLASS_BATTERY } from "../../data/sensor";
|
||||||
|
import { FIXED_DEVICE_CLASS_ICONS, UNIT_C, UNIT_F } from "../const";
|
||||||
|
import { batteryStateIcon } from "./battery_icon";
|
||||||
|
|
||||||
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
||||||
const dclass = stateObj?.attributes.device_class;
|
const dclass = stateObj?.attributes.device_class;
|
||||||
@ -12,12 +13,12 @@ export const sensorIcon = (stateObj?: HassEntity): string | undefined => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
|
if (dclass === SENSOR_DEVICE_CLASS_BATTERY) {
|
||||||
return stateObj ? batteryIcon(stateObj) : "hass:battery";
|
return stateObj ? batteryStateIcon(stateObj) : mdiBattery;
|
||||||
}
|
}
|
||||||
|
|
||||||
const unit = stateObj?.attributes.unit_of_measurement;
|
const unit = stateObj?.attributes.unit_of_measurement;
|
||||||
if (unit === UNIT_C || unit === UNIT_F) {
|
if (unit === UNIT_C || unit === UNIT_F) {
|
||||||
return "hass:thermometer";
|
return mdiThermometer;
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -4,13 +4,9 @@ import { DEFAULT_DOMAIN_ICON } from "../const";
|
|||||||
import { computeDomain } from "./compute_domain";
|
import { computeDomain } from "./compute_domain";
|
||||||
import { domainIcon } from "./domain_icon";
|
import { domainIcon } from "./domain_icon";
|
||||||
|
|
||||||
export const stateIcon = (state?: HassEntity) => {
|
export const stateIconPath = (state?: HassEntity) => {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
return DEFAULT_DOMAIN_ICON;
|
return DEFAULT_DOMAIN_ICON;
|
||||||
}
|
}
|
||||||
if (state.attributes.icon) {
|
|
||||||
return state.attributes.icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
return domainIcon(computeDomain(state.entity_id), state);
|
return domainIcon(computeDomain(state.entity_id), state);
|
||||||
};
|
};
|
24
src/common/entity/strip_prefix_from_entity_name.ts
Normal file
24
src/common/entity/strip_prefix_from_entity_name.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* Strips a device name from an entity name.
|
||||||
|
* @param entityName the entity name
|
||||||
|
* @param lowerCasedPrefixWithSpaceSuffix the prefix to strip, lower cased with a space suffix
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const stripPrefixFromEntityName = (
|
||||||
|
entityName: string,
|
||||||
|
lowerCasedPrefixWithSpaceSuffix: string
|
||||||
|
) => {
|
||||||
|
if (!entityName.toLowerCase().startsWith(lowerCasedPrefixWithSpaceSuffix)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newName = entityName.substring(lowerCasedPrefixWithSpaceSuffix.length);
|
||||||
|
|
||||||
|
// If first word already has an upper case letter (e.g. from brand name)
|
||||||
|
// leave as-is, otherwise capitalize the first word.
|
||||||
|
return hasUpperCase(newName.substr(0, newName.indexOf(" ")))
|
||||||
|
? newName
|
||||||
|
: newName[0].toUpperCase() + newName.slice(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasUpperCase = (str: string): boolean => str.toLowerCase() !== str;
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiClose, mdiMagnify } from "@mdi/js";
|
import { mdiClose, mdiMagnify } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||||
@ -11,11 +10,15 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
|
import "../../components/ha-icon-button";
|
||||||
import "../../components/ha-svg-icon";
|
import "../../components/ha-svg-icon";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
import { fireEvent } from "../dom/fire_event";
|
import { fireEvent } from "../dom/fire_event";
|
||||||
|
|
||||||
@customElement("search-input")
|
@customElement("search-input")
|
||||||
class SearchInput extends LitElement {
|
class SearchInput extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public filter?: string;
|
@property() public filter?: string;
|
||||||
|
|
||||||
@property({ type: Boolean, attribute: "no-label-float" })
|
@property({ type: Boolean, attribute: "no-label-float" })
|
||||||
@ -50,13 +53,12 @@ class SearchInput extends LitElement {
|
|||||||
</slot>
|
</slot>
|
||||||
${this.filter &&
|
${this.filter &&
|
||||||
html`
|
html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
@click=${this._clearSearch}
|
@click=${this._clearSearch}
|
||||||
title="Clear"
|
.label=${this.hass.localize("ui.common.clear")}
|
||||||
>
|
.path=${mdiClose}
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
></ha-icon-button>
|
||||||
</mwc-icon-button>
|
|
||||||
`}
|
`}
|
||||||
</paper-input>
|
</paper-input>
|
||||||
`;
|
`;
|
||||||
@ -90,10 +92,10 @@ class SearchInput extends LitElement {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-svg-icon,
|
ha-svg-icon,
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
}
|
}
|
||||||
ha-svg-icon.prefix {
|
ha-svg-icon.prefix {
|
||||||
|
@ -12,8 +12,8 @@ export const slugify = (value: string, delimiter = "_") => {
|
|||||||
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
|
.replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special characters
|
||||||
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
|
.replace(/&/g, `${delimiter}and${delimiter}`) // Replace & with 'and'
|
||||||
.replace(/[^\w-]+/g, "") // Remove all non-word characters
|
.replace(/[^\w-]+/g, "") // Remove all non-word characters
|
||||||
.replace(/-/, delimiter) // Replace - with delimiter
|
.replace(/-/g, delimiter) // Replace - with delimiter
|
||||||
.replace(new RegExp(`/${delimiter}${delimiter}+/`, "g"), delimiter) // Replace multiple delimiters with single delimiter
|
.replace(new RegExp(`(${delimiter})\\1+`, "g"), "$1") // Replace multiple delimiters with single delimiter
|
||||||
.replace(new RegExp(`/^${delimiter}+/`), "") // Trim delimiter from start of text
|
.replace(new RegExp(`^${delimiter}+`), "") // Trim delimiter from start of text
|
||||||
.replace(new RegExp(`/-+$/`), ""); // Trim delimiter from end of text
|
.replace(new RegExp(`${delimiter}+$`), ""); // Trim delimiter from end of text
|
||||||
};
|
};
|
||||||
|
@ -1,57 +1,57 @@
|
|||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
|
|
||||||
export const iconColorCSS = css`
|
export const iconColorCSS = css`
|
||||||
ha-icon[data-domain="alert"][data-state="on"],
|
ha-state-icon[data-domain="alert"][data-state="on"],
|
||||||
ha-icon[data-domain="automation"][data-state="on"],
|
ha-state-icon[data-domain="automation"][data-state="on"],
|
||||||
ha-icon[data-domain="binary_sensor"][data-state="on"],
|
ha-state-icon[data-domain="binary_sensor"][data-state="on"],
|
||||||
ha-icon[data-domain="calendar"][data-state="on"],
|
ha-state-icon[data-domain="calendar"][data-state="on"],
|
||||||
ha-icon[data-domain="camera"][data-state="streaming"],
|
ha-state-icon[data-domain="camera"][data-state="streaming"],
|
||||||
ha-icon[data-domain="cover"][data-state="open"],
|
ha-state-icon[data-domain="cover"][data-state="open"],
|
||||||
ha-icon[data-domain="fan"][data-state="on"],
|
ha-state-icon[data-domain="fan"][data-state="on"],
|
||||||
ha-icon[data-domain="humidifier"][data-state="on"],
|
ha-state-icon[data-domain="humidifier"][data-state="on"],
|
||||||
ha-icon[data-domain="light"][data-state="on"],
|
ha-state-icon[data-domain="light"][data-state="on"],
|
||||||
ha-icon[data-domain="input_boolean"][data-state="on"],
|
ha-state-icon[data-domain="input_boolean"][data-state="on"],
|
||||||
ha-icon[data-domain="lock"][data-state="unlocked"],
|
ha-state-icon[data-domain="lock"][data-state="unlocked"],
|
||||||
ha-icon[data-domain="media_player"][data-state="on"],
|
ha-state-icon[data-domain="media_player"][data-state="on"],
|
||||||
ha-icon[data-domain="media_player"][data-state="paused"],
|
ha-state-icon[data-domain="media_player"][data-state="paused"],
|
||||||
ha-icon[data-domain="media_player"][data-state="playing"],
|
ha-state-icon[data-domain="media_player"][data-state="playing"],
|
||||||
ha-icon[data-domain="script"][data-state="on"],
|
ha-state-icon[data-domain="script"][data-state="on"],
|
||||||
ha-icon[data-domain="sun"][data-state="above_horizon"],
|
ha-state-icon[data-domain="sun"][data-state="above_horizon"],
|
||||||
ha-icon[data-domain="switch"][data-state="on"],
|
ha-state-icon[data-domain="switch"][data-state="on"],
|
||||||
ha-icon[data-domain="timer"][data-state="active"],
|
ha-state-icon[data-domain="timer"][data-state="active"],
|
||||||
ha-icon[data-domain="vacuum"][data-state="cleaning"],
|
ha-state-icon[data-domain="vacuum"][data-state="cleaning"],
|
||||||
ha-icon[data-domain="group"][data-state="on"],
|
ha-state-icon[data-domain="group"][data-state="on"],
|
||||||
ha-icon[data-domain="group"][data-state="home"],
|
ha-state-icon[data-domain="group"][data-state="home"],
|
||||||
ha-icon[data-domain="group"][data-state="open"],
|
ha-state-icon[data-domain="group"][data-state="open"],
|
||||||
ha-icon[data-domain="group"][data-state="locked"],
|
ha-state-icon[data-domain="group"][data-state="locked"],
|
||||||
ha-icon[data-domain="group"][data-state="problem"] {
|
ha-state-icon[data-domain="group"][data-state="problem"] {
|
||||||
color: var(--paper-item-icon-active-color, #fdd835);
|
color: var(--paper-item-icon-active-color, #fdd835);
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="climate"][data-state="cooling"] {
|
ha-state-icon[data-domain="climate"][data-state="cooling"] {
|
||||||
color: var(--cool-color, var(--state-climate-cool-color));
|
color: var(--cool-color, var(--state-climate-cool-color));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="climate"][data-state="heating"] {
|
ha-state-icon[data-domain="climate"][data-state="heating"] {
|
||||||
color: var(--heat-color, var(--state-climate-heat-color));
|
color: var(--heat-color, var(--state-climate-heat-color));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
ha-state-icon[data-domain="climate"][data-state="drying"] {
|
||||||
color: var(--dry-color, var(--state-climate-dry-color));
|
color: var(--dry-color, var(--state-climate-dry-color));
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="alarm_control_panel"] {
|
ha-state-icon[data-domain="alarm_control_panel"] {
|
||||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||||
}
|
}
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
ha-state-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||||
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
||||||
}
|
}
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
ha-state-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
ha-state-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||||
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
||||||
animation: pulse 1s infinite;
|
animation: pulse 1s infinite;
|
||||||
}
|
}
|
||||||
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
ha-state-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||||
color: var(--alarm-color-triggered, var(--label-badge-red));
|
color: var(--alarm-color-triggered, var(--label-badge-red));
|
||||||
animation: pulse 1s infinite;
|
animation: pulse 1s infinite;
|
||||||
}
|
}
|
||||||
@ -68,13 +68,13 @@ export const iconColorCSS = css`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ha-icon[data-domain="plant"][data-state="problem"],
|
ha-state-icon[data-domain="plant"][data-state="problem"],
|
||||||
ha-icon[data-domain="zwave"][data-state="dead"] {
|
ha-state-icon[data-domain="zwave"][data-state="dead"] {
|
||||||
color: var(--state-icon-error-color);
|
color: var(--state-icon-error-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Color the icon if unavailable */
|
/* Color the icon if unavailable */
|
||||||
ha-icon[data-state="unavailable"] {
|
ha-state-icon[data-state="unavailable"] {
|
||||||
color: var(--state-unavailable-color);
|
color: var(--state-unavailable-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -12,21 +12,19 @@ import { HomeAssistant } from "../../types";
|
|||||||
import "./ha-chart-base";
|
import "./ha-chart-base";
|
||||||
import type { TimeLineData } from "./timeline-chart/const";
|
import type { TimeLineData } from "./timeline-chart/const";
|
||||||
|
|
||||||
/** Binary sensor device classes for which the static colors for on/off need to be inverted.
|
/** Binary sensor device classes for which the static colors for on/off are NOT inverted.
|
||||||
* List the ones were "off" = good or normal state = should be rendered "green".
|
* List the ones were "on" = good or normal state => should be rendered "green".
|
||||||
|
* Note: It is now a "not inverted" list (compared to the past) since we now have more inverted ones.
|
||||||
*/
|
*/
|
||||||
const BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED = new Set([
|
const BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED = new Set([
|
||||||
"battery",
|
"battery_charging",
|
||||||
"door",
|
"connectivity",
|
||||||
"garage_door",
|
"light",
|
||||||
"gas",
|
"moving",
|
||||||
"lock",
|
"plug",
|
||||||
"motion",
|
"power",
|
||||||
"opening",
|
"presence",
|
||||||
"problem",
|
"update",
|
||||||
"safety",
|
|
||||||
"smoke",
|
|
||||||
"window",
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const STATIC_STATE_COLORS = new Set([
|
const STATIC_STATE_COLORS = new Set([
|
||||||
@ -47,7 +45,7 @@ const invertOnOff = (entityState?: HassEntity) =>
|
|||||||
entityState &&
|
entityState &&
|
||||||
computeDomain(entityState.entity_id) === "binary_sensor" &&
|
computeDomain(entityState.entity_id) === "binary_sensor" &&
|
||||||
"device_class" in entityState.attributes &&
|
"device_class" in entityState.attributes &&
|
||||||
BINARY_SENSOR_DEVICE_CLASS_COLOR_INVERTED.has(
|
!BINARY_SENSOR_DEVICE_CLASS_COLOR_NOT_INVERTED.has(
|
||||||
entityState.attributes.device_class!
|
entityState.attributes.device_class!
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ import "../ha-checkbox";
|
|||||||
import type { HaCheckbox } from "../ha-checkbox";
|
import type { HaCheckbox } from "../ha-checkbox";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import { filterData, sortData } from "./sort-filter";
|
import { filterData, sortData } from "./sort-filter";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -69,7 +70,7 @@ export interface DataTableSortColumnData {
|
|||||||
|
|
||||||
export interface DataTableColumnData extends DataTableSortColumnData {
|
export interface DataTableColumnData extends DataTableSortColumnData {
|
||||||
title: TemplateResult | string;
|
title: TemplateResult | string;
|
||||||
type?: "numeric" | "icon" | "icon-button";
|
type?: "numeric" | "icon" | "icon-button" | "overflow-menu";
|
||||||
template?: <T>(data: any, row: T) => TemplateResult | string;
|
template?: <T>(data: any, row: T) => TemplateResult | string;
|
||||||
width?: string;
|
width?: string;
|
||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
@ -93,6 +94,8 @@ export interface SortableColumnContainer {
|
|||||||
|
|
||||||
@customElement("ha-data-table")
|
@customElement("ha-data-table")
|
||||||
export class HaDataTable extends LitElement {
|
export class HaDataTable extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||||
|
|
||||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||||
@ -232,6 +235,7 @@ export class HaDataTable extends LitElement {
|
|||||||
? html`
|
? html`
|
||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
<search-input
|
<search-input
|
||||||
|
.hass=${this.hass}
|
||||||
@value-changed=${this._handleSearchChange}
|
@value-changed=${this._handleSearchChange}
|
||||||
.label=${this.searchLabel}
|
.label=${this.searchLabel}
|
||||||
.noLabelFloat=${this.noLabelFloat}
|
.noLabelFloat=${this.noLabelFloat}
|
||||||
@ -277,15 +281,13 @@ export class HaDataTable extends LitElement {
|
|||||||
}
|
}
|
||||||
const sorted = key === this._sortColumn;
|
const sorted = key === this._sortColumn;
|
||||||
const classes = {
|
const classes = {
|
||||||
"mdc-data-table__header-cell--numeric": Boolean(
|
"mdc-data-table__header-cell--numeric":
|
||||||
column.type === "numeric"
|
column.type === "numeric",
|
||||||
),
|
"mdc-data-table__header-cell--icon": column.type === "icon",
|
||||||
"mdc-data-table__header-cell--icon": Boolean(
|
"mdc-data-table__header-cell--icon-button":
|
||||||
column.type === "icon"
|
column.type === "icon-button",
|
||||||
),
|
"mdc-data-table__header-cell--overflow-menu":
|
||||||
"mdc-data-table__header-cell--icon-button": Boolean(
|
column.type === "overflow-menu",
|
||||||
column.type === "icon-button"
|
|
||||||
),
|
|
||||||
sortable: Boolean(column.sortable),
|
sortable: Boolean(column.sortable),
|
||||||
"not-sorted": Boolean(column.sortable && !sorted),
|
"not-sorted": Boolean(column.sortable && !sorted),
|
||||||
grows: Boolean(column.grows),
|
grows: Boolean(column.grows),
|
||||||
@ -401,14 +403,14 @@ export class HaDataTable extends LitElement {
|
|||||||
<div
|
<div
|
||||||
role="cell"
|
role="cell"
|
||||||
class="mdc-data-table__cell ${classMap({
|
class="mdc-data-table__cell ${classMap({
|
||||||
"mdc-data-table__cell--numeric": Boolean(
|
"mdc-data-table__cell--numeric":
|
||||||
column.type === "numeric"
|
column.type === "numeric",
|
||||||
),
|
"mdc-data-table__cell--icon":
|
||||||
"mdc-data-table__cell--icon": Boolean(
|
column.type === "icon",
|
||||||
column.type === "icon"
|
|
||||||
),
|
|
||||||
"mdc-data-table__cell--icon-button":
|
"mdc-data-table__cell--icon-button":
|
||||||
Boolean(column.type === "icon-button"),
|
column.type === "icon-button",
|
||||||
|
"mdc-data-table__cell--overflow-menu":
|
||||||
|
column.type === "overflow-menu",
|
||||||
grows: Boolean(column.grows),
|
grows: Boolean(column.grows),
|
||||||
forceLTR: Boolean(column.forceLTR),
|
forceLTR: Boolean(column.forceLTR),
|
||||||
})}"
|
})}"
|
||||||
@ -743,10 +745,16 @@ export class HaDataTable extends LitElement {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell--icon:first-child ha-icon {
|
.mdc-data-table__cell--icon:first-child ha-icon,
|
||||||
|
.mdc-data-table__cell--icon:first-child ha-state-icon,
|
||||||
|
.mdc-data-table__cell--icon:first-child ha-svg-icon {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon {
|
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-icon,
|
||||||
|
:host([dir="rtl"])
|
||||||
|
.mdc-data-table__cell--icon:first-child
|
||||||
|
ha-state-icon,
|
||||||
|
:host([dir="rtl"]) .mdc-data-table__cell--icon:first-child ha-svg-icon {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
@ -759,40 +767,65 @@ export class HaDataTable extends LitElement {
|
|||||||
margin-left: -8px;
|
margin-left: -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--overflow-menu,
|
||||||
|
.mdc-data-table__header-cell--overflow-menu,
|
||||||
.mdc-data-table__header-cell--icon-button,
|
.mdc-data-table__header-cell--icon-button,
|
||||||
.mdc-data-table__cell--icon-button {
|
.mdc-data-table__cell--icon-button {
|
||||||
width: 56px;
|
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__header-cell--icon-button,
|
||||||
|
.mdc-data-table__cell--icon-button {
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--overflow-menu,
|
||||||
.mdc-data-table__cell--icon-button {
|
.mdc-data-table__cell--icon-button {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
text-overflow: clip;
|
text-overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__header-cell--icon-button:first-child,
|
.mdc-data-table__header-cell--icon-button:first-child,
|
||||||
.mdc-data-table__cell--icon-button:first-child {
|
.mdc-data-table__cell--icon-button:first-child,
|
||||||
width: 64px;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
:host([dir="rtl"])
|
|
||||||
.mdc-data-table__header-cell--icon-button:first-child,
|
|
||||||
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:first-child {
|
|
||||||
padding-left: auto;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdc-data-table__header-cell--icon-button:last-child,
|
.mdc-data-table__header-cell--icon-button:last-child,
|
||||||
.mdc-data-table__cell--icon-button:last-child {
|
.mdc-data-table__cell--icon-button:last-child {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child,
|
|
||||||
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child {
|
|
||||||
padding-right: auto;
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--overflow-menu:first-child,
|
||||||
|
.mdc-data-table__header-cell--overflow-menu:first-child,
|
||||||
|
.mdc-data-table__header-cell--icon-button:first-child,
|
||||||
|
.mdc-data-table__cell--icon-button:first-child {
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
:host([dir="rtl"])
|
||||||
|
.mdc-data-table__header-cell--overflow-menu:first-child,
|
||||||
|
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child,
|
||||||
|
:host([dir="rtl"])
|
||||||
|
.mdc-data-table__header-cell--overflow-menu:first-child,
|
||||||
|
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:first-child {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdc-data-table__cell--overflow-menu:last-child,
|
||||||
|
.mdc-data-table__header-cell--overflow-menu:last-child,
|
||||||
|
.mdc-data-table__header-cell--icon-button:last-child,
|
||||||
|
.mdc-data-table__cell--icon-button:last-child {
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
:host([dir="rtl"])
|
||||||
|
.mdc-data-table__header-cell--overflow-menu:last-child,
|
||||||
|
:host([dir="rtl"]) .mdc-data-table__cell--overflow-menu:last-child,
|
||||||
|
:host([dir="rtl"]) .mdc-data-table__header-cell--icon-button:last-child,
|
||||||
|
:host([dir="rtl"]) .mdc-data-table__cell--icon-button:last-child {
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-left: 16px;
|
||||||
|
}
|
||||||
|
.mdc-data-table__cell--overflow-menu,
|
||||||
|
.mdc-data-table__header-cell--overflow-menu {
|
||||||
|
overflow: initial;
|
||||||
|
}
|
||||||
.mdc-data-table__cell--icon-button a {
|
.mdc-data-table__cell--icon-button a {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -37,6 +36,7 @@ import {
|
|||||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./ha-devices-picker";
|
import "./ha-devices-picker";
|
||||||
|
|
||||||
@ -324,29 +324,25 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
>
|
>
|
||||||
<div class="suffix" slot="suffix">
|
<div class="suffix" slot="suffix">
|
||||||
${this.value
|
${this.value
|
||||||
? html`<mwc-icon-button
|
? html`<ha-icon-button
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.device-picker.clear"
|
"ui.components.device-picker.clear"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiClose}
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
no-ripple
|
no-ripple
|
||||||
>
|
></ha-icon-button> `
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button> `
|
|
||||||
: ""}
|
: ""}
|
||||||
${areas.length > 0
|
${areas.length > 0
|
||||||
? html`
|
? html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.device-picker.show_devices"
|
"ui.components.device-picker.show_devices"
|
||||||
)}
|
)}
|
||||||
class="toggle-button"
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
></ha-svg-icon>
|
class="toggle-button"
|
||||||
</mwc-icon-button>
|
></ha-icon-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
@ -408,7 +404,7 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
.suffix {
|
.suffix {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -338,7 +338,7 @@ export class HaDevicePicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-input > mwc-icon-button {
|
paper-input > ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
/**
|
|
||||||
@license
|
|
||||||
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
|
||||||
This code may only be used under the BSD style license found at
|
|
||||||
http://polymer.github.io/LICENSE.txt The complete set of authors may be found at
|
|
||||||
http://polymer.github.io/AUTHORS.txt The complete set of contributors may be
|
|
||||||
found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as
|
|
||||||
part of the polymer project is also subject to an additional IP rights grant
|
|
||||||
found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
Fixes issue with not using shadow dom properly in iron-overlay-behavior/icon-focusables-helper.js
|
|
||||||
*/
|
|
||||||
import { IronFocusablesHelper } from "@polymer/iron-overlay-behavior/iron-focusables-helper";
|
|
||||||
import { dom } from "@polymer/polymer/lib/legacy/polymer.dom";
|
|
||||||
|
|
||||||
export const HaIronFocusablesHelper = {
|
|
||||||
/**
|
|
||||||
* Returns a sorted array of tabbable nodes, including the root node.
|
|
||||||
* It searches the tabbable nodes in the light and shadow dom of the chidren,
|
|
||||||
* sorting the result by tabindex.
|
|
||||||
* @param {!Node} node
|
|
||||||
* @return {!Array<!HTMLElement>}
|
|
||||||
*/
|
|
||||||
getTabbableNodes: function (node) {
|
|
||||||
const result = [];
|
|
||||||
// If there is at least one element with tabindex > 0, we need to sort
|
|
||||||
// the final array by tabindex.
|
|
||||||
const needsSortByTabIndex = this._collectTabbableNodes(node, result);
|
|
||||||
if (needsSortByTabIndex) {
|
|
||||||
return IronFocusablesHelper._sortByTabIndex(result);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for nodes that are tabbable and adds them to the `result` array.
|
|
||||||
* Returns if the `result` array needs to be sorted by tabindex.
|
|
||||||
* @param {!Node} node The starting point for the search; added to `result`
|
|
||||||
* if tabbable.
|
|
||||||
* @param {!Array<!HTMLElement>} result
|
|
||||||
* @return {boolean}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_collectTabbableNodes: function (node, result) {
|
|
||||||
// If not an element or not visible, no need to explore children.
|
|
||||||
if (
|
|
||||||
node.nodeType !== Node.ELEMENT_NODE ||
|
|
||||||
!IronFocusablesHelper._isVisible(node)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const element = /** @type {!HTMLElement} */ (node);
|
|
||||||
const tabIndex = IronFocusablesHelper._normalizedTabIndex(element);
|
|
||||||
let needsSort = tabIndex > 0;
|
|
||||||
if (tabIndex >= 0) {
|
|
||||||
result.push(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
// In ShadowDOM v1, tab order is affected by the order of distrubution.
|
|
||||||
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B];
|
|
||||||
// in ShadowDOM v0 tab order is not affected by the distrubution order,
|
|
||||||
// in fact getTabbableNodes(#root) returns [#B, #A].
|
|
||||||
// <div id="root">
|
|
||||||
// <!-- shadow -->
|
|
||||||
// <slot name="a">
|
|
||||||
// <slot name="b">
|
|
||||||
// <!-- /shadow -->
|
|
||||||
// <input id="A" slot="a">
|
|
||||||
// <input id="B" slot="b" tabindex="1">
|
|
||||||
// </div>
|
|
||||||
// TODO(valdrin) support ShadowDOM v1 when upgrading to Polymer v2.0.
|
|
||||||
let children;
|
|
||||||
if (element.localName === "content" || element.localName === "slot") {
|
|
||||||
children = dom(element).getDistributedNodes();
|
|
||||||
} else {
|
|
||||||
// /////////////////////////
|
|
||||||
// Use shadow root if possible, will check for distributed nodes.
|
|
||||||
// THIS IS THE CHANGED LINE
|
|
||||||
children = dom(element.shadowRoot || element.root || element).children;
|
|
||||||
// /////////////////////////
|
|
||||||
}
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
// Ensure method is always invoked to collect tabbable children.
|
|
||||||
needsSort = this._collectTabbableNodes(children[i], result) || needsSort;
|
|
||||||
}
|
|
||||||
return needsSort;
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
import "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import type { PaperDialogElement } from "@polymer/paper-dialog/paper-dialog";
|
|
||||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
|
||||||
import type { Constructor } from "../../types";
|
|
||||||
import { HaIronFocusablesHelper } from "./ha-iron-focusables-helper";
|
|
||||||
|
|
||||||
const paperDialogClass = customElements.get(
|
|
||||||
"paper-dialog"
|
|
||||||
) as Constructor<PaperDialogElement>;
|
|
||||||
|
|
||||||
// behavior that will override existing iron-overlay-behavior and call the fixed implementation
|
|
||||||
const haTabFixBehaviorImpl = {
|
|
||||||
get _focusableNodes() {
|
|
||||||
return HaIronFocusablesHelper.getTabbableNodes(this);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// paper-dialog that uses the haTabFixBehaviorImpl behavior
|
|
||||||
// export class HaPaperDialog extends paperDialogClass {}
|
|
||||||
// @ts-ignore
|
|
||||||
export class HaPaperDialog
|
|
||||||
extends mixinBehaviors([haTabFixBehaviorImpl], paperDialogClass)
|
|
||||||
implements PaperDialogElement {}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"ha-paper-dialog": HaPaperDialog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
customElements.define("ha-paper-dialog", HaPaperDialog);
|
|
@ -1,7 +1,7 @@
|
|||||||
import { html, LitElement } from "lit";
|
import { html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { batteryIcon } from "../../common/entity/battery_icon";
|
import { batteryStateIcon } from "../../common/entity/battery_icon";
|
||||||
import "../ha-icon";
|
import "../ha-svg-icon";
|
||||||
|
|
||||||
@customElement("ha-battery-icon")
|
@customElement("ha-battery-icon")
|
||||||
export class HaBatteryIcon extends LitElement {
|
export class HaBatteryIcon extends LitElement {
|
||||||
@ -11,9 +11,18 @@ export class HaBatteryIcon extends LitElement {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<ha-icon
|
<ha-svg-icon
|
||||||
.icon=${batteryIcon(this.batteryStateObj, this.batteryChargingStateObj)}
|
.path=${batteryStateIcon(
|
||||||
></ha-icon>
|
this.batteryStateObj,
|
||||||
|
this.batteryChargingStateObj
|
||||||
|
)}
|
||||||
|
></ha-svg-icon>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-battery-icon": HaBatteryIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -18,6 +17,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
|||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import { formatAttributeName } from "../../util/hass-attributes-util";
|
import { formatAttributeName } from "../../util/hass-attributes-util";
|
||||||
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
|
|
||||||
@ -114,31 +114,27 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
<div class="suffix" slot="suffix">
|
<div class="suffix" slot="suffix">
|
||||||
${this.value
|
${this.value
|
||||||
? html`
|
? html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.entity.entity-picker.clear"
|
"ui.components.entity.entity-picker.clear"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiClose}
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
no-ripple
|
no-ripple
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.entity.entity-attribute-picker.show_attributes"
|
"ui.components.entity.entity-attribute-picker.show_attributes"
|
||||||
)}
|
)}
|
||||||
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
class="toggle-button"
|
class="toggle-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</div>
|
</div>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
@ -178,7 +174,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
.suffix {
|
.suffix {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
@ -21,6 +20,7 @@ import { computeDomain } from "../../common/entity/compute_domain";
|
|||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
import "../ha-icon-button";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
|
|
||||||
@ -267,31 +267,27 @@ export class HaEntityPicker extends LitElement {
|
|||||||
<div class="suffix" slot="suffix">
|
<div class="suffix" slot="suffix">
|
||||||
${this.value && !this.hideClearIcon
|
${this.value && !this.hideClearIcon
|
||||||
? html`
|
? html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.entity.entity-picker.clear"
|
"ui.components.entity.entity-picker.clear"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiClose}
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
no-ripple
|
no-ripple
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.entity.entity-picker.show_entities"
|
"ui.components.entity.entity-picker.show_entities"
|
||||||
)}
|
)}
|
||||||
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
class="toggle-button"
|
class="toggle-button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</div>
|
</div>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
@ -340,7 +336,7 @@ export class HaEntityPicker extends LitElement {
|
|||||||
.suffix {
|
.suffix {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiFlash, mdiFlashOff } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -41,15 +42,15 @@ export class HaEntityToggle extends LitElement {
|
|||||||
if (this.stateObj.attributes.assumed_state) {
|
if (this.stateObj.attributes.assumed_state) {
|
||||||
return html`
|
return html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
aria-label=${`Turn ${computeStateName(this.stateObj)} off`}
|
.label=${`Turn ${computeStateName(this.stateObj)} off`}
|
||||||
icon="hass:flash-off"
|
.path=${mdiFlashOff}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@click=${this._turnOff}
|
@click=${this._turnOff}
|
||||||
?state-active=${!this._isOn}
|
?state-active=${!this._isOn}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
aria-label=${`Turn ${computeStateName(this.stateObj)} on`}
|
.label=${`Turn ${computeStateName(this.stateObj)} on`}
|
||||||
icon="hass:flash"
|
.path=${mdiFlash}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
@click=${this._turnOn}
|
@click=${this._turnOn}
|
||||||
?state-active=${this._isOn}
|
?state-active=${this._isOn}
|
||||||
|
@ -14,19 +14,18 @@ import secondsToDuration from "../../common/datetime/seconds_to_duration";
|
|||||||
import { computeStateDisplay } from "../../common/entity/compute_state_display";
|
import { computeStateDisplay } from "../../common/entity/compute_state_display";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||||
import { stateIcon } from "../../common/entity/state_icon";
|
|
||||||
import { formatNumber } from "../../common/number/format_number";
|
import { formatNumber } from "../../common/number/format_number";
|
||||||
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
import { UNAVAILABLE, UNKNOWN } from "../../data/entity";
|
||||||
import { timerTimeRemaining } from "../../data/timer";
|
import { timerTimeRemaining } from "../../data/timer";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-label-badge";
|
import "../ha-label-badge";
|
||||||
import "../ha-icon";
|
import "../ha-state-icon";
|
||||||
|
|
||||||
@customElement("ha-state-label-badge")
|
@customElement("ha-state-label-badge")
|
||||||
export class HaStateLabelBadge extends LitElement {
|
export class HaStateLabelBadge extends LitElement {
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property() public state?: HassEntity;
|
@property({ attribute: false }) public state?: HassEntity;
|
||||||
|
|
||||||
@property() public name?: string;
|
@property() public name?: string;
|
||||||
|
|
||||||
@ -69,16 +68,23 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rendering priority inside badge:
|
||||||
|
// 1. Icon directly defined in badge config
|
||||||
|
// 2. Image directly defined in badge config
|
||||||
|
// 3. Image taken from entity picture
|
||||||
|
// 4. Icon determined via entity state
|
||||||
|
// 5. Value string as fallback
|
||||||
const domain = computeStateDomain(entityState);
|
const domain = computeStateDomain(entityState);
|
||||||
|
|
||||||
const value = this._computeValue(domain, entityState);
|
const showIcon = this.icon || this._computeShowIcon(domain, entityState);
|
||||||
const icon = this.icon ? this.icon : this._computeIcon(domain, entityState);
|
|
||||||
const image = this.icon
|
const image = this.icon
|
||||||
? ""
|
? ""
|
||||||
: this.image
|
: this.image
|
||||||
? this.image
|
? this.image
|
||||||
: entityState.attributes.entity_picture_local ||
|
: entityState.attributes.entity_picture_local ||
|
||||||
entityState.attributes.entity_picture;
|
entityState.attributes.entity_picture;
|
||||||
|
const value =
|
||||||
|
!image && !showIcon ? this._computeValue(domain, entityState) : undefined;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-label-badge
|
<ha-label-badge
|
||||||
@ -95,8 +101,13 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.description=${this.name ?? computeStateName(entityState)}
|
.description=${this.name ?? computeStateName(entityState)}
|
||||||
>
|
>
|
||||||
${!image && icon ? html`<ha-icon .icon=${icon}></ha-icon>` : ""}
|
${!image && showIcon
|
||||||
${value && (this.icon || !this.image)
|
? html`<ha-state-icon
|
||||||
|
.icon=${this.icon}
|
||||||
|
.state=${entityState}
|
||||||
|
></ha-state-icon>`
|
||||||
|
: ""}
|
||||||
|
${value && !image && !showIcon
|
||||||
? html`<span class=${value && value.length > 4 ? "big" : ""}
|
? html`<span class=${value && value.length > 4 ? "big" : ""}
|
||||||
>${value}</span
|
>${value}</span
|
||||||
>`
|
>`
|
||||||
@ -144,9 +155,9 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeIcon(domain: string, entityState: HassEntity) {
|
private _computeShowIcon(domain: string, entityState: HassEntity): boolean {
|
||||||
if (entityState.state === UNAVAILABLE) {
|
if (entityState.state === UNAVAILABLE) {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
switch (domain) {
|
switch (domain) {
|
||||||
case "alarm_control_panel":
|
case "alarm_control_panel":
|
||||||
@ -156,17 +167,13 @@ export class HaStateLabelBadge extends LitElement {
|
|||||||
case "person":
|
case "person":
|
||||||
case "scene":
|
case "scene":
|
||||||
case "sun":
|
case "sun":
|
||||||
return stateIcon(entityState);
|
return true;
|
||||||
case "timer":
|
case "timer":
|
||||||
return entityState.state === "active"
|
return true;
|
||||||
? "hass:timer-outline"
|
|
||||||
: "hass:timer-off-outline";
|
|
||||||
case "sensor":
|
case "sensor":
|
||||||
return entityState.attributes.device_class === "moon__phase"
|
return entityState.attributes.device_class === "moon__phase";
|
||||||
? stateIcon(entityState)
|
|
||||||
: null;
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiCheck } from "@mdi/js";
|
import { mdiCheck } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import "@polymer/paper-item/paper-icon-item";
|
||||||
@ -289,7 +288,7 @@ export class HaStatisticPicker extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-input > mwc-icon-button {
|
paper-input > ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiAlert } from "@mdi/js";
|
||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -12,10 +13,9 @@ import { ifDefined } from "lit/directives/if-defined";
|
|||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { computeActiveState } from "../../common/entity/compute_active_state";
|
import { computeActiveState } from "../../common/entity/compute_active_state";
|
||||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||||
import { stateIcon } from "../../common/entity/state_icon";
|
|
||||||
import { iconColorCSS } from "../../common/style/icon_color_css";
|
import { iconColorCSS } from "../../common/style/icon_color_css";
|
||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import "../ha-icon";
|
import "../ha-state-icon";
|
||||||
|
|
||||||
export class StateBadge extends LitElement {
|
export class StateBadge extends LitElement {
|
||||||
public hass?: HomeAssistant;
|
public hass?: HomeAssistant;
|
||||||
@ -39,7 +39,7 @@ export class StateBadge extends LitElement {
|
|||||||
// We either need a `stateObj` or one override
|
// We either need a `stateObj` or one override
|
||||||
if (!stateObj && !this.overrideIcon && !this.overrideImage) {
|
if (!stateObj && !this.overrideIcon && !this.overrideImage) {
|
||||||
return html`<div class="missing">
|
return html`<div class="missing">
|
||||||
<ha-icon icon="hass:alert"></ha-icon>
|
<ha-svg-icon .path=${mdiAlert}></ha-svg-icon>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,8 +49,7 @@ export class StateBadge extends LitElement {
|
|||||||
|
|
||||||
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
const domain = stateObj ? computeStateDomain(stateObj) : undefined;
|
||||||
|
|
||||||
return html`
|
return html`<ha-state-icon
|
||||||
<ha-icon
|
|
||||||
style=${styleMap(this._iconStyle)}
|
style=${styleMap(this._iconStyle)}
|
||||||
data-domain=${ifDefined(
|
data-domain=${ifDefined(
|
||||||
this.stateColor || (domain === "light" && this.stateColor !== false)
|
this.stateColor || (domain === "light" && this.stateColor !== false)
|
||||||
@ -58,9 +57,9 @@ export class StateBadge extends LitElement {
|
|||||||
: undefined
|
: undefined
|
||||||
)}
|
)}
|
||||||
data-state=${stateObj ? computeActiveState(stateObj) : ""}
|
data-state=${stateObj ? computeActiveState(stateObj) : ""}
|
||||||
.icon=${this.overrideIcon || (stateObj ? stateIcon(stateObj) : "")}
|
.icon=${this.overrideIcon}
|
||||||
></ha-icon>
|
.state=${stateObj}
|
||||||
`;
|
></ha-state-icon>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
public willUpdate(changedProps: PropertyValues) {
|
||||||
@ -154,7 +153,7 @@ export class StateBadge extends LitElement {
|
|||||||
:host([icon]:focus) {
|
:host([icon]:focus) {
|
||||||
background: var(--divider-color);
|
background: var(--divider-color);
|
||||||
}
|
}
|
||||||
ha-icon {
|
ha-state-icon {
|
||||||
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
transition: color 0.3s ease-in-out, filter 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
.missing {
|
.missing {
|
||||||
|
@ -53,6 +53,7 @@ class StateInfo extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.last_changed}
|
.datetime=${this.stateObj.last_changed}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -64,6 +65,7 @@ class StateInfo extends LitElement {
|
|||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.datetime=${this.stateObj.last_updated}
|
.datetime=${this.stateObj.last_updated}
|
||||||
|
capitalize
|
||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import {
|
import {
|
||||||
mdiAlertCircleOutline,
|
mdiAlertCircleOutline,
|
||||||
mdiAlertOutline,
|
mdiAlertOutline,
|
||||||
@ -11,6 +10,7 @@ import { css, html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import "./ha-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
const ALERT_ICONS = {
|
const ALERT_ICONS = {
|
||||||
@ -66,12 +66,11 @@ class HaAlert extends LitElement {
|
|||||||
.label=${this.actionText}
|
.label=${this.actionText}
|
||||||
></mwc-button>`
|
></mwc-button>`
|
||||||
: this.dismissable
|
: this.dismissable
|
||||||
? html`<mwc-icon-button
|
? html`<ha-icon-button
|
||||||
@click=${this._dismiss_clicked}
|
@click=${this._dismiss_clicked}
|
||||||
aria-label="Dismiss alert"
|
label="Dismiss alert"
|
||||||
>
|
.path=${mdiClose}
|
||||||
<ha-svg-icon .path=${mdiClose}> </ha-svg-icon>
|
></ha-icon-button>`
|
||||||
</mwc-icon-button> `
|
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -140,7 +139,7 @@ class HaAlert extends LitElement {
|
|||||||
mwc-button {
|
mwc-button {
|
||||||
--mdc-theme-primary: var(--primary-text-color);
|
--mdc-theme-primary: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
--mdc-icon-button-size: 36px;
|
--mdc-icon-button-size: 36px;
|
||||||
}
|
}
|
||||||
.issue-type.info > .icon {
|
.issue-type.info > .icon {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiCheck, mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -42,6 +41,7 @@ import { SubscribeMixin } from "../mixins/subscribe-mixin";
|
|||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
import type { HaDevicePickerDeviceFilterFunc } from "./device/ha-device-picker";
|
||||||
|
import "./ha-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
|
||||||
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
const rowRenderer: ComboBoxLitRenderer<AreaRegistryEntry> = (
|
||||||
@ -362,28 +362,24 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
>
|
>
|
||||||
${this.value
|
${this.value
|
||||||
? html`
|
? html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.components.area-picker.clear"
|
"ui.components.area-picker.clear"
|
||||||
)}
|
)}
|
||||||
|
.path=${mdiClose}
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.components.area-picker.toggle")}
|
.label=${this.hass.localize("ui.components.area-picker.toggle")}
|
||||||
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
class="toggle-button"
|
class="toggle-button"
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
`;
|
`;
|
||||||
@ -457,7 +453,7 @@ export class HaAreaPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-input > mwc-icon-button {
|
paper-input > ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button";
|
|
||||||
import type { Corner } from "@material/mwc-menu";
|
import type { Corner } from "@material/mwc-menu";
|
||||||
import "@material/mwc-menu/mwc-menu-surface";
|
import "@material/mwc-menu/mwc-menu-surface";
|
||||||
import { mdiFilterVariant } from "@mdi/js";
|
import { mdiFilterVariant } from "@mdi/js";
|
||||||
@ -12,7 +11,7 @@ import type { HomeAssistant } from "../types";
|
|||||||
import "./device/ha-device-picker";
|
import "./device/ha-device-picker";
|
||||||
import "./entity/ha-entity-picker";
|
import "./entity/ha-entity-picker";
|
||||||
import "./ha-area-picker";
|
import "./ha-area-picker";
|
||||||
import "./ha-svg-icon";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// for fire event
|
// for fire event
|
||||||
@ -55,9 +54,10 @@ export class HaRelatedFilterButtonMenu extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<mwc-icon-button @click=${this._handleClick}>
|
<ha-icon-button
|
||||||
<ha-svg-icon .path=${mdiFilterVariant}></ha-svg-icon>
|
@click=${this._handleClick}
|
||||||
</mwc-icon-button>
|
.path=${mdiFilterVariant}
|
||||||
|
></ha-icon-button>
|
||||||
<mwc-menu-surface
|
<mwc-menu-surface
|
||||||
.open=${this._open}
|
.open=${this._open}
|
||||||
.anchor=${this}
|
.anchor=${this}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import { Button } from "@material/mwc-button/mwc-button";
|
||||||
import type { Button } from "@material/mwc-button/mwc-button";
|
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, queryAll } from "lit/decorators";
|
import { customElement, property, queryAll } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import type { ToggleButton } from "../types";
|
import type { ToggleButton } from "../types";
|
||||||
import "./ha-svg-icon";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
@customElement("ha-button-toggle-group")
|
@customElement("ha-button-toggle-group")
|
||||||
export class HaButtonToggleGroup extends LitElement {
|
export class HaButtonToggleGroup extends LitElement {
|
||||||
@ -25,14 +23,13 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
<div>
|
<div>
|
||||||
${this.buttons.map((button) =>
|
${this.buttons.map((button) =>
|
||||||
button.iconPath
|
button.iconPath
|
||||||
? html`<mwc-icon-button
|
? html`<ha-icon-button
|
||||||
.label=${button.label}
|
.label=${button.label}
|
||||||
|
.path=${button.iconPath}
|
||||||
.value=${button.value}
|
.value=${button.value}
|
||||||
?active=${this.active === button.value}
|
?active=${this.active === button.value}
|
||||||
@click=${this._handleClick}
|
@click=${this._handleClick}
|
||||||
>
|
></ha-icon-button>`
|
||||||
<ha-svg-icon .path=${button.iconPath}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>`
|
|
||||||
: html`<mwc-button
|
: html`<mwc-button
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
width: this.fullWidth
|
width: this.fullWidth
|
||||||
@ -77,16 +74,16 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
--mdc-shape-small: 0;
|
--mdc-shape-small: 0;
|
||||||
--mdc-button-outline-width: 1px 0 1px 1px;
|
--mdc-button-outline-width: 1px 0 1px 1px;
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
border: 1px solid var(--primary-color);
|
border: 1px solid var(--primary-color);
|
||||||
border-right-width: 0px;
|
border-right-width: 0px;
|
||||||
}
|
}
|
||||||
mwc-icon-button,
|
ha-icon-button,
|
||||||
mwc-button {
|
mwc-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
mwc-icon-button::before,
|
ha-icon-button::before,
|
||||||
mwc-button::before {
|
mwc-button::before {
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -99,23 +96,23 @@ export class HaButtonToggleGroup extends LitElement {
|
|||||||
content: "";
|
content: "";
|
||||||
transition: opacity 15ms linear, background-color 15ms linear;
|
transition: opacity 15ms linear, background-color 15ms linear;
|
||||||
}
|
}
|
||||||
mwc-icon-button[active]::before,
|
ha-icon-button[active]::before,
|
||||||
mwc-button[active]::before {
|
mwc-button[active]::before {
|
||||||
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
||||||
}
|
}
|
||||||
mwc-icon-button:first-child,
|
ha-icon-button:first-child,
|
||||||
mwc-button:first-child {
|
mwc-button:first-child {
|
||||||
--mdc-shape-small: 4px 0 0 4px;
|
--mdc-shape-small: 4px 0 0 4px;
|
||||||
border-radius: 4px 0 0 4px;
|
border-radius: 4px 0 0 4px;
|
||||||
}
|
}
|
||||||
mwc-icon-button:last-child,
|
ha-icon-button:last-child,
|
||||||
mwc-button:last-child {
|
mwc-button:last-child {
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
--mdc-shape-small: 0 4px 4px 0;
|
--mdc-shape-small: 0 4px 4px 0;
|
||||||
--mdc-button-outline-width: 1px;
|
--mdc-button-outline-width: 1px;
|
||||||
}
|
}
|
||||||
mwc-icon-button:only-child,
|
ha-icon-button:only-child,
|
||||||
mwc-button:only-child {
|
mwc-button:only-child {
|
||||||
--mdc-shape-small: 4px;
|
--mdc-shape-small: 4px;
|
||||||
border-right-width: 1px;
|
border-right-width: 1px;
|
||||||
|
@ -15,9 +15,12 @@ import {
|
|||||||
CAMERA_SUPPORT_STREAM,
|
CAMERA_SUPPORT_STREAM,
|
||||||
computeMJPEGStreamUrl,
|
computeMJPEGStreamUrl,
|
||||||
fetchStreamUrl,
|
fetchStreamUrl,
|
||||||
|
STREAM_TYPE_HLS,
|
||||||
|
STREAM_TYPE_WEB_RTC,
|
||||||
} from "../data/camera";
|
} from "../data/camera";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-hls-player";
|
import "./ha-hls-player";
|
||||||
|
import "./ha-web-rtc-player";
|
||||||
|
|
||||||
@customElement("ha-camera-stream")
|
@customElement("ha-camera-stream")
|
||||||
class HaCameraStream extends LitElement {
|
class HaCameraStream extends LitElement {
|
||||||
@ -34,8 +37,8 @@ class HaCameraStream extends LitElement {
|
|||||||
@property({ type: Boolean, attribute: "allow-exoplayer" })
|
@property({ type: Boolean, attribute: "allow-exoplayer" })
|
||||||
public allowExoPlayer = false;
|
public allowExoPlayer = false;
|
||||||
|
|
||||||
// We keep track if we should force MJPEG with a string
|
// We keep track if we should force MJPEG if there was a failure
|
||||||
// that way it automatically resets if we change entity.
|
// to get the HLS stream url. This is reset if we change entities.
|
||||||
@state() private _forceMJPEG?: string;
|
@state() private _forceMJPEG?: string;
|
||||||
|
|
||||||
@state() private _url?: string;
|
@state() private _url?: string;
|
||||||
@ -48,7 +51,8 @@ class HaCameraStream extends LitElement {
|
|||||||
!this._shouldRenderMJPEG &&
|
!this._shouldRenderMJPEG &&
|
||||||
this.stateObj &&
|
this.stateObj &&
|
||||||
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
|
(changedProps.get("stateObj") as CameraEntity | undefined)?.entity_id !==
|
||||||
this.stateObj.entity_id
|
this.stateObj.entity_id &&
|
||||||
|
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_HLS
|
||||||
) {
|
) {
|
||||||
this._forceMJPEG = undefined;
|
this._forceMJPEG = undefined;
|
||||||
this._url = undefined;
|
this._url = undefined;
|
||||||
@ -70,24 +74,19 @@ class HaCameraStream extends LitElement {
|
|||||||
if (!this.stateObj) {
|
if (!this.stateObj) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
if (__DEMO__ || this._shouldRenderMJPEG) {
|
||||||
return html`
|
return html` <img
|
||||||
${__DEMO__ || this._shouldRenderMJPEG
|
|
||||||
? html`
|
|
||||||
<img
|
|
||||||
.src=${__DEMO__
|
.src=${__DEMO__
|
||||||
? this.stateObj!.attributes.entity_picture!
|
? this.stateObj.attributes.entity_picture!
|
||||||
: this._connected
|
: this._connected
|
||||||
? computeMJPEGStreamUrl(this.stateObj)
|
? computeMJPEGStreamUrl(this.stateObj)
|
||||||
: ""}
|
: ""}
|
||||||
.alt=${`Preview of the ${computeStateName(
|
.alt=${`Preview of the ${computeStateName(this.stateObj)} camera.`}
|
||||||
this.stateObj
|
/>`;
|
||||||
)} camera.`}
|
}
|
||||||
/>
|
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_HLS) {
|
||||||
`
|
return this._url
|
||||||
: this._url
|
? html` <ha-hls-player
|
||||||
? html`
|
|
||||||
<ha-hls-player
|
|
||||||
autoplay
|
autoplay
|
||||||
playsinline
|
playsinline
|
||||||
.allowExoPlayer=${this.allowExoPlayer}
|
.allowExoPlayer=${this.allowExoPlayer}
|
||||||
@ -95,18 +94,44 @@ class HaCameraStream extends LitElement {
|
|||||||
.controls=${this.controls}
|
.controls=${this.controls}
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.url=${this._url}
|
.url=${this._url}
|
||||||
></ha-hls-player>
|
></ha-hls-player>`
|
||||||
`
|
: html``;
|
||||||
: ""}
|
}
|
||||||
`;
|
if (this.stateObj.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC) {
|
||||||
|
return html` <ha-web-rtc-player
|
||||||
|
autoplay
|
||||||
|
playsinline
|
||||||
|
.muted=${this.muted}
|
||||||
|
.controls=${this.controls}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.entityid=${this.stateObj.entity_id}
|
||||||
|
></ha-web-rtc-player>`;
|
||||||
|
}
|
||||||
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _shouldRenderMJPEG() {
|
private get _shouldRenderMJPEG() {
|
||||||
return (
|
if (this._forceMJPEG === this.stateObj!.entity_id) {
|
||||||
this._forceMJPEG === this.stateObj!.entity_id ||
|
// Fallback when unable to fetch stream url
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
!isComponentLoaded(this.hass!, "stream") ||
|
!isComponentLoaded(this.hass!, "stream") ||
|
||||||
!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)
|
!supportsFeature(this.stateObj!, CAMERA_SUPPORT_STREAM)
|
||||||
);
|
) {
|
||||||
|
// Steaming is not supported by the camera so fallback to MJPEG stream
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
this.stateObj!.attributes.frontend_stream_type === STREAM_TYPE_WEB_RTC &&
|
||||||
|
typeof RTCPeerConnection === "undefined"
|
||||||
|
) {
|
||||||
|
// Stream requires WebRTC but browser does not support, so fallback to
|
||||||
|
// MJPEG stream.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Render stream
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getStreamUrl(): Promise<void> {
|
private async _getStreamUrl(): Promise<void> {
|
||||||
|
@ -3,6 +3,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import { EventsMixin } from "../mixins/events-mixin";
|
import { EventsMixin } from "../mixins/events-mixin";
|
||||||
|
import "./ha-icon";
|
||||||
import "./ha-icon-button";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -40,16 +41,14 @@ class HaClimateControl extends EventsMixin(PolymerElement) {
|
|||||||
<div id="target_temperature">[[value]] [[units]]</div>
|
<div id="target_temperature">[[value]] [[units]]</div>
|
||||||
<div class="control-buttons">
|
<div class="control-buttons">
|
||||||
<div>
|
<div>
|
||||||
<ha-icon-button
|
<ha-icon-button on-click="incrementValue">
|
||||||
icon="hass:chevron-up"
|
<ha-icon icon="hass:chevron-up"></ha-icon>
|
||||||
on-click="incrementValue"
|
</ha-icon-button>
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ha-icon-button
|
<ha-icon-button on-click="decrementValue">
|
||||||
icon="hass:chevron-down"
|
<ha-icon icon="hass:chevron-down"></ha-icon>
|
||||||
on-click="decrementValue"
|
</ha-icon-button>
|
||||||
></ha-icon-button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
@ -11,7 +10,7 @@ import { customElement, property, query, state } from "lit/decorators";
|
|||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { PolymerChangedEvent } from "../polymer-types";
|
import { PolymerChangedEvent } from "../polymer-types";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-svg-icon";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
// eslint-disable-next-line lit/prefer-static-styles
|
// eslint-disable-next-line lit/prefer-static-styles
|
||||||
const defaultRowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
const defaultRowRenderer: ComboBoxLitRenderer<string> = (item) => html`<style>
|
||||||
@ -94,26 +93,22 @@ export class HaComboBox extends LitElement {
|
|||||||
>
|
>
|
||||||
${this.value
|
${this.value
|
||||||
? html`
|
? html`
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.components.combo-box.clear")}
|
.label=${this.hass.localize("ui.components.combo-box.clear")}
|
||||||
|
.path=${mdiClose}
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
class="clear-button"
|
class="clear-button"
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass.localize("ui.components.combo-box.show")}
|
.label=${this.hass.localize("ui.components.combo-box.show")}
|
||||||
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
class="toggle-button"
|
class="toggle-button"
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon
|
|
||||||
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
`;
|
`;
|
||||||
@ -146,7 +141,7 @@ export class HaComboBox extends LitElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-input > mwc-icon-button {
|
paper-input > ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiStop } from "@mdi/js";
|
||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -45,10 +46,11 @@ class HaCoverControls extends LitElement {
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.open_cover"
|
"ui.dialogs.more_info_control.open_cover"
|
||||||
)}
|
)}
|
||||||
.icon=${computeOpenIcon(this.stateObj)}
|
|
||||||
@click=${this._onOpenTap}
|
@click=${this._onOpenTap}
|
||||||
.disabled=${this._computeOpenDisabled()}
|
.disabled=${this._computeOpenDisabled()}
|
||||||
></ha-icon-button>
|
.path=${computeOpenIcon(this.stateObj)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
hidden: !this._entityObj.supportsStop,
|
hidden: !this._entityObj.supportsStop,
|
||||||
@ -56,7 +58,7 @@ class HaCoverControls extends LitElement {
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.stop_cover"
|
"ui.dialogs.more_info_control.stop_cover"
|
||||||
)}
|
)}
|
||||||
icon="hass:stop"
|
.path=${mdiStop}
|
||||||
@click=${this._onStopTap}
|
@click=${this._onStopTap}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
@ -67,10 +69,11 @@ class HaCoverControls extends LitElement {
|
|||||||
.label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.close_cover"
|
"ui.dialogs.more_info_control.close_cover"
|
||||||
)}
|
)}
|
||||||
.icon=${computeCloseIcon(this.stateObj)}
|
|
||||||
@click=${this._onCloseTap}
|
@click=${this._onCloseTap}
|
||||||
.disabled=${this._computeClosedDisabled()}
|
.disabled=${this._computeClosedDisabled()}
|
||||||
></ha-icon-button>
|
.path=${computeCloseIcon(this.stateObj)}
|
||||||
|
>
|
||||||
|
</ha-icon-button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js";
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
@ -39,10 +40,10 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !this._entityObj.supportsOpenTilt,
|
invisible: !this._entityObj.supportsOpenTilt,
|
||||||
})}
|
})}
|
||||||
label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.open_tilt_cover"
|
"ui.dialogs.more_info_control.open_tilt_cover"
|
||||||
)}
|
)}
|
||||||
icon="hass:arrow-top-right"
|
.path=${mdiArrowTopRight}
|
||||||
@click=${this._onOpenTiltTap}
|
@click=${this._onOpenTiltTap}
|
||||||
.disabled=${this._computeOpenDisabled()}
|
.disabled=${this._computeOpenDisabled()}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
@ -50,8 +51,8 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !this._entityObj.supportsStopTilt,
|
invisible: !this._entityObj.supportsStopTilt,
|
||||||
})}
|
})}
|
||||||
label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
.label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
||||||
icon="hass:stop"
|
.path=${mdiStop}
|
||||||
@click=${this._onStopTiltTap}
|
@click=${this._onStopTiltTap}
|
||||||
.disabled=${this.stateObj.state === UNAVAILABLE}
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
@ -59,10 +60,10 @@ class HaCoverTiltControls extends LitElement {
|
|||||||
class=${classMap({
|
class=${classMap({
|
||||||
invisible: !this._entityObj.supportsCloseTilt,
|
invisible: !this._entityObj.supportsCloseTilt,
|
||||||
})}
|
})}
|
||||||
label=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.close_tilt_cover"
|
"ui.dialogs.more_info_control.close_tilt_cover"
|
||||||
)}
|
)}
|
||||||
icon="hass:arrow-bottom-left"
|
.path=${mdiArrowBottomLeft}
|
||||||
@click=${this._onCloseTiltTap}
|
@click=${this._onCloseTiltTap}
|
||||||
.disabled=${this._computeClosedDisabled()}
|
.disabled=${this._computeClosedDisabled()}
|
||||||
></ha-icon-button>`;
|
></ha-icon-button>`;
|
||||||
|
@ -4,29 +4,27 @@ import { css, CSSResultGroup, html, TemplateResult } from "lit";
|
|||||||
import { customElement } from "lit/decorators";
|
import { customElement } from "lit/decorators";
|
||||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "./ha-icon-button";
|
||||||
import "./ha-svg-icon";
|
|
||||||
|
|
||||||
export const createCloseHeading = (
|
export const createCloseHeading = (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
title: string | TemplateResult
|
title: string | TemplateResult
|
||||||
) => html`
|
) => html`
|
||||||
<span class="header_title">${title}</span>
|
<span class="header_title">${title}</span>
|
||||||
<mwc-icon-button
|
<ha-icon-button
|
||||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
.label=${hass.localize("ui.dialogs.generic.close")}
|
||||||
|
.path=${mdiClose}
|
||||||
dialogAction="close"
|
dialogAction="close"
|
||||||
class="header_button"
|
class="header_button"
|
||||||
dir=${computeRTLDirection(hass)}
|
dir=${computeRTLDirection(hass)}
|
||||||
>
|
></ha-icon-button>
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@customElement("ha-dialog")
|
@customElement("ha-dialog")
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
export class HaDialog extends Dialog {
|
export class HaDialog extends Dialog {
|
||||||
public scrollToPos(x: number, y: number) {
|
public scrollToPos(x: number, y: number) {
|
||||||
this.contentElement.scrollTo(x, y);
|
this.contentElement?.scrollTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderHeading() {
|
protected renderHeading() {
|
||||||
@ -46,6 +44,12 @@ export class HaDialog extends Dialog {
|
|||||||
justify-content: var(--justify-action-buttons, flex-end);
|
justify-content: var(--justify-action-buttons, flex-end);
|
||||||
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
padding-bottom: max(env(safe-area-inset-bottom), 8px);
|
||||||
}
|
}
|
||||||
|
.mdc-dialog__actions span:nth-child(1) {
|
||||||
|
flex: var(--secondary-action-button-flex, unset);
|
||||||
|
}
|
||||||
|
.mdc-dialog__actions span:nth-child(2) {
|
||||||
|
flex: var(--primary-action-button-flex, unset);
|
||||||
|
}
|
||||||
.mdc-dialog__container {
|
.mdc-dialog__container {
|
||||||
align-items: var(--vertial-align-dialog, center);
|
align-items: var(--vertial-align-dialog, center);
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,12 @@ class HaDurationInput extends LitElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public suffix?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean }) public required?: boolean;
|
@property({ type: Boolean }) public required?: boolean;
|
||||||
|
|
||||||
@property({ type: Boolean }) public enableMillisecond?: boolean;
|
@property({ type: Boolean }) public enableMillisecond?: boolean;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("paper-time-input", true) private _input?: HTMLElement;
|
@query("paper-time-input", true) private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
@ -36,6 +36,7 @@ class HaDurationInput extends LitElement {
|
|||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
.autoValidate=${this.required}
|
.autoValidate=${this.required}
|
||||||
|
.disabled=${this.disabled}
|
||||||
error-message="Required"
|
error-message="Required"
|
||||||
enable-second
|
enable-second
|
||||||
.enableMillisecond=${this.enableMillisecond}
|
.enableMillisecond=${this.enableMillisecond}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiClose } from "@mdi/js";
|
import { mdiClose } from "@mdi/js";
|
||||||
import "@polymer/iron-input/iron-input";
|
import "@polymer/iron-input/iron-input";
|
||||||
import "@polymer/paper-input/paper-input-container";
|
import "@polymer/paper-input/paper-input-container";
|
||||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
import "./ha-circular-progress";
|
import "./ha-circular-progress";
|
||||||
import "./ha-svg-icon";
|
import "./ha-icon-button";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
@ -17,6 +17,8 @@ declare global {
|
|||||||
|
|
||||||
@customElement("ha-file-upload")
|
@customElement("ha-file-upload")
|
||||||
export class HaFileUpload extends LitElement {
|
export class HaFileUpload extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property() public accept!: string;
|
@property() public accept!: string;
|
||||||
|
|
||||||
@property() public icon!: string;
|
@property() public icon!: string;
|
||||||
@ -82,15 +84,20 @@ export class HaFileUpload extends LitElement {
|
|||||||
${this.value}
|
${this.value}
|
||||||
</iron-input>
|
</iron-input>
|
||||||
${this.value
|
${this.value
|
||||||
? html`<mwc-icon-button
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
slot="suffix"
|
slot="suffix"
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
>
|
.label=${this.hass.localize("ui.common.close")}
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
.path=${mdiClose}
|
||||||
</mwc-icon-button>`
|
></ha-icon-button>
|
||||||
: html`<mwc-icon-button slot="suffix">
|
`
|
||||||
<ha-svg-icon .path=${this.icon}></ha-svg-icon>
|
: html`
|
||||||
</mwc-icon-button>`}
|
<ha-icon-button
|
||||||
|
slot="suffix"
|
||||||
|
.path=${this.icon}
|
||||||
|
></ha-icon-button>
|
||||||
|
`}
|
||||||
</paper-input-container>
|
</paper-input-container>
|
||||||
</label>
|
</label>
|
||||||
`}
|
`}
|
||||||
@ -154,7 +161,7 @@ export class HaFileUpload extends LitElement {
|
|||||||
max-width: 125px;
|
max-width: 125px;
|
||||||
max-height: 125px;
|
max-height: 125px;
|
||||||
}
|
}
|
||||||
mwc-icon-button {
|
ha-icon-button {
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
--mdc-icon-size: 20px;
|
--mdc-icon-size: 20px;
|
||||||
}
|
}
|
||||||
|
37
src/components/ha-form/compute-initial-ha-form-data.ts
Normal file
37
src/components/ha-form/compute-initial-ha-form-data.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { HaFormSchema } from "./types";
|
||||||
|
|
||||||
|
export const computeInitialHaFormData = (
|
||||||
|
schema: HaFormSchema[]
|
||||||
|
): Record<string, any> => {
|
||||||
|
const data = {};
|
||||||
|
schema.forEach((field) => {
|
||||||
|
if (field.description?.suggested_value) {
|
||||||
|
data[field.name] = field.description.suggested_value;
|
||||||
|
} else if ("default" in field) {
|
||||||
|
data[field.name] = field.default;
|
||||||
|
} else if (!field.required) {
|
||||||
|
// Do nothing.
|
||||||
|
} else if (field.type === "boolean") {
|
||||||
|
data[field.name] = false;
|
||||||
|
} else if (field.type === "string") {
|
||||||
|
data[field.name] = "";
|
||||||
|
} else if (field.type === "integer") {
|
||||||
|
data[field.name] = "valueMin" in field ? field.valueMin : 0;
|
||||||
|
} else if (field.type === "constant") {
|
||||||
|
data[field.name] = field.value;
|
||||||
|
} else if (field.type === "float") {
|
||||||
|
data[field.name] = 0.0;
|
||||||
|
} else if (field.type === "select") {
|
||||||
|
if (field.options.length) {
|
||||||
|
data[field.name] = field.options[0][0];
|
||||||
|
}
|
||||||
|
} else if (field.type === "positive_time_period_dict") {
|
||||||
|
data[field.name] = {
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
@ -1,13 +1,14 @@
|
|||||||
import "@polymer/paper-checkbox/paper-checkbox";
|
import "@material/mwc-formfield";
|
||||||
import type { PaperCheckboxElement } from "@polymer/paper-checkbox/paper-checkbox";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import type {
|
import type {
|
||||||
HaFormBooleanData,
|
HaFormBooleanData,
|
||||||
HaFormBooleanSchema,
|
HaFormBooleanSchema,
|
||||||
HaFormElement,
|
HaFormElement,
|
||||||
} from "./ha-form";
|
} from "./types";
|
||||||
|
import type { HaCheckbox } from "../ha-checkbox";
|
||||||
|
import "../ha-checkbox";
|
||||||
|
|
||||||
@customElement("ha-form-boolean")
|
@customElement("ha-form-boolean")
|
||||||
export class HaFormBoolean extends LitElement implements HaFormElement {
|
export class HaFormBoolean extends LitElement implements HaFormElement {
|
||||||
@ -17,9 +18,9 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property() public suffix!: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("paper-checkbox", true) private _input?: HTMLElement;
|
@query("ha-checkbox", true) private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -29,26 +30,21 @@ export class HaFormBoolean extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-checkbox .checked=${this.data} @change=${this._valueChanged}>
|
<mwc-formfield .label=${this.label}>
|
||||||
${this.label}
|
<ha-checkbox
|
||||||
</paper-checkbox>
|
.checked=${this.data}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._valueChanged}
|
||||||
|
></ha-checkbox>
|
||||||
|
</mwc-formfield>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event) {
|
private _valueChanged(ev: Event) {
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: (ev.target as PaperCheckboxElement).checked,
|
value: (ev.target as HaCheckbox).checked,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return css`
|
|
||||||
paper-checkbox {
|
|
||||||
display: block;
|
|
||||||
padding: 22px 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,14 +1,6 @@
|
|||||||
import {
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { HaFormConstantSchema, HaFormElement } from "./types";
|
||||||
import { HaFormConstantSchema, HaFormElement } from "./ha-form";
|
|
||||||
|
|
||||||
@customElement("ha-form-constant")
|
@customElement("ha-form-constant")
|
||||||
export class HaFormConstant extends LitElement implements HaFormElement {
|
export class HaFormConstant extends LitElement implements HaFormElement {
|
||||||
@ -16,13 +8,6 @@ export class HaFormConstant extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
protected firstUpdated(changedProps: PropertyValues) {
|
|
||||||
super.firstUpdated(changedProps);
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: this.schema.value,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<span class="label">${this.label}</span>: ${this.schema.value}`;
|
return html`<span class="label">${this.label}</span>: ${this.schema.value}`;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-textfield";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { TextField } from "@material/mwc-textfield";
|
||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult, PropertyValues } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./ha-form";
|
import { HaFormElement, HaFormFloatData, HaFormFloatSchema } from "./types";
|
||||||
|
|
||||||
@customElement("ha-form-float")
|
@customElement("ha-form-float")
|
||||||
export class HaFormFloat extends LitElement implements HaFormElement {
|
export class HaFormFloat extends LitElement implements HaFormElement {
|
||||||
@ -13,9 +13,9 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property() public suffix!: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("paper-input", true) private _input?: HTMLElement;
|
@query("mwc-textfield") private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -25,33 +25,60 @@ export class HaFormFloat extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<paper-input
|
<mwc-textfield
|
||||||
|
inputMode="decimal"
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this._value}
|
.value=${this.data !== undefined ? this.data : ""}
|
||||||
|
.disabled=${this.disabled}
|
||||||
.required=${this.schema.required}
|
.required=${this.schema.required}
|
||||||
.autoValidate=${this.schema.required}
|
.autoValidate=${this.schema.required}
|
||||||
@value-changed=${this._valueChanged}
|
.suffix=${this.schema.description?.suffix}
|
||||||
>
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
<span suffix slot="suffix">${this.suffix}</span>
|
@input=${this._valueChanged}
|
||||||
</paper-input>
|
></mwc-textfield>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
return this.data;
|
if (changedProps.has("schema")) {
|
||||||
|
this.toggleAttribute("own-margin", !!this.schema.required);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event) {
|
private _valueChanged(ev: Event) {
|
||||||
const value: number | undefined = (ev.target as PaperInputElement).value
|
const source = ev.target as TextField;
|
||||||
? Number((ev.target as PaperInputElement).value)
|
const rawValue = source.value;
|
||||||
: undefined;
|
|
||||||
if (this._value === value) {
|
let value: number | undefined;
|
||||||
|
|
||||||
|
if (rawValue !== "") {
|
||||||
|
value = parseFloat(rawValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect anything changed
|
||||||
|
if (this.data === value) {
|
||||||
|
// parseFloat will drop invalid text at the end, in that case update textfield
|
||||||
|
const newRawValue = value === undefined ? "" : String(value);
|
||||||
|
if (source.value !== newRawValue) {
|
||||||
|
source.value = newRawValue;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host([own-margin]) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
mwc-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-textfield";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { TextField } from "@material/mwc-textfield";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import "@material/mwc-slider";
|
||||||
|
import type { Slider } from "@material/mwc-slider";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { HaCheckbox } from "../ha-checkbox";
|
import { HaCheckbox } from "../ha-checkbox";
|
||||||
import "../ha-slider";
|
import { HaFormElement, HaFormIntegerData, HaFormIntegerSchema } from "./types";
|
||||||
import type { HaSlider } from "../ha-slider";
|
|
||||||
import {
|
|
||||||
HaFormElement,
|
|
||||||
HaFormIntegerData,
|
|
||||||
HaFormIntegerSchema,
|
|
||||||
} from "./ha-form";
|
|
||||||
|
|
||||||
@customElement("ha-form-integer")
|
@customElement("ha-form-integer")
|
||||||
export class HaFormInteger extends LitElement implements HaFormElement {
|
export class HaFormInteger extends LitElement implements HaFormElement {
|
||||||
@ -20,10 +23,12 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
|
|
||||||
@property() public suffix?: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("paper-input ha-slider") private _input?: HTMLElement;
|
@query("paper-input ha-slider") private _input?: HTMLElement;
|
||||||
|
|
||||||
|
private _lastValue?: HaFormIntegerData;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
this._input.focus();
|
this._input.focus();
|
||||||
@ -31,66 +36,116 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return "valueMin" in this.schema && "valueMax" in this.schema
|
if ("valueMin" in this.schema && "valueMax" in this.schema) {
|
||||||
? html`
|
return html`
|
||||||
<div>
|
<div>
|
||||||
${this.label}
|
${this.label}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
${this.schema.optional && this.schema.default === undefined
|
${this.schema.optional
|
||||||
? html`
|
? html`
|
||||||
<ha-checkbox
|
<ha-checkbox
|
||||||
@change=${this._handleCheckboxChange}
|
@change=${this._handleCheckboxChange}
|
||||||
.checked=${this.data !== undefined}
|
.checked=${this.data !== undefined}
|
||||||
|
.disabled=${this.disabled}
|
||||||
></ha-checkbox>
|
></ha-checkbox>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<ha-slider
|
<mwc-slider
|
||||||
pin
|
discrete
|
||||||
editable
|
|
||||||
.value=${this._value}
|
.value=${this._value}
|
||||||
.min=${this.schema.valueMin}
|
.min=${this.schema.valueMin}
|
||||||
.max=${this.schema.valueMax}
|
.max=${this.schema.valueMax}
|
||||||
.disabled=${this.data === undefined &&
|
.disabled=${this.disabled ||
|
||||||
this.schema.optional &&
|
(this.data === undefined && this.schema.optional)}
|
||||||
this.schema.default === undefined}
|
@change=${this._valueChanged}
|
||||||
@value-changed=${this._valueChanged}
|
></mwc-slider>
|
||||||
></ha-slider>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<paper-input
|
|
||||||
type="number"
|
|
||||||
.label=${this.label}
|
|
||||||
.value=${this._value}
|
|
||||||
.required=${this.schema.required}
|
|
||||||
.autoValidate=${this.schema.required}
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></paper-input>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
return html`
|
||||||
return (
|
<mwc-textfield
|
||||||
this.data ||
|
type="number"
|
||||||
this.schema.description?.suggested_value ||
|
inputMode="numeric"
|
||||||
this.schema.default ||
|
.label=${this.label}
|
||||||
0
|
.value=${this.data !== undefined ? this.data : ""}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.required=${this.schema.required}
|
||||||
|
.autoValidate=${this.schema.required}
|
||||||
|
.suffix=${this.schema.description?.suffix}
|
||||||
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
></mwc-textfield>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
if (changedProps.has("schema")) {
|
||||||
|
this.toggleAttribute(
|
||||||
|
"own-margin",
|
||||||
|
!("valueMin" in this.schema && "valueMax" in this.schema) &&
|
||||||
|
!!this.schema.required
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get _value() {
|
||||||
|
if (this.data !== undefined) {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.schema.optional) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.schema.description?.suggested_value || this.schema.default || 0;
|
||||||
|
}
|
||||||
|
|
||||||
private _handleCheckboxChange(ev: Event) {
|
private _handleCheckboxChange(ev: Event) {
|
||||||
const checked = (ev.target as HaCheckbox).checked;
|
const checked = (ev.target as HaCheckbox).checked;
|
||||||
|
let value: HaFormIntegerData | undefined;
|
||||||
|
if (checked) {
|
||||||
|
for (const candidate of [
|
||||||
|
this._lastValue,
|
||||||
|
this.schema.description?.suggested_value as HaFormIntegerData,
|
||||||
|
this.schema.default,
|
||||||
|
0,
|
||||||
|
]) {
|
||||||
|
if (candidate !== undefined) {
|
||||||
|
value = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// We track last value so user can disable and enable a field without losing
|
||||||
|
// their value.
|
||||||
|
this._lastValue = this.data;
|
||||||
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: checked ? this._value : undefined,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event) {
|
private _valueChanged(ev: Event) {
|
||||||
const value = Number((ev.target as PaperInputElement | HaSlider).value);
|
const source = ev.target as TextField | Slider;
|
||||||
if (this._value === value) {
|
const rawValue = source.value;
|
||||||
|
|
||||||
|
let value: number | undefined;
|
||||||
|
|
||||||
|
if (rawValue !== "") {
|
||||||
|
value = parseInt(String(rawValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.data === value) {
|
||||||
|
// parseInt will drop invalid text at the end, in that case update textfield
|
||||||
|
const newRawValue = value === undefined ? "" : String(value);
|
||||||
|
if (source.value !== newRawValue) {
|
||||||
|
source.value = newRawValue;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
@ -98,12 +153,17 @@ export class HaFormInteger extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
|
:host([own-margin]) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
ha-slider {
|
mwc-slider {
|
||||||
width: 100%;
|
flex: 1;
|
||||||
margin-right: 16px;
|
}
|
||||||
|
mwc-textfield {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,35 @@
|
|||||||
import { mdiMenuDown } from "@mdi/js";
|
import { mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
import "@polymer/paper-checkbox/paper-checkbox";
|
import "@material/mwc-textfield";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-formfield";
|
||||||
import "@polymer/paper-item/paper-icon-item";
|
import {
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
css,
|
||||||
import "@polymer/paper-menu-button/paper-menu-button";
|
CSSResultGroup,
|
||||||
import "@polymer/paper-ripple/paper-ripple";
|
html,
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
LitElement,
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
TemplateResult,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../ha-button-menu";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import {
|
import {
|
||||||
HaFormElement,
|
HaFormElement,
|
||||||
HaFormMultiSelectData,
|
HaFormMultiSelectData,
|
||||||
HaFormMultiSelectSchema,
|
HaFormMultiSelectSchema,
|
||||||
} from "./ha-form";
|
} from "./types";
|
||||||
|
import "../ha-checkbox";
|
||||||
|
import type { HaCheckbox } from "../ha-checkbox";
|
||||||
|
|
||||||
|
function optionValue(item: string | string[]): string {
|
||||||
|
return Array.isArray(item) ? item[0] : item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function optionLabel(item: string | string[]): string {
|
||||||
|
return Array.isArray(item) ? item[1] || item[0] : item;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SHOW_ALL_ENTRIES_LIMIT = 6;
|
||||||
|
|
||||||
@customElement("ha-form-multi_select")
|
@customElement("ha-form-multi_select")
|
||||||
export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||||
@ -23,11 +39,11 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property() public suffix!: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@state() private _init = false;
|
@state() private _opened = false;
|
||||||
|
|
||||||
@query("paper-menu-button", true) private _input?: HTMLElement;
|
@query("ha-button-menu") private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus(): void {
|
public focus(): void {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -36,118 +52,144 @@ export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
const options = Array.isArray(this.schema.options)
|
const options = Object.entries(this.schema.options);
|
||||||
? this.schema.options
|
|
||||||
: Object.entries(this.schema.options!);
|
|
||||||
|
|
||||||
const data = this.data || [];
|
const data = this.data || [];
|
||||||
|
|
||||||
|
const renderedOptions = options.map((item: string | [string, string]) => {
|
||||||
|
const value = optionValue(item);
|
||||||
return html`
|
return html`
|
||||||
<paper-menu-button horizontal-align="right" vertical-offset="8">
|
<mwc-formfield .label=${optionLabel(item)}>
|
||||||
<div class="dropdown-trigger" slot="dropdown-trigger">
|
<ha-checkbox
|
||||||
<paper-ripple></paper-ripple>
|
.checked=${data.includes(value)}
|
||||||
<paper-input
|
.value=${value}
|
||||||
id="input"
|
.disabled=${this.disabled}
|
||||||
type="text"
|
@change=${this._valueChanged}
|
||||||
readonly
|
></ha-checkbox>
|
||||||
value=${data
|
</mwc-formfield>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// We will just render all checkboxes.
|
||||||
|
if (options.length < SHOW_ALL_ENTRIES_LIMIT) {
|
||||||
|
return html`<div>${this.label}${renderedOptions}</div> `;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-button-menu
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
fixed
|
||||||
|
corner="BOTTOM_START"
|
||||||
|
@opened=${this._handleOpen}
|
||||||
|
@closed=${this._handleClose}
|
||||||
|
>
|
||||||
|
<mwc-textfield
|
||||||
|
slot="trigger"
|
||||||
|
.label=${this.label}
|
||||||
|
.value=${data
|
||||||
.map((value) => this.schema.options![value] || value)
|
.map((value) => this.schema.options![value] || value)
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
label=${this.label}
|
.disabled=${this.disabled}
|
||||||
input-role="button"
|
tabindex="-1"
|
||||||
input-aria-haspopup="listbox"
|
></mwc-textfield>
|
||||||
autocomplete="off"
|
|
||||||
>
|
|
||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.path=${mdiMenuDown}
|
slot="trigger"
|
||||||
suffix
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
slot="suffix"
|
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</paper-input>
|
${renderedOptions}
|
||||||
</div>
|
</ha-button-menu>
|
||||||
<paper-listbox
|
|
||||||
multi
|
|
||||||
slot="dropdown-content"
|
|
||||||
attr-for-selected="item-value"
|
|
||||||
.selectedValues=${data}
|
|
||||||
@selected-items-changed=${this._valueChanged}
|
|
||||||
@iron-select=${this._onSelect}
|
|
||||||
>
|
|
||||||
${
|
|
||||||
// TS doesn't work with union array types https://github.com/microsoft/TypeScript/issues/36390
|
|
||||||
// @ts-ignore
|
|
||||||
options.map((item: string | [string, string]) => {
|
|
||||||
const value = this._optionValue(item);
|
|
||||||
return html`
|
|
||||||
<paper-icon-item .itemValue=${value}>
|
|
||||||
<paper-checkbox
|
|
||||||
.checked=${data.includes(value)}
|
|
||||||
slot="item-icon"
|
|
||||||
></paper-checkbox>
|
|
||||||
${this._optionLabel(item)}
|
|
||||||
</paper-icon-item>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</paper-listbox>
|
|
||||||
</paper-menu-button>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
const input = (
|
const { formElement, mdcRoot } =
|
||||||
this.shadowRoot?.querySelector("paper-input")?.inputElement as any
|
this.shadowRoot?.querySelector("mwc-textfield") || ({} as any);
|
||||||
)?.inputElement;
|
if (formElement) {
|
||||||
if (input) {
|
formElement.style.textOverflow = "ellipsis";
|
||||||
input.style.textOverflow = "ellipsis";
|
}
|
||||||
|
if (mdcRoot) {
|
||||||
|
mdcRoot.style.cursor = "pointer";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _optionValue(item: string | string[]): string {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
return Array.isArray(item) ? item[0] : item;
|
if (changedProps.has("schema")) {
|
||||||
|
this.toggleAttribute(
|
||||||
|
"own-margin",
|
||||||
|
Object.keys(this.schema.options).length >= SHOW_ALL_ENTRIES_LIMIT &&
|
||||||
|
!!this.schema.required
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _optionLabel(item: string | string[]): string {
|
|
||||||
return Array.isArray(item) ? item[1] || item[0] : item;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onSelect(ev: Event) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent): void {
|
private _valueChanged(ev: CustomEvent): void {
|
||||||
if (!ev.detail.value || !this._init) {
|
const { value, checked } = ev.target as HaCheckbox;
|
||||||
// ignore first call because that is the init of the component
|
|
||||||
this._init = true;
|
let newValue: string[];
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
if (!this.data) {
|
||||||
|
newValue = [value];
|
||||||
|
} else if (this.data.includes(value)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
newValue = [...this.data, value];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!this.data.includes(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
newValue = this.data.filter((v) => v !== value);
|
||||||
|
}
|
||||||
|
|
||||||
fireEvent(
|
fireEvent(this, "value-changed", {
|
||||||
this,
|
value: newValue,
|
||||||
"value-changed",
|
});
|
||||||
{
|
}
|
||||||
value: ev.detail.value.map((element) => element.itemValue),
|
|
||||||
},
|
private _handleOpen(ev: Event): void {
|
||||||
{ bubbles: false }
|
ev.stopPropagation();
|
||||||
);
|
this._opened = true;
|
||||||
|
this.toggleAttribute("opened", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleClose(ev: Event): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._opened = false;
|
||||||
|
this.toggleAttribute("opened", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-menu-button {
|
:host([own-margin]) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
ha-button-menu {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
cursor: pointer;
|
||||||
--paper-item-icon-width: 34px;
|
|
||||||
}
|
}
|
||||||
paper-ripple {
|
mwc-formfield {
|
||||||
top: 12px;
|
display: block;
|
||||||
left: 0px;
|
padding-right: 16px;
|
||||||
bottom: 8px;
|
|
||||||
right: 0px;
|
|
||||||
}
|
}
|
||||||
paper-input {
|
mwc-textfield {
|
||||||
text-overflow: ellipsis;
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
ha-svg-icon {
|
||||||
|
color: var(--input-dropdown-icon-color);
|
||||||
|
position: absolute;
|
||||||
|
right: 1em;
|
||||||
|
top: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
:host([opened]) ha-svg-icon {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
:host([opened]) ha-button-menu {
|
||||||
|
--mdc-text-field-idle-line-color: var(--input-hover-line-color);
|
||||||
|
--mdc-text-field-label-ink-color: var(--primary-color);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { html, LitElement, TemplateResult } from "lit";
|
import { html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import "../ha-duration-input";
|
import "../ha-duration-input";
|
||||||
import { HaFormElement, HaFormTimeData, HaFormTimeSchema } from "./ha-form";
|
import { HaFormElement, HaFormTimeData, HaFormTimeSchema } from "./types";
|
||||||
|
|
||||||
@customElement("ha-form-positive_time_period_dict")
|
@customElement("ha-form-positive_time_period_dict")
|
||||||
export class HaFormTimePeriod extends LitElement implements HaFormElement {
|
export class HaFormTimePeriod extends LitElement implements HaFormElement {
|
||||||
@ -11,7 +11,7 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property() public suffix!: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("ha-time-input", true) private _input?: HTMLElement;
|
@query("ha-time-input", true) private _input?: HTMLElement;
|
||||||
|
|
||||||
@ -27,6 +27,7 @@ export class HaFormTimePeriod extends LitElement implements HaFormElement {
|
|||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.required=${this.schema.required}
|
.required=${this.schema.required}
|
||||||
.data=${this.data}
|
.data=${this.data}
|
||||||
|
.disabled=${this.disabled}
|
||||||
></ha-duration-input>
|
></ha-duration-input>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-select";
|
||||||
import { mdiClose, mdiMenuDown } from "@mdi/js";
|
import type { Select } from "@material/mwc-select";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import "@polymer/paper-item/paper-item";
|
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
|
||||||
import "@polymer/paper-menu-button/paper-menu-button";
|
|
||||||
import "@polymer/paper-ripple/paper-ripple";
|
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-svg-icon";
|
import "../ha-radio";
|
||||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form";
|
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./types";
|
||||||
|
|
||||||
|
import { stopPropagation } from "../../common/dom/stop_propagation";
|
||||||
|
import type { HaRadio } from "../ha-radio";
|
||||||
|
|
||||||
@customElement("ha-form-select")
|
@customElement("ha-form-select")
|
||||||
export class HaFormSelect extends LitElement implements HaFormElement {
|
export class HaFormSelect extends LitElement implements HaFormElement {
|
||||||
@ -19,9 +18,9 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property() public suffix!: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@query("ha-paper-dropdown-menu", true) private _input?: HTMLElement;
|
@query("mwc-select", true) private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -30,90 +29,70 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
|
if (!this.schema.optional && this.schema.options!.length < 6) {
|
||||||
return html`
|
return html`
|
||||||
<paper-menu-button horizontal-align="right" vertical-offset="8">
|
<div>
|
||||||
<div class="dropdown-trigger" slot="dropdown-trigger">
|
${this.label}
|
||||||
<paper-ripple></paper-ripple>
|
${this.schema.options.map(
|
||||||
<paper-input
|
([value, label]) => html`
|
||||||
id="input"
|
<mwc-formfield .label=${label}>
|
||||||
type="text"
|
<ha-radio
|
||||||
readonly
|
.checked=${value === this.data}
|
||||||
value=${this.data}
|
.value=${value}
|
||||||
label=${this.label}
|
.disabled=${this.disabled}
|
||||||
input-role="button"
|
@change=${this._valueChanged}
|
||||||
input-aria-haspopup="listbox"
|
></ha-radio>
|
||||||
autocomplete="off"
|
</mwc-formfield>
|
||||||
>
|
|
||||||
${this.data && this.schema.optional
|
|
||||||
? html`<mwc-icon-button
|
|
||||||
slot="suffix"
|
|
||||||
class="clear-button"
|
|
||||||
@click=${this._clearValue}
|
|
||||||
>
|
|
||||||
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>`
|
|
||||||
: ""}
|
|
||||||
<mwc-icon-button slot="suffix">
|
|
||||||
<ha-svg-icon .path=${mdiMenuDown}></ha-svg-icon>
|
|
||||||
</mwc-icon-button>
|
|
||||||
</paper-input>
|
|
||||||
</div>
|
|
||||||
<paper-listbox
|
|
||||||
slot="dropdown-content"
|
|
||||||
attr-for-selected="item-value"
|
|
||||||
.selected=${this.data}
|
|
||||||
@selected-item-changed=${this._valueChanged}
|
|
||||||
>
|
|
||||||
${
|
|
||||||
// TS doesn't work with union array types https://github.com/microsoft/TypeScript/issues/36390
|
|
||||||
// @ts-ignore
|
|
||||||
this.schema.options!.map(
|
|
||||||
(item: string | [string, string]) => html`
|
|
||||||
<paper-item .itemValue=${this._optionValue(item)}>
|
|
||||||
${this._optionLabel(item)}
|
|
||||||
</paper-item>
|
|
||||||
`
|
`
|
||||||
)
|
)}
|
||||||
}
|
</div>
|
||||||
</paper-listbox>
|
|
||||||
</paper-menu-button>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _optionValue(item: string | [string, string]) {
|
return html`
|
||||||
return Array.isArray(item) ? item[0] : item;
|
<mwc-select
|
||||||
}
|
fixedMenuPosition
|
||||||
|
naturalMenuWidth
|
||||||
private _optionLabel(item: string | [string, string]) {
|
.label=${this.label}
|
||||||
return Array.isArray(item) ? item[1] || item[0] : item;
|
.value=${this.data}
|
||||||
}
|
.disabled=${this.disabled}
|
||||||
|
@closed=${stopPropagation}
|
||||||
private _clearValue(ev: CustomEvent) {
|
@selected=${this._valueChanged}
|
||||||
ev.stopPropagation();
|
>
|
||||||
fireEvent(this, "value-changed", { value: undefined });
|
${this.schema.optional
|
||||||
|
? html`<mwc-list-item value=""></mwc-list-item>`
|
||||||
|
: ""}
|
||||||
|
${this.schema.options!.map(
|
||||||
|
([value, label]) => html`
|
||||||
|
<mwc-list-item .value=${value}>${label}</mwc-list-item>
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
</mwc-select>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
private _valueChanged(ev: CustomEvent) {
|
||||||
if (!ev.detail.value) {
|
ev.stopPropagation();
|
||||||
|
let value: string | undefined = (ev.target as Select | HaRadio).value;
|
||||||
|
|
||||||
|
if (value === this.data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
value = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: ev.detail.value.itemValue,
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
paper-menu-button {
|
mwc-select,
|
||||||
|
mwc-formfield {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
paper-input > mwc-icon-button {
|
|
||||||
--mdc-icon-button-size: 24px;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
.clear-button {
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,22 @@
|
|||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
|
||||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@material/mwc-textfield";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
import type { TextField } from "@material/mwc-textfield";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import {
|
||||||
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit";
|
||||||
import { customElement, property, state, query } from "lit/decorators";
|
import { customElement, property, state, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import "../ha-svg-icon";
|
import "../ha-icon-button";
|
||||||
import type {
|
import type {
|
||||||
HaFormElement,
|
HaFormElement,
|
||||||
HaFormStringData,
|
HaFormStringData,
|
||||||
HaFormStringSchema,
|
HaFormStringSchema,
|
||||||
} from "./ha-form";
|
} from "./types";
|
||||||
|
|
||||||
const MASKED_FIELDS = ["password", "secret", "token"];
|
const MASKED_FIELDS = ["password", "secret", "token"];
|
||||||
|
|
||||||
@ -22,11 +28,11 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
@property() public label!: string;
|
@property() public label!: string;
|
||||||
|
|
||||||
@property() public suffix!: string;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@state() private _unmaskedPassword = false;
|
@state() private _unmaskedPassword = false;
|
||||||
|
|
||||||
@query("paper-input") private _input?: HTMLElement;
|
@query("mwc-textfield") private _input?: HTMLElement;
|
||||||
|
|
||||||
public focus(): void {
|
public focus(): void {
|
||||||
if (this._input) {
|
if (this._input) {
|
||||||
@ -35,51 +41,58 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return MASKED_FIELDS.some((field) => this.schema.name.includes(field))
|
const isPassword = MASKED_FIELDS.some((field) =>
|
||||||
? html`
|
this.schema.name.includes(field)
|
||||||
<paper-input
|
);
|
||||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
return html`
|
||||||
|
<mwc-textfield
|
||||||
|
.type=${!isPassword
|
||||||
|
? this._stringType
|
||||||
|
: this._unmaskedPassword
|
||||||
|
? "text"
|
||||||
|
: "password"}
|
||||||
.label=${this.label}
|
.label=${this.label}
|
||||||
.value=${this.data}
|
.value=${this.data || ""}
|
||||||
|
.disabled=${this.disabled}
|
||||||
.required=${this.schema.required}
|
.required=${this.schema.required}
|
||||||
.autoValidate=${this.schema.required}
|
.autoValidate=${this.schema.required}
|
||||||
@value-changed=${this._valueChanged}
|
.suffix=${isPassword
|
||||||
>
|
? // reserve some space for the icon.
|
||||||
<mwc-icon-button
|
html`<div style="width: 24px"></div>`
|
||||||
|
: this.schema.description?.suffix}
|
||||||
|
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||||
|
@input=${this._valueChanged}
|
||||||
|
></mwc-textfield>
|
||||||
|
${isPassword
|
||||||
|
? html`<ha-icon-button
|
||||||
toggles
|
toggles
|
||||||
slot="suffix"
|
.label="Click to toggle between masked and clear password"
|
||||||
id="iconButton"
|
|
||||||
title="Click to toggle between masked and clear password"
|
|
||||||
@click=${this._toggleUnmaskedPassword}
|
@click=${this._toggleUnmaskedPassword}
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
><ha-svg-icon
|
|
||||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||||
></ha-svg-icon>
|
></ha-icon-button>`
|
||||||
</mwc-icon-button>
|
: ""}
|
||||||
</paper-input>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<paper-input
|
|
||||||
.type=${this._stringType}
|
|
||||||
.label=${this.label}
|
|
||||||
.value=${this.data}
|
|
||||||
.required=${this.schema.required}
|
|
||||||
.autoValidate=${this.schema.required}
|
|
||||||
error-message="Required"
|
|
||||||
@value-changed=${this._valueChanged}
|
|
||||||
></paper-input>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProps: PropertyValues): void {
|
||||||
|
if (changedProps.has("schema")) {
|
||||||
|
this.toggleAttribute("own-margin", !!this.schema.required);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _toggleUnmaskedPassword(): void {
|
private _toggleUnmaskedPassword(): void {
|
||||||
this._unmaskedPassword = !this._unmaskedPassword;
|
this._unmaskedPassword = !this._unmaskedPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _valueChanged(ev: Event): void {
|
private _valueChanged(ev: Event): void {
|
||||||
const value = (ev.target as PaperInputElement).value;
|
let value: string | undefined = (ev.target as TextField).value;
|
||||||
if (this.data === value) {
|
if (this.data === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (value === "" && this.schema.optional) {
|
||||||
|
value = undefined;
|
||||||
|
}
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value,
|
value,
|
||||||
});
|
});
|
||||||
@ -99,7 +112,20 @@ export class HaFormString extends LitElement implements HaFormElement {
|
|||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
mwc-icon-button {
|
:host {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
:host([own-margin]) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
mwc-textfield {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-icon-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: 12px;
|
||||||
--mdc-icon-button-size: 24px;
|
--mdc-icon-button-size: 24px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { css, CSSResultGroup, html, LitElement } from "lit";
|
|||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
import { HaDurationData } from "../ha-duration-input";
|
import "../ha-alert";
|
||||||
import "./ha-form-boolean";
|
import "./ha-form-boolean";
|
||||||
import "./ha-form-constant";
|
import "./ha-form-constant";
|
||||||
import "./ha-form-float";
|
import "./ha-form-float";
|
||||||
@ -11,158 +11,80 @@ import "./ha-form-multi_select";
|
|||||||
import "./ha-form-positive_time_period_dict";
|
import "./ha-form-positive_time_period_dict";
|
||||||
import "./ha-form-select";
|
import "./ha-form-select";
|
||||||
import "./ha-form-string";
|
import "./ha-form-string";
|
||||||
|
import { HaFormElement, HaFormDataContainer, HaFormSchema } from "./types";
|
||||||
|
|
||||||
export type HaFormSchema =
|
const getValue = (obj, item) => (obj ? obj[item.name] : null);
|
||||||
| HaFormConstantSchema
|
|
||||||
| HaFormStringSchema
|
|
||||||
| HaFormIntegerSchema
|
|
||||||
| HaFormFloatSchema
|
|
||||||
| HaFormBooleanSchema
|
|
||||||
| HaFormSelectSchema
|
|
||||||
| HaFormMultiSelectSchema
|
|
||||||
| HaFormTimeSchema;
|
|
||||||
|
|
||||||
export interface HaFormBaseSchema {
|
|
||||||
name: string;
|
|
||||||
default?: HaFormData;
|
|
||||||
required?: boolean;
|
|
||||||
optional?: boolean;
|
|
||||||
description?: { suffix?: string; suggested_value?: HaFormData };
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
|
||||||
type: "constant";
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
|
||||||
type: "integer";
|
|
||||||
default?: HaFormIntegerData;
|
|
||||||
valueMin?: number;
|
|
||||||
valueMax?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormSelectSchema extends HaFormBaseSchema {
|
|
||||||
type: "select";
|
|
||||||
options?: string[] | Array<[string, string]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
|
||||||
type: "multi_select";
|
|
||||||
options?: Record<string, string> | string[] | Array<[string, string]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
|
||||||
type: "float";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormStringSchema extends HaFormBaseSchema {
|
|
||||||
type: "string";
|
|
||||||
format?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormBooleanSchema extends HaFormBaseSchema {
|
|
||||||
type: "boolean";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormTimeSchema extends HaFormBaseSchema {
|
|
||||||
type: "positive_time_period_dict";
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HaFormDataContainer {
|
|
||||||
[key: string]: HaFormData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HaFormData =
|
|
||||||
| HaFormStringData
|
|
||||||
| HaFormIntegerData
|
|
||||||
| HaFormFloatData
|
|
||||||
| HaFormBooleanData
|
|
||||||
| HaFormSelectData
|
|
||||||
| HaFormMultiSelectData
|
|
||||||
| HaFormTimeData;
|
|
||||||
|
|
||||||
export type HaFormStringData = string;
|
|
||||||
export type HaFormIntegerData = number;
|
|
||||||
export type HaFormFloatData = number;
|
|
||||||
export type HaFormBooleanData = boolean;
|
|
||||||
export type HaFormSelectData = string;
|
|
||||||
export type HaFormMultiSelectData = string[];
|
|
||||||
export type HaFormTimeData = HaDurationData;
|
|
||||||
|
|
||||||
export interface HaFormElement extends LitElement {
|
|
||||||
schema: HaFormSchema | HaFormSchema[];
|
|
||||||
data?: HaFormDataContainer | HaFormData;
|
|
||||||
label?: string;
|
|
||||||
suffix?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement("ha-form")
|
@customElement("ha-form")
|
||||||
export class HaForm extends LitElement implements HaFormElement {
|
export class HaForm extends LitElement implements HaFormElement {
|
||||||
@property() public data!: HaFormDataContainer | HaFormData;
|
@property() public data!: HaFormDataContainer;
|
||||||
|
|
||||||
@property() public schema!: HaFormSchema | HaFormSchema[];
|
@property() public schema!: HaFormSchema[];
|
||||||
|
|
||||||
@property() public error;
|
@property() public error?: Record<string, string>;
|
||||||
|
|
||||||
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
@property() public computeError?: (schema: HaFormSchema, error) => string;
|
||||||
|
|
||||||
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
@property() public computeLabel?: (schema: HaFormSchema) => string;
|
||||||
|
|
||||||
@property() public computeSuffix?: (schema: HaFormSchema) => string;
|
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
const input =
|
const root = this.shadowRoot?.querySelector(".root");
|
||||||
this.shadowRoot!.getElementById("child-form") ||
|
if (!root) {
|
||||||
this.shadowRoot!.querySelector("ha-form");
|
|
||||||
if (!input) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
(input as HTMLElement).focus();
|
for (const child of root.children) {
|
||||||
|
if (child.tagName !== "HA-ALERT") {
|
||||||
|
(child as HTMLElement).focus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (Array.isArray(this.schema)) {
|
|
||||||
return html`
|
return html`
|
||||||
|
<div class="root">
|
||||||
${this.error && this.error.base
|
${this.error && this.error.base
|
||||||
? html`
|
? html`
|
||||||
<div class="error">
|
<ha-alert alert-type="error">
|
||||||
${this._computeError(this.error.base, this.schema)}
|
${this._computeError(this.error.base, this.schema)}
|
||||||
</div>
|
</ha-alert>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${this.schema.map(
|
${this.schema.map((item) => {
|
||||||
(item) => html`
|
const error = getValue(this.error, item);
|
||||||
<ha-form
|
return html`
|
||||||
.data=${this._getValue(this.data, item)}
|
${error
|
||||||
.schema=${item}
|
? html`
|
||||||
.error=${this._getValue(this.error, item)}
|
<ha-alert own-margin alert-type="error">
|
||||||
@value-changed=${this._valueChanged}
|
${this._computeError(error, item)}
|
||||||
.computeError=${this.computeError}
|
</ha-alert>
|
||||||
.computeLabel=${this.computeLabel}
|
|
||||||
.computeSuffix=${this.computeSuffix}
|
|
||||||
></ha-form>
|
|
||||||
`
|
`
|
||||||
)}
|
: ""}
|
||||||
|
${dynamicElement(`ha-form-${item.type}`, {
|
||||||
|
schema: item,
|
||||||
|
data: getValue(this.data, item),
|
||||||
|
label: this._computeLabel(item),
|
||||||
|
disabled: this.disabled,
|
||||||
|
})}
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
protected createRenderRoot() {
|
||||||
${this.error
|
const root = super.createRenderRoot();
|
||||||
? html`
|
// attach it as soon as possible to make sure we fetch all events.
|
||||||
<div class="error">
|
root.addEventListener("value-changed", (ev) => {
|
||||||
${this._computeError(this.error, this.schema)}
|
ev.stopPropagation();
|
||||||
</div>
|
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||||
`
|
fireEvent(this, "value-changed", {
|
||||||
: ""}
|
value: { ...this.data, [schema.name]: ev.detail.value },
|
||||||
${dynamicElement(`ha-form-${this.schema.type}`, {
|
});
|
||||||
schema: this.schema,
|
});
|
||||||
data: this.data,
|
return root;
|
||||||
label: this._computeLabel(this.schema),
|
|
||||||
suffix: this._computeSuffix(this.schema),
|
|
||||||
id: "child-form",
|
|
||||||
})}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeLabel(schema: HaFormSchema) {
|
private _computeLabel(schema: HaFormSchema) {
|
||||||
@ -173,38 +95,25 @@ export class HaForm extends LitElement implements HaFormElement {
|
|||||||
: "";
|
: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private _computeSuffix(schema: HaFormSchema) {
|
|
||||||
return this.computeSuffix
|
|
||||||
? this.computeSuffix(schema)
|
|
||||||
: schema && schema.description
|
|
||||||
? schema.description.suffix
|
|
||||||
: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
private _computeError(error, schema: HaFormSchema | HaFormSchema[]) {
|
||||||
return this.computeError ? this.computeError(error, schema) : error;
|
return this.computeError ? this.computeError(error, schema) : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getValue(obj, item) {
|
|
||||||
if (obj) {
|
|
||||||
return obj[item.name];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _valueChanged(ev: CustomEvent) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
|
||||||
const data = this.data as HaFormDataContainer;
|
|
||||||
fireEvent(this, "value-changed", {
|
|
||||||
value: { ...data, [schema.name]: ev.detail.value },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
|
// .root has overflow: auto to avoid margin collapse
|
||||||
return css`
|
return css`
|
||||||
.error {
|
.root {
|
||||||
color: var(--error-color);
|
margin-bottom: -24px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.root > * {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.root > *:not([own-margin]) {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
ha-alert[own-margin] {
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
86
src/components/ha-form/types.ts
Normal file
86
src/components/ha-form/types.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import type { LitElement } from "lit";
|
||||||
|
import type { HaDurationData } from "../ha-duration-input";
|
||||||
|
|
||||||
|
export type HaFormSchema =
|
||||||
|
| HaFormConstantSchema
|
||||||
|
| HaFormStringSchema
|
||||||
|
| HaFormIntegerSchema
|
||||||
|
| HaFormFloatSchema
|
||||||
|
| HaFormBooleanSchema
|
||||||
|
| HaFormSelectSchema
|
||||||
|
| HaFormMultiSelectSchema
|
||||||
|
| HaFormTimeSchema;
|
||||||
|
|
||||||
|
export interface HaFormBaseSchema {
|
||||||
|
name: string;
|
||||||
|
default?: HaFormData;
|
||||||
|
required?: boolean;
|
||||||
|
optional?: boolean;
|
||||||
|
description?: { suffix?: string; suggested_value?: HaFormData };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormConstantSchema extends HaFormBaseSchema {
|
||||||
|
type: "constant";
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||||
|
type: "integer";
|
||||||
|
default?: HaFormIntegerData;
|
||||||
|
valueMin?: number;
|
||||||
|
valueMax?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormSelectSchema extends HaFormBaseSchema {
|
||||||
|
type: "select";
|
||||||
|
options: Array<[string, string]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
||||||
|
type: "multi_select";
|
||||||
|
options: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
||||||
|
type: "float";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormStringSchema extends HaFormBaseSchema {
|
||||||
|
type: "string";
|
||||||
|
format?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormBooleanSchema extends HaFormBaseSchema {
|
||||||
|
type: "boolean";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormTimeSchema extends HaFormBaseSchema {
|
||||||
|
type: "positive_time_period_dict";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HaFormDataContainer {
|
||||||
|
[key: string]: HaFormData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HaFormData =
|
||||||
|
| HaFormStringData
|
||||||
|
| HaFormIntegerData
|
||||||
|
| HaFormFloatData
|
||||||
|
| HaFormBooleanData
|
||||||
|
| HaFormSelectData
|
||||||
|
| HaFormMultiSelectData
|
||||||
|
| HaFormTimeData;
|
||||||
|
|
||||||
|
export type HaFormStringData = string;
|
||||||
|
export type HaFormIntegerData = number;
|
||||||
|
export type HaFormFloatData = number;
|
||||||
|
export type HaFormBooleanData = boolean;
|
||||||
|
export type HaFormSelectData = string;
|
||||||
|
export type HaFormMultiSelectData = string[];
|
||||||
|
export type HaFormTimeData = HaDurationData;
|
||||||
|
|
||||||
|
export interface HaFormElement extends LitElement {
|
||||||
|
schema: HaFormSchema | HaFormSchema[];
|
||||||
|
data?: HaFormDataContainer | HaFormData;
|
||||||
|
label?: string;
|
||||||
|
}
|
@ -8,7 +8,6 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, query } from "lit/decorators";
|
import { customElement, property, query } from "lit/decorators";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
|
||||||
import { nextRender } from "../common/util/render-status";
|
import { nextRender } from "../common/util/render-status";
|
||||||
import { getExternalConfig } from "../external_app/external_config";
|
import { getExternalConfig } from "../external_app/external_config";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
@ -65,7 +64,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
.muted=${this.muted}
|
.muted=${this.muted}
|
||||||
?playsinline=${this.playsInline}
|
?playsinline=${this.playsInline}
|
||||||
?controls=${this.controls}
|
?controls=${this.controls}
|
||||||
@loadeddata=${this._elementResized}
|
|
||||||
></video>
|
></video>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -191,6 +189,7 @@ class HaHLSPlayer extends LitElement {
|
|||||||
fragLoadingTimeOut: 30000,
|
fragLoadingTimeOut: 30000,
|
||||||
manifestLoadingTimeOut: 30000,
|
manifestLoadingTimeOut: 30000,
|
||||||
levelLoadingTimeOut: 30000,
|
levelLoadingTimeOut: 30000,
|
||||||
|
maxLiveSyncPlaybackRate: 2,
|
||||||
});
|
});
|
||||||
this._hlsPolyfillInstance = hls;
|
this._hlsPolyfillInstance = hls;
|
||||||
hls.attachMedia(videoEl);
|
hls.attachMedia(videoEl);
|
||||||
@ -206,10 +205,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _elementResized() {
|
|
||||||
fireEvent(this, "iron-resize");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _cleanUp() {
|
private _cleanUp() {
|
||||||
if (this._hlsPolyfillInstance) {
|
if (this._hlsPolyfillInstance) {
|
||||||
this._hlsPolyfillInstance.destroy();
|
this._hlsPolyfillInstance.destroy();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user