mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-09 04:59:37 +00:00
Compare commits
162 Commits
fix-more-i
...
20200228.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7b057eaa77 | ||
![]() |
d7aaed05b7 | ||
![]() |
c5fe5565bb | ||
![]() |
a1a1763897 | ||
![]() |
724357683c | ||
![]() |
0d6de9fe73 | ||
![]() |
5646045e9e | ||
![]() |
17c7a3bbac | ||
![]() |
8d65eb1fdf | ||
![]() |
2298a55b16 | ||
![]() |
33d65bcefc | ||
![]() |
3cc7deda04 | ||
![]() |
e2de660bec | ||
![]() |
6b1e5a525f | ||
![]() |
93565f0ed9 | ||
![]() |
143d1162b6 | ||
![]() |
788d616fa2 | ||
![]() |
0de9471a5d | ||
![]() |
b229071248 | ||
![]() |
6d145730a5 | ||
![]() |
f02bb67485 | ||
![]() |
52ded635ff | ||
![]() |
a6d73828b8 | ||
![]() |
1d052fa5bb | ||
![]() |
38d758b52f | ||
![]() |
9162e9c318 | ||
![]() |
189ea00768 | ||
![]() |
25d6427aed | ||
![]() |
8a61442cf2 | ||
![]() |
106d405699 | ||
![]() |
1f23e9062f | ||
![]() |
231b498ea5 | ||
![]() |
a256e5abfa | ||
![]() |
028b370ead | ||
![]() |
18abc6adf7 | ||
![]() |
95aa29d6ca | ||
![]() |
5d2242dd16 | ||
![]() |
de8bca6967 | ||
![]() |
12234de20e | ||
![]() |
b41369a2ad | ||
![]() |
6e35c79c14 | ||
![]() |
22e4c0512e | ||
![]() |
3606b8077f | ||
![]() |
3a90a65ba8 | ||
![]() |
e59987a8ed | ||
![]() |
22d8ce0fd9 | ||
![]() |
01eae3876b | ||
![]() |
2e43f390a4 | ||
![]() |
65421fa551 | ||
![]() |
fc88922ce3 | ||
![]() |
52609dded9 | ||
![]() |
6d54496187 | ||
![]() |
2a6c38066d | ||
![]() |
924c7804c9 | ||
![]() |
23f34fa7ae | ||
![]() |
7046cba1f7 | ||
![]() |
4be1040a14 | ||
![]() |
68baeb83cb | ||
![]() |
aa94e45582 | ||
![]() |
2c58a9f802 | ||
![]() |
0a41a4f066 | ||
![]() |
e265d9581c | ||
![]() |
4675579f79 | ||
![]() |
52ae01ea74 | ||
![]() |
099430238c | ||
![]() |
af3626b215 | ||
![]() |
52ea3a5ce8 | ||
![]() |
2ab2ade642 | ||
![]() |
1cc3936ec3 | ||
![]() |
322eef1c0f | ||
![]() |
0964130782 | ||
![]() |
be9ec50e3a | ||
![]() |
da1dd45169 | ||
![]() |
9a7f7f119d | ||
![]() |
b1a414c840 | ||
![]() |
2718ada9f9 | ||
![]() |
46a596ce34 | ||
![]() |
7036cefa72 | ||
![]() |
fb7fbf2dac | ||
![]() |
49b0c8d549 | ||
![]() |
8f9a6bd544 | ||
![]() |
24e4b0b772 | ||
![]() |
1c86bd2f8b | ||
![]() |
363f548f13 | ||
![]() |
51ce481e77 | ||
![]() |
30e5611812 | ||
![]() |
67706a312d | ||
![]() |
f4eb3380b4 | ||
![]() |
73934afc7d | ||
![]() |
9d2a0c0502 | ||
![]() |
9ec75531a8 | ||
![]() |
91bdb8f742 | ||
![]() |
d8ae3439de | ||
![]() |
2d018fff6c | ||
![]() |
7d37dc6cde | ||
![]() |
c60033027d | ||
![]() |
3f7c29a6f6 | ||
![]() |
b2243f480c | ||
![]() |
f5384e8bc8 | ||
![]() |
ecc6fcf862 | ||
![]() |
46cc2aec94 | ||
![]() |
c62a5a6dcd | ||
![]() |
f6b10232ec | ||
![]() |
87559c0938 | ||
![]() |
7903541689 | ||
![]() |
c93e1b0123 | ||
![]() |
e261fafdb3 | ||
![]() |
485e2fde25 | ||
![]() |
6feaf64c90 | ||
![]() |
6b115bf06a | ||
![]() |
f45785fafe | ||
![]() |
ec046bc925 | ||
![]() |
ab5733718b | ||
![]() |
1077fb2945 | ||
![]() |
b7a84cdd60 | ||
![]() |
78102f5882 | ||
![]() |
4ea11bd928 | ||
![]() |
785aefa028 | ||
![]() |
5c2004bcc1 | ||
![]() |
156d944ca1 | ||
![]() |
97a6354a72 | ||
![]() |
49422c3f63 | ||
![]() |
0b8700f725 | ||
![]() |
c5aa000a97 | ||
![]() |
4cdc4765f7 | ||
![]() |
a95290235d | ||
![]() |
fb9d7ac2d8 | ||
![]() |
d48a4e0ac6 | ||
![]() |
d33e035db7 | ||
![]() |
1437b4c4b6 | ||
![]() |
9fce60065b | ||
![]() |
d052b9ede8 | ||
![]() |
8cee5c729e | ||
![]() |
88bdf7c7ec | ||
![]() |
2c006e99f2 | ||
![]() |
e7e8dff0ec | ||
![]() |
981c798e22 | ||
![]() |
4613d8b1f6 | ||
![]() |
ba4e1949c4 | ||
![]() |
cc6686a790 | ||
![]() |
f791412f73 | ||
![]() |
0c8ac17dcb | ||
![]() |
9e11fe868e | ||
![]() |
7d91515bf5 | ||
![]() |
e0565c35ab | ||
![]() |
e5387e5806 | ||
![]() |
8a4c52aeb7 | ||
![]() |
15e7b8117c | ||
![]() |
d1703ba3e8 | ||
![]() |
c977f22047 | ||
![]() |
2e47aa1905 | ||
![]() |
c72105dca3 | ||
![]() |
e01f1cfcac | ||
![]() |
50d0671abe | ||
![]() |
e176357fbf | ||
![]() |
de1b127ac2 | ||
![]() |
1dad7c81da | ||
![]() |
e980e93969 | ||
![]() |
57fc56f836 | ||
![]() |
05113e1809 | ||
![]() |
1479ce9d56 | ||
![]() |
ce8caa34f5 |
127
.github/workflows/ci.yaml
vendored
Normal file
127
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build icons
|
||||
run: ./node_modules/.bin/gulp gen-icons-hassio gen-icons-mdi gen-icons-app
|
||||
- name: Build translations
|
||||
run: ./node_modules/.bin/gulp build-translations
|
||||
- name: Run eslint
|
||||
run: ./node_modules/.bin/eslint src hassio/src gallery/src
|
||||
- name: Run tslint
|
||||
run: ./node_modules/.bin/tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts'
|
||||
- name: Run tsc
|
||||
run: ./node_modules/.bin/tsc
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Run Mocha
|
||||
run: npm run mocha
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-app
|
||||
env:
|
||||
TRAVIS: "true"
|
||||
supervisor:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-hassio
|
||||
env:
|
||||
TRAVIS: "true"
|
39
.github/workflows/demo.yaml
vendored
Normal file
39
.github/workflows/demo.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Demo
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Demo
|
||||
run: ./node_modules/.bin/gulp build-demo
|
||||
- name: Deploy to Netlify
|
||||
uses: netlify/actions/cli@master
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
18
.travis.yml
18
.travis.yml
@@ -1,18 +0,0 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- bower_components
|
||||
install: yarn install
|
||||
script:
|
||||
- npm run build
|
||||
- hassio/script/build_hassio
|
||||
# Because else eslint fails because hassio has cleaned that build
|
||||
- ./node_modules/.bin/gulp gen-icons-app
|
||||
- npm run test
|
||||
# - xvfb-run wct --module-resolution=node --npm
|
||||
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
|
||||
dist: trusty
|
||||
addons:
|
||||
sauce_connect: true
|
@@ -34,6 +34,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
|
||||
},
|
||||
],
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
[
|
||||
require("@babel/plugin-proposal-decorators").default,
|
||||
{ decoratorsBeforeExport: true },
|
||||
|
@@ -57,7 +57,7 @@ const handler = (done) => (err, stats) => {
|
||||
|
||||
gulp.task("webpack-watch-app", () => {
|
||||
// we are not calling done, so this command will run forever
|
||||
webpack(bothBuilds(createAppConfig, { isProdBuild: false })).watch(
|
||||
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
|
||||
{},
|
||||
handler()
|
||||
);
|
||||
|
@@ -15,6 +15,7 @@ import {
|
||||
import {
|
||||
LovelaceConfig,
|
||||
getLovelaceCollection,
|
||||
fetchResources,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import "./hc-launch-screen";
|
||||
import { castContext } from "../cast_context";
|
||||
@@ -23,6 +24,8 @@ import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
|
||||
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
|
||||
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
|
||||
|
||||
let resourcesLoaded = false;
|
||||
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@property() private _showDemo = false;
|
||||
@@ -34,6 +37,7 @@ export class HcMain extends HassElement {
|
||||
@property() private _error?: string;
|
||||
|
||||
private _unsubLovelace?: UnsubscribeFunc;
|
||||
private _urlPath?: string | null;
|
||||
|
||||
public processIncomingMessage(msg: HassMessage) {
|
||||
if (msg.type === "connect") {
|
||||
@@ -108,6 +112,7 @@ export class HcMain extends HassElement {
|
||||
if (this.hass) {
|
||||
status.hassUrl = this.hass.auth.data.hassUrl;
|
||||
status.lovelacePath = this._lovelacePath!;
|
||||
status.urlPath = this._urlPath;
|
||||
}
|
||||
|
||||
if (senderId) {
|
||||
@@ -163,8 +168,19 @@ export class HcMain extends HassElement {
|
||||
this._error = "Cannot show Lovelace because we're not connected.";
|
||||
return;
|
||||
}
|
||||
if (!this._unsubLovelace) {
|
||||
const llColl = getLovelaceCollection(this.hass!.connection);
|
||||
if (!resourcesLoaded) {
|
||||
resourcesLoaded = true;
|
||||
loadLovelaceResources(
|
||||
await fetchResources(this.hass!.connection),
|
||||
this.hass!.auth.data.hassUrl
|
||||
);
|
||||
}
|
||||
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
|
||||
this._urlPath = msg.urlPath;
|
||||
if (this._unsubLovelace) {
|
||||
this._unsubLovelace();
|
||||
}
|
||||
const llColl = getLovelaceCollection(this.hass!.connection, msg.urlPath);
|
||||
// We first do a single refresh because we need to check if there is LL
|
||||
// configuration.
|
||||
try {
|
||||
@@ -194,12 +210,6 @@ export class HcMain extends HassElement {
|
||||
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
||||
castContext.setApplicationState(lovelaceConfig.title!);
|
||||
this._lovelaceConfig = lovelaceConfig;
|
||||
if (lovelaceConfig.resources) {
|
||||
loadLovelaceResources(
|
||||
lovelaceConfig.resources,
|
||||
this.hass!.auth.data.hassUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleShowDemo(_msg: ShowDemoMessage) {
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 20 KiB |
@@ -395,7 +395,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
cards: [
|
||||
{
|
||||
entity: "script.air_cleaner_quiet",
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
@@ -408,7 +408,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
{
|
||||
entity: "script.air_cleaner_auto",
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
@@ -421,7 +421,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
{
|
||||
entity: "script.air_cleaner_turbo",
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
name: "AC bed",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
@@ -434,7 +434,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
{
|
||||
entity: "script.ac_off",
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
name: "AC",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
@@ -447,7 +447,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
{
|
||||
entity: "script.ac_on",
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
name: "AC",
|
||||
tap_action: {
|
||||
action: "call-service",
|
||||
@@ -658,7 +658,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
action: "call-service",
|
||||
service: "script.goodnight",
|
||||
},
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
icon: "mdi:weather-night",
|
||||
},
|
||||
{
|
||||
@@ -670,7 +670,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
service: "scene.turn_on",
|
||||
},
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
icon: "mdi:coffee-outline",
|
||||
},
|
||||
{
|
||||
@@ -682,7 +682,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
service: "scene.turn_on",
|
||||
},
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
icon: "mdi:television-classic",
|
||||
},
|
||||
],
|
||||
@@ -743,7 +743,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
service: "light.toggle",
|
||||
},
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
icon: "mdi:page-layout-footer",
|
||||
},
|
||||
{
|
||||
@@ -755,7 +755,7 @@ export const demoLovelaceTeachingbirds: DemoConfig["lovelace"] = () => ({
|
||||
},
|
||||
service: "light.toggle",
|
||||
},
|
||||
type: "entity-button",
|
||||
type: "button",
|
||||
icon: "mdi:page-layout-header",
|
||||
},
|
||||
],
|
||||
|
@@ -15,14 +15,14 @@ const CONFIGS = [
|
||||
{
|
||||
heading: "Basic example",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "With Name",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
name: Bedroom
|
||||
entity: light.bed_light
|
||||
`,
|
||||
@@ -30,7 +30,7 @@ const CONFIGS = [
|
||||
{
|
||||
heading: "With Icon",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
icon: mdi:hotel
|
||||
`,
|
||||
@@ -38,7 +38,7 @@ const CONFIGS = [
|
||||
{
|
||||
heading: "Without State",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
show_state: false
|
||||
`,
|
||||
@@ -46,7 +46,7 @@ const CONFIGS = [
|
||||
{
|
||||
heading: "Custom Tap Action (toggle)",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
tap_action:
|
||||
action: toggle
|
||||
@@ -55,7 +55,7 @@ const CONFIGS = [
|
||||
{
|
||||
heading: "Running Service",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
entity: light.bed_light
|
||||
service: light.toggle
|
||||
`,
|
||||
@@ -63,13 +63,13 @@ const CONFIGS = [
|
||||
{
|
||||
heading: "Invalid Entity",
|
||||
config: `
|
||||
- type: entity-button
|
||||
- type: button
|
||||
entity: sensor.invalid_entity
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
class DemoEntityButtonEntity extends PolymerElement {
|
||||
class DemoButtonEntity extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<demo-cards
|
||||
@@ -97,4 +97,4 @@ class DemoEntityButtonEntity extends PolymerElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("demo-hui-entity-button-card", DemoEntityButtonEntity);
|
||||
customElements.define("demo-hui-button-card", DemoButtonEntity);
|
||||
|
@@ -128,22 +128,27 @@ class HassioAddonAudio extends LitElement {
|
||||
|
||||
private _setInputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
this._selectedInput = device || null;
|
||||
this._selectedInput = device;
|
||||
}
|
||||
|
||||
private _setOutputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
this._selectedOutput = device || null;
|
||||
this._selectedOutput = device;
|
||||
}
|
||||
|
||||
private async _addonChanged(): Promise<void> {
|
||||
this._selectedInput = this.addon.audio_input;
|
||||
this._selectedOutput = this.addon.audio_output;
|
||||
this._selectedInput =
|
||||
this.addon.audio_input === null ? "default" : this.addon.audio_input;
|
||||
this._selectedOutput =
|
||||
this.addon.audio_output === null ? "default" : this.addon.audio_output;
|
||||
if (this._outputDevices) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noDevice: HassioHardwareAudioDevice = { device: null, name: "-" };
|
||||
const noDevice: HassioHardwareAudioDevice = {
|
||||
device: "default",
|
||||
name: "Default",
|
||||
};
|
||||
|
||||
try {
|
||||
const { audio } = await fetchHassioHardwareAudio(this.hass);
|
||||
@@ -168,8 +173,10 @@ class HassioAddonAudio extends LitElement {
|
||||
private async _saveSettings(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
audio_input: this._selectedInput || null,
|
||||
audio_output: this._selectedOutput || null,
|
||||
audio_input:
|
||||
this._selectedInput === "default" ? null : this._selectedInput,
|
||||
audio_output:
|
||||
this._selectedOutput === "default" ? null : this._selectedOutput,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
|
@@ -452,7 +452,7 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
.disabled=${!this.addon.available || this._installing}
|
||||
.progress=${this._installing}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
|
@@ -42,7 +42,9 @@ export class HassioUpdate extends LitElement {
|
||||
!!value &&
|
||||
(value.last_version
|
||||
? value.version !== value.last_version
|
||||
: value.version !== value.version_latest)
|
||||
: value.version_latest
|
||||
? value.version !== value.version_latest
|
||||
: false)
|
||||
);
|
||||
}).length;
|
||||
|
||||
@@ -102,7 +104,7 @@ export class HassioUpdate extends LitElement {
|
||||
releaseNotesUrl: string,
|
||||
icon?: string
|
||||
): TemplateResult {
|
||||
if (lastVersion === curVersion) {
|
||||
if (!lastVersion || lastVersion === curVersion) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
|
@@ -148,7 +148,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
!confirm(`WARNING:
|
||||
Beta releases are for testers and early adopters and can contain unstable code changes. Make sure you have backups of your data before you activate this feature.
|
||||
|
||||
This inludes beta releases for:
|
||||
This includes beta releases for:
|
||||
- Home Assistant (Release Candidates)
|
||||
- Hass.io supervisor
|
||||
- Host system`)
|
||||
|
56
package.json
56
package.json
@@ -18,15 +18,15 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@material/chips": "^3.2.0",
|
||||
"@material/data-table": "^3.2.0",
|
||||
"@material/mwc-base": "^0.10.0",
|
||||
"@material/mwc-button": "^0.10.0",
|
||||
"@material/mwc-checkbox": "^0.10.0",
|
||||
"@material/mwc-dialog": "^0.10.0",
|
||||
"@material/mwc-fab": "^0.10.0",
|
||||
"@material/mwc-ripple": "^0.10.0",
|
||||
"@material/mwc-switch": "^0.10.0",
|
||||
"@material/chips": "^5.0.0",
|
||||
"@material/data-table": "^5.0.0",
|
||||
"@material/mwc-base": "^0.13.0",
|
||||
"@material/mwc-button": "^0.13.0",
|
||||
"@material/mwc-checkbox": "^0.13.0",
|
||||
"@material/mwc-dialog": "^0.13.0",
|
||||
"@material/mwc-fab": "^0.13.0",
|
||||
"@material/mwc-ripple": "^0.13.0",
|
||||
"@material/mwc-switch": "^0.13.0",
|
||||
"@mdi/svg": "4.9.95",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
"@polymer/app-localize-behavior": "^3.0.1",
|
||||
@@ -70,8 +70,8 @@
|
||||
"@polymer/paper-tooltip": "^3.0.1",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"@thomasloven/round-slider": "0.3.7",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.6",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.3",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@webcomponents/shadycss": "^1.9.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "~2.8.0",
|
||||
@@ -79,12 +79,13 @@
|
||||
"codemirror": "^5.49.0",
|
||||
"cpx": "^1.5.0",
|
||||
"deep-clone-simple": "^1.1.1",
|
||||
"deep-freeze": "^0.0.1",
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^3.0.2",
|
||||
"fuse.js": "^3.4.4",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^4.4.0",
|
||||
"home-assistant-js-websocket": "4.4.1",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"leaflet": "^1.4.0",
|
||||
@@ -108,16 +109,17 @@
|
||||
"xss": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.4",
|
||||
"@babel/plugin-external-helpers": "^7.7.4",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.7.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.7.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
|
||||
"@babel/plugin-transform-react-jsx": "^7.7.4",
|
||||
"@babel/preset-env": "^7.7.4",
|
||||
"@babel/preset-typescript": "^7.7.4",
|
||||
"@babel/core": "^7.8.4",
|
||||
"@babel/plugin-external-helpers": "^7.8.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-decorators": "^7.8.3",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-react-jsx": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.4",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/chromecast-caf-receiver": "^3.0.12",
|
||||
"@types/chromecast-caf-sender": "^1.0.1",
|
||||
@@ -185,7 +187,15 @@
|
||||
"resolutions": {
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "^1.1.2"
|
||||
"lit-html": "^1.1.2",
|
||||
"@material/button": "^5.0.0",
|
||||
"@material/checkbox": "^5.0.0",
|
||||
"@material/dialog": "^5.0.0",
|
||||
"@material/fab": "^5.0.0",
|
||||
"@material/switch": "^5.0.0",
|
||||
"@material/ripple": "^5.0.0",
|
||||
"@material/dom": "^5.0.0",
|
||||
"@material/touch-target": "^5.0.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200130.0",
|
||||
version="20200228.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -21,6 +21,7 @@ export interface ConnectMessage extends BaseCastMessage {
|
||||
export interface ShowLovelaceViewMessage extends BaseCastMessage {
|
||||
type: "show_lovelace_view";
|
||||
viewPath: string | number | null;
|
||||
urlPath: string | null;
|
||||
}
|
||||
|
||||
export interface ShowDemoMessage extends BaseCastMessage {
|
||||
@@ -43,11 +44,13 @@ export const castSendAuth = (cast: CastManager, auth: Auth) =>
|
||||
|
||||
export const castSendShowLovelaceView = (
|
||||
cast: CastManager,
|
||||
viewPath: ShowLovelaceViewMessage["viewPath"]
|
||||
viewPath: ShowLovelaceViewMessage["viewPath"],
|
||||
urlPath?: string | null
|
||||
) =>
|
||||
cast.sendMessage({
|
||||
type: "show_lovelace_view",
|
||||
viewPath,
|
||||
urlPath: urlPath || null,
|
||||
});
|
||||
|
||||
export const castSendShowDemo = (cast: CastManager) =>
|
||||
|
@@ -8,6 +8,7 @@ export interface ReceiverStatusMessage extends BaseCastMessage {
|
||||
showDemo: boolean;
|
||||
hassUrl?: string;
|
||||
lovelacePath?: string | number | null;
|
||||
urlPath?: string | null;
|
||||
}
|
||||
|
||||
export type SenderMessage = ReceiverStatusMessage;
|
||||
|
@@ -44,6 +44,7 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"light",
|
||||
"lock",
|
||||
"media_player",
|
||||
"person",
|
||||
"script",
|
||||
"sun",
|
||||
"timer",
|
||||
|
31
src/common/datetime/check_options_support.ts
Normal file
31
src/common/datetime/check_options_support.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
// Check for support of native locale string options
|
||||
function checkToLocaleDateStringSupportsOptions() {
|
||||
try {
|
||||
new Date().toLocaleDateString("i");
|
||||
} catch (e) {
|
||||
return e.name === "RangeError";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkToLocaleTimeStringSupportsOptions() {
|
||||
try {
|
||||
new Date().toLocaleTimeString("i");
|
||||
} catch (e) {
|
||||
return e.name === "RangeError";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkToLocaleStringSupportsOptions() {
|
||||
try {
|
||||
new Date().toLocaleString("i");
|
||||
} catch (e) {
|
||||
return e.name === "RangeError";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const toLocaleDateStringSupportsOptions = checkToLocaleDateStringSupportsOptions();
|
||||
export const toLocaleTimeStringSupportsOptions = checkToLocaleTimeStringSupportsOptions();
|
||||
export const toLocaleStringSupportsOptions = checkToLocaleStringSupportsOptions();
|
@@ -1,20 +1,11 @@
|
||||
import fecha from "fecha";
|
||||
import { toLocaleDateStringSupportsOptions } from "./check_options_support";
|
||||
|
||||
// Check for support of native locale string options
|
||||
function toLocaleDateStringSupportsOptions() {
|
||||
try {
|
||||
new Date().toLocaleDateString("i");
|
||||
} catch (e) {
|
||||
return e.name === "RangeError";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default toLocaleDateStringSupportsOptions()
|
||||
export const formatDate = toLocaleDateStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleDateString(locales, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "mediumDate");
|
||||
: (dateObj: Date) => fecha.format(dateObj, "longDate");
|
||||
|
@@ -1,16 +1,7 @@
|
||||
import fecha from "fecha";
|
||||
import { toLocaleStringSupportsOptions } from "./check_options_support";
|
||||
|
||||
// Check for support of native locale string options
|
||||
function toLocaleStringSupportsOptions() {
|
||||
try {
|
||||
new Date().toLocaleString("i");
|
||||
} catch (e) {
|
||||
return e.name === "RangeError";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default toLocaleStringSupportsOptions()
|
||||
export const formatDateTime = toLocaleStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleString(locales, {
|
||||
year: "numeric",
|
||||
@@ -19,4 +10,24 @@ export default toLocaleStringSupportsOptions()
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "haDateTime");
|
||||
: (dateObj: Date) =>
|
||||
fecha.format(
|
||||
dateObj,
|
||||
`${fecha.masks.longDate}, ${fecha.masks.shortTime}`
|
||||
);
|
||||
|
||||
export const formatDateTimeWithSeconds = toLocaleStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleString(locales, {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) =>
|
||||
fecha.format(
|
||||
dateObj,
|
||||
`${fecha.masks.longDate}, ${fecha.masks.mediumTime}`
|
||||
);
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import fecha from "fecha";
|
||||
import { toLocaleTimeStringSupportsOptions } from "./check_options_support";
|
||||
|
||||
// Check for support of native locale string options
|
||||
function toLocaleTimeStringSupportsOptions() {
|
||||
try {
|
||||
new Date().toLocaleTimeString("i");
|
||||
} catch (e) {
|
||||
return e.name === "RangeError";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export default toLocaleTimeStringSupportsOptions()
|
||||
export const formatTime = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleTimeString(locales, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "shortTime");
|
||||
|
||||
export const formatTimeWithSeconds = toLocaleTimeStringSupportsOptions
|
||||
? (dateObj: Date, locales: string) =>
|
||||
dateObj.toLocaleTimeString(locales, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
})
|
||||
: (dateObj: Date) => fecha.format(dateObj, "mediumTime");
|
||||
|
@@ -4,7 +4,7 @@ export const dynamicElement = directive(
|
||||
(tag: string, properties?: { [key: string]: any }) => (part: Part): void => {
|
||||
if (!(part instanceof NodePart)) {
|
||||
throw new Error(
|
||||
"dynamicContentDirective can only be used in content bindings"
|
||||
"dynamicElementDirective can only be used in content bindings"
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import formatDateTime from "../datetime/format_date_time";
|
||||
import formatDate from "../datetime/format_date";
|
||||
import formatTime from "../datetime/format_time";
|
||||
import { formatDateTime } from "../datetime/format_date_time";
|
||||
import { formatDate } from "../datetime/format_date";
|
||||
import { formatTime } from "../datetime/format_time";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
|
||||
export const computeStateDisplay = (
|
||||
|
@@ -23,7 +23,7 @@ const fixedIcons = {
|
||||
homeassistant: "hass:home-assistant",
|
||||
homekit: "hass:home-automation",
|
||||
image_processing: "hass:image-filter-frames",
|
||||
input_boolean: "hass:drawing",
|
||||
input_boolean: "hass:toggle-switch-outline",
|
||||
input_datetime: "hass:calendar-clock",
|
||||
input_number: "hass:ray-vertex",
|
||||
input_select: "hass:format-list-bulleted",
|
||||
|
@@ -7,14 +7,18 @@ import {
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../dom/fire_event";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@material/mwc-button";
|
||||
import "../../components/ha-icon";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@customElement("search-input")
|
||||
class SearchInput extends LitElement {
|
||||
@property() public filter?: string;
|
||||
@property({ type: Boolean, attribute: "no-label-float" })
|
||||
public noLabelFloat? = false;
|
||||
@property({ type: Boolean, attribute: "no-underline" })
|
||||
public noUnderline = false;
|
||||
|
||||
public focus() {
|
||||
this.shadowRoot!.querySelector("paper-input")!.focus();
|
||||
@@ -22,18 +26,24 @@ class SearchInput extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
.no-underline {
|
||||
--paper-input-container-underline: {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="search-container">
|
||||
<paper-input
|
||||
class=${classMap({ "no-underline": this.noUnderline })}
|
||||
autofocus
|
||||
label="Search"
|
||||
.value=${this.filter}
|
||||
@value-changed=${this._filterInputChanged}
|
||||
.noLabelFloat=${this.noLabelFloat}
|
||||
>
|
||||
<iron-icon
|
||||
icon="hass:magnify"
|
||||
slot="prefix"
|
||||
class="prefix"
|
||||
></iron-icon>
|
||||
<ha-icon icon="hass:magnify" slot="prefix" class="prefix"></ha-icon>
|
||||
${this.filter &&
|
||||
html`
|
||||
<paper-icon-button
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { css } from "lit-element";
|
||||
|
||||
export const iconColorCSS = css`
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"],
|
||||
ha-icon[data-domain="alert"][data-state="on"],
|
||||
ha-icon[data-domain="automation"][data-state="on"],
|
||||
ha-icon[data-domain="binary_sensor"][data-state="on"],
|
||||
@@ -30,6 +29,38 @@ export const iconColorCSS = css`
|
||||
color: var(--heat-color, #ff8100);
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
||||
color: var(--dry-color, #efbd07);
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"] {
|
||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="disarmed"] {
|
||||
color: var(--alarm-color-disarmed, var(--label-badge-green));
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="pending"],
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="arming"] {
|
||||
color: var(--alarm-color-pending, var(--label-badge-yellow));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"][data-state="triggered"] {
|
||||
color: var(--alarm-color-triggered, var(--label-badge-red));
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ha-icon[data-domain="plant"][data-state="problem"],
|
||||
ha-icon[data-domain="zwave"][data-state="dead"] {
|
||||
color: var(--error-state-color, #db4437);
|
||||
|
@@ -80,6 +80,7 @@ export interface DataTableColumnData extends DataTableSortColumnData {
|
||||
|
||||
export interface DataTableRowData {
|
||||
[key: string]: any;
|
||||
selectable?: boolean;
|
||||
}
|
||||
|
||||
@customElement("ha-data-table")
|
||||
@@ -101,6 +102,8 @@ export class HaDataTable extends BaseElement {
|
||||
@property({ type: String }) private _sortColumn?: string;
|
||||
@property({ type: String }) private _sortDirection: SortingDirection = null;
|
||||
@property({ type: Array }) private _filteredData: DataTableRowData[] = [];
|
||||
@query("slot[name='header']") private _header!: HTMLSlotElement;
|
||||
@query(".scroller") private _scroller!: HTMLDivElement;
|
||||
private _sortColumns: {
|
||||
[key: string]: DataTableSortColumnData;
|
||||
} = {};
|
||||
@@ -170,7 +173,7 @@ export class HaDataTable extends BaseElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="mdc-data-table">
|
||||
<slot name="header">
|
||||
<slot name="header" @slotchange=${this._calcScrollHeight}>
|
||||
${this._filterable
|
||||
? html`
|
||||
<div class="table-header">
|
||||
@@ -181,112 +184,116 @@ export class HaDataTable extends BaseElement {
|
||||
`
|
||||
: ""}
|
||||
</slot>
|
||||
<table class="mdc-data-table__table">
|
||||
<thead>
|
||||
<tr class="mdc-data-table__header-row">
|
||||
${this.selectable
|
||||
? html`
|
||||
<div class="scroller">
|
||||
<table class="mdc-data-table__table">
|
||||
<thead>
|
||||
<tr class="mdc-data-table__header-row">
|
||||
${this.selectable
|
||||
? html`
|
||||
<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--checkbox"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleHeaderRowCheckboxChange}
|
||||
.indeterminate=${this._headerIndeterminate}
|
||||
.checked=${this._headerChecked}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</th>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
const sorted = key === this._sortColumn;
|
||||
const classes = {
|
||||
"mdc-data-table__header-cell--numeric": Boolean(
|
||||
column.type && column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__header-cell--icon": Boolean(
|
||||
column.type && column.type === "icon"
|
||||
),
|
||||
sortable: Boolean(column.sortable),
|
||||
"not-sorted": Boolean(column.sortable && !sorted),
|
||||
};
|
||||
return html`
|
||||
<th
|
||||
class="mdc-data-table__header-cell mdc-data-table__header-cell--checkbox"
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
@click=${this._handleHeaderClick}
|
||||
data-column-id="${key}"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleHeaderRowCheckboxChange}
|
||||
.indeterminate=${this._headerIndeterminate}
|
||||
.checked=${this._headerChecked}
|
||||
>
|
||||
</ha-checkbox>
|
||||
${column.sortable
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${sorted && this._sortDirection === "desc"
|
||||
? "hass:arrow-down"
|
||||
: "hass:arrow-up"}
|
||||
></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
<span>${column.title}</span>
|
||||
</th>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
const sorted = key === this._sortColumn;
|
||||
const classes = {
|
||||
"mdc-data-table__header-cell--numeric": Boolean(
|
||||
column.type && column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__header-cell--icon": Boolean(
|
||||
column.type && column.type === "icon"
|
||||
),
|
||||
sortable: Boolean(column.sortable),
|
||||
"not-sorted": Boolean(column.sortable && !sorted),
|
||||
};
|
||||
return html`
|
||||
<th
|
||||
class="mdc-data-table__header-cell ${classMap(classes)}"
|
||||
role="columnheader"
|
||||
scope="col"
|
||||
@click=${this._handleHeaderClick}
|
||||
data-column-id="${key}"
|
||||
`;
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mdc-data-table__content">
|
||||
${repeat(
|
||||
this._filteredData!,
|
||||
(row: DataTableRowData) => row[this.id],
|
||||
(row: DataTableRowData) => html`
|
||||
<tr
|
||||
data-row-id="${row[this.id]}"
|
||||
@click=${this._handleRowClick}
|
||||
class="mdc-data-table__row"
|
||||
.selectable=${row.selectable !== false}
|
||||
>
|
||||
${column.sortable
|
||||
${this.selectable
|
||||
? html`
|
||||
<ha-icon
|
||||
.icon=${sorted && this._sortDirection === "desc"
|
||||
? "hass:arrow-down"
|
||||
: "hass:arrow-up"}
|
||||
></ha-icon>
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleRowCheckboxChange}
|
||||
.disabled=${row.selectable === false}
|
||||
.checked=${this._checkedRows.includes(
|
||||
String(row[this.id])
|
||||
)}
|
||||
>
|
||||
</ha-checkbox>
|
||||
</td>
|
||||
`
|
||||
: ""}
|
||||
<span>${column.title}</span>
|
||||
</th>
|
||||
`;
|
||||
})}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="mdc-data-table__content">
|
||||
${repeat(
|
||||
this._filteredData!,
|
||||
(row: DataTableRowData) => row[this.id],
|
||||
(row: DataTableRowData) => html`
|
||||
<tr
|
||||
data-row-id="${row[this.id]}"
|
||||
@click=${this._handleRowClick}
|
||||
class="mdc-data-table__row"
|
||||
>
|
||||
${this.selectable
|
||||
? html`
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
return html`
|
||||
<td
|
||||
class="mdc-data-table__cell mdc-data-table__cell--checkbox"
|
||||
class="mdc-data-table__cell ${classMap({
|
||||
"mdc-data-table__cell--numeric": Boolean(
|
||||
column.type && column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__cell--icon": Boolean(
|
||||
column.type && column.type === "icon"
|
||||
),
|
||||
})}"
|
||||
>
|
||||
<ha-checkbox
|
||||
class="mdc-data-table__row-checkbox"
|
||||
@change=${this._handleRowCheckboxChange}
|
||||
.checked=${this._checkedRows.includes(
|
||||
String(row[this.id])
|
||||
)}
|
||||
>
|
||||
</ha-checkbox>
|
||||
${column.template
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</td>
|
||||
`
|
||||
: ""}
|
||||
${Object.entries(this.columns).map((columnEntry) => {
|
||||
const [key, column] = columnEntry;
|
||||
return html`
|
||||
<td
|
||||
class="mdc-data-table__cell ${classMap({
|
||||
"mdc-data-table__cell--numeric": Boolean(
|
||||
column.type && column.type === "numeric"
|
||||
),
|
||||
"mdc-data-table__cell--icon": Boolean(
|
||||
column.type && column.type === "icon"
|
||||
),
|
||||
})}"
|
||||
>
|
||||
${column.template
|
||||
? column.template(row[key], row)
|
||||
: row[key]}
|
||||
</td>
|
||||
`;
|
||||
})}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
})}
|
||||
</tr>
|
||||
`
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -294,9 +301,12 @@ export class HaDataTable extends BaseElement {
|
||||
protected createAdapter(): MDCDataTableAdapter {
|
||||
return {
|
||||
addClassAtRowIndex: (rowIndex: number, cssClasses: string) => {
|
||||
if (!(this.rowElements[rowIndex] as any).selectable) {
|
||||
return;
|
||||
}
|
||||
this.rowElements[rowIndex].classList.add(cssClasses);
|
||||
},
|
||||
getRowCount: () => this.data.length,
|
||||
getRowCount: () => this.rowElements.length,
|
||||
getRowElements: () => this.rowElements,
|
||||
getRowIdAtIndex: (rowIndex: number) => this._getRowIdAtIndex(rowIndex),
|
||||
getRowIndexByChildElement: (el: Element) =>
|
||||
@@ -305,7 +315,7 @@ export class HaDataTable extends BaseElement {
|
||||
isCheckboxAtRowIndexChecked: (rowIndex: number) =>
|
||||
this._checkedRows.includes(this._getRowIdAtIndex(rowIndex)),
|
||||
isHeaderRowCheckboxChecked: () => this._headerChecked,
|
||||
isRowsSelectable: () => true,
|
||||
isRowsSelectable: () => this.selectable,
|
||||
notifyRowSelectionChanged: () => undefined,
|
||||
notifySelectedAll: () => undefined,
|
||||
notifyUnselectedAll: () => undefined,
|
||||
@@ -328,6 +338,9 @@ export class HaDataTable extends BaseElement {
|
||||
this._headerIndeterminate = indeterminate;
|
||||
},
|
||||
setRowCheckboxCheckedAtIndex: (rowIndex: number, checked: boolean) => {
|
||||
if (!(this.rowElements[rowIndex] as any).selectable) {
|
||||
return;
|
||||
}
|
||||
this._setRowChecked(this._getRowIdAtIndex(rowIndex), checked);
|
||||
},
|
||||
};
|
||||
@@ -434,6 +447,11 @@ export class HaDataTable extends BaseElement {
|
||||
this._debounceSearch(ev.detail.value);
|
||||
}
|
||||
|
||||
private async _calcScrollHeight() {
|
||||
await this.updateComplete;
|
||||
this._scroller.style.maxHeight = `calc(100% - ${this._header.clientHeight}px)`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
/* default mdc styles, colors changed, without checkbox styles */
|
||||
@@ -507,6 +525,7 @@ export class HaDataTable extends BaseElement {
|
||||
padding-left: 16px;
|
||||
/* @noflip */
|
||||
padding-right: 0;
|
||||
width: 40px;
|
||||
}
|
||||
[dir="rtl"] .mdc-data-table__header-cell--checkbox,
|
||||
.mdc-data-table__header-cell--checkbox[dir="rtl"],
|
||||
@@ -549,6 +568,19 @@ export class HaDataTable extends BaseElement {
|
||||
.mdc-data-table__cell--icon {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child ha-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child state-badge {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell {
|
||||
@@ -578,42 +610,66 @@ export class HaDataTable extends BaseElement {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* custom from here */
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mdc-data-table {
|
||||
display: block;
|
||||
border-width: var(--data-table-border-width, 1px);
|
||||
height: 100%;
|
||||
}
|
||||
.mdc-data-table__header-cell {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.mdc-data-table__header-cell span {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon)
|
||||
span {
|
||||
position: relative;
|
||||
left: -24px;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted > * {
|
||||
.mdc-data-table__header-cell > * {
|
||||
transition: left 0.2s ease 0s;
|
||||
}
|
||||
.mdc-data-table__header-cell ha-icon {
|
||||
top: 15px;
|
||||
position: absolute;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted ha-icon {
|
||||
left: -36px;
|
||||
left: -20px;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon):hover
|
||||
.mdc-data-table__header-cell:not(.not-sorted) span,
|
||||
.mdc-data-table__header-cell.not-sorted:hover span {
|
||||
left: 24px;
|
||||
}
|
||||
.mdc-data-table__header-cell.mdc-data-table__header-cell--numeric:not(.not-sorted)
|
||||
span,
|
||||
.mdc-data-table__header-cell.mdc-data-table__header-cell--numeric.not-sorted:hover
|
||||
span {
|
||||
left: 0px;
|
||||
left: 12px;
|
||||
}
|
||||
.mdc-data-table__header-cell:not(.not-sorted) ha-icon,
|
||||
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
|
||||
left: 0px;
|
||||
left: 12px;
|
||||
}
|
||||
.table-header {
|
||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||
}
|
||||
search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.scroller {
|
||||
overflow: auto;
|
||||
}
|
||||
slot[name="header"] {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
DeviceEntityLookup,
|
||||
} from "../../data/device_registry";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
@@ -30,7 +31,6 @@ import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
|
@@ -22,6 +22,7 @@ import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
computeDeviceName,
|
||||
DeviceEntityLookup,
|
||||
} from "../../data/device_registry";
|
||||
import { compare } from "../../common/string/compare";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
@@ -29,7 +30,6 @@ import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import { DeviceEntityLookup } from "../../panels/config/devices/ha-devices-data-table";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
|
@@ -6,7 +6,7 @@ import { Debouncer } from "@polymer/polymer/lib/utils/debounce";
|
||||
import { timeOut } from "@polymer/polymer/lib/utils/async";
|
||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||
|
||||
import formatTime from "../../common/datetime/format_time";
|
||||
import { formatTime } from "../../common/datetime/format_time";
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
/* global Chart moment Color */
|
||||
|
||||
|
@@ -10,6 +10,9 @@ import {
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
import hassAttributeUtil from "../util/hass-attributes-util";
|
||||
import { until } from "lit-html/directives/until";
|
||||
|
||||
let jsYamlPromise: Promise<typeof import("js-yaml")>;
|
||||
|
||||
@customElement("ha-attributes")
|
||||
class HaAttributes extends LitElement {
|
||||
@@ -32,7 +35,7 @@ class HaAttributes extends LitElement {
|
||||
<div class="data-entry">
|
||||
<div class="key">${attribute.replace(/_/g, " ")}</div>
|
||||
<div class="value">
|
||||
${this.formatAttributeValue(attribute)}
|
||||
${this.formatAttribute(attribute)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@@ -63,6 +66,10 @@ class HaAttributes extends LitElement {
|
||||
color: var(--secondary-text-color);
|
||||
text-align: right;
|
||||
}
|
||||
pre {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -75,18 +82,31 @@ class HaAttributes extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private formatAttributeValue(attribute: string): string {
|
||||
private formatAttribute(attribute: string): string | TemplateResult {
|
||||
if (!this.stateObj) {
|
||||
return "-";
|
||||
}
|
||||
const value = this.stateObj.attributes[attribute];
|
||||
return this.formatAttributeValue(value);
|
||||
}
|
||||
|
||||
private formatAttributeValue(value: any): string | TemplateResult {
|
||||
if (value === null) {
|
||||
return "-";
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.join(", ");
|
||||
if (
|
||||
(Array.isArray(value) && value.some((val) => val instanceof Object)) ||
|
||||
(!Array.isArray(value) && value instanceof Object)
|
||||
) {
|
||||
if (!jsYamlPromise) {
|
||||
jsYamlPromise = import(/* webpackChunkName: "js-yaml" */ "js-yaml");
|
||||
}
|
||||
const yaml = jsYamlPromise.then((jsYaml) => jsYaml.safeDump(value));
|
||||
return html`
|
||||
<pre>${until(yaml, "")}</pre>
|
||||
`;
|
||||
}
|
||||
return value instanceof Object ? JSON.stringify(value, null, 2) : value;
|
||||
return Array.isArray(value) ? value.join(", ") : value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ const MwcCheckbox = customElements.get("mwc-checkbox") as Constructor<Checkbox>;
|
||||
|
||||
@customElement("ha-checkbox")
|
||||
export class HaCheckbox extends MwcCheckbox {
|
||||
protected firstUpdated() {
|
||||
public firstUpdated() {
|
||||
super.firstUpdated();
|
||||
this.style.setProperty("--mdc-theme-secondary", "var(--primary-color)");
|
||||
}
|
||||
|
@@ -1,12 +1,23 @@
|
||||
import { customElement, CSSResult, css } from "lit-element";
|
||||
import { customElement, CSSResult, css, html } from "lit-element";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@material/mwc-dialog";
|
||||
import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
||||
// tslint:disable-next-line
|
||||
import { Dialog } from "@material/mwc-dialog";
|
||||
import { Constructor } from "../types";
|
||||
import { Constructor, HomeAssistant } from "../types";
|
||||
// tslint:disable-next-line
|
||||
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||
|
||||
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
${title}
|
||||
<paper-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
class="close_button"
|
||||
></paper-icon-button>
|
||||
`;
|
||||
|
||||
@customElement("ha-dialog")
|
||||
export class HaDialog extends MwcDialog {
|
||||
protected static get styles(): CSSResult[] {
|
||||
@@ -19,6 +30,15 @@ export class HaDialog extends MwcDialog {
|
||||
.mdc-dialog__container {
|
||||
align-items: var(--vertial-align-dialog, center);
|
||||
}
|
||||
.mdc-dialog__title::before {
|
||||
display: block;
|
||||
height: 20px;
|
||||
}
|
||||
.close_button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
158
src/components/ha-form/ha-form-multi_select.ts
Normal file
158
src/components/ha-form/ha-form-multi_select.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-menu-button/paper-menu-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-ripple/paper-ripple";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import {
|
||||
HaFormElement,
|
||||
HaFormMultiSelectData,
|
||||
HaFormMultiSelectSchema,
|
||||
} from "./ha-form";
|
||||
|
||||
@customElement("ha-form-multi_select")
|
||||
export class HaFormMultiSelect extends LitElement implements HaFormElement {
|
||||
@property() public schema!: HaFormMultiSelectSchema;
|
||||
@property() public data!: HaFormMultiSelectData;
|
||||
@property() public label!: string;
|
||||
@property() public suffix!: string;
|
||||
@property() private _init = false;
|
||||
@query("paper-menu-button") private _input?: HTMLElement;
|
||||
|
||||
public focus(): void {
|
||||
if (this._input) {
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const options = Array.isArray(this.schema.options)
|
||||
? this.schema.options
|
||||
: Object.entries(this.schema.options!);
|
||||
|
||||
const data = this.data || [];
|
||||
return html`
|
||||
<paper-menu-button horizontal-align="right" vertical-offset="8">
|
||||
<div class="dropdown-trigger" slot="dropdown-trigger">
|
||||
<paper-ripple></paper-ripple>
|
||||
<paper-input
|
||||
id="input"
|
||||
type="text"
|
||||
readonly
|
||||
value=${data
|
||||
.map((value) => this.schema.options![value] || value)
|
||||
.join(", ")}
|
||||
label=${this.label}
|
||||
input-role="button"
|
||||
input-aria-haspopup="listbox"
|
||||
autocomplete="off"
|
||||
>
|
||||
<iron-icon
|
||||
icon="paper-dropdown-menu:arrow-drop-down"
|
||||
suffix
|
||||
slot="suffix"
|
||||
></iron-icon>
|
||||
</paper-input>
|
||||
</div>
|
||||
<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() {
|
||||
this.updateComplete.then(() => {
|
||||
const input = (this.shadowRoot?.querySelector("paper-input")
|
||||
?.inputElement as any)?.inputElement;
|
||||
if (input) {
|
||||
input.style.textOverflow = "ellipsis";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _optionValue(item: string | string[]): string {
|
||||
return Array.isArray(item) ? item[0] : item;
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!ev.detail.value || !this._init) {
|
||||
// ignore first call because that is the init of the component
|
||||
this._init = true;
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{
|
||||
value: ev.detail.value.map((element) => element.itemValue),
|
||||
},
|
||||
{ bubbles: false }
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-menu-button {
|
||||
display: block;
|
||||
padding: 0;
|
||||
--paper-item-icon-width: 34px;
|
||||
}
|
||||
paper-ripple {
|
||||
top: 12px;
|
||||
left: 0px;
|
||||
bottom: 8px;
|
||||
right: 0px;
|
||||
}
|
||||
paper-input {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-form-multi_select": HaFormMultiSelect;
|
||||
}
|
||||
}
|
@@ -5,6 +5,8 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
query,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { HaFormElement, HaFormSelectData, HaFormSelectSchema } from "./ha-form";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -36,8 +38,10 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
.selected=${this.data}
|
||||
@selected-item-changed=${this._valueChanged}
|
||||
>
|
||||
${this.schema.options!.map(
|
||||
(item) => html`
|
||||
${// 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>
|
||||
@@ -48,12 +52,12 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _optionValue(item) {
|
||||
private _optionValue(item: string | [string, string]) {
|
||||
return Array.isArray(item) ? item[0] : item;
|
||||
}
|
||||
|
||||
private _optionLabel(item) {
|
||||
return Array.isArray(item) ? item[1] : item;
|
||||
private _optionLabel(item: string | [string, string]) {
|
||||
return Array.isArray(item) ? item[1] || item[0] : item;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
@@ -64,6 +68,14 @@ export class HaFormSelect extends LitElement implements HaFormElement {
|
||||
value: ev.detail.value.itemValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -12,6 +12,7 @@ import "./ha-form-integer";
|
||||
import "./ha-form-float";
|
||||
import "./ha-form-boolean";
|
||||
import "./ha-form-select";
|
||||
import "./ha-form-multi_select";
|
||||
import "./ha-form-positive_time_period_dict";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { dynamicElement } from "../../common/dom/dynamic-element-directive";
|
||||
@@ -22,6 +23,7 @@ export type HaFormSchema =
|
||||
| HaFormFloatSchema
|
||||
| HaFormBooleanSchema
|
||||
| HaFormSelectSchema
|
||||
| HaFormMultiSelectSchema
|
||||
| HaFormTimeSchema;
|
||||
|
||||
export interface HaFormBaseSchema {
|
||||
@@ -41,7 +43,12 @@ export interface HaFormIntegerSchema extends HaFormBaseSchema {
|
||||
|
||||
export interface HaFormSelectSchema extends HaFormBaseSchema {
|
||||
type: "select";
|
||||
options?: string[];
|
||||
options?: string[] | Array<[string, string]>;
|
||||
}
|
||||
|
||||
export interface HaFormMultiSelectSchema extends HaFormBaseSchema {
|
||||
type: "multi_select";
|
||||
options?: { [key: string]: string } | string[] | Array<[string, string]>;
|
||||
}
|
||||
|
||||
export interface HaFormFloatSchema extends HaFormBaseSchema {
|
||||
@@ -71,6 +78,7 @@ export type HaFormData =
|
||||
| HaFormFloatData
|
||||
| HaFormBooleanData
|
||||
| HaFormSelectData
|
||||
| HaFormMultiSelectData
|
||||
| HaFormTimeData;
|
||||
|
||||
export type HaFormStringData = string;
|
||||
@@ -78,6 +86,7 @@ export type HaFormIntegerData = number;
|
||||
export type HaFormFloatData = number;
|
||||
export type HaFormBooleanData = boolean;
|
||||
export type HaFormSelectData = string;
|
||||
export type HaFormMultiSelectData = string[];
|
||||
export interface HaFormTimeData {
|
||||
hours?: number;
|
||||
minutes?: number;
|
||||
|
65
src/components/ha-icon-input.ts
Normal file
65
src/components/ha-icon-input.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "./ha-icon";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-icon-input")
|
||||
export class HaIconInput extends LitElement {
|
||||
@property() public value?: string;
|
||||
@property() public label?: string;
|
||||
@property() public placeholder?: string;
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-input
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
auto-validate
|
||||
.errorMessage=${this.errorMessage}
|
||||
pattern="^\\S+:\\S+$"
|
||||
>
|
||||
${this.value || this.placeholder
|
||||
? html`
|
||||
<ha-icon .icon=${this.value || this.placeholder} slot="suffix">
|
||||
</ha-icon>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this.value = ev.detail.value;
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{ value: ev.detail.value },
|
||||
{
|
||||
bubbles: false,
|
||||
composed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-icon {
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@@ -68,6 +68,11 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
if (!this._related) {
|
||||
return html``;
|
||||
}
|
||||
if (Object.keys(this._related).length === 0) {
|
||||
return html`
|
||||
${this.hass.localize("ui.components.related-items.no_related_found")}
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
${this._related.config_entry && this._entries
|
||||
? this._related.config_entry.map((relatedConfigEntryId) => {
|
||||
|
@@ -46,7 +46,18 @@ const SORT_VALUE_URL_PATHS = {
|
||||
config: 11,
|
||||
};
|
||||
|
||||
const panelSorter = (a, b) => {
|
||||
const panelSorter = (a: PanelInfo, b: PanelInfo) => {
|
||||
// Put all the Lovelace at the top.
|
||||
const aLovelace = a.component_name === "lovelace";
|
||||
const bLovelace = b.component_name === "lovelace";
|
||||
|
||||
if (aLovelace && !bLovelace) {
|
||||
return -1;
|
||||
}
|
||||
if (bLovelace) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aBuiltIn = a.url_path in SORT_VALUE_URL_PATHS;
|
||||
const bBuiltIn = b.url_path in SORT_VALUE_URL_PATHS;
|
||||
|
||||
@@ -597,6 +608,7 @@ class HaSidebar extends LitElement {
|
||||
|
||||
paper-icon-item .item-text {
|
||||
display: none;
|
||||
max-width: calc(100% - 56px);
|
||||
}
|
||||
:host([expanded]) paper-icon-item .item-text {
|
||||
display: block;
|
||||
|
@@ -6,6 +6,13 @@ import { afterNextRender } from "../common/util/render-status";
|
||||
// tslint:disable-next-line
|
||||
import { HaCodeEditor } from "./ha-code-editor";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"editor-refreshed": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = (obj: object) => {
|
||||
if (typeof obj !== "object") {
|
||||
return false;
|
||||
@@ -37,6 +44,7 @@ export class HaYamlEditor extends LitElement {
|
||||
if (this._editor?.codemirror) {
|
||||
this._editor.codemirror.refresh();
|
||||
}
|
||||
afterNextRender(() => fireEvent(this, "editor-refreshed"));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -41,7 +41,8 @@ export interface MarkerLocation {
|
||||
id: string;
|
||||
icon?: string;
|
||||
radius_color?: string;
|
||||
editable?: boolean;
|
||||
location_editable?: boolean;
|
||||
radius_editable?: boolean;
|
||||
}
|
||||
|
||||
@customElement("ha-locations-editor")
|
||||
@@ -208,7 +209,7 @@ export class HaLocationsEditor extends LitElement {
|
||||
}
|
||||
);
|
||||
circle.addTo(this._leafletMap!);
|
||||
if (location.editable) {
|
||||
if (location.radius_editable || location.location_editable) {
|
||||
// @ts-ignore
|
||||
circle.editing.enable();
|
||||
// @ts-ignore
|
||||
@@ -230,19 +231,25 @@ export class HaLocationsEditor extends LitElement {
|
||||
// @ts-ignore
|
||||
(ev: MouseEvent) => this._markerClicked(ev)
|
||||
);
|
||||
resizeMarker.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateRadius(ev)
|
||||
);
|
||||
if (location.radius_editable) {
|
||||
resizeMarker.addEventListener(
|
||||
"dragend",
|
||||
// @ts-ignore
|
||||
(ev: DragEndEvent) => this._updateRadius(ev)
|
||||
);
|
||||
} else {
|
||||
resizeMarker.remove();
|
||||
}
|
||||
this._locationMarkers![location.id] = circle;
|
||||
} else {
|
||||
this._circles[location.id] = circle;
|
||||
}
|
||||
}
|
||||
if (!location.radius || !location.editable) {
|
||||
if (
|
||||
!location.radius ||
|
||||
(!location.radius_editable && !location.location_editable)
|
||||
) {
|
||||
const options: MarkerOptions = {
|
||||
draggable: Boolean(location.editable),
|
||||
title: location.name,
|
||||
};
|
||||
|
||||
|
311
src/components/map/ha-map.ts
Normal file
311
src/components/map/ha-map.ts
Normal file
@@ -0,0 +1,311 @@
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { Circle, Layer, Map, Marker } from "leaflet";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import {
|
||||
LeafletModuleType,
|
||||
setupLeafletMap,
|
||||
} from "../../common/dom/setup-leaflet-map";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { computeStateName } from "../../common/entity/compute_state_name";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import "../../panels/map/ha-entity-marker";
|
||||
|
||||
@customElement("ha-map")
|
||||
class HaMap extends LitElement {
|
||||
@property() public hass?: HomeAssistant;
|
||||
|
||||
@property() public entities?: string[];
|
||||
@property() public darkMode = false;
|
||||
@property() public zoom?: number;
|
||||
// tslint:disable-next-line
|
||||
private Leaflet?: LeafletModuleType;
|
||||
private _leafletMap?: Map;
|
||||
// @ts-ignore
|
||||
private _resizeObserver?: ResizeObserver;
|
||||
private _debouncedResizeListener = debounce(
|
||||
() => {
|
||||
if (!this._leafletMap) {
|
||||
return;
|
||||
}
|
||||
this._leafletMap.invalidateSize();
|
||||
},
|
||||
100,
|
||||
false
|
||||
);
|
||||
private _mapItems: Array<Marker | Circle> = [];
|
||||
private _mapZones: Array<Marker | Circle> = [];
|
||||
private _connected = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
this._connected = true;
|
||||
if (this.hasUpdated) {
|
||||
this.loadMap();
|
||||
this._attachObserver();
|
||||
}
|
||||
}
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this._connected = false;
|
||||
|
||||
if (this._leafletMap) {
|
||||
this._leafletMap.remove();
|
||||
this._leafletMap = undefined;
|
||||
this.Leaflet = undefined;
|
||||
}
|
||||
|
||||
if (this._resizeObserver) {
|
||||
this._resizeObserver.unobserve(this._mapEl);
|
||||
} else {
|
||||
window.removeEventListener("resize", this._debouncedResizeListener);
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.entities) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<div id="map"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this.loadMap();
|
||||
|
||||
if (this._connected) {
|
||||
this._attachObserver();
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
if (changedProps.has("hass")) {
|
||||
this._drawEntities();
|
||||
this._fitMap();
|
||||
}
|
||||
}
|
||||
|
||||
private get _mapEl(): HTMLDivElement {
|
||||
return this.shadowRoot!.getElementById("map") as HTMLDivElement;
|
||||
}
|
||||
|
||||
private async loadMap(): Promise<void> {
|
||||
[this._leafletMap, this.Leaflet] = await setupLeafletMap(
|
||||
this._mapEl,
|
||||
this.darkMode
|
||||
);
|
||||
this._drawEntities();
|
||||
this._leafletMap.invalidateSize();
|
||||
this._fitMap();
|
||||
}
|
||||
|
||||
private _fitMap(): void {
|
||||
if (!this._leafletMap || !this.Leaflet || !this.hass) {
|
||||
return;
|
||||
}
|
||||
if (this._mapItems.length === 0) {
|
||||
this._leafletMap.setView(
|
||||
new this.Leaflet.LatLng(
|
||||
this.hass.config.latitude,
|
||||
this.hass.config.longitude
|
||||
),
|
||||
this.zoom || 14
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const bounds = this.Leaflet.latLngBounds(
|
||||
this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : []
|
||||
);
|
||||
this._leafletMap.fitBounds(bounds.pad(0.5));
|
||||
|
||||
if (this.zoom && this._leafletMap.getZoom() > this.zoom) {
|
||||
this._leafletMap.setZoom(this.zoom);
|
||||
}
|
||||
}
|
||||
|
||||
private _drawEntities(): void {
|
||||
const hass = this.hass;
|
||||
const map = this._leafletMap;
|
||||
const Leaflet = this.Leaflet;
|
||||
if (!hass || !map || !Leaflet) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._mapItems) {
|
||||
this._mapItems.forEach((marker) => marker.remove());
|
||||
}
|
||||
const mapItems: Layer[] = (this._mapItems = []);
|
||||
|
||||
if (this._mapZones) {
|
||||
this._mapZones.forEach((marker) => marker.remove());
|
||||
}
|
||||
const mapZones: Layer[] = (this._mapZones = []);
|
||||
|
||||
const allEntities = this.entities!.concat();
|
||||
|
||||
for (const entity of allEntities) {
|
||||
const entityId = entity;
|
||||
const stateObj = hass.states[entityId];
|
||||
if (!stateObj) {
|
||||
continue;
|
||||
}
|
||||
const title = computeStateName(stateObj);
|
||||
const {
|
||||
latitude,
|
||||
longitude,
|
||||
passive,
|
||||
icon,
|
||||
radius,
|
||||
entity_picture: entityPicture,
|
||||
gps_accuracy: gpsAccuracy,
|
||||
} = stateObj.attributes;
|
||||
|
||||
if (!(latitude && longitude)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (computeStateDomain(stateObj) === "zone") {
|
||||
// DRAW ZONE
|
||||
if (passive) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// create icon
|
||||
let iconHTML = "";
|
||||
if (icon) {
|
||||
const el = document.createElement("ha-icon");
|
||||
el.setAttribute("icon", icon);
|
||||
iconHTML = el.outerHTML;
|
||||
} else {
|
||||
const el = document.createElement("span");
|
||||
el.innerHTML = title;
|
||||
iconHTML = el.outerHTML;
|
||||
}
|
||||
|
||||
// create marker with the icon
|
||||
mapZones.push(
|
||||
Leaflet.marker([latitude, longitude], {
|
||||
icon: Leaflet.divIcon({
|
||||
html: iconHTML,
|
||||
iconSize: [24, 24],
|
||||
className: this.darkMode ? "dark" : "light",
|
||||
}),
|
||||
interactive: false,
|
||||
title,
|
||||
})
|
||||
);
|
||||
|
||||
// create circle around it
|
||||
mapZones.push(
|
||||
Leaflet.circle([latitude, longitude], {
|
||||
interactive: false,
|
||||
color: "#FF9800",
|
||||
radius,
|
||||
})
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// DRAW ENTITY
|
||||
// create icon
|
||||
const entityName = title
|
||||
.split(" ")
|
||||
.map((part) => part[0])
|
||||
.join("")
|
||||
.substr(0, 3);
|
||||
|
||||
// create market with the icon
|
||||
mapItems.push(
|
||||
Leaflet.marker([latitude, longitude], {
|
||||
icon: Leaflet.divIcon({
|
||||
// Leaflet clones this element before adding it to the map. This messes up
|
||||
// our Polymer object and we can't pass data through. Thus we hack like this.
|
||||
html: `
|
||||
<ha-entity-marker
|
||||
entity-id="${entityId}"
|
||||
entity-name="${entityName}"
|
||||
entity-picture="${entityPicture || ""}"
|
||||
></ha-entity-marker>
|
||||
`,
|
||||
iconSize: [48, 48],
|
||||
className: "",
|
||||
}),
|
||||
title: computeStateName(stateObj),
|
||||
})
|
||||
);
|
||||
|
||||
// create circle around if entity has accuracy
|
||||
if (gpsAccuracy) {
|
||||
mapItems.push(
|
||||
Leaflet.circle([latitude, longitude], {
|
||||
interactive: false,
|
||||
color: "#0288D1",
|
||||
radius: gpsAccuracy,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._mapItems.forEach((marker) => map.addLayer(marker));
|
||||
this._mapZones.forEach((marker) => map.addLayer(marker));
|
||||
}
|
||||
|
||||
private _attachObserver(): void {
|
||||
// Observe changes to map size and invalidate to prevent broken rendering
|
||||
// Uses ResizeObserver in Chrome, otherwise window resize event
|
||||
|
||||
// @ts-ignore
|
||||
if (typeof ResizeObserver === "function") {
|
||||
// @ts-ignore
|
||||
this._resizeObserver = new ResizeObserver(() =>
|
||||
this._debouncedResizeListener()
|
||||
);
|
||||
this._resizeObserver.observe(this._mapEl);
|
||||
} else {
|
||||
window.addEventListener("resize", this._debouncedResizeListener);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 300px;
|
||||
}
|
||||
#map {
|
||||
height: 100%;
|
||||
}
|
||||
#map.dark {
|
||||
background: #090909;
|
||||
}
|
||||
|
||||
.dark {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.light {
|
||||
color: #000000;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-map": HaMap;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "./entity/ha-chart-base";
|
||||
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import formatDateTime from "../common/datetime/format_date_time";
|
||||
import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
||||
|
||||
class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
@@ -317,7 +317,7 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
const item = items[0];
|
||||
const date = data.datasets[item.datasetIndex].data[item.index].x;
|
||||
|
||||
return formatDateTime(date, this.hass.language);
|
||||
return formatDateTimeWithSeconds(date, this.hass.language);
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
|
@@ -6,7 +6,7 @@ import LocalizeMixin from "../mixins/localize-mixin";
|
||||
|
||||
import "./entity/ha-chart-base";
|
||||
|
||||
import formatDateTime from "../common/datetime/format_date_time";
|
||||
import { formatDateTimeWithSeconds } from "../common/datetime/format_date_time";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
|
||||
class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
@@ -165,8 +165,8 @@ class StateHistoryChartTimeline extends LocalizeMixin(PolymerElement) {
|
||||
const formatTooltipLabel = (item, data) => {
|
||||
const values = data.datasets[item.datasetIndex].data[item.index];
|
||||
|
||||
const start = formatDateTime(values[0], this.hass.language);
|
||||
const end = formatDateTime(values[1], this.hass.language);
|
||||
const start = formatDateTimeWithSeconds(values[0], this.hass.language);
|
||||
const end = formatDateTimeWithSeconds(values[1], this.hass.language);
|
||||
const state = values[2];
|
||||
|
||||
return [state, start, end];
|
||||
|
@@ -173,6 +173,12 @@ export type Condition =
|
||||
| DeviceCondition
|
||||
| LogicalCondition;
|
||||
|
||||
export const triggerAutomation = (hass: HomeAssistant, entityId: string) => {
|
||||
hass.callService("automation", "trigger", {
|
||||
entity_id: entityId,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteAutomation = (hass: HomeAssistant, id: string) =>
|
||||
hass.callApi("DELETE", `config/automation/config/${id}`);
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import { HomeAssistant } from "../types";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
interface CacheConfig {
|
||||
export interface CacheConfig {
|
||||
refresh: number;
|
||||
cacheKey: string;
|
||||
hoursToShow: number;
|
||||
|
@@ -1,3 +1,5 @@
|
||||
import { HaFormSchema } from "../components/ha-form/ha-form";
|
||||
|
||||
export interface DataEntryFlowProgressedEvent {
|
||||
type: "data_entry_flow_progressed";
|
||||
data: {
|
||||
@@ -7,12 +9,6 @@ export interface DataEntryFlowProgressedEvent {
|
||||
};
|
||||
}
|
||||
|
||||
export interface FieldSchema {
|
||||
name: string;
|
||||
default?: any;
|
||||
optional: boolean;
|
||||
}
|
||||
|
||||
export interface DataEntryFlowProgress {
|
||||
flow_id: string;
|
||||
handler: string;
|
||||
@@ -27,7 +23,7 @@ export interface DataEntryFlowStepForm {
|
||||
flow_id: string;
|
||||
handler: string;
|
||||
step_id: string;
|
||||
data_schema: FieldSchema[];
|
||||
data_schema: HaFormSchema[];
|
||||
errors: { [key: string]: string };
|
||||
description_placeholders: { [key: string]: string };
|
||||
}
|
||||
|
@@ -99,49 +99,65 @@ export const deviceAutomationsEqual = (
|
||||
export const localizeDeviceAutomationAction = (
|
||||
hass: HomeAssistant,
|
||||
action: DeviceAction
|
||||
) => {
|
||||
): string => {
|
||||
const state = action.entity_id ? hass.states[action.entity_id] : undefined;
|
||||
return hass.localize(
|
||||
`component.${action.domain}.device_automation.action_type.${action.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : "<unknown>",
|
||||
"subtype",
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${action.domain}.device_automation.action_subtype.${action.subtype}`
|
||||
)
|
||||
`component.${action.domain}.device_automation.action_type.${action.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : action.entity_id || "<unknown>",
|
||||
"subtype",
|
||||
action.subtype
|
||||
? hass.localize(
|
||||
`component.${action.domain}.device_automation.action_subtype.${action.subtype}`
|
||||
) || action.subtype
|
||||
: ""
|
||||
) || (action.subtype ? `"${action.subtype}" ${action.type}` : action.type!)
|
||||
);
|
||||
};
|
||||
|
||||
export const localizeDeviceAutomationCondition = (
|
||||
hass: HomeAssistant,
|
||||
condition: DeviceCondition
|
||||
) => {
|
||||
): string => {
|
||||
const state = condition.entity_id
|
||||
? hass.states[condition.entity_id]
|
||||
: undefined;
|
||||
return hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_type.${condition.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : "<unknown>",
|
||||
"subtype",
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
|
||||
)
|
||||
`component.${condition.domain}.device_automation.condition_type.${condition.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : condition.entity_id || "<unknown>",
|
||||
"subtype",
|
||||
condition.subtype
|
||||
? hass.localize(
|
||||
`component.${condition.domain}.device_automation.condition_subtype.${condition.subtype}`
|
||||
) || condition.subtype
|
||||
: ""
|
||||
) ||
|
||||
(condition.subtype
|
||||
? `"${condition.subtype}" ${condition.type}`
|
||||
: condition.type!)
|
||||
);
|
||||
};
|
||||
|
||||
export const localizeDeviceAutomationTrigger = (
|
||||
hass: HomeAssistant,
|
||||
trigger: DeviceTrigger
|
||||
) => {
|
||||
): string => {
|
||||
const state = trigger.entity_id ? hass.states[trigger.entity_id] : undefined;
|
||||
return hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : "<unknown>",
|
||||
"subtype",
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
|
||||
)
|
||||
`component.${trigger.domain}.device_automation.trigger_type.${trigger.type}`,
|
||||
"entity_name",
|
||||
state ? computeStateName(state) : trigger.entity_id || "<unknown>",
|
||||
"subtype",
|
||||
trigger.subtype
|
||||
? hass.localize(
|
||||
`component.${trigger.domain}.device_automation.trigger_subtype.${trigger.subtype}`
|
||||
) || trigger.subtype
|
||||
: ""
|
||||
) ||
|
||||
(trigger.subtype ? `"${trigger.subtype}" ${trigger.type}` : trigger.type!)
|
||||
);
|
||||
};
|
||||
|
@@ -17,6 +17,10 @@ export interface DeviceRegistryEntry {
|
||||
name_by_user?: string;
|
||||
}
|
||||
|
||||
export interface DeviceEntityLookup {
|
||||
[deviceId: string]: EntityRegistryEntry[];
|
||||
}
|
||||
|
||||
export interface DeviceRegistryEntryMutableParams {
|
||||
area_id?: string | null;
|
||||
name_by_user?: string | null;
|
||||
|
@@ -6,14 +6,23 @@ import { debounce } from "../common/util/debounce";
|
||||
export interface EntityRegistryEntry {
|
||||
entity_id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
platform: string;
|
||||
config_entry_id?: string;
|
||||
device_id?: string;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
unique_id: string;
|
||||
capabilities: object;
|
||||
original_name?: string;
|
||||
original_icon?: string;
|
||||
}
|
||||
|
||||
export interface EntityRegistryEntryUpdateParams {
|
||||
name?: string | null;
|
||||
icon?: string | null;
|
||||
disabled_by?: string | null;
|
||||
new_entity_id?: string;
|
||||
}
|
||||
@@ -29,12 +38,21 @@ export const computeEntityRegistryName = (
|
||||
return state ? computeStateName(state) : null;
|
||||
};
|
||||
|
||||
export const getExtendedEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
): Promise<ExtEntityRegistryEntry> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/get",
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
||||
export const updateEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
): Promise<EntityRegistryEntry> =>
|
||||
hass.callWS<EntityRegistryEntry>({
|
||||
): Promise<ExtEntityRegistryEntry> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/update",
|
||||
entity_id: entityId,
|
||||
...updates,
|
||||
|
4
src/data/external.ts
Normal file
4
src/data/external.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const isExternal =
|
||||
window.externalApp ||
|
||||
window.webkit?.messageHandlers?.getExternalAuth ||
|
||||
location.search.includes("external_auth=1");
|
@@ -59,3 +59,12 @@ export const getOptimisticFrontendUserDataCollection = <
|
||||
`_frontendUserData-${userDataKey}`,
|
||||
() => fetchFrontendUserData(conn, userDataKey)
|
||||
);
|
||||
|
||||
export const subscribeFrontendUserData = <UserDataKey extends ValidUserDataKey>(
|
||||
conn: Connection,
|
||||
userDataKey: UserDataKey,
|
||||
onChange: (state: FrontendUserData[UserDataKey] | null) => void
|
||||
) =>
|
||||
getOptimisticFrontendUserDataCollection(conn, userDataKey).subscribe(
|
||||
onChange
|
||||
);
|
||||
|
@@ -1,11 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService("input_select", "select_option", {
|
||||
option,
|
||||
entity_id: entity,
|
||||
});
|
43
src/data/input_boolean.ts
Normal file
43
src/data/input_boolean.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputBoolean {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: boolean;
|
||||
}
|
||||
|
||||
export interface InputBooleanMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: boolean;
|
||||
}
|
||||
|
||||
export const fetchInputBoolean = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputBoolean[]>({ type: "input_boolean/list" });
|
||||
|
||||
export const createInputBoolean = (
|
||||
hass: HomeAssistant,
|
||||
values: InputBooleanMutableParams
|
||||
) =>
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputBoolean = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputBooleanMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/update",
|
||||
input_boolean_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputBoolean = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_boolean/delete",
|
||||
input_boolean_id: id,
|
||||
});
|
@@ -1,5 +1,22 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputDateTime {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
has_time: boolean;
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export interface InputDateTimeMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
has_time: boolean;
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export const setInputDateTimeValue = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@@ -9,3 +26,32 @@ export const setInputDateTimeValue = (
|
||||
const param = { entity_id: entityId, time, date };
|
||||
hass.callService(entityId.split(".", 1)[0], "set_datetime", param);
|
||||
};
|
||||
|
||||
export const fetchInputDateTime = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputDateTime[]>({ type: "input_datetime/list" });
|
||||
|
||||
export const createInputDateTime = (
|
||||
hass: HomeAssistant,
|
||||
values: InputDateTimeMutableParams
|
||||
) =>
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputDateTime = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputDateTimeMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/update",
|
||||
input_datetime_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputDateTime = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_datetime/delete",
|
||||
input_datetime_id: id,
|
||||
});
|
||||
|
53
src/data/input_number.ts
Normal file
53
src/data/input_number.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputNumber {
|
||||
id: string;
|
||||
name: string;
|
||||
min: number;
|
||||
max: number;
|
||||
icon?: string;
|
||||
initial?: number;
|
||||
step?: number;
|
||||
mode?: "box" | "slider";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export interface InputNumberMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
mode: "box" | "slider";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export const fetchInputNumber = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputNumber[]>({ type: "input_number/list" });
|
||||
|
||||
export const createInputNumber = (
|
||||
hass: HomeAssistant,
|
||||
values: InputNumberMutableParams
|
||||
) =>
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputNumber = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputNumberMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/update",
|
||||
input_number_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputNumber = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_number/delete",
|
||||
input_number_id: id,
|
||||
});
|
55
src/data/input_select.ts
Normal file
55
src/data/input_select.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputSelect {
|
||||
id: string;
|
||||
name: string;
|
||||
options: string[];
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
}
|
||||
|
||||
export interface InputSelectMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService("input_select", "select_option", {
|
||||
option,
|
||||
entity_id: entity,
|
||||
});
|
||||
|
||||
export const fetchInputSelect = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputSelect[]>({ type: "input_select/list" });
|
||||
|
||||
export const createInputSelect = (
|
||||
hass: HomeAssistant,
|
||||
values: InputSelectMutableParams
|
||||
) =>
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputSelect = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputSelectMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/update",
|
||||
input_select_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputSelect = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_select/delete",
|
||||
input_select_id: id,
|
||||
});
|
@@ -1,7 +1,57 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputText {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
pattern?: string;
|
||||
mode?: "text" | "password";
|
||||
}
|
||||
|
||||
export interface InputTextMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
min: number;
|
||||
max: number;
|
||||
pattern: string;
|
||||
mode: "text" | "password";
|
||||
}
|
||||
|
||||
export const setValue = (hass: HomeAssistant, entity: string, value: string) =>
|
||||
hass.callService(entity.split(".", 1)[0], "set_value", {
|
||||
value,
|
||||
entity_id: entity,
|
||||
});
|
||||
|
||||
export const fetchInputText = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputText[]>({ type: "input_text/list" });
|
||||
|
||||
export const createInputText = (
|
||||
hass: HomeAssistant,
|
||||
values: InputTextMutableParams
|
||||
) =>
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputText = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputTextMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/update",
|
||||
input_text_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputText = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_text/delete",
|
||||
input_text_id: id,
|
||||
});
|
||||
|
10
src/data/integration.ts
Normal file
10
src/data/integration.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
export const integrationDocsUrl = (domain: string) =>
|
||||
`https://www.home-assistant.io/integrations/${domain}`;
|
||||
|
||||
export const integrationIssuesUrl = (domain: string) =>
|
||||
`https://github.com/home-assistant/home-assistant/issues?q=is%3Aissue+is%3Aopen+label%3A%22integration%3A+${domain}%22`;
|
||||
|
||||
export const domainToName = (localize: LocalizeFunc, domain: string) =>
|
||||
localize(`domain.${domain}`) || domain;
|
@@ -1,12 +1,57 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Connection, getCollection } from "home-assistant-js-websocket";
|
||||
import {
|
||||
Connection,
|
||||
getCollection,
|
||||
HassEventBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
|
||||
export interface LovelaceConfig {
|
||||
title?: string;
|
||||
views: LovelaceViewConfig[];
|
||||
background?: string;
|
||||
resources?: Array<{ type: "css" | "js" | "module" | "html"; url: string }>;
|
||||
}
|
||||
|
||||
export interface LovelaceResource {
|
||||
id: string;
|
||||
type: "css" | "js" | "module" | "html";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface LovelaceResourcesMutableParams {
|
||||
res_type: "css" | "js" | "module" | "html";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type LovelaceDashboard =
|
||||
| LovelaceYamlDashboard
|
||||
| LovelaceStorageDashboard;
|
||||
|
||||
interface LovelaceGenericDashboard {
|
||||
id: string;
|
||||
url_path: string;
|
||||
require_admin: boolean;
|
||||
sidebar?: { icon: string; title: string };
|
||||
}
|
||||
|
||||
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
|
||||
mode: "yaml";
|
||||
filename: string;
|
||||
}
|
||||
|
||||
export interface LovelaceStorageDashboard extends LovelaceGenericDashboard {
|
||||
mode: "storage";
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardMutableParams {
|
||||
require_admin: boolean;
|
||||
sidebar: { icon: string; title: string } | null;
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardCreateParams
|
||||
extends LovelaceDashboardMutableParams {
|
||||
url_path: string;
|
||||
mode: "storage";
|
||||
}
|
||||
|
||||
export interface LovelaceViewConfig {
|
||||
@@ -95,47 +140,139 @@ export type ActionConfig =
|
||||
| NoActionConfig
|
||||
| CustomActionConfig;
|
||||
|
||||
type LovelaceUpdatedEvent = HassEventBase & {
|
||||
event_type: "lovelace_updated";
|
||||
data: {
|
||||
url_path: string | null;
|
||||
mode: "yaml" | "storage";
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchResources = (conn: Connection): Promise<LovelaceResource[]> =>
|
||||
conn.sendMessagePromise({
|
||||
type: "lovelace/resources",
|
||||
});
|
||||
|
||||
export const createResource = (
|
||||
hass: HomeAssistant,
|
||||
values: LovelaceResourcesMutableParams
|
||||
) =>
|
||||
hass.callWS<LovelaceResource>({
|
||||
type: "lovelace/resources/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateResource = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<LovelaceResourcesMutableParams>
|
||||
) =>
|
||||
hass.callWS<LovelaceResource>({
|
||||
type: "lovelace/resources/update",
|
||||
resource_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteResource = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "lovelace/resources/delete",
|
||||
resource_id: id,
|
||||
});
|
||||
|
||||
export const fetchDashboards = (
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceDashboard[]> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/dashboards/list",
|
||||
});
|
||||
|
||||
export const createDashboard = (
|
||||
hass: HomeAssistant,
|
||||
values: LovelaceDashboardCreateParams
|
||||
) =>
|
||||
hass.callWS<LovelaceDashboard>({
|
||||
type: "lovelace/dashboards/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateDashboard = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<LovelaceDashboardMutableParams>
|
||||
) =>
|
||||
hass.callWS<LovelaceDashboard>({
|
||||
type: "lovelace/dashboards/update",
|
||||
dashboard_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteDashboard = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "lovelace/dashboards/delete",
|
||||
dashboard_id: id,
|
||||
});
|
||||
|
||||
export const fetchConfig = (
|
||||
conn: Connection,
|
||||
urlPath: string | null,
|
||||
force: boolean
|
||||
): Promise<LovelaceConfig> =>
|
||||
conn.sendMessagePromise({
|
||||
type: "lovelace/config",
|
||||
url_path: urlPath,
|
||||
force,
|
||||
});
|
||||
|
||||
export const saveConfig = (
|
||||
hass: HomeAssistant,
|
||||
urlPath: string | null,
|
||||
config: LovelaceConfig
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/save",
|
||||
url_path: urlPath,
|
||||
config,
|
||||
});
|
||||
|
||||
export const deleteConfig = (hass: HomeAssistant): Promise<void> =>
|
||||
export const deleteConfig = (
|
||||
hass: HomeAssistant,
|
||||
urlPath: string | null
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/delete",
|
||||
url_path: urlPath,
|
||||
});
|
||||
|
||||
export const subscribeLovelaceUpdates = (
|
||||
conn: Connection,
|
||||
urlPath: string | null,
|
||||
onChange: () => void
|
||||
) => conn.subscribeEvents(onChange, "lovelace_updated");
|
||||
) =>
|
||||
conn.subscribeEvents<LovelaceUpdatedEvent>((ev) => {
|
||||
if (ev.data.url_path === urlPath) {
|
||||
onChange();
|
||||
}
|
||||
}, "lovelace_updated");
|
||||
|
||||
export const getLovelaceCollection = (conn: Connection) =>
|
||||
export const getLovelaceCollection = (
|
||||
conn: Connection,
|
||||
urlPath: string | null = null
|
||||
) =>
|
||||
getCollection(
|
||||
conn,
|
||||
"_lovelace",
|
||||
(conn2) => fetchConfig(conn2, false),
|
||||
`_lovelace_${urlPath ?? ""}`,
|
||||
(conn2) => fetchConfig(conn2, urlPath, false),
|
||||
(_conn, store) =>
|
||||
subscribeLovelaceUpdates(conn, () =>
|
||||
fetchConfig(conn, false).then((config) => store.setState(config, true))
|
||||
subscribeLovelaceUpdates(conn, urlPath, () =>
|
||||
fetchConfig(conn, urlPath, false).then((config) =>
|
||||
store.setState(config, true)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export interface WindowWithLovelaceProm extends Window {
|
||||
llConfProm?: Promise<LovelaceConfig>;
|
||||
llResProm?: Promise<LovelaceResource[]>;
|
||||
}
|
||||
|
||||
export interface ActionHandlerOptions {
|
||||
|
@@ -3,8 +3,19 @@ import { HomeAssistant } from "../types";
|
||||
import { timeCachePromiseFunc } from "../common/util/time-cache-function-promise";
|
||||
|
||||
export const SUPPORT_PAUSE = 1;
|
||||
export const SUPPORT_SEEK = 2;
|
||||
export const SUPPORT_VOLUME_SET = 4;
|
||||
export const SUPPORT_VOLUME_MUTE = 8;
|
||||
export const SUPPORT_PREVIOUS_TRACK = 16;
|
||||
export const SUPPORT_NEXT_TRACK = 32;
|
||||
export const SUPPORT_TURN_ON = 128;
|
||||
export const SUPPORT_TURN_OFF = 256;
|
||||
export const SUPPORT_PLAY_MEDIA = 512;
|
||||
export const SUPPORT_VOLUME_BUTTONS = 1024;
|
||||
export const SUPPORT_SELECT_SOURCE = 2048;
|
||||
export const SUPPORT_STOP = 4096;
|
||||
export const SUPPORTS_PLAY = 16384;
|
||||
export const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||
export const OFF_STATES = ["off", "idle"];
|
||||
|
||||
export interface MediaPlayerThumbnail {
|
||||
|
2
src/data/sensor.ts
Normal file
2
src/data/sensor.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const SENSOR_DEVICE_CLASS_BATTERY = "battery";
|
||||
export const SENSOR_DEVICE_CLASS_TIMESTAMP = "timestamp";
|
@@ -1,6 +1,7 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface LoggedError {
|
||||
name: string;
|
||||
message: string;
|
||||
level: string;
|
||||
source: string;
|
||||
@@ -14,3 +15,8 @@ export interface LoggedError {
|
||||
|
||||
export const fetchSystemLog = (hass: HomeAssistant) =>
|
||||
hass.callApi<LoggedError[]>("GET", "error/all");
|
||||
|
||||
export const getLoggedErrorIntegration = (item: LoggedError) =>
|
||||
item.name.startsWith("homeassistant.components.")
|
||||
? item.name.split(".")[2]
|
||||
: undefined;
|
||||
|
22
src/data/vacuum.ts
Normal file
22
src/data/vacuum.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
export const VACUUM_SUPPORT_PAUSE = 4;
|
||||
export const VACUUM_SUPPORT_STOP = 8;
|
||||
export const VACUUM_SUPPORT_RETURN_HOME = 16;
|
||||
export const VACUUM_SUPPORT_FAN_SPEED = 32;
|
||||
export const VACUUM_SUPPORT_BATTERY = 64;
|
||||
export const VACUUM_SUPPORT_STATUS = 128;
|
||||
export const VACUUM_SUPPORT_LOCATE = 512;
|
||||
export const VACUUM_SUPPORT_CLEAN_SPOT = 1024;
|
||||
export const VACUUM_SUPPORT_START = 8192;
|
||||
|
||||
export type VacuumEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
battery_level: number;
|
||||
fan_speed: any;
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
@@ -8,7 +8,7 @@ const fetchThemes = (conn) =>
|
||||
|
||||
const subscribeUpdates = (conn, store) =>
|
||||
conn.subscribeEvents(
|
||||
(event) => store.setState(event.data, true),
|
||||
() => fetchThemes(conn).then((data) => store.setState(data, true)),
|
||||
"themes_updated"
|
||||
);
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { navigate } from "../common/navigate";
|
||||
|
||||
export const defaultRadiusColor = "#FF9800";
|
||||
export const homeRadiusColor: string = "#03a9f4";
|
||||
@@ -48,3 +49,19 @@ export const deleteZone = (hass: HomeAssistant, zoneId: string) =>
|
||||
type: "zone/delete",
|
||||
zone_id: zoneId,
|
||||
});
|
||||
|
||||
let inititialZoneEditorData: Partial<ZoneMutableParams> | undefined;
|
||||
|
||||
export const showZoneEditor = (
|
||||
el: HTMLElement,
|
||||
data?: Partial<ZoneMutableParams>
|
||||
) => {
|
||||
inititialZoneEditorData = data;
|
||||
navigate(el, "/config/zone/new");
|
||||
};
|
||||
|
||||
export const getZoneEditorInitData = () => {
|
||||
const data = inititialZoneEditorData;
|
||||
inititialZoneEditorData = undefined;
|
||||
return data;
|
||||
};
|
||||
|
@@ -115,7 +115,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.entities.editor.update"
|
||||
"ui.dialogs.config_entry_system_options.update"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
|
@@ -7,8 +7,8 @@ import {
|
||||
DataEntryFlowStepForm,
|
||||
DataEntryFlowStep,
|
||||
DataEntryFlowStepAbort,
|
||||
FieldSchema,
|
||||
} from "../../data/data_entry_flow";
|
||||
import { HaFormSchema } from "../../components/ha-form/ha-form";
|
||||
|
||||
export interface FlowConfig {
|
||||
loadDevicesAndAreas: boolean;
|
||||
@@ -45,7 +45,7 @@ export interface FlowConfig {
|
||||
renderShowFormStepFieldLabel(
|
||||
hass: HomeAssistant,
|
||||
step: DataEntryFlowStepForm,
|
||||
field: FieldSchema
|
||||
field: HaFormSchema
|
||||
): string;
|
||||
|
||||
renderShowFormStepFieldError(
|
||||
|
@@ -47,7 +47,7 @@ export const showOptionsFlowDialog = (
|
||||
renderShowFormStepHeader(hass, step) {
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${step.handler}.options.step.${step.step_id}.title`
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.title`
|
||||
) || hass.localize(`ui.dialogs.options_flow.form.header`)
|
||||
);
|
||||
},
|
||||
@@ -55,7 +55,7 @@ export const showOptionsFlowDialog = (
|
||||
renderShowFormStepDescription(hass, step) {
|
||||
const description = localizeKey(
|
||||
hass.localize,
|
||||
`component.${step.handler}.config.step.${step.step_id}.description`,
|
||||
`component.${configEntry.domain}.options.step.${step.step_id}.description`,
|
||||
step.description_placeholders
|
||||
);
|
||||
return description
|
||||
|
@@ -18,8 +18,10 @@ import "../../resources/ha-style";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
import { DataEntryFlowStepForm, FieldSchema } from "../../data/data_entry_flow";
|
||||
import { DataEntryFlowStepForm } from "../../data/data_entry_flow";
|
||||
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||
// tslint:disable-next-line
|
||||
import { HaFormSchema } from "../../components/ha-form/ha-form";
|
||||
|
||||
@customElement("step-flow-form")
|
||||
class StepFlowForm extends LitElement {
|
||||
@@ -176,7 +178,7 @@ class StepFlowForm extends LitElement {
|
||||
this._stepData = ev.detail.value;
|
||||
}
|
||||
|
||||
private _labelCallback = (field: FieldSchema): string =>
|
||||
private _labelCallback = (field: HaFormSchema): string =>
|
||||
this.flowConfig.renderShowFormStepFieldLabel(this.hass, this.step, field);
|
||||
|
||||
private _errorCallback = (error: string) =>
|
||||
|
@@ -20,6 +20,7 @@ import "../../common/search/search-input";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||
import { configFlowContentStyles } from "./styles";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
interface HandlerObj {
|
||||
name: string;
|
||||
@@ -39,7 +40,8 @@ class StepFlowPickHandler extends LitElement {
|
||||
private _getHandlers = memoizeOne((h: string[], filter?: string) => {
|
||||
const handlers: HandlerObj[] = h.map((handler) => {
|
||||
return {
|
||||
name: this.hass.localize(`component.${handler}.config.title`),
|
||||
name:
|
||||
this.hass.localize(`component.${handler}.config.title`) || handler,
|
||||
slug: handler,
|
||||
};
|
||||
});
|
||||
@@ -68,7 +70,10 @@ class StepFlowPickHandler extends LitElement {
|
||||
.filter=${this.filter}
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input>
|
||||
<div style=${styleMap({ width: `${this._width}px` })}>
|
||||
<div
|
||||
style=${styleMap({ width: `${this._width}px` })}
|
||||
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
|
||||
>
|
||||
${handlers.map(
|
||||
(handler: HandlerObj) =>
|
||||
html`
|
||||
@@ -142,6 +147,14 @@ class StepFlowPickHandler extends LitElement {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
@media all and (max-height: 1px) {
|
||||
div {
|
||||
max-height: calc(100vh - 205px);
|
||||
}
|
||||
div.advanced {
|
||||
max-height: calc(100vh - 300px);
|
||||
}
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@@ -129,6 +129,10 @@ class DialogBox extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
:host([inert]) {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
ha-paper-dialog {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
|
@@ -8,7 +8,6 @@ import "../resources/ha-style";
|
||||
import "./more-info/more-info-controls";
|
||||
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
|
||||
import DialogMixin from "../mixins/dialog-mixin";
|
||||
|
||||
@@ -80,8 +79,7 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
class="no-padding"
|
||||
hass="[[hass]]"
|
||||
state-obj="[[stateObj]]"
|
||||
dialog-element="[[_dialogElement]]"
|
||||
registry-entry="[[_registryInfo]]"
|
||||
dialog-element="[[_dialogElement()]]"
|
||||
large="{{large}}"
|
||||
></more-info-controls>
|
||||
`;
|
||||
@@ -102,9 +100,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
observer: "_largeChanged",
|
||||
},
|
||||
|
||||
_dialogElement: Object,
|
||||
_registryInfo: Object,
|
||||
|
||||
dataDomain: {
|
||||
computed: "_computeDomain(stateObj)",
|
||||
reflectToAttribute: true,
|
||||
@@ -116,9 +111,8 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
return ["_dialogOpenChanged(opened)"];
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this._dialogElement = this;
|
||||
_dialogElement() {
|
||||
return this;
|
||||
}
|
||||
|
||||
_computeDomain(stateObj) {
|
||||
@@ -129,11 +123,10 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
return hass.states[hass.moreInfoEntityId] || null;
|
||||
}
|
||||
|
||||
async _stateObjChanged(newVal, oldVal) {
|
||||
async _stateObjChanged(newVal) {
|
||||
if (!newVal) {
|
||||
this.setProperties({
|
||||
opened: false,
|
||||
_registryInfo: null,
|
||||
large: false,
|
||||
});
|
||||
return;
|
||||
@@ -146,25 +139,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
this.opened = true;
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
!isComponentLoaded(this.hass, "config") ||
|
||||
(oldVal && oldVal.entity_id === newVal.entity_id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hass.user.is_admin) {
|
||||
try {
|
||||
const info = await this.hass.callWS({
|
||||
type: "config/entity_registry/get",
|
||||
entity_id: newVal.entity_id,
|
||||
});
|
||||
this._registryInfo = info;
|
||||
} catch (err) {
|
||||
this._registryInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_dialogOpenChanged(newVal) {
|
||||
|
@@ -13,6 +13,7 @@ import "@material/mwc-button";
|
||||
import "../../../components/ha-relative-time";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { triggerAutomation } from "../../../data/automation";
|
||||
|
||||
@customElement("more-info-automation")
|
||||
class MoreInfoAutomation extends LitElement {
|
||||
@@ -42,9 +43,7 @@ class MoreInfoAutomation extends LitElement {
|
||||
}
|
||||
|
||||
private handleAction() {
|
||||
this.hass.callService("automation", "trigger", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
triggerAutomation(this.hass, this.stateObj!.entity_id);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
|
@@ -45,7 +45,7 @@ class MoreInfoCamera extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-camera-stream
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.stateObj="${this.stateObj}"
|
||||
showcontrols
|
||||
></ha-camera-stream>
|
||||
|
@@ -100,7 +100,8 @@ class MoreInfoClimate extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${stateObj.attributes.temperature !== undefined
|
||||
${stateObj.attributes.temperature !== undefined &&
|
||||
stateObj.attributes.temperature !== null
|
||||
? html`
|
||||
<ha-climate-control
|
||||
.value=${stateObj.attributes.temperature}
|
||||
@@ -112,8 +113,10 @@ class MoreInfoClimate extends LitElement {
|
||||
></ha-climate-control>
|
||||
`
|
||||
: ""}
|
||||
${stateObj.attributes.target_temp_low !== undefined ||
|
||||
stateObj.attributes.target_temp_high !== undefined
|
||||
${(stateObj.attributes.target_temp_low !== undefined &&
|
||||
stateObj.attributes.target_temp_low !== null) ||
|
||||
(stateObj.attributes.target_temp_high !== undefined &&
|
||||
stateObj.attributes.target_temp_high !== null)
|
||||
? html`
|
||||
<ha-climate-control
|
||||
.value=${stateObj.attributes.target_temp_low}
|
||||
|
@@ -16,6 +16,7 @@ import "./more-info-input_datetime";
|
||||
import "./more-info-light";
|
||||
import "./more-info-lock";
|
||||
import "./more-info-media_player";
|
||||
import "./more-info-person";
|
||||
import "./more-info-script";
|
||||
import "./more-info-sun";
|
||||
import "./more-info-timer";
|
||||
|
86
src/dialogs/more-info/controls/more-info-person.ts
Normal file
86
src/dialogs/more-info/controls/more-info-person.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import "@material/mwc-button";
|
||||
|
||||
import "../../../components/map/ha-map";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { showZoneEditor } from "../../../data/zone";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
@customElement("more-info-person")
|
||||
class MoreInfoPerson extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public stateObj?: HassEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-attributes
|
||||
.stateObj=${this.stateObj}
|
||||
extraFilters="id,user_id,editable"
|
||||
></ha-attributes>
|
||||
${this.stateObj.attributes.latitude && this.stateObj.attributes.longitude
|
||||
? html`
|
||||
<ha-map
|
||||
.hass=${this.hass}
|
||||
.entities=${[this.stateObj.entity_id]}
|
||||
></ha-map>
|
||||
`
|
||||
: ""}
|
||||
${!__DEMO__ &&
|
||||
this.hass.user?.is_admin &&
|
||||
this.stateObj.state === "not_home" &&
|
||||
this.stateObj.attributes.latitude &&
|
||||
this.stateObj.attributes.longitude
|
||||
? html`
|
||||
<div class="actions">
|
||||
<mwc-button @click=${this._handleAction}>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.person.create_zone"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAction() {
|
||||
showZoneEditor(this, {
|
||||
latitude: this.stateObj!.attributes.latitude,
|
||||
longitude: this.stateObj!.attributes.longitude,
|
||||
});
|
||||
fireEvent(this, "hass-more-info", { entityId: null });
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.actions {
|
||||
margin: 36px 0 8px 0;
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-person": MoreInfoPerson;
|
||||
}
|
||||
}
|
@@ -11,7 +11,7 @@ import { HassEntity } from "home-assistant-js-websocket";
|
||||
|
||||
import "../../../components/ha-relative-time";
|
||||
|
||||
import formatTime from "../../../common/datetime/format_time";
|
||||
import { formatTime } from "../../../common/datetime/format_time";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
@customElement("more-info-sun")
|
||||
|
@@ -1,263 +0,0 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
|
||||
class MoreInfoVacuum extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex iron-flex-alignment"></style>
|
||||
<style>
|
||||
:host {
|
||||
@apply --paper-font-body1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.status-subtitle {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="horizontal justified layout">
|
||||
<div hidden$="[[!supportsStatus(stateObj)]]">
|
||||
<span class="status-subtitle">Status: </span
|
||||
><span><strong>[[stateObj.attributes.status]]</strong></span>
|
||||
</div>
|
||||
<div hidden$="[[!supportsBattery(stateObj)]]">
|
||||
<span
|
||||
><iron-icon icon="[[stateObj.attributes.battery_icon]]"></iron-icon>
|
||||
[[stateObj.attributes.battery_level]] %</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div hidden$="[[!supportsCommandBar(stateObj)]]">
|
||||
<p></p>
|
||||
<div class="status-subtitle">Vacuum cleaner commands:</div>
|
||||
<div class="horizontal justified layout">
|
||||
<template is="dom-if" if="[[supportsStart(stateObj)]]">
|
||||
<div>
|
||||
<paper-icon-button
|
||||
icon="hass:play"
|
||||
on-click="onStart"
|
||||
title="Start"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<div hidden$="[[!supportsPause(stateObj)]]">
|
||||
<paper-icon-button
|
||||
icon="hass:pause"
|
||||
on-click="onPause"
|
||||
title="Pause"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!supportsStart(stateObj)]]">
|
||||
<div hidden$="[[!supportsPause(stateObj)]]">
|
||||
<paper-icon-button
|
||||
icon="hass:play-pause"
|
||||
on-click="onPlayPause"
|
||||
title="Pause"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div hidden$="[[!supportsStop(stateObj)]]">
|
||||
<paper-icon-button
|
||||
icon="hass:stop"
|
||||
on-click="onStop"
|
||||
title="Stop"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<div hidden$="[[!supportsCleanSpot(stateObj)]]">
|
||||
<paper-icon-button
|
||||
icon="hass:broom"
|
||||
on-click="onCleanSpot"
|
||||
title="Clean spot"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<div hidden$="[[!supportsLocate(stateObj)]]">
|
||||
<paper-icon-button
|
||||
icon="hass:map-marker"
|
||||
on-click="onLocate"
|
||||
title="Locate"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
<div hidden$="[[!supportsReturnHome(stateObj)]]">
|
||||
<paper-icon-button
|
||||
icon="hass:home-map-marker"
|
||||
on-click="onReturnHome"
|
||||
title="Return home"
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div hidden$="[[!supportsFanSpeed(stateObj)]]">
|
||||
<div class="horizontal justified layout">
|
||||
<ha-paper-dropdown-menu
|
||||
label-float=""
|
||||
dynamic-align=""
|
||||
label="Fan speed"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="[[stateObj.attributes.fan_speed]]"
|
||||
on-selected-changed="fanSpeedChanged"
|
||||
attr-for-selected="item-name"
|
||||
>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[stateObj.attributes.fan_speed_list]]"
|
||||
>
|
||||
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
<div
|
||||
style="justify-content: center; align-self: center; padding-top: 1.3em"
|
||||
>
|
||||
<span
|
||||
><iron-icon icon="hass:fan"></iron-icon>
|
||||
[[stateObj.attributes.fan_speed]]</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
</div>
|
||||
<ha-attributes
|
||||
state-obj="[[stateObj]]"
|
||||
extra-filters="fan_speed,fan_speed_list,status,battery_level,battery_icon"
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
inDialog: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
supportsPause(stateObj) {
|
||||
return supportsFeature(stateObj, 4);
|
||||
}
|
||||
|
||||
supportsStop(stateObj) {
|
||||
return supportsFeature(stateObj, 8);
|
||||
}
|
||||
|
||||
supportsReturnHome(stateObj) {
|
||||
return supportsFeature(stateObj, 16);
|
||||
}
|
||||
|
||||
supportsFanSpeed(stateObj) {
|
||||
return supportsFeature(stateObj, 32);
|
||||
}
|
||||
|
||||
supportsBattery(stateObj) {
|
||||
return supportsFeature(stateObj, 64);
|
||||
}
|
||||
|
||||
supportsStatus(stateObj) {
|
||||
return supportsFeature(stateObj, 128);
|
||||
}
|
||||
|
||||
supportsLocate(stateObj) {
|
||||
return supportsFeature(stateObj, 512);
|
||||
}
|
||||
|
||||
supportsCleanSpot(stateObj) {
|
||||
return supportsFeature(stateObj, 1024);
|
||||
}
|
||||
|
||||
supportsStart(stateObj) {
|
||||
return supportsFeature(stateObj, 8192);
|
||||
}
|
||||
|
||||
supportsCommandBar(stateObj) {
|
||||
return (
|
||||
supportsFeature(stateObj, 4) |
|
||||
supportsFeature(stateObj, 8) |
|
||||
supportsFeature(stateObj, 16) |
|
||||
supportsFeature(stateObj, 512) |
|
||||
supportsFeature(stateObj, 1024) |
|
||||
supportsFeature(stateObj, 8192)
|
||||
);
|
||||
}
|
||||
|
||||
fanSpeedChanged(ev) {
|
||||
var oldVal = this.stateObj.attributes.fan_speed;
|
||||
var newVal = ev.detail.value;
|
||||
|
||||
if (!newVal || oldVal === newVal) return;
|
||||
|
||||
this.hass.callService("vacuum", "set_fan_speed", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
fan_speed: newVal,
|
||||
});
|
||||
}
|
||||
|
||||
onStop() {
|
||||
this.hass.callService("vacuum", "stop", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
onPlayPause() {
|
||||
this.hass.callService("vacuum", "start_pause", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
onPause() {
|
||||
this.hass.callService("vacuum", "pause", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
onStart() {
|
||||
this.hass.callService("vacuum", "start", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
onLocate() {
|
||||
this.hass.callService("vacuum", "locate", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
onCleanSpot() {
|
||||
this.hass.callService("vacuum", "clean_spot", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
onReturnHome() {
|
||||
this.hass.callService("vacuum", "return_to_base", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-vacuum", MoreInfoVacuum);
|
259
src/dialogs/more-info/controls/more-info-vacuum.ts
Normal file
259
src/dialogs/more-info/controls/more-info-vacuum.ts
Normal file
@@ -0,0 +1,259 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-attributes";
|
||||
import {
|
||||
VACUUM_SUPPORT_BATTERY,
|
||||
VACUUM_SUPPORT_CLEAN_SPOT,
|
||||
VACUUM_SUPPORT_FAN_SPEED,
|
||||
VACUUM_SUPPORT_LOCATE,
|
||||
VACUUM_SUPPORT_PAUSE,
|
||||
VACUUM_SUPPORT_RETURN_HOME,
|
||||
VACUUM_SUPPORT_START,
|
||||
VACUUM_SUPPORT_STATUS,
|
||||
VACUUM_SUPPORT_STOP,
|
||||
VacuumEntity,
|
||||
} from "../../../data/vacuum";
|
||||
|
||||
interface VacuumCommand {
|
||||
translationKey: string;
|
||||
icon: string;
|
||||
serviceName: string;
|
||||
isVisible: (stateObj: VacuumEntity) => boolean;
|
||||
}
|
||||
|
||||
const VACUUM_COMMANDS: VacuumCommand[] = [
|
||||
{
|
||||
translationKey: "start",
|
||||
icon: "hass:play",
|
||||
serviceName: "start",
|
||||
isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_START),
|
||||
},
|
||||
{
|
||||
translationKey: "pause",
|
||||
icon: "hass:pause",
|
||||
serviceName: "pause",
|
||||
isVisible: (stateObj) =>
|
||||
// We need also to check if Start is supported because if not we show play-pause
|
||||
supportsFeature(stateObj, VACUUM_SUPPORT_START) &&
|
||||
supportsFeature(stateObj, VACUUM_SUPPORT_PAUSE),
|
||||
},
|
||||
{
|
||||
translationKey: "start_pause",
|
||||
icon: "hass:play-pause",
|
||||
serviceName: "start_pause",
|
||||
isVisible: (stateObj) =>
|
||||
// If start is supported, we don't show this button
|
||||
!supportsFeature(stateObj, VACUUM_SUPPORT_START) &&
|
||||
supportsFeature(stateObj, VACUUM_SUPPORT_PAUSE),
|
||||
},
|
||||
{
|
||||
translationKey: "stop",
|
||||
icon: "hass:stop",
|
||||
serviceName: "stop",
|
||||
isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_STOP),
|
||||
},
|
||||
{
|
||||
translationKey: "clean_spot",
|
||||
icon: "hass:broom",
|
||||
serviceName: "clean_spot",
|
||||
isVisible: (stateObj) =>
|
||||
supportsFeature(stateObj, VACUUM_SUPPORT_CLEAN_SPOT),
|
||||
},
|
||||
{
|
||||
translationKey: "locate",
|
||||
icon: "hass:map-marker",
|
||||
serviceName: "locate",
|
||||
isVisible: (stateObj) => supportsFeature(stateObj, VACUUM_SUPPORT_LOCATE),
|
||||
},
|
||||
{
|
||||
translationKey: "return_home",
|
||||
icon: "hass:home-map-marker",
|
||||
serviceName: "return_to_base",
|
||||
isVisible: (stateObj) =>
|
||||
supportsFeature(stateObj, VACUUM_SUPPORT_RETURN_HOME),
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("more-info-vacuum")
|
||||
class MoreInfoVacuum extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: VacuumEntity;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const stateObj = this.stateObj;
|
||||
|
||||
const filterExtraAttributes =
|
||||
"fan_speed,fan_speed_list,status,battery_level,battery_icon";
|
||||
|
||||
return html`
|
||||
<div class="flex-horizontal">
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_STATUS)
|
||||
? html`
|
||||
<div>
|
||||
<span class="status-subtitle"
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.status"
|
||||
)}:
|
||||
</span>
|
||||
<span><strong>${stateObj.attributes.status}</strong></span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_BATTERY)
|
||||
? html`
|
||||
<div>
|
||||
<span>
|
||||
<iron-icon
|
||||
.icon=${stateObj.attributes.battery_icon}
|
||||
></iron-icon>
|
||||
${stateObj.attributes.battery_level}%
|
||||
</span>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
${VACUUM_COMMANDS.some((item) => item.isVisible(stateObj))
|
||||
? html`
|
||||
<div>
|
||||
<p></p>
|
||||
<div class="status-subtitle">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.commands"
|
||||
)}
|
||||
</div>
|
||||
<div class="flex-horizontal">
|
||||
${VACUUM_COMMANDS.filter((item) =>
|
||||
item.isVisible(stateObj)
|
||||
).map(
|
||||
(item) => html`
|
||||
<div>
|
||||
<paper-icon-button
|
||||
.icon=${item.icon}
|
||||
.entry=${item}
|
||||
@click=${this.callService}
|
||||
.title=${this.hass!.localize(
|
||||
`ui.dialogs.more_info_control.vacuum.${item.translationKey}`
|
||||
)}
|
||||
></paper-icon-button>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${supportsFeature(stateObj, VACUUM_SUPPORT_FAN_SPEED)
|
||||
? html`
|
||||
<div>
|
||||
<div class="flex-horizontal">
|
||||
<ha-paper-dropdown-menu
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.more_info_control.vacuum.fan_speed"
|
||||
)}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${stateObj.attributes.fan_speed}
|
||||
@iron-select=${this.handleFanSpeedChanged}
|
||||
attr-for-selected="item-name"
|
||||
>
|
||||
${stateObj.attributes.fan_speed_list!.map(
|
||||
(mode) => html`
|
||||
<paper-item .itemName=${mode}>
|
||||
${mode}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
<div
|
||||
style="justify-content: center; align-self: center; padding-top: 1.3em"
|
||||
>
|
||||
<span>
|
||||
<iron-icon icon="hass:fan"></iron-icon>
|
||||
${stateObj.attributes.fan_speed}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
|
||||
<ha-attributes
|
||||
.stateObj=${this.stateObj}
|
||||
.extraFilters=${filterExtraAttributes}
|
||||
></ha-attributes>
|
||||
`;
|
||||
}
|
||||
|
||||
private callService(ev: CustomEvent) {
|
||||
const entry = (ev.target! as any).entry as VacuumCommand;
|
||||
this.hass.callService("vacuum", entry.serviceName, {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private handleFanSpeedChanged(ev: CustomEvent) {
|
||||
const oldVal = this.stateObj!.attributes.fan_speed;
|
||||
const newVal = ev.detail.item.itemName;
|
||||
|
||||
if (!newVal || oldVal === newVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass.callService("vacuum", "set_fan_speed", {
|
||||
entity_id: this.stateObj!.entity_id,
|
||||
fan_speed: newVal,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
@apply --paper-font-body1;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.status-subtitle {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.flex-horizontal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-vacuum": MoreInfoVacuum;
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import { showEntityRegistryDetailDialog } from "../../panels/config/entities/show-dialog-entity-registry-detail";
|
||||
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
||||
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
@@ -88,7 +88,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="main-title" main-title="" on-click="enlarge">
|
||||
[[_computeStateName(stateObj)]]
|
||||
</div>
|
||||
<template is="dom-if" if="[[registryEntry]]">
|
||||
<template is="dom-if" if="[[_computeConfig(hass)]]">
|
||||
<paper-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
@@ -221,6 +221,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return stateObj ? computeStateName(stateObj) : "";
|
||||
}
|
||||
|
||||
_computeConfig(hass) {
|
||||
return hass.user.is_admin && isComponentLoaded(hass, "config");
|
||||
}
|
||||
|
||||
_computeEdit(hass, stateObj) {
|
||||
const domain = this._computeDomain(stateObj);
|
||||
return (
|
||||
@@ -260,7 +264,9 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
_gotoSettings() {
|
||||
showEntityRegistryDetailDialog(this, { entry: this.registryEntry });
|
||||
showEntityEditorDialog(this, {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
this.fire("hass-more-info", { entityId: null });
|
||||
}
|
||||
|
||||
|
@@ -36,13 +36,13 @@ export class HuiNotificationItem extends LitElement {
|
||||
return "entity_id" in this.notification
|
||||
? html`
|
||||
<configurator-notification-item
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.notification="${this.notification}"
|
||||
></configurator-notification-item>
|
||||
`
|
||||
: html`
|
||||
<persistent-notification-item
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.notification="${this.notification}"
|
||||
></persistent-notification-item>
|
||||
`;
|
||||
|
@@ -39,7 +39,7 @@ export class HuiPersistentNotificationItem extends LitElement {
|
||||
<div class="time">
|
||||
<span>
|
||||
<ha-relative-time
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.datetime="${this.notification.created_at}"
|
||||
></ha-relative-time>
|
||||
<paper-tooltip
|
||||
|
@@ -10,12 +10,18 @@ import {
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
import { loadTokens, saveTokens } from "../common/auth/token_storage";
|
||||
import { isExternal } from "../data/external";
|
||||
import { subscribePanels } from "../data/ws-panels";
|
||||
import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { hassUrl } from "../data/auth";
|
||||
import { fetchConfig, WindowWithLovelaceProm } from "../data/lovelace";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
import {
|
||||
fetchConfig,
|
||||
fetchResources,
|
||||
WindowWithLovelaceProm,
|
||||
} from "../data/lovelace";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -23,11 +29,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const isExternal =
|
||||
window.externalApp ||
|
||||
window.webkit?.messageHandlers?.getExternalAuth ||
|
||||
location.search.includes("external_auth=1");
|
||||
|
||||
const authProm = isExternal
|
||||
? () =>
|
||||
import(
|
||||
@@ -88,9 +89,15 @@ window.hassConnection.then(({ conn }) => {
|
||||
subscribePanels(conn, noop);
|
||||
subscribeThemes(conn, noop);
|
||||
subscribeUser(conn, noop);
|
||||
subscribeFrontendUserData(conn, "core", noop);
|
||||
|
||||
if (location.pathname === "/" || location.pathname.startsWith("/lovelace/")) {
|
||||
(window as WindowWithLovelaceProm).llConfProm = fetchConfig(conn, false);
|
||||
(window as WindowWithLovelaceProm).llConfProm = fetchConfig(
|
||||
conn,
|
||||
null,
|
||||
false
|
||||
);
|
||||
(window as WindowWithLovelaceProm).llResProm = fetchResources(conn);
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -17,4 +17,5 @@ export const demoConfig: HassConfig = {
|
||||
version: "DEMO",
|
||||
whitelist_external_dirs: [],
|
||||
config_source: "storage",
|
||||
safe_mode: false,
|
||||
};
|
||||
|
157
src/layouts/hass-tabs-subpage-data-table.ts
Normal file
157
src/layouts/hass-tabs-subpage-data-table.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../components/data-table/ha-data-table";
|
||||
// tslint:disable-next-line
|
||||
import {
|
||||
HaDataTable,
|
||||
DataTableColumnContainer,
|
||||
DataTableRowData,
|
||||
} from "../components/data-table/ha-data-table";
|
||||
import "./hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../types";
|
||||
// tslint:disable-next-line
|
||||
import { PageNavigation } from "./hass-tabs-subpage";
|
||||
|
||||
@customElement("hass-tabs-subpage-data-table")
|
||||
export class HaTabsSubpageDataTable extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide!: boolean;
|
||||
@property({ type: Boolean, reflect: true }) public narrow!: boolean;
|
||||
/**
|
||||
* Object with the columns.
|
||||
* @type {Object}
|
||||
*/
|
||||
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||
/**
|
||||
* Data to show in the table.
|
||||
* @type {Array}
|
||||
*/
|
||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||
/**
|
||||
* Should rows be selectable.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
@property({ type: Boolean }) public selectable = false;
|
||||
/**
|
||||
* Field with a unique id per entry in data.
|
||||
* @type {String}
|
||||
*/
|
||||
@property({ type: String }) public id = "id";
|
||||
/**
|
||||
* String to filter the data in the data table on.
|
||||
* @type {String}
|
||||
*/
|
||||
@property({ type: String }) public filter = "";
|
||||
/**
|
||||
* What path to use when the back button is pressed.
|
||||
* @type {String}
|
||||
* @attr back-path
|
||||
*/
|
||||
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||
/**
|
||||
* Function to call when the back button is pressed.
|
||||
* @type {() => void}
|
||||
*/
|
||||
@property() public backCallback?: () => void;
|
||||
@property() public route!: Route;
|
||||
/**
|
||||
* Array of tabs to show on the page.
|
||||
* @type {Array}
|
||||
*/
|
||||
@property() public tabs!: PageNavigation[];
|
||||
@query("ha-data-table") private _dataTable!: HaDataTable;
|
||||
|
||||
public clearSelection() {
|
||||
this._dataTable.clearSelection();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hass-tabs-subpage
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.backPath=${this.backPath}
|
||||
.backCallback=${this.backCallback}
|
||||
.route=${this.route}
|
||||
.tabs=${this.tabs}
|
||||
>
|
||||
${this.narrow
|
||||
? html`
|
||||
<div slot="header">
|
||||
<slot name="header">
|
||||
<div class="search-toolbar">
|
||||
<search-input
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
></search-input>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<ha-data-table
|
||||
.columns=${this.columns}
|
||||
.data=${this.data}
|
||||
.filter=${this.filter}
|
||||
.selectable=${this.selectable}
|
||||
.id=${this.id}
|
||||
>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<div slot="header">
|
||||
<slot name="header">
|
||||
<slot name="header">
|
||||
<div class="table-header">
|
||||
<search-input
|
||||
no-label-float
|
||||
no-underline
|
||||
@value-changed=${this._handleSearchChange}
|
||||
></search-input></div></slot
|
||||
></slot>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<div slot="header"></div>
|
||||
`}
|
||||
</ha-data-table>
|
||||
</hass-tabs-subpage>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSearchChange(ev: CustomEvent) {
|
||||
this.filter = ev.detail.value;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-data-table {
|
||||
width: 100%;
|
||||
--data-table-border-width: 0;
|
||||
}
|
||||
:host(:not([narrow])) ha-data-table {
|
||||
height: calc(100vh - 65px);
|
||||
display: block;
|
||||
}
|
||||
.table-header {
|
||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||
}
|
||||
.search-toolbar {
|
||||
margin-left: -24px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
search-input {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ import { Route, HomeAssistant } from "../types";
|
||||
import { navigate } from "../common/navigate";
|
||||
import "@material/mwc-ripple";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
export interface PageNavigation {
|
||||
path: string;
|
||||
@@ -22,7 +23,7 @@ export interface PageNavigation {
|
||||
component?: string;
|
||||
name?: string;
|
||||
core?: boolean;
|
||||
exportOnly?: boolean;
|
||||
advancedOnly?: boolean;
|
||||
icon?: string;
|
||||
info?: any;
|
||||
}
|
||||
@@ -33,12 +34,57 @@ class HassTabsSubpage extends LitElement {
|
||||
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||
@property() public backCallback?: () => void;
|
||||
@property({ type: Boolean }) public hassio = false;
|
||||
@property({ type: Boolean }) public showAdvanced = false;
|
||||
@property() public route!: Route;
|
||||
@property() public tabs!: PageNavigation[];
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
@property() private _activeTab: number = -1;
|
||||
|
||||
private _getTabs = memoizeOne(
|
||||
(
|
||||
tabs: PageNavigation[],
|
||||
activeTab: number,
|
||||
showAdvanced: boolean | undefined,
|
||||
_components,
|
||||
_language
|
||||
) => {
|
||||
const shownTabs = tabs.filter(
|
||||
(page) =>
|
||||
(!page.component ||
|
||||
page.core ||
|
||||
isComponentLoaded(this.hass, page.component)) &&
|
||||
(!page.advancedOnly || showAdvanced)
|
||||
);
|
||||
|
||||
return shownTabs.map(
|
||||
(page, index) => html`
|
||||
<div
|
||||
class="tab ${classMap({
|
||||
active: index === activeTab,
|
||||
})}"
|
||||
@click=${this._tabTapped}
|
||||
.path=${page.path}
|
||||
>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-icon .icon=${page.icon}></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${!this.narrow || index === activeTab
|
||||
? html`
|
||||
<span class="name"
|
||||
>${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: name}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("route")) {
|
||||
@@ -49,6 +95,14 @@ class HassTabsSubpage extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const tabs = this._getTabs(
|
||||
this.tabs,
|
||||
this._activeTab,
|
||||
this.hass.userData?.showAdvanced,
|
||||
this.hass.config.components,
|
||||
this.hass.language
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
@@ -56,41 +110,18 @@ class HassTabsSubpage extends LitElement {
|
||||
.hassio=${this.hassio}
|
||||
@click=${this._backTapped}
|
||||
></ha-paper-icon-button-arrow-prev>
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${this.tabs.map((page, index) =>
|
||||
(!page.component ||
|
||||
page.core ||
|
||||
isComponentLoaded(this.hass, page.component)) &&
|
||||
(!page.exportOnly || this.showAdvanced)
|
||||
? html`
|
||||
<div
|
||||
class="tab ${classMap({
|
||||
active: index === this._activeTab,
|
||||
})}"
|
||||
@click=${this._tabTapped}
|
||||
.path=${page.path}
|
||||
>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-icon .icon=${page.icon}></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${!this.narrow || index === this._activeTab
|
||||
? html`
|
||||
<span class="name"
|
||||
>${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: name}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
</div>
|
||||
|
||||
${this.narrow
|
||||
? html`
|
||||
<div main-title><slot name="header"></slot></div>
|
||||
`
|
||||
: ""}
|
||||
${tabs.length > 1 || !this.narrow
|
||||
? html`
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${tabs}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div id="toolbar-icon">
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
@@ -138,11 +169,6 @@ class HassTabsSubpage extends LitElement {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host([narrow]) .toolbar {
|
||||
background-color: var(--primary-background-color);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#tabbar {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
} from "./hass-router-page";
|
||||
import { removeInitSkeleton } from "../util/init-skeleton";
|
||||
|
||||
const CACHE_COMPONENTS = ["lovelace", "states", "developer-tools"];
|
||||
const CACHE_URL_PATHS = ["lovelace", "states", "developer-tools"];
|
||||
const COMPONENTS = {
|
||||
calendar: () =>
|
||||
import(
|
||||
@@ -69,11 +69,10 @@ const COMPONENTS = {
|
||||
|
||||
const getRoutes = (panels: Panels): RouterOptions => {
|
||||
const routes: RouterOptions["routes"] = {};
|
||||
|
||||
Object.values(panels).forEach((panel) => {
|
||||
const data: RouteOptions = {
|
||||
tag: `ha-panel-${panel.component_name}`,
|
||||
cache: CACHE_COMPONENTS.includes(panel.component_name),
|
||||
cache: CACHE_URL_PATHS.includes(panel.url_path),
|
||||
};
|
||||
if (panel.component_name in COMPONENTS) {
|
||||
data.load = COMPONENTS[panel.component_name];
|
||||
|
@@ -82,6 +82,10 @@ class NotificationManager extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
mwc-button {
|
||||
color: var(--primary-color);
|
||||
font-weight: bold;
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
|
@@ -13,6 +13,8 @@ import { Action } from "../../../../data/script";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import "./ha-automation-action-row";
|
||||
|
||||
import { HaDeviceAction } from "./types/ha-automation-action-device_id";
|
||||
|
||||
@customElement("ha-automation-action")
|
||||
export default class HaAutomationAction extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@@ -46,7 +48,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
|
||||
private _addAction() {
|
||||
const actions = this.actions.concat({
|
||||
service: "",
|
||||
...HaDeviceAction.defaultConfig,
|
||||
});
|
||||
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
|
@@ -9,7 +9,7 @@ import {
|
||||
import "@material/mwc-button";
|
||||
import "../../../../components/ha-card";
|
||||
|
||||
import { HaStateCondition } from "./types/ha-automation-condition-state";
|
||||
import { HaDeviceCondition } from "./types/ha-automation-condition-device";
|
||||
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
@@ -48,8 +48,8 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
private _addCondition() {
|
||||
const conditions = this.conditions.concat({
|
||||
condition: "state",
|
||||
...HaStateCondition.defaultConfig,
|
||||
condition: "device",
|
||||
...HaDeviceCondition.defaultConfig,
|
||||
});
|
||||
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
|
@@ -26,7 +26,7 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
<ha-entity-picker
|
||||
.value="${entity_id}"
|
||||
@value-changed="${this._entityPicked}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<paper-input
|
||||
|
@@ -22,6 +22,7 @@ import {
|
||||
deleteAutomation,
|
||||
getAutomationEditorInitData,
|
||||
Trigger,
|
||||
triggerAutomation,
|
||||
} from "../../../data/automation";
|
||||
import { Action } from "../../../data/script";
|
||||
import {
|
||||
@@ -36,6 +37,8 @@ import "./condition/ha-automation-condition";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
import "../../../layouts/hass-tabs-subpage";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { HaDeviceAction } from "./action/types/ha-automation-action-device_id";
|
||||
import { HaDeviceTrigger } from "./trigger/types/ha-automation-trigger-device";
|
||||
|
||||
export class HaAutomationEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@@ -74,132 +77,155 @@ export class HaAutomationEditor extends LitElement {
|
||||
<div class="errors">${this._errors}</div>
|
||||
`
|
||||
: ""}
|
||||
<div
|
||||
class="${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}"
|
||||
>
|
||||
${this._config
|
||||
? html`
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
</paper-input>
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-textarea>
|
||||
</div>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.introduction"
|
||||
${this._config
|
||||
? html`
|
||||
${this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config?.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
${!this.narrow
|
||||
? html`
|
||||
<span slot="header">${this._config.alias}</span>
|
||||
`
|
||||
: ""}
|
||||
<span slot="introduction">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.introduction"
|
||||
)}
|
||||
</span>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<paper-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.alias"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/trigger/"
|
||||
target="_blank"
|
||||
name="alias"
|
||||
.value=${this._config.alias}
|
||||
@value-changed=${this._valueChanged}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||
</paper-input>
|
||||
<ha-textarea
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.label"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-trigger
|
||||
.triggers=${this._config.trigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger>
|
||||
</ha-config-section>
|
||||
.placeholder=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.description.placeholder"
|
||||
)}
|
||||
name="description"
|
||||
.value=${this._config.description}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-textarea>
|
||||
</div>
|
||||
${this.creatingNew
|
||||
? ""
|
||||
: html`
|
||||
<div
|
||||
class="card-actions layout horizontal justified center"
|
||||
>
|
||||
<div class="layout horizontal center">
|
||||
<ha-entity-toggle
|
||||
.hass=${this.hass}
|
||||
.stateObj=${this.automation}
|
||||
></ha-entity-toggle>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable_disable"
|
||||
)}
|
||||
</div>
|
||||
<mwc-button @click=${this._excuteAutomation}>
|
||||
${this.hass.localize("ui.card.automation.trigger")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`}
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
"ui.panel.config.automation.editor.triggers.introduction"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/conditions/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-condition
|
||||
.conditions=${this._config.condition || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
</ha-config-section>
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/trigger/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-trigger
|
||||
.triggers=${this._config.trigger}
|
||||
@value-changed=${this._triggerChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-trigger>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
"ui.panel.config.automation.editor.conditions.introduction"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/action/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/scripts/conditions/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-condition
|
||||
.conditions=${this._config.condition || []}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-condition>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section .isWide=${this.isWide}>
|
||||
<span slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.header"
|
||||
)}
|
||||
</span>
|
||||
<span slot="introduction">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.introduction"
|
||||
)}
|
||||
</p>
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/action/"
|
||||
target="_blank"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.learn_more"
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<ha-automation-action
|
||||
.actions=${this._config.action}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
></ha-automation-action>
|
||||
</ha-config-section>
|
||||
`
|
||||
: ""}
|
||||
<ha-fab
|
||||
?is-wide="${this.isWide}"
|
||||
?narrow="${this.narrow}"
|
||||
@@ -273,9 +299,9 @@ export class HaAutomationEditor extends LitElement {
|
||||
"ui.panel.config.automation.editor.default_name"
|
||||
),
|
||||
description: "",
|
||||
trigger: [{ platform: "state" }],
|
||||
trigger: [{ platform: "device", ...HaDeviceTrigger.defaultConfig }],
|
||||
condition: [],
|
||||
action: [{ service: "" }],
|
||||
action: [{ ...HaDeviceAction.defaultConfig }],
|
||||
...initData,
|
||||
};
|
||||
}
|
||||
@@ -317,6 +343,10 @@ export class HaAutomationEditor extends LitElement {
|
||||
this._dirty = true;
|
||||
}
|
||||
|
||||
private _excuteAutomation() {
|
||||
triggerAutomation(this.hass, this.automation.entity_id);
|
||||
}
|
||||
|
||||
private _backTapped(): void {
|
||||
if (this._dirty) {
|
||||
showConfirmationDialog(this, {
|
||||
@@ -389,6 +419,9 @@ export class HaAutomationEditor extends LitElement {
|
||||
span[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-entity-toggle {
|
||||
margin-right: 8px;
|
||||
}
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
|
@@ -28,7 +28,7 @@ import {
|
||||
showAutomationEditor,
|
||||
AutomationConfig,
|
||||
} from "../../../data/automation";
|
||||
import format_date_time from "../../../common/datetime/format_date_time";
|
||||
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { showThingtalkDialog } from "./show-dialog-thingtalk";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
@@ -102,7 +102,7 @@ class HaAutomationPicker extends LitElement {
|
||||
"ui.card.automation.last_triggered"
|
||||
)}: ${
|
||||
automation.attributes.last_triggered
|
||||
? format_date_time(
|
||||
? formatDateTime(
|
||||
new Date(automation.attributes.last_triggered),
|
||||
this.hass.language
|
||||
)
|
||||
|
@@ -13,7 +13,7 @@ import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
|
||||
import "./ha-automation-trigger-row";
|
||||
import { HaStateTrigger } from "./types/ha-automation-trigger-state";
|
||||
import { HaDeviceTrigger } from "./types/ha-automation-trigger-device";
|
||||
import { Trigger } from "../../../../data/automation";
|
||||
|
||||
@customElement("ha-automation-trigger")
|
||||
@@ -47,8 +47,8 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
private _addTrigger() {
|
||||
const triggers = this.triggers.concat({
|
||||
platform: "state",
|
||||
...HaStateTrigger.defaultConfig,
|
||||
platform: "device",
|
||||
...HaDeviceTrigger.defaultConfig,
|
||||
});
|
||||
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
|
@@ -42,7 +42,7 @@ export default class HaNumericStateTrigger extends LitElement {
|
||||
<ha-entity-picker
|
||||
.value="${entity_id}"
|
||||
@value-changed="${this._entityPicked}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<paper-input
|
||||
|
@@ -16,7 +16,7 @@ import "./cloud-remote-pref";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
import { fetchCloudSubscriptionInfo } from "../../../../data/cloud";
|
||||
|
||||
import formatDateTime from "../../../../common/datetime/format_date_time";
|
||||
import { formatDateTime } from "../../../../common/datetime/format_date_time";
|
||||
import LocalizeMixin from "../../../../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user