mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-16 20:59:26 +00:00
Compare commits
1 Commits
20240703.0
...
dashboard_
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ebe5207b6e |
@@ -8,7 +8,6 @@
|
|||||||
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
"postCreateCommand": "sudo apt update && sudo apt upgrade -y && sudo apt install -y libpcap-dev",
|
||||||
"postStartCommand": "script/bootstrap",
|
"postStartCommand": "script/bootstrap",
|
||||||
"containerEnv": {
|
"containerEnv": {
|
||||||
"DEV_CONTAINER": "1",
|
|
||||||
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
@@ -32,7 +32,4 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
return version[1];
|
return version[1];
|
||||||
},
|
},
|
||||||
isDevContainer() {
|
|
||||||
return process.env.DEV_CONTAINER === "1";
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@@ -244,11 +244,11 @@ const createTranslations = async () => {
|
|||||||
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
// TODO: This is a naive interpretation of BCP47 that should be improved.
|
||||||
// Will be OK for now as long as we don't have anything more complicated
|
// Will be OK for now as long as we don't have anything more complicated
|
||||||
// than a base translation + region.
|
// than a base translation + region.
|
||||||
const masterStream = gulp
|
gulp
|
||||||
.src(`${workDir}/en.json`)
|
.src(`${workDir}/en.json`)
|
||||||
.pipe(new PassThrough({ objectMode: true }));
|
.pipe(new PassThrough({ objectMode: true }))
|
||||||
masterStream.pipe(hashStream, { end: false });
|
.pipe(hashStream, { end: false });
|
||||||
const mergesFinished = [finished(masterStream)];
|
const mergesFinished = [];
|
||||||
for (const translationFile of translationFiles) {
|
for (const translationFile of translationFiles) {
|
||||||
const locale = basename(translationFile, ".json");
|
const locale = basename(translationFile, ".json");
|
||||||
const subtags = locale.split("-");
|
const subtags = locale.split("-");
|
||||||
|
@@ -40,12 +40,8 @@ const runDevServer = async ({
|
|||||||
compiler,
|
compiler,
|
||||||
contentBase,
|
contentBase,
|
||||||
port,
|
port,
|
||||||
listenHost = undefined,
|
listenHost = "localhost",
|
||||||
}) => {
|
}) => {
|
||||||
if (listenHost === undefined) {
|
|
||||||
// For dev container, we need to listen on all hosts
|
|
||||||
listenHost = env.isDevContainer() ? "0.0.0.0" : "localhost";
|
|
||||||
}
|
|
||||||
const server = new WebpackDevServer(
|
const server = new WebpackDevServer(
|
||||||
{
|
{
|
||||||
hot: false,
|
hot: false,
|
||||||
|
@@ -74,9 +74,6 @@ const createWebpackConfig = ({
|
|||||||
resolve: {
|
resolve: {
|
||||||
fullySpecified: false,
|
fullySpecified: false,
|
||||||
},
|
},
|
||||||
parser: {
|
|
||||||
worker: ["*context.audioWorklet.addModule()", "..."],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
@@ -95,15 +92,11 @@ const createWebpackConfig = ({
|
|||||||
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
// Disable splitting for web workers and worklets because imports of
|
// Disable splitting for web workers with ESM output
|
||||||
// external chunks are broken for:
|
// Imports of external chunks are broken
|
||||||
// - ESM output: https://github.com/webpack/webpack/issues/17014
|
chunks: latestBuild
|
||||||
// - Worklets use `importScripts`: https://github.com/webpack/webpack/issues/11543
|
? (chunk) => !chunk.canBeInitial() && !/^.+-worker$/.test(chunk.name)
|
||||||
chunks: (chunk) =>
|
: undefined,
|
||||||
!chunk.canBeInitial() &&
|
|
||||||
!new RegExp(`^.+-work${latestBuild ? "(?:let|er)" : "let"}$`).test(
|
|
||||||
chunk.name
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@@ -232,5 +232,17 @@ http:
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</hc-layout>
|
</hc-layout>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
|
||||||
|
(function (d, t) {
|
||||||
|
var g = d.createElement(t),
|
||||||
|
s = d.getElementsByTagName(t)[0];
|
||||||
|
g.src =
|
||||||
|
("https:" == location.protocol ? "//ssl" : "//www") +
|
||||||
|
".google-analytics.com/ga.js";
|
||||||
|
s.parentNode.insertBefore(g, s);
|
||||||
|
})(document, "script");
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -14,6 +14,12 @@
|
|||||||
--background-color: #41bdf5;
|
--background-color: #41bdf5;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
||||||
|
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||||
|
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
||||||
|
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||||
|
@@ -11,4 +11,10 @@
|
|||||||
font-size: initial;
|
font-size: initial;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
var _gaq=[['_setAccount','UA-57927901-10'],['_trackPageview']];
|
||||||
|
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
|
||||||
|
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
|
||||||
|
s.parentNode.insertBefore(g,s)}(document,'script'));
|
||||||
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "../../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./layout/hc-connect";
|
import "./layout/hc-connect";
|
||||||
|
|
||||||
import("../../../src/resources/ha-style");
|
import("../../../src/resources/ha-style");
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { isFrontpageEmbed } from "../../util/is_frontpage";
|
|
||||||
import { DemoConfig } from "../types";
|
import { DemoConfig } from "../types";
|
||||||
|
|
||||||
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
||||||
@@ -6,20 +5,36 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
views: [
|
views: [
|
||||||
{
|
{
|
||||||
type: "sections",
|
type: "sections",
|
||||||
title: isFrontpageEmbed ? "Home Assistant" : "Demo",
|
title: "Demo",
|
||||||
path: "home",
|
path: "home",
|
||||||
icon: "mdi:home-assistant",
|
icon: "mdi:home-assistant",
|
||||||
sections: [
|
sections: [
|
||||||
...(isFrontpageEmbed
|
{
|
||||||
? []
|
title: "Welcome 👋",
|
||||||
: [
|
cards: [{ type: "custom:ha-demo-card" }],
|
||||||
{
|
},
|
||||||
title: "Welcome 👋",
|
|
||||||
cards: [{ type: "custom:ha-demo-card" }],
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
{
|
{
|
||||||
cards: [
|
cards: [
|
||||||
|
{
|
||||||
|
type: "tile",
|
||||||
|
entity: "cover.living_room_garden_shutter",
|
||||||
|
name: "Garden",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "tile",
|
||||||
|
entity: "cover.living_room_graveyard_shutter",
|
||||||
|
name: "Rear",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "tile",
|
||||||
|
entity: "cover.living_room_left_shutter",
|
||||||
|
name: "Left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "tile",
|
||||||
|
entity: "cover.living_room_right_shutter",
|
||||||
|
name: "Right",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "light.floor_lamp",
|
entity: "light.floor_lamp",
|
||||||
@@ -45,11 +60,6 @@ export const demoLovelaceSections: DemoConfig["lovelace"] = () => ({
|
|||||||
detail: 1,
|
detail: 1,
|
||||||
name: "Temperature",
|
name: "Temperature",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: "tile",
|
|
||||||
entity: "cover.living_room_garden_shutter",
|
|
||||||
name: "Blinds",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
type: "tile",
|
type: "tile",
|
||||||
entity: "media_player.living_room_nest_mini",
|
entity: "media_player.living_room_nest_mini",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import "./util/is_frontpage";
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./ha-demo";
|
import "./ha-demo";
|
||||||
|
|
||||||
import("../../src/resources/ha-style");
|
import("../../src/resources/ha-style");
|
||||||
|
@@ -93,5 +93,16 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
|
||||||
|
<script>
|
||||||
|
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
||||||
|
(function (d, t) {
|
||||||
|
var g = d.createElement(t),
|
||||||
|
s = d.getElementsByTagName(t)[0];
|
||||||
|
g.src =
|
||||||
|
("https:" == location.protocol ? "//ssl" : "//www") +
|
||||||
|
".google-analytics.com/ga.js";
|
||||||
|
s.parentNode.insertBefore(g, s);
|
||||||
|
})(document, "script");
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
export const isFrontpageEmbed = document.location.search === "?frontpage";
|
|
@@ -1,8 +1,6 @@
|
|||||||
import type { IFuseOptions } from "fuse.js";
|
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import { stripDiacritics } from "../../../src/common/string/strip-diacritics";
|
import type { IFuseOptions } from "fuse.js";
|
||||||
import { StoreAddon } from "../../../src/data/supervisor/store";
|
import { StoreAddon } from "../../../src/data/supervisor/store";
|
||||||
import { getStripDiacriticsFn } from "../../../src/util/fuse";
|
|
||||||
|
|
||||||
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
export function filterAndSort(addons: StoreAddon[], filter: string) {
|
||||||
const options: IFuseOptions<StoreAddon> = {
|
const options: IFuseOptions<StoreAddon> = {
|
||||||
@@ -10,8 +8,7 @@ export function filterAndSort(addons: StoreAddon[], filter: string) {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(addons, options);
|
const fuse = new Fuse(addons, options);
|
||||||
return fuse.search(stripDiacritics(filter)).map((result) => result.item);
|
return fuse.search(filter).map((result) => result.item);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../../src/resources/compatibility";
|
import "../../src/resources/compatibility";
|
||||||
|
import "../../src/resources/safari-14-attachshadow-patch";
|
||||||
import "./hassio-main";
|
import "./hassio-main";
|
||||||
|
|
||||||
import("../../src/resources/ha-style");
|
import("../../src/resources/ha-style");
|
||||||
|
18
package.json
18
package.json
@@ -80,7 +80,7 @@
|
|||||||
"@material/mwc-top-app-bar": "0.27.0",
|
"@material/mwc-top-app-bar": "0.27.0",
|
||||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||||
"@material/web": "1.5.1",
|
"@material/web": "1.5.0",
|
||||||
"@mdi/js": "7.4.47",
|
"@mdi/js": "7.4.47",
|
||||||
"@mdi/svg": "7.4.47",
|
"@mdi/svg": "7.4.47",
|
||||||
"@polymer/paper-item": "3.0.1",
|
"@polymer/paper-item": "3.0.1",
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
"@babel/plugin-transform-runtime": "7.24.7",
|
"@babel/plugin-transform-runtime": "7.24.7",
|
||||||
"@babel/preset-env": "7.24.7",
|
"@babel/preset-env": "7.24.7",
|
||||||
"@babel/preset-typescript": "7.24.7",
|
"@babel/preset-typescript": "7.24.7",
|
||||||
"@bundle-stats/plugin-webpack-filter": "4.13.3",
|
"@bundle-stats/plugin-webpack-filter": "4.13.2",
|
||||||
"@koa/cors": "5.0.0",
|
"@koa/cors": "5.0.0",
|
||||||
"@lokalise/node-api": "12.5.0",
|
"@lokalise/node-api": "12.5.0",
|
||||||
"@octokit/auth-oauth-device": "7.1.1",
|
"@octokit/auth-oauth-device": "7.1.1",
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "15.2.3",
|
"@rollup/plugin-node-resolve": "15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.7",
|
"@rollup/plugin-replace": "5.0.7",
|
||||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||||
"@types/chromecast-caf-receiver": "6.0.16",
|
"@types/chromecast-caf-receiver": "6.0.15",
|
||||||
"@types/chromecast-caf-sender": "1.0.10",
|
"@types/chromecast-caf-sender": "1.0.10",
|
||||||
"@types/color-name": "1.1.4",
|
"@types/color-name": "1.1.4",
|
||||||
"@types/glob": "8.1.0",
|
"@types/glob": "8.1.0",
|
||||||
@@ -178,15 +178,15 @@
|
|||||||
"@types/leaflet-draw": "1.0.11",
|
"@types/leaflet-draw": "1.0.11",
|
||||||
"@types/lodash.merge": "4.6.9",
|
"@types/lodash.merge": "4.6.9",
|
||||||
"@types/luxon": "3.4.2",
|
"@types/luxon": "3.4.2",
|
||||||
"@types/mocha": "10.0.7",
|
"@types/mocha": "10.0.6",
|
||||||
"@types/qrcode": "1.5.5",
|
"@types/qrcode": "1.5.5",
|
||||||
"@types/serve-handler": "6.1.4",
|
"@types/serve-handler": "6.1.4",
|
||||||
"@types/sortablejs": "1.15.8",
|
"@types/sortablejs": "1.15.8",
|
||||||
"@types/tar": "6.1.13",
|
"@types/tar": "6.1.13",
|
||||||
"@types/ua-parser-js": "0.7.39",
|
"@types/ua-parser-js": "0.7.39",
|
||||||
"@types/webspeechapi": "0.0.29",
|
"@types/webspeechapi": "0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "7.14.1",
|
"@typescript-eslint/eslint-plugin": "7.13.1",
|
||||||
"@typescript-eslint/parser": "7.14.1",
|
"@typescript-eslint/parser": "7.13.1",
|
||||||
"@web/dev-server": "0.1.38",
|
"@web/dev-server": "0.1.38",
|
||||||
"@web/dev-server-rollup": "0.4.1",
|
"@web/dev-server-rollup": "0.4.1",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
"eslint-import-resolver-webpack": "0.13.8",
|
"eslint-import-resolver-webpack": "0.13.8",
|
||||||
"eslint-plugin-import": "2.29.1",
|
"eslint-plugin-import": "2.29.1",
|
||||||
"eslint-plugin-lit": "1.14.0",
|
"eslint-plugin-lit": "1.14.0",
|
||||||
"eslint-plugin-lit-a11y": "4.1.3",
|
"eslint-plugin-lit-a11y": "4.1.2",
|
||||||
"eslint-plugin-unused-imports": "4.0.0",
|
"eslint-plugin-unused-imports": "4.0.0",
|
||||||
"eslint-plugin-wc": "2.1.0",
|
"eslint-plugin-wc": "2.1.0",
|
||||||
"fancy-log": "2.0.0",
|
"fancy-log": "2.0.0",
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
"lodash.template": "4.5.0",
|
"lodash.template": "4.5.0",
|
||||||
"magic-string": "0.30.10",
|
"magic-string": "0.30.10",
|
||||||
"map-stream": "0.0.7",
|
"map-stream": "0.0.7",
|
||||||
"mocha": "10.5.0",
|
"mocha": "10.4.0",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
"open": "10.1.0",
|
"open": "10.1.0",
|
||||||
"pinst": "3.0.0",
|
"pinst": "3.0.0",
|
||||||
@@ -237,7 +237,7 @@
|
|||||||
"terser-webpack-plugin": "5.3.10",
|
"terser-webpack-plugin": "5.3.10",
|
||||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||||
"ts-lit-plugin": "2.0.2",
|
"ts-lit-plugin": "2.0.2",
|
||||||
"typescript": "5.5.2",
|
"typescript": "5.4.5",
|
||||||
"webpack": "5.92.1",
|
"webpack": "5.92.1",
|
||||||
"webpack-cli": "5.1.4",
|
"webpack-cli": "5.1.4",
|
||||||
"webpack-dev-server": "5.0.4",
|
"webpack-dev-server": "5.0.4",
|
||||||
|
BIN
public/static/images/logo_twitter.png
Normal file
BIN
public/static/images/logo_twitter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@@ -1,3 +0,0 @@
|
|||||||
<svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 430 B |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20240703.0"
|
version = "20240610.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { stripDiacritics } from "../strip-diacritics";
|
|
||||||
import { fuzzyScore } from "./filter";
|
import { fuzzyScore } from "./filter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,10 +19,10 @@ export const fuzzySequentialMatch = (
|
|||||||
for (const word of item.strings) {
|
for (const word of item.strings) {
|
||||||
const scores = fuzzyScore(
|
const scores = fuzzyScore(
|
||||||
filter,
|
filter,
|
||||||
stripDiacritics(filter.toLowerCase()),
|
filter.toLowerCase(),
|
||||||
0,
|
0,
|
||||||
word,
|
word,
|
||||||
stripDiacritics(word.toLowerCase()),
|
word.toLowerCase(),
|
||||||
0,
|
0,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
@@ -1,2 +0,0 @@
|
|||||||
export const stripDiacritics = (str: string) =>
|
|
||||||
str.normalize("NFD").replace(/[\u0300-\u036F]/g, "");
|
|
@@ -1,313 +0,0 @@
|
|||||||
import "@material/mwc-list";
|
|
||||||
import { mdiDrag, mdiEye, mdiEyeOff } from "@mdi/js";
|
|
||||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
|
||||||
import { classMap } from "lit/directives/class-map";
|
|
||||||
import { repeat } from "lit/directives/repeat";
|
|
||||||
import memoizeOne from "memoize-one";
|
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { createCloseHeading } from "../ha-dialog";
|
|
||||||
import "../ha-list-item";
|
|
||||||
import "../ha-sortable";
|
|
||||||
import "../ha-button";
|
|
||||||
import { DataTableColumnContainer, DataTableColumnData } from "./ha-data-table";
|
|
||||||
import { DataTableSettingsDialogParams } from "./show-dialog-data-table-settings";
|
|
||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
|
|
||||||
@customElement("dialog-data-table-settings")
|
|
||||||
export class DialogDataTableSettings extends LitElement {
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@state() private _params?: DataTableSettingsDialogParams;
|
|
||||||
|
|
||||||
@state() private _columnOrder?: string[];
|
|
||||||
|
|
||||||
@state() private _hiddenColumns?: string[];
|
|
||||||
|
|
||||||
public showDialog(params: DataTableSettingsDialogParams) {
|
|
||||||
this._params = params;
|
|
||||||
this._columnOrder = params.columnOrder;
|
|
||||||
this._hiddenColumns = params.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
public closeDialog() {
|
|
||||||
this._params = undefined;
|
|
||||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
|
||||||
}
|
|
||||||
|
|
||||||
private _sortedColumns = memoizeOne(
|
|
||||||
(
|
|
||||||
columns: DataTableColumnContainer,
|
|
||||||
columnOrder: string[] | undefined,
|
|
||||||
hiddenColumns: string[] | undefined
|
|
||||||
) =>
|
|
||||||
Object.keys(columns)
|
|
||||||
.filter((col) => !columns[col].hidden)
|
|
||||||
.sort((a, b) => {
|
|
||||||
const orderA = columnOrder?.indexOf(a) ?? -1;
|
|
||||||
const orderB = columnOrder?.indexOf(b) ?? -1;
|
|
||||||
const hiddenA =
|
|
||||||
hiddenColumns?.includes(a) ?? Boolean(columns[a].defaultHidden);
|
|
||||||
const hiddenB =
|
|
||||||
hiddenColumns?.includes(b) ?? Boolean(columns[b].defaultHidden);
|
|
||||||
if (hiddenA !== hiddenB) {
|
|
||||||
return hiddenA ? 1 : -1;
|
|
||||||
}
|
|
||||||
if (orderA !== orderB) {
|
|
||||||
if (orderA === -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (orderB === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orderA - orderB;
|
|
||||||
})
|
|
||||||
.reduce(
|
|
||||||
(arr, key) => {
|
|
||||||
arr.push({ key, ...columns[key] });
|
|
||||||
return arr;
|
|
||||||
},
|
|
||||||
[] as (DataTableColumnData & { key: string })[]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
|
||||||
if (!this._params) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = this._sortedColumns(
|
|
||||||
this._params.columns,
|
|
||||||
this._columnOrder,
|
|
||||||
this._hiddenColumns
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<ha-dialog
|
|
||||||
open
|
|
||||||
@closed=${this.closeDialog}
|
|
||||||
.heading=${createCloseHeading(
|
|
||||||
this.hass,
|
|
||||||
this.hass.localize("ui.components.data-table.settings.header")
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ha-sortable
|
|
||||||
@item-moved=${this._columnMoved}
|
|
||||||
draggable-selector=".draggable"
|
|
||||||
handle-selector=".handle"
|
|
||||||
>
|
|
||||||
<mwc-list>
|
|
||||||
${repeat(
|
|
||||||
columns,
|
|
||||||
(col) => col.key,
|
|
||||||
(col, _idx) => {
|
|
||||||
const canMove = !col.main && col.moveable !== false;
|
|
||||||
const canHide = !col.main && col.hideable !== false;
|
|
||||||
const isVisible = !(this._columnOrder &&
|
|
||||||
this._columnOrder.includes(col.key)
|
|
||||||
? this._hiddenColumns?.includes(col.key) ?? col.defaultHidden
|
|
||||||
: col.defaultHidden);
|
|
||||||
|
|
||||||
return html`<ha-list-item
|
|
||||||
hasMeta
|
|
||||||
class=${classMap({
|
|
||||||
hidden: !isVisible,
|
|
||||||
draggable: canMove && isVisible,
|
|
||||||
})}
|
|
||||||
graphic="icon"
|
|
||||||
noninteractive
|
|
||||||
>${col.title || col.label || col.key}
|
|
||||||
${canMove && isVisible
|
|
||||||
? html`<ha-svg-icon
|
|
||||||
class="handle"
|
|
||||||
.path=${mdiDrag}
|
|
||||||
slot="graphic"
|
|
||||||
></ha-svg-icon>`
|
|
||||||
: nothing}
|
|
||||||
<ha-icon-button
|
|
||||||
tabindex="0"
|
|
||||||
class="action"
|
|
||||||
.disabled=${!canHide}
|
|
||||||
.hidden=${!isVisible}
|
|
||||||
.path=${isVisible ? mdiEye : mdiEyeOff}
|
|
||||||
slot="meta"
|
|
||||||
.label=${this.hass!.localize(
|
|
||||||
`ui.components.data-table.settings.${isVisible ? "hide" : "show"}`,
|
|
||||||
{ title: typeof col.title === "string" ? col.title : "" }
|
|
||||||
)}
|
|
||||||
.column=${col.key}
|
|
||||||
@click=${this._toggle}
|
|
||||||
></ha-icon-button>
|
|
||||||
</ha-list-item>`;
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
</mwc-list>
|
|
||||||
</ha-sortable>
|
|
||||||
<ha-button slot="secondaryAction" @click=${this._reset}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.components.data-table.settings.restore"
|
|
||||||
)}</ha-button
|
|
||||||
>
|
|
||||||
<ha-button slot="primaryAction" @click=${this.closeDialog}>
|
|
||||||
${this.hass.localize("ui.components.data-table.settings.done")}
|
|
||||||
</ha-button>
|
|
||||||
</ha-dialog>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _columnMoved(ev: CustomEvent): void {
|
|
||||||
ev.stopPropagation();
|
|
||||||
if (!this._params) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { oldIndex, newIndex } = ev.detail;
|
|
||||||
|
|
||||||
const columns = this._sortedColumns(
|
|
||||||
this._params.columns,
|
|
||||||
this._columnOrder,
|
|
||||||
this._hiddenColumns
|
|
||||||
);
|
|
||||||
|
|
||||||
const columnOrder = columns.map((column) => column.key);
|
|
||||||
|
|
||||||
const option = columnOrder.splice(oldIndex, 1)[0];
|
|
||||||
columnOrder.splice(newIndex, 0, option);
|
|
||||||
|
|
||||||
this._columnOrder = columnOrder;
|
|
||||||
|
|
||||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
|
||||||
}
|
|
||||||
|
|
||||||
_toggle(ev) {
|
|
||||||
if (!this._params) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const column = ev.target.column;
|
|
||||||
const wasHidden = ev.target.hidden;
|
|
||||||
|
|
||||||
const hidden = [
|
|
||||||
...(this._hiddenColumns ??
|
|
||||||
Object.entries(this._params.columns)
|
|
||||||
.filter(([_key, col]) => col.defaultHidden)
|
|
||||||
.map(([key]) => key)),
|
|
||||||
];
|
|
||||||
if (wasHidden && hidden.includes(column)) {
|
|
||||||
hidden.splice(hidden.indexOf(column), 1);
|
|
||||||
} else if (!wasHidden) {
|
|
||||||
hidden.push(column);
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = this._sortedColumns(
|
|
||||||
this._params.columns,
|
|
||||||
this._columnOrder,
|
|
||||||
hidden
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!this._columnOrder) {
|
|
||||||
this._columnOrder = columns.map((col) => col.key);
|
|
||||||
} else {
|
|
||||||
const newOrder = this._columnOrder.filter((col) => col !== column);
|
|
||||||
|
|
||||||
// Array.findLastIndex when supported or core-js polyfill
|
|
||||||
const findLastIndex = (
|
|
||||||
arr: Array<any>,
|
|
||||||
fn: (item: any, index: number, arr: Array<any>) => boolean
|
|
||||||
) => {
|
|
||||||
for (let i = arr.length - 1; i >= 0; i--) {
|
|
||||||
if (fn(arr[i], i, arr)) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
let lastMoveable = findLastIndex(
|
|
||||||
newOrder,
|
|
||||||
(col) =>
|
|
||||||
col !== column &&
|
|
||||||
!hidden.includes(col) &&
|
|
||||||
!this._params!.columns[col].main &&
|
|
||||||
this._params!.columns[col].moveable !== false
|
|
||||||
);
|
|
||||||
|
|
||||||
if (lastMoveable === -1) {
|
|
||||||
lastMoveable = newOrder.length - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
columns.forEach((col) => {
|
|
||||||
if (!newOrder.includes(col.key)) {
|
|
||||||
if (col.moveable === false) {
|
|
||||||
newOrder.unshift(col.key);
|
|
||||||
} else {
|
|
||||||
newOrder.splice(lastMoveable + 1, 0, col.key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (col.defaultHidden) {
|
|
||||||
hidden.push(col.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._columnOrder = newOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._hiddenColumns = hidden;
|
|
||||||
|
|
||||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
|
||||||
}
|
|
||||||
|
|
||||||
_reset() {
|
|
||||||
this._columnOrder = undefined;
|
|
||||||
this._hiddenColumns = undefined;
|
|
||||||
|
|
||||||
this._params!.onUpdate(this._columnOrder, this._hiddenColumns);
|
|
||||||
this.closeDialog();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
|
||||||
return [
|
|
||||||
haStyleDialog,
|
|
||||||
css`
|
|
||||||
ha-dialog {
|
|
||||||
--mdc-dialog-max-width: 500px;
|
|
||||||
--dialog-z-index: 10;
|
|
||||||
--dialog-content-padding: 0 8px;
|
|
||||||
}
|
|
||||||
@media all and (max-width: 451px) {
|
|
||||||
ha-dialog {
|
|
||||||
--vertical-align-dialog: flex-start;
|
|
||||||
--dialog-surface-margin-top: 250px;
|
|
||||||
--ha-dialog-border-radius: 28px 28px 0 0;
|
|
||||||
--mdc-dialog-min-height: calc(100% - 250px);
|
|
||||||
--mdc-dialog-max-height: calc(100% - 250px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ha-list-item {
|
|
||||||
--mdc-list-side-padding: 12px;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
.hidden {
|
|
||||||
color: var(--disabled-text-color);
|
|
||||||
}
|
|
||||||
.handle {
|
|
||||||
cursor: move; /* fallback if grab cursor is unsupported */
|
|
||||||
cursor: grab;
|
|
||||||
}
|
|
||||||
.actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
ha-icon-button {
|
|
||||||
display: block;
|
|
||||||
margin: -12px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface HTMLElementTagNameMap {
|
|
||||||
"dialog-data-table-settings": DialogDataTableSettings;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -65,10 +65,6 @@ export interface DataTableSortColumnData {
|
|||||||
valueColumn?: string;
|
valueColumn?: string;
|
||||||
direction?: SortingDirection;
|
direction?: SortingDirection;
|
||||||
groupable?: boolean;
|
groupable?: boolean;
|
||||||
moveable?: boolean;
|
|
||||||
hideable?: boolean;
|
|
||||||
defaultHidden?: boolean;
|
|
||||||
showNarrow?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
||||||
@@ -83,7 +79,6 @@ export interface DataTableColumnData<T = any> extends DataTableSortColumnData {
|
|||||||
| "overflow-menu"
|
| "overflow-menu"
|
||||||
| "flex";
|
| "flex";
|
||||||
template?: (row: T) => TemplateResult | string | typeof nothing;
|
template?: (row: T) => TemplateResult | string | typeof nothing;
|
||||||
extraTemplate?: (row: T) => TemplateResult | string | typeof nothing;
|
|
||||||
width?: string;
|
width?: string;
|
||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
grows?: boolean;
|
grows?: boolean;
|
||||||
@@ -110,8 +105,6 @@ const UNDEFINED_GROUP_KEY = "zzzzz_undefined";
|
|||||||
export class HaDataTable extends LitElement {
|
export class HaDataTable extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
|
||||||
|
|
||||||
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
@property({ type: Object }) public columns: DataTableColumnContainer = {};
|
||||||
|
|
||||||
@property({ type: Array }) public data: DataTableRowData[] = [];
|
@property({ type: Array }) public data: DataTableRowData[] = [];
|
||||||
@@ -152,10 +145,6 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
@property({ attribute: false }) public initialCollapsedGroups?: string[];
|
||||||
|
|
||||||
@property({ attribute: false }) public hiddenColumns?: string[];
|
|
||||||
|
|
||||||
@property({ attribute: false }) public columnOrder?: string[];
|
|
||||||
|
|
||||||
@state() private _filterable = false;
|
@state() private _filterable = false;
|
||||||
|
|
||||||
@state() private _filter = "";
|
@state() private _filter = "";
|
||||||
@@ -246,7 +235,6 @@ export class HaDataTable extends LitElement {
|
|||||||
(column: ClonedDataTableColumnData) => {
|
(column: ClonedDataTableColumnData) => {
|
||||||
delete column.title;
|
delete column.title;
|
||||||
delete column.template;
|
delete column.template;
|
||||||
delete column.extraTemplate;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -284,44 +272,12 @@ export class HaDataTable extends LitElement {
|
|||||||
this._sortFilterData();
|
this._sortFilterData();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (properties.has("selectable") || properties.has("hiddenColumns")) {
|
if (properties.has("selectable")) {
|
||||||
this._items = [...this._items];
|
this._items = [...this._items];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _sortedColumns = memoizeOne(
|
|
||||||
(columns: DataTableColumnContainer, columnOrder?: string[]) => {
|
|
||||||
if (!columnOrder || !columnOrder.length) {
|
|
||||||
return columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(columns)
|
|
||||||
.sort((a, b) => {
|
|
||||||
const orderA = columnOrder!.indexOf(a);
|
|
||||||
const orderB = columnOrder!.indexOf(b);
|
|
||||||
if (orderA !== orderB) {
|
|
||||||
if (orderA === -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (orderB === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orderA - orderB;
|
|
||||||
})
|
|
||||||
.reduce((obj, key) => {
|
|
||||||
obj[key] = columns[key];
|
|
||||||
return obj;
|
|
||||||
}, {}) as DataTableColumnContainer;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const columns = this._sortedColumns(this.columns, this.columnOrder);
|
|
||||||
|
|
||||||
const renderRow = (row: DataTableRowData, index: number) =>
|
|
||||||
this._renderRow(columns, this.narrow, row, index);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="mdc-data-table">
|
<div class="mdc-data-table">
|
||||||
<slot name="header" @slotchange=${this._calcTableHeight}>
|
<slot name="header" @slotchange=${this._calcTableHeight}>
|
||||||
@@ -370,14 +326,9 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${Object.entries(columns).map(([key, column]) => {
|
${Object.entries(this.columns).map(([key, column]) => {
|
||||||
if (
|
if (column.hidden) {
|
||||||
column.hidden ||
|
return "";
|
||||||
(this.columnOrder && this.columnOrder.includes(key)
|
|
||||||
? this.hiddenColumns?.includes(key) ?? column.defaultHidden
|
|
||||||
: column.defaultHidden)
|
|
||||||
) {
|
|
||||||
return nothing;
|
|
||||||
}
|
}
|
||||||
const sorted = key === this.sortColumn;
|
const sorted = key === this.sortColumn;
|
||||||
const classes = {
|
const classes = {
|
||||||
@@ -448,7 +399,7 @@ export class HaDataTable extends LitElement {
|
|||||||
@scroll=${this._saveScrollPos}
|
@scroll=${this._saveScrollPos}
|
||||||
.items=${this._items}
|
.items=${this._items}
|
||||||
.keyFunction=${this._keyFunction}
|
.keyFunction=${this._keyFunction}
|
||||||
.renderItem=${renderRow}
|
.renderItem=${this._renderRow}
|
||||||
></lit-virtualizer>
|
></lit-virtualizer>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@@ -458,12 +409,7 @@ export class HaDataTable extends LitElement {
|
|||||||
|
|
||||||
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
private _keyFunction = (row: DataTableRowData) => row?.[this.id] || row;
|
||||||
|
|
||||||
private _renderRow = (
|
private _renderRow = (row: DataTableRowData, index: number) => {
|
||||||
columns: DataTableColumnContainer,
|
|
||||||
narrow: boolean,
|
|
||||||
row: DataTableRowData,
|
|
||||||
index: number
|
|
||||||
) => {
|
|
||||||
// not sure how this happens...
|
// not sure how this happens...
|
||||||
if (!row) {
|
if (!row) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -508,14 +454,8 @@ export class HaDataTable extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
${Object.entries(columns).map(([key, column]) => {
|
${Object.entries(this.columns).map(([key, column]) => {
|
||||||
if (
|
if (column.hidden) {
|
||||||
(narrow && !column.main && !column.showNarrow) ||
|
|
||||||
column.hidden ||
|
|
||||||
(this.columnOrder && this.columnOrder.includes(key)
|
|
||||||
? this.hiddenColumns?.includes(key) ?? column.defaultHidden
|
|
||||||
: column.defaultHidden)
|
|
||||||
) {
|
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
@@ -542,38 +482,7 @@ export class HaDataTable extends LitElement {
|
|||||||
})
|
})
|
||||||
: ""}
|
: ""}
|
||||||
>
|
>
|
||||||
${column.template
|
${column.template ? column.template(row) : row[key]}
|
||||||
? column.template(row)
|
|
||||||
: narrow && column.main
|
|
||||||
? html`<div class="primary">${row[key]}</div>
|
|
||||||
<div class="secondary">
|
|
||||||
${Object.entries(columns)
|
|
||||||
.filter(
|
|
||||||
([key2, column2]) =>
|
|
||||||
!column2.hidden &&
|
|
||||||
!column2.main &&
|
|
||||||
!column2.showNarrow &&
|
|
||||||
!(this.columnOrder &&
|
|
||||||
this.columnOrder.includes(key2)
|
|
||||||
? this.hiddenColumns?.includes(key2) ??
|
|
||||||
column2.defaultHidden
|
|
||||||
: column2.defaultHidden)
|
|
||||||
)
|
|
||||||
.map(
|
|
||||||
([key2, column2], i) =>
|
|
||||||
html`${i !== 0
|
|
||||||
? " ⸱ "
|
|
||||||
: nothing}${column2.template
|
|
||||||
? column2.template(row)
|
|
||||||
: row[key2]}`
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
${column.extraTemplate
|
|
||||||
? column.extraTemplate(row)
|
|
||||||
: nothing}`
|
|
||||||
: html`${row[key]}${column.extraTemplate
|
|
||||||
? column.extraTemplate(row)
|
|
||||||
: nothing}`}
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
@@ -952,7 +861,6 @@ export class HaDataTable extends LitElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mdc-data-table__cell {
|
.mdc-data-table__cell {
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
import { fireEvent } from "../../common/dom/fire_event";
|
|
||||||
import { DataTableColumnContainer } from "./ha-data-table";
|
|
||||||
|
|
||||||
export interface DataTableSettingsDialogParams {
|
|
||||||
columns: DataTableColumnContainer;
|
|
||||||
onUpdate: (
|
|
||||||
columnOrder: string[] | undefined,
|
|
||||||
hiddenColumns: string[] | undefined
|
|
||||||
) => void;
|
|
||||||
hiddenColumns?: string[];
|
|
||||||
columnOrder?: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loadDataTableSettingsDialog = () =>
|
|
||||||
import("./dialog-data-table-settings");
|
|
||||||
|
|
||||||
export const showDataTableSettingsDialog = (
|
|
||||||
element: HTMLElement,
|
|
||||||
dialogParams: DataTableSettingsDialogParams
|
|
||||||
): void => {
|
|
||||||
fireEvent(element, "show-dialog", {
|
|
||||||
dialogTag: "dialog-data-table-settings",
|
|
||||||
dialogImport: loadDataTableSettingsDialog,
|
|
||||||
dialogParams,
|
|
||||||
});
|
|
||||||
};
|
|
@@ -1,6 +1,5 @@
|
|||||||
import { expose } from "comlink";
|
import { expose } from "comlink";
|
||||||
import { stringCompare } from "../../common/string/compare";
|
import { stringCompare } from "../../common/string/compare";
|
||||||
import { stripDiacritics } from "../../common/string/strip-diacritics";
|
|
||||||
import type {
|
import type {
|
||||||
ClonedDataTableColumnData,
|
ClonedDataTableColumnData,
|
||||||
DataTableRowData,
|
DataTableRowData,
|
||||||
@@ -13,18 +12,20 @@ const filterData = (
|
|||||||
columns: SortableColumnContainer,
|
columns: SortableColumnContainer,
|
||||||
filter: string
|
filter: string
|
||||||
) => {
|
) => {
|
||||||
filter = stripDiacritics(filter.toLowerCase());
|
filter = filter.toUpperCase();
|
||||||
return data.filter((row) =>
|
return data.filter((row) =>
|
||||||
Object.entries(columns).some((columnEntry) => {
|
Object.entries(columns).some((columnEntry) => {
|
||||||
const [key, column] = columnEntry;
|
const [key, column] = columnEntry;
|
||||||
if (column.filterable) {
|
if (column.filterable) {
|
||||||
const value = String(
|
if (
|
||||||
column.filterKey
|
String(
|
||||||
? row[column.valueColumn || key][column.filterKey]
|
column.filterKey
|
||||||
: row[column.valueColumn || key]
|
? row[column.valueColumn || key][column.filterKey]
|
||||||
);
|
: row[column.valueColumn || key]
|
||||||
|
)
|
||||||
if (stripDiacritics(value).toLowerCase().includes(filter)) {
|
.toUpperCase()
|
||||||
|
.includes(filter)
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -90,8 +90,7 @@ class HaAnsiToHtml extends LitElement {
|
|||||||
|
|
||||||
private _parseTextToColoredPre(text) {
|
private _parseTextToColoredPre(text) {
|
||||||
const pre = document.createElement("pre");
|
const pre = document.createElement("pre");
|
||||||
// eslint-disable-next-line no-control-regex
|
const re = /\033(?:\[(.*?)[@-~]|\].*?(?:\007|\033\\))/g;
|
||||||
const re = /\x1b(?:\[(.*?)[@-~]|\].*?(?:\x07|\x1b\\))/g;
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
const state: State = {
|
const state: State = {
|
||||||
|
@@ -7,7 +7,6 @@ import { mdiRestore } from "@mdi/js";
|
|||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { conditionalClamp } from "../common/number/clamp";
|
|
||||||
|
|
||||||
type GridSizeValue = {
|
type GridSizeValue = {
|
||||||
rows?: number;
|
rows?: number;
|
||||||
@@ -43,10 +42,6 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
const disabledColumns =
|
|
||||||
this.columnMin !== undefined && this.columnMin === this.columnMax;
|
|
||||||
const disabledRows =
|
|
||||||
this.rowMin !== undefined && this.rowMin === this.rowMax;
|
|
||||||
return html`
|
return html`
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<ha-grid-layout-slider
|
<ha-grid-layout-slider
|
||||||
@@ -60,7 +55,6 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
.value=${this.value?.columns}
|
.value=${this.value?.columns}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@slider-moved=${this._sliderMoved}
|
@slider-moved=${this._sliderMoved}
|
||||||
.disabled=${disabledColumns}
|
|
||||||
></ha-grid-layout-slider>
|
></ha-grid-layout-slider>
|
||||||
<ha-grid-layout-slider
|
<ha-grid-layout-slider
|
||||||
aria-label=${this.hass.localize(
|
aria-label=${this.hass.localize(
|
||||||
@@ -74,7 +68,6 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
.value=${this.value?.rows}
|
.value=${this.value?.rows}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
@slider-moved=${this._sliderMoved}
|
@slider-moved=${this._sliderMoved}
|
||||||
.disabled=${disabledRows}
|
|
||||||
></ha-grid-layout-slider>
|
></ha-grid-layout-slider>
|
||||||
${!this.isDefault
|
${!this.isDefault
|
||||||
? html`
|
? html`
|
||||||
@@ -107,11 +100,17 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
.map((_, index) => {
|
.map((_, index) => {
|
||||||
const row = Math.floor(index / this.columns) + 1;
|
const row = Math.floor(index / this.columns) + 1;
|
||||||
const column = (index % this.columns) + 1;
|
const column = (index % this.columns) + 1;
|
||||||
|
const disabled =
|
||||||
|
(this.rowMin !== undefined && row < this.rowMin) ||
|
||||||
|
(this.rowMax !== undefined && row > this.rowMax) ||
|
||||||
|
(this.columnMin !== undefined && column < this.columnMin) ||
|
||||||
|
(this.columnMax !== undefined && column > this.columnMax);
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="cell"
|
class="cell"
|
||||||
data-row=${row}
|
data-row=${row}
|
||||||
data-column=${column}
|
data-column=${column}
|
||||||
|
?disabled=${disabled}
|
||||||
@click=${this._cellClick}
|
@click=${this._cellClick}
|
||||||
></div>
|
></div>
|
||||||
`;
|
`;
|
||||||
@@ -127,16 +126,11 @@ export class HaGridSizeEditor extends LitElement {
|
|||||||
|
|
||||||
_cellClick(ev) {
|
_cellClick(ev) {
|
||||||
const cell = ev.currentTarget as HTMLElement;
|
const cell = ev.currentTarget as HTMLElement;
|
||||||
|
if (cell.getAttribute("disabled") !== null) return;
|
||||||
const rows = Number(cell.getAttribute("data-row"));
|
const rows = Number(cell.getAttribute("data-row"));
|
||||||
const columns = Number(cell.getAttribute("data-column"));
|
const columns = Number(cell.getAttribute("data-column"));
|
||||||
const clampedRow = conditionalClamp(rows, this.rowMin, this.rowMax);
|
|
||||||
const clampedColumn = conditionalClamp(
|
|
||||||
columns,
|
|
||||||
this.columnMin,
|
|
||||||
this.columnMax
|
|
||||||
);
|
|
||||||
fireEvent(this, "value-changed", {
|
fireEvent(this, "value-changed", {
|
||||||
value: { rows: clampedRow, columns: clampedColumn },
|
value: { rows, columns },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,92 +1,72 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiCamera } from "@mdi/js";
|
import { mdiCamera } from "@mdi/js";
|
||||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||||
import { css, html, LitElement, nothing, PropertyValues } from "lit";
|
|
||||||
import { customElement, property, query, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import type QrScanner from "qr-scanner";
|
import type QrScanner from "qr-scanner";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||||
import { LocalizeFunc } from "../common/translations/localize";
|
import { LocalizeFunc } from "../common/translations/localize";
|
||||||
import { addExternalBarCodeListener } from "../external_app/external_app_entrypoint";
|
|
||||||
import { HomeAssistant } from "../types";
|
|
||||||
import "./ha-alert";
|
import "./ha-alert";
|
||||||
import "./ha-button-menu";
|
import "./ha-button-menu";
|
||||||
import "./ha-list-item";
|
|
||||||
import "./ha-textfield";
|
import "./ha-textfield";
|
||||||
import type { HaTextField } from "./ha-textfield";
|
import type { HaTextField } from "./ha-textfield";
|
||||||
|
|
||||||
@customElement("ha-qr-scanner")
|
@customElement("ha-qr-scanner")
|
||||||
class HaQrScanner extends LitElement {
|
class HaQrScanner extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public localize!: LocalizeFunc;
|
@property({ attribute: false }) public localize!: LocalizeFunc;
|
||||||
|
|
||||||
@property() public description?: string;
|
|
||||||
|
|
||||||
@property({ attribute: "alternative_option_label" })
|
|
||||||
public alternativeOptionLabel?: string;
|
|
||||||
|
|
||||||
@property() public error?: string;
|
|
||||||
|
|
||||||
@state() private _cameras?: QrScanner.Camera[];
|
@state() private _cameras?: QrScanner.Camera[];
|
||||||
|
|
||||||
@state() private _manual = false;
|
@state() private _error?: string;
|
||||||
|
|
||||||
private _qrScanner?: QrScanner;
|
private _qrScanner?: QrScanner;
|
||||||
|
|
||||||
private _qrNotFoundCount = 0;
|
private _qrNotFoundCount = 0;
|
||||||
|
|
||||||
private _removeListener?: UnsubscribeFunc;
|
@query("video", true) private _video!: HTMLVideoElement;
|
||||||
|
|
||||||
@query("video", true) private _video?: HTMLVideoElement;
|
@query("#canvas-container", true) private _canvasContainer!: HTMLDivElement;
|
||||||
|
|
||||||
@query("#canvas-container", true) private _canvasContainer?: HTMLDivElement;
|
|
||||||
|
|
||||||
@query("ha-textfield") private _manualInput?: HaTextField;
|
@query("ha-textfield") private _manualInput?: HaTextField;
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
if (this._nativeBarcodeScanner) {
|
|
||||||
this._closeExternalScanner();
|
|
||||||
}
|
|
||||||
if (this._qrScanner) {
|
if (this._qrScanner) {
|
||||||
this._qrScanner.stop();
|
this._qrScanner.stop();
|
||||||
this._qrScanner.destroy();
|
this._qrScanner.destroy();
|
||||||
this._qrScanner = undefined;
|
this._qrScanner = undefined;
|
||||||
}
|
}
|
||||||
while (this._canvasContainer?.lastChild) {
|
while (this._canvasContainer.lastChild) {
|
||||||
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
this._canvasContainer.removeChild(this._canvasContainer.lastChild);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public connectedCallback(): void {
|
public connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
if (this.hasUpdated) {
|
if (this.hasUpdated && navigator.mediaDevices) {
|
||||||
this._loadQrScanner();
|
this._loadQrScanner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated() {
|
protected firstUpdated() {
|
||||||
this._loadQrScanner();
|
if (navigator.mediaDevices) {
|
||||||
|
this._loadQrScanner();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues) {
|
protected updated(changedProps: PropertyValues) {
|
||||||
if (changedProps.has("error") && this.error) {
|
if (changedProps.has("_error") && this._error) {
|
||||||
alert(`error: ${this.error}`);
|
fireEvent(this, "qr-code-error", { message: this._error });
|
||||||
this._notifyExternalScanner(this.error);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render(): TemplateResult {
|
||||||
if (this._nativeBarcodeScanner && !this._manual) {
|
return html`${this._error
|
||||||
return nothing;
|
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||||
}
|
|
||||||
|
|
||||||
return html`${this.error
|
|
||||||
? html`<ha-alert alert-type="error">${this.error}</ha-alert>`
|
|
||||||
: ""}
|
: ""}
|
||||||
${navigator.mediaDevices && !this._manual
|
${navigator.mediaDevices
|
||||||
? html`<video></video>
|
? html`<video></video>
|
||||||
<div id="canvas-container">
|
<div id="canvas-container">
|
||||||
${this._cameras && this._cameras.length > 1
|
${this._cameras && this._cameras.length > 1
|
||||||
@@ -100,26 +80,21 @@ class HaQrScanner extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
${this._cameras!.map(
|
${this._cameras!.map(
|
||||||
(camera) => html`
|
(camera) => html`
|
||||||
<ha-list-item
|
<mwc-list-item
|
||||||
.value=${camera.id}
|
.value=${camera.id}
|
||||||
@click=${this._cameraChanged}
|
@click=${this._cameraChanged}
|
||||||
|
>${camera.label}</mwc-list-item
|
||||||
>
|
>
|
||||||
${camera.label}
|
|
||||||
</ha-list-item>
|
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
</ha-button-menu>`
|
</ha-button-menu>`
|
||||||
: nothing}
|
: ""}
|
||||||
</div>`
|
</div>`
|
||||||
: html`${this._manual
|
: html`<ha-alert alert-type="warning">
|
||||||
? nothing
|
${!window.isSecureContext
|
||||||
: html`<ha-alert alert-type="warning">
|
? this.localize("ui.components.qr-scanner.only_https_supported")
|
||||||
${!window.isSecureContext
|
: this.localize("ui.components.qr-scanner.not_supported")}
|
||||||
? this.localize(
|
</ha-alert>
|
||||||
"ui.components.qr-scanner.only_https_supported"
|
|
||||||
)
|
|
||||||
: this.localize("ui.components.qr-scanner.not_supported")}
|
|
||||||
</ha-alert>`}
|
|
||||||
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
<p>${this.localize("ui.components.qr-scanner.manual_input")}</p>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ha-textfield
|
<ha-textfield
|
||||||
@@ -127,44 +102,33 @@ class HaQrScanner extends LitElement {
|
|||||||
@keyup=${this._manualKeyup}
|
@keyup=${this._manualKeyup}
|
||||||
@paste=${this._manualPaste}
|
@paste=${this._manualPaste}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
<mwc-button @click=${this._manualSubmit}>
|
<mwc-button @click=${this._manualSubmit}
|
||||||
${this.localize("ui.common.submit")}
|
>${this.localize("ui.common.submit")}</mwc-button
|
||||||
</mwc-button>
|
>
|
||||||
</div>`}`;
|
</div>`}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _nativeBarcodeScanner(): boolean {
|
|
||||||
return Boolean(this.hass.auth.external?.config.hasBarCodeScanner);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _loadQrScanner() {
|
private async _loadQrScanner() {
|
||||||
if (this._nativeBarcodeScanner) {
|
|
||||||
this._openExternalScanner();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!navigator.mediaDevices) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const QrScanner = (await import("qr-scanner")).default;
|
const QrScanner = (await import("qr-scanner")).default;
|
||||||
if (!(await QrScanner.hasCamera())) {
|
if (!(await QrScanner.hasCamera())) {
|
||||||
this._reportError("No camera found");
|
this._error = "No camera found";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
QrScanner.WORKER_PATH = "/static/js/qr-scanner-worker.min.js";
|
||||||
this._listCameras(QrScanner);
|
this._listCameras(QrScanner);
|
||||||
this._qrScanner = new QrScanner(
|
this._qrScanner = new QrScanner(
|
||||||
this._video!,
|
this._video,
|
||||||
this._qrCodeScanned,
|
this._qrCodeScanned,
|
||||||
this._qrCodeError
|
this._qrCodeError
|
||||||
);
|
);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const canvas = this._qrScanner.$canvas;
|
const canvas = this._qrScanner.$canvas;
|
||||||
this._canvasContainer!.appendChild(canvas);
|
this._canvasContainer.appendChild(canvas);
|
||||||
canvas.style.display = "block";
|
canvas.style.display = "block";
|
||||||
try {
|
try {
|
||||||
await this._qrScanner.start();
|
await this._qrScanner.start();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
this._reportError(err);
|
this._error = err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,16 +140,16 @@ class HaQrScanner extends LitElement {
|
|||||||
if (err === "No QR code found") {
|
if (err === "No QR code found") {
|
||||||
this._qrNotFoundCount++;
|
this._qrNotFoundCount++;
|
||||||
if (this._qrNotFoundCount === 250) {
|
if (this._qrNotFoundCount === 250) {
|
||||||
this._reportError(err);
|
this._error = err;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._reportError(err.message || err);
|
this._error = err.message || err;
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(err);
|
console.log(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
private _qrCodeScanned = (qrCodeString: string): void => {
|
private _qrCodeScanned = async (qrCodeString: string): Promise<void> => {
|
||||||
this._qrNotFoundCount = 0;
|
this._qrNotFoundCount = 0;
|
||||||
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
fireEvent(this, "qr-code-scanned", { value: qrCodeString });
|
||||||
};
|
};
|
||||||
@@ -211,62 +175,6 @@ class HaQrScanner extends LitElement {
|
|||||||
this._qrScanner?.setCamera((ev.target as any).value);
|
this._qrScanner?.setCamera((ev.target as any).value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openExternalScanner() {
|
|
||||||
this._removeListener = addExternalBarCodeListener((msg) => {
|
|
||||||
if (msg.command === "bar_code/scan_result") {
|
|
||||||
if (msg.payload.format !== "qr_code") {
|
|
||||||
this._notifyExternalScanner(
|
|
||||||
`Wrong barcode scanned! ${msg.payload.format}: ${msg.payload.rawValue}, we need a QR code.`
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this._qrCodeScanned(msg.payload.rawValue);
|
|
||||||
}
|
|
||||||
} else if (msg.command === "bar_code/aborted") {
|
|
||||||
this._closeExternalScanner();
|
|
||||||
if (msg.payload.reason === "canceled") {
|
|
||||||
fireEvent(this, "qr-code-closed");
|
|
||||||
} else {
|
|
||||||
this._manual = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
this.hass.auth.external!.fireMessage({
|
|
||||||
type: "bar_code/scan",
|
|
||||||
payload: {
|
|
||||||
title: this.title || "Scan QR code",
|
|
||||||
description: this.description || "Scan a barcode.",
|
|
||||||
alternative_option_label:
|
|
||||||
this.alternativeOptionLabel || "Click to manually enter the barcode",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _closeExternalScanner() {
|
|
||||||
this._removeListener?.();
|
|
||||||
this._removeListener = undefined;
|
|
||||||
this.hass.auth.external!.fireMessage({
|
|
||||||
type: "bar_code/close",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _notifyExternalScanner(message: string) {
|
|
||||||
if (!this.hass.auth.external) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.hass.auth.external.fireMessage({
|
|
||||||
type: "bar_code/notify",
|
|
||||||
payload: {
|
|
||||||
message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.error = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _reportError(message: string) {
|
|
||||||
fireEvent(this, "qr-code-error", { message });
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
canvas {
|
canvas {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -302,7 +210,6 @@ declare global {
|
|||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"qr-code-scanned": { value: string };
|
"qr-code-scanned": { value: string };
|
||||||
"qr-code-error": { message: string };
|
"qr-code-error": { message: string };
|
||||||
"qr-code-closed": undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { AreaSelector } from "../../data/selector";
|
import type { AreaSelector } from "../../data/selector";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -38,8 +37,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
|
||||||
|
|
||||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
private _hasIntegration(selector: AreaSelector) {
|
private _hasIntegration(selector: AreaSelector) {
|
||||||
@@ -75,12 +72,6 @@ export class HaAreaSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
|
||||||
this._configEntries = [];
|
|
||||||
getConfigEntries(this.hass).then((entries) => {
|
|
||||||
this._configEntries = entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -145,9 +136,7 @@ export class HaAreaSelector extends LitElement {
|
|||||||
const deviceIntegrations = this._entitySources
|
const deviceIntegrations = this._entitySources
|
||||||
? this._deviceIntegrationLookup(
|
? this._deviceIntegrationLookup(
|
||||||
this._entitySources,
|
this._entitySources,
|
||||||
Object.values(this.hass.entities),
|
Object.values(this.hass.entities)
|
||||||
Object.values(this.hass.devices),
|
|
||||||
this._configEntries
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { DeviceSelector } from "../../data/selector";
|
import type { DeviceSelector } from "../../data/selector";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -28,8 +27,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
|
||||||
|
|
||||||
@property() public value?: any;
|
@property() public value?: any;
|
||||||
|
|
||||||
@property() public label?: string;
|
@property() public label?: string;
|
||||||
@@ -78,12 +75,6 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
|
||||||
this._configEntries = [];
|
|
||||||
getConfigEntries(this.hass).then((entries) => {
|
|
||||||
this._configEntries = entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -132,9 +123,7 @@ export class HaDeviceSelector extends LitElement {
|
|||||||
const deviceIntegrations = this._entitySources
|
const deviceIntegrations = this._entitySources
|
||||||
? this._deviceIntegrationLookup(
|
? this._deviceIntegrationLookup(
|
||||||
this._entitySources,
|
this._entitySources,
|
||||||
Object.values(this.hass.entities),
|
Object.values(this.hass.entities)
|
||||||
Object.values(this.hass.devices),
|
|
||||||
this._configEntries
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@@ -11,7 +11,6 @@ import {
|
|||||||
fetchEntitySourcesWithCache,
|
fetchEntitySourcesWithCache,
|
||||||
} from "../../data/entity_sources";
|
} from "../../data/entity_sources";
|
||||||
import type { FloorSelector } from "../../data/selector";
|
import type { FloorSelector } from "../../data/selector";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../data/config_entries";
|
|
||||||
import {
|
import {
|
||||||
filterSelectorDevices,
|
filterSelectorDevices,
|
||||||
filterSelectorEntities,
|
filterSelectorEntities,
|
||||||
@@ -38,8 +37,6 @@ export class HaFloorSelector extends LitElement {
|
|||||||
|
|
||||||
@state() private _entitySources?: EntitySources;
|
@state() private _entitySources?: EntitySources;
|
||||||
|
|
||||||
@state() private _configEntries?: ConfigEntry[];
|
|
||||||
|
|
||||||
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
private _deviceIntegrationLookup = memoizeOne(getDeviceIntegrationLookup);
|
||||||
|
|
||||||
private _hasIntegration(selector: FloorSelector) {
|
private _hasIntegration(selector: FloorSelector) {
|
||||||
@@ -75,12 +72,6 @@ export class HaFloorSelector extends LitElement {
|
|||||||
this._entitySources = sources;
|
this._entitySources = sources;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!this._configEntries && this._hasIntegration(this.selector)) {
|
|
||||||
this._configEntries = [];
|
|
||||||
getConfigEntries(this.hass).then((entries) => {
|
|
||||||
this._configEntries = entries;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -145,9 +136,7 @@ export class HaFloorSelector extends LitElement {
|
|||||||
const deviceIntegrations = this._entitySources
|
const deviceIntegrations = this._entitySources
|
||||||
? this._deviceIntegrationLookup(
|
? this._deviceIntegrationLookup(
|
||||||
this._entitySources,
|
this._entitySources,
|
||||||
Object.values(this.hass.entities),
|
Object.values(this.hass.entities)
|
||||||
Object.values(this.hass.devices),
|
|
||||||
this._configEntries
|
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
@@ -65,8 +65,6 @@ interface ExtHassService extends Omit<HassService, "fields"> {
|
|||||||
Omit<HassService["fields"][string], "selector"> & {
|
Omit<HassService["fields"][string], "selector"> & {
|
||||||
key: string;
|
key: string;
|
||||||
selector?: Selector;
|
selector?: Selector;
|
||||||
fields?: Record<string, Omit<HassService["fields"][string], "selector">>;
|
|
||||||
collapsed?: boolean;
|
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
hasSelector: string[];
|
hasSelector: string[];
|
||||||
@@ -249,7 +247,20 @@ export class HaServiceControl extends LitElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
private _getTargetedEntities = memoizeOne((target, value) => {
|
private _filterFields = memoizeOne(
|
||||||
|
(serviceData: ExtHassService | undefined, value: this["value"]) =>
|
||||||
|
serviceData?.fields?.filter(
|
||||||
|
(field) =>
|
||||||
|
!field.filter ||
|
||||||
|
this._filterField(serviceData.target, field.filter, value)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private _filterField(
|
||||||
|
target: ExtHassService["target"],
|
||||||
|
filter: ExtHassService["fields"][number]["filter"],
|
||||||
|
value: this["value"]
|
||||||
|
) {
|
||||||
const targetSelector = target ? { target } : { target: {} };
|
const targetSelector = target ? { target } : { target: {} };
|
||||||
const targetEntities =
|
const targetEntities =
|
||||||
ensureArray(
|
ensureArray(
|
||||||
@@ -319,13 +330,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return targetEntities;
|
|
||||||
});
|
|
||||||
|
|
||||||
private _filterField(
|
|
||||||
filter: ExtHassService["fields"][number]["filter"],
|
|
||||||
targetEntities: string[]
|
|
||||||
) {
|
|
||||||
if (!targetEntities.length) {
|
if (!targetEntities.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -387,10 +391,7 @@ export class HaServiceControl extends LitElement {
|
|||||||
serviceData?.fields.some((field) => showOptionalToggle(field))
|
serviceData?.fields.some((field) => showOptionalToggle(field))
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetEntities = this._getTargetedEntities(
|
const filteredFields = this._filterFields(serviceData, this._value);
|
||||||
serviceData?.target,
|
|
||||||
this._value
|
|
||||||
);
|
|
||||||
|
|
||||||
const domain = this._value?.service
|
const domain = this._value?.service
|
||||||
? computeDomain(this._value.service)
|
? computeDomain(this._value.service)
|
||||||
@@ -484,115 +485,75 @@ export class HaServiceControl extends LitElement {
|
|||||||
.defaultValue=${this._value?.data}
|
.defaultValue=${this._value?.data}
|
||||||
@value-changed=${this._dataChanged}
|
@value-changed=${this._dataChanged}
|
||||||
></ha-yaml-editor>`
|
></ha-yaml-editor>`
|
||||||
: serviceData?.fields.map((dataField) =>
|
: filteredFields?.map((dataField) => {
|
||||||
dataField.fields
|
const selector = dataField?.selector ?? { text: undefined };
|
||||||
? html`<ha-expansion-panel
|
const type = Object.keys(selector)[0];
|
||||||
leftChevron
|
const enhancedSelector = ["action", "condition", "trigger"].includes(
|
||||||
.expanded=${!dataField.collapsed}
|
type
|
||||||
.header=${this.hass.localize(
|
)
|
||||||
`component.${domain}.services.${serviceName}.sections.${dataField.key}.name`
|
? {
|
||||||
) ||
|
[type]: {
|
||||||
dataField.name ||
|
...selector[type],
|
||||||
dataField.key}
|
path: [dataField.key],
|
||||||
>
|
},
|
||||||
${Object.entries(dataField.fields).map(([key, field]) =>
|
}
|
||||||
this._renderField(
|
: selector;
|
||||||
{ key, ...field },
|
|
||||||
hasOptional,
|
const showOptional = showOptionalToggle(dataField);
|
||||||
domain,
|
|
||||||
serviceName,
|
return dataField.selector &&
|
||||||
targetEntities
|
(!dataField.advanced ||
|
||||||
)
|
this.showAdvanced ||
|
||||||
)}
|
(this._value?.data &&
|
||||||
</ha-expansion-panel>`
|
this._value.data[dataField.key] !== undefined))
|
||||||
: this._renderField(
|
? html`<ha-settings-row .narrow=${this.narrow}>
|
||||||
dataField,
|
${!showOptional
|
||||||
hasOptional,
|
? hasOptional
|
||||||
domain,
|
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
||||||
serviceName,
|
: ""
|
||||||
targetEntities
|
: html`<ha-checkbox
|
||||||
)
|
.key=${dataField.key}
|
||||||
)} `;
|
.checked=${this._checkedKeys.has(dataField.key) ||
|
||||||
|
(this._value?.data &&
|
||||||
|
this._value.data[dataField.key] !== undefined)}
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
@change=${this._checkboxChanged}
|
||||||
|
slot="prefix"
|
||||||
|
></ha-checkbox>`}
|
||||||
|
<span slot="heading"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`
|
||||||
|
) ||
|
||||||
|
dataField.name ||
|
||||||
|
dataField.key}</span
|
||||||
|
>
|
||||||
|
<span slot="description"
|
||||||
|
>${this.hass.localize(
|
||||||
|
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`
|
||||||
|
) || dataField?.description}</span
|
||||||
|
>
|
||||||
|
<ha-selector
|
||||||
|
.disabled=${this.disabled ||
|
||||||
|
(showOptional &&
|
||||||
|
!this._checkedKeys.has(dataField.key) &&
|
||||||
|
(!this._value?.data ||
|
||||||
|
this._value.data[dataField.key] === undefined))}
|
||||||
|
.hass=${this.hass}
|
||||||
|
.selector=${enhancedSelector}
|
||||||
|
.key=${dataField.key}
|
||||||
|
@value-changed=${this._serviceDataChanged}
|
||||||
|
.value=${this._value?.data
|
||||||
|
? this._value.data[dataField.key]
|
||||||
|
: undefined}
|
||||||
|
.placeholder=${dataField.default}
|
||||||
|
.localizeValue=${this._localizeValueCallback}
|
||||||
|
@item-moved=${this._itemMoved}
|
||||||
|
></ha-selector>
|
||||||
|
</ha-settings-row>`
|
||||||
|
: "";
|
||||||
|
})} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderField = (
|
|
||||||
dataField: ExtHassService["fields"][number],
|
|
||||||
hasOptional: boolean,
|
|
||||||
domain: string | undefined,
|
|
||||||
serviceName: string | undefined,
|
|
||||||
targetEntities: string[]
|
|
||||||
) => {
|
|
||||||
if (
|
|
||||||
dataField.filter &&
|
|
||||||
!this._filterField(dataField.filter, targetEntities)
|
|
||||||
) {
|
|
||||||
return nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selector = dataField?.selector ?? { text: undefined };
|
|
||||||
const type = Object.keys(selector)[0];
|
|
||||||
const enhancedSelector = ["action", "condition", "trigger"].includes(type)
|
|
||||||
? {
|
|
||||||
[type]: {
|
|
||||||
...selector[type],
|
|
||||||
path: [dataField.key],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: selector;
|
|
||||||
|
|
||||||
const showOptional = showOptionalToggle(dataField);
|
|
||||||
|
|
||||||
return dataField.selector &&
|
|
||||||
(!dataField.advanced ||
|
|
||||||
this.showAdvanced ||
|
|
||||||
(this._value?.data && this._value.data[dataField.key] !== undefined))
|
|
||||||
? html`<ha-settings-row .narrow=${this.narrow}>
|
|
||||||
${!showOptional
|
|
||||||
? hasOptional
|
|
||||||
? html`<div slot="prefix" class="checkbox-spacer"></div>`
|
|
||||||
: ""
|
|
||||||
: html`<ha-checkbox
|
|
||||||
.key=${dataField.key}
|
|
||||||
.checked=${this._checkedKeys.has(dataField.key) ||
|
|
||||||
(this._value?.data &&
|
|
||||||
this._value.data[dataField.key] !== undefined)}
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
@change=${this._checkboxChanged}
|
|
||||||
slot="prefix"
|
|
||||||
></ha-checkbox>`}
|
|
||||||
<span slot="heading"
|
|
||||||
>${this.hass.localize(
|
|
||||||
`component.${domain}.services.${serviceName}.fields.${dataField.key}.name`
|
|
||||||
) ||
|
|
||||||
dataField.name ||
|
|
||||||
dataField.key}</span
|
|
||||||
>
|
|
||||||
<span slot="description"
|
|
||||||
>${this.hass.localize(
|
|
||||||
`component.${domain}.services.${serviceName}.fields.${dataField.key}.description`
|
|
||||||
) || dataField?.description}</span
|
|
||||||
>
|
|
||||||
<ha-selector
|
|
||||||
.disabled=${this.disabled ||
|
|
||||||
(showOptional &&
|
|
||||||
!this._checkedKeys.has(dataField.key) &&
|
|
||||||
(!this._value?.data ||
|
|
||||||
this._value.data[dataField.key] === undefined))}
|
|
||||||
.hass=${this.hass}
|
|
||||||
.selector=${enhancedSelector}
|
|
||||||
.key=${dataField.key}
|
|
||||||
@value-changed=${this._serviceDataChanged}
|
|
||||||
.value=${this._value?.data
|
|
||||||
? this._value.data[dataField.key]
|
|
||||||
: undefined}
|
|
||||||
.placeholder=${dataField.default}
|
|
||||||
.localizeValue=${this._localizeValueCallback}
|
|
||||||
@item-moved=${this._itemMoved}
|
|
||||||
></ha-selector>
|
|
||||||
</ha-settings-row>`
|
|
||||||
: "";
|
|
||||||
};
|
|
||||||
|
|
||||||
private _localizeValueCallback = (key: string) => {
|
private _localizeValueCallback = (key: string) => {
|
||||||
if (!this._value?.service) {
|
if (!this._value?.service) {
|
||||||
return "";
|
return "";
|
||||||
@@ -878,11 +839,6 @@ export class HaServiceControl extends LitElement {
|
|||||||
.description p {
|
.description p {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
ha-expansion-panel {
|
|
||||||
--ha-card-border-radius: 0;
|
|
||||||
--expansion-panel-summary-padding: 0 16px;
|
|
||||||
--expansion-panel-content-padding: 0;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -352,22 +352,6 @@ export const saveAutomationConfig = (
|
|||||||
config: AutomationConfig
|
config: AutomationConfig
|
||||||
) => hass.callApi<void>("POST", `config/automation/config/${id}`, config);
|
) => hass.callApi<void>("POST", `config/automation/config/${id}`, config);
|
||||||
|
|
||||||
export const normalizeAutomationConfig = <
|
|
||||||
T extends Partial<AutomationConfig> | AutomationConfig,
|
|
||||||
>(
|
|
||||||
config: T
|
|
||||||
): T => {
|
|
||||||
// Normalize data: ensure trigger, action and condition are lists
|
|
||||||
// Happens when people copy paste their automations into the config
|
|
||||||
for (const key of ["trigger", "condition", "action"]) {
|
|
||||||
const value = config[key];
|
|
||||||
if (value && !Array.isArray(value)) {
|
|
||||||
config[key] = [value];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const showAutomationEditor = (data?: Partial<AutomationConfig>) => {
|
export const showAutomationEditor = (data?: Partial<AutomationConfig>) => {
|
||||||
initialAutomationEditorData = data;
|
initialAutomationEditorData = data;
|
||||||
navigate("/config/automation/edit/new");
|
navigate("/config/automation/edit/new");
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
import { ManualAutomationConfig } from "./automation";
|
|
||||||
import { ManualScriptConfig } from "./script";
|
|
||||||
import { Selector } from "./selector";
|
import { Selector } from "./selector";
|
||||||
|
|
||||||
export type BlueprintDomain = "automation" | "script";
|
export type BlueprintDomain = "automation" | "script";
|
||||||
@@ -44,11 +42,6 @@ export interface BlueprintImportResult {
|
|||||||
validation_errors: string[] | null;
|
validation_errors: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlueprintSubstituteResults {
|
|
||||||
automation: { substituted_config: ManualAutomationConfig };
|
|
||||||
script: { substituted_config: ManualScriptConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) =>
|
export const fetchBlueprints = (hass: HomeAssistant, domain: BlueprintDomain) =>
|
||||||
hass.callWS<Blueprints>({ type: "blueprint/list", domain });
|
hass.callWS<Blueprints>({ type: "blueprint/list", domain });
|
||||||
|
|
||||||
@@ -98,18 +91,3 @@ export const getBlueprintSourceType = (
|
|||||||
}
|
}
|
||||||
return "community";
|
return "community";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const substituteBlueprint = <
|
|
||||||
T extends BlueprintDomain = BlueprintDomain,
|
|
||||||
>(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
domain: T,
|
|
||||||
path: string,
|
|
||||||
input: Record<string, any>
|
|
||||||
) =>
|
|
||||||
hass.callWS<BlueprintSubstituteResults[T]>({
|
|
||||||
type: "blueprint/substitute",
|
|
||||||
domain,
|
|
||||||
path,
|
|
||||||
input,
|
|
||||||
});
|
|
||||||
|
@@ -5,7 +5,6 @@ import type {
|
|||||||
EntityRegistryDisplayEntry,
|
EntityRegistryDisplayEntry,
|
||||||
EntityRegistryEntry,
|
EntityRegistryEntry,
|
||||||
} from "./entity_registry";
|
} from "./entity_registry";
|
||||||
import { ConfigEntry } from "./config_entries";
|
|
||||||
import type { EntitySources } from "./entity_sources";
|
import type { EntitySources } from "./entity_sources";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -143,11 +142,9 @@ export const getDeviceEntityDisplayLookup = (
|
|||||||
|
|
||||||
export const getDeviceIntegrationLookup = (
|
export const getDeviceIntegrationLookup = (
|
||||||
entitySources: EntitySources,
|
entitySources: EntitySources,
|
||||||
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[],
|
entities: EntityRegistryDisplayEntry[] | EntityRegistryEntry[]
|
||||||
devices?: DeviceRegistryEntry[],
|
): Record<string, string[]> => {
|
||||||
configEntries?: ConfigEntry[]
|
const deviceIntegrations: Record<string, string[]> = {};
|
||||||
): Record<string, Set<string>> => {
|
|
||||||
const deviceIntegrations: Record<string, Set<string>> = {};
|
|
||||||
|
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
const source = entitySources[entity.entity_id];
|
const source = entitySources[entity.entity_id];
|
||||||
@@ -155,22 +152,10 @@ export const getDeviceIntegrationLookup = (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceIntegrations[entity.device_id!] =
|
if (!deviceIntegrations[entity.device_id!]) {
|
||||||
deviceIntegrations[entity.device_id!] || new Set<string>();
|
deviceIntegrations[entity.device_id!] = [];
|
||||||
deviceIntegrations[entity.device_id!].add(source.domain);
|
|
||||||
}
|
|
||||||
// Lookup devices that have no entities
|
|
||||||
if (devices && configEntries) {
|
|
||||||
for (const device of devices) {
|
|
||||||
for (const config_entry_id of device.config_entries) {
|
|
||||||
const entry = configEntries.find((e) => e.entry_id === config_entry_id);
|
|
||||||
if (entry?.domain) {
|
|
||||||
deviceIntegrations[device.id] =
|
|
||||||
deviceIntegrations[device.id] || new Set<string>();
|
|
||||||
deviceIntegrations[device.id].add(entry.domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
deviceIntegrations[entity.device_id!].push(source.domain);
|
||||||
}
|
}
|
||||||
return deviceIntegrations;
|
return deviceIntegrations;
|
||||||
};
|
};
|
||||||
|
@@ -696,7 +696,7 @@ export const entityMeetsTargetSelector = (
|
|||||||
export const filterSelectorDevices = (
|
export const filterSelectorDevices = (
|
||||||
filterDevice: DeviceSelectorFilter,
|
filterDevice: DeviceSelectorFilter,
|
||||||
device: DeviceRegistryEntry,
|
device: DeviceRegistryEntry,
|
||||||
deviceIntegrationLookup?: Record<string, Set<string>> | undefined
|
deviceIntegrationLookup?: Record<string, string[]> | undefined
|
||||||
): boolean => {
|
): boolean => {
|
||||||
const {
|
const {
|
||||||
manufacturer: filterManufacturer,
|
manufacturer: filterManufacturer,
|
||||||
@@ -713,7 +713,7 @@ export const filterSelectorDevices = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filterIntegration && deviceIntegrationLookup) {
|
if (filterIntegration && deviceIntegrationLookup) {
|
||||||
if (!deviceIntegrationLookup?.[device.id]?.has(filterIntegration)) {
|
if (!deviceIntegrationLookup?.[device.id]?.includes(filterIntegration)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import "../auth/ha-authorize";
|
import "../auth/ha-authorize";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import("../resources/ha-style");
|
import("../resources/ha-style");
|
||||||
import("@polymer/polymer/lib/utils/settings").then(
|
import("@polymer/polymer/lib/utils/settings").then(
|
||||||
|
@@ -25,6 +25,7 @@ import { subscribePanels } from "../data/ws-panels";
|
|||||||
import { subscribeThemes } from "../data/ws-themes";
|
import { subscribeThemes } from "../data/ws-themes";
|
||||||
import { subscribeUser } from "../data/ws-user";
|
import { subscribeUser } from "../data/ws-user";
|
||||||
import type { ExternalAuth } from "../external_app/external_auth";
|
import type { ExternalAuth } from "../external_app/external_auth";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
window.name = MAIN_WINDOW_NAME;
|
window.name = MAIN_WINDOW_NAME;
|
||||||
(window as any).frontendVersion = __VERSION__;
|
(window as any).frontendVersion = __VERSION__;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import { CSSResult } from "lit";
|
import { CSSResult } from "lit";
|
||||||
import { fireEvent } from "../common/dom/fire_event";
|
import { fireEvent } from "../common/dom/fire_event";
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
// Compat needs to be first import
|
// Compat needs to be first import
|
||||||
import "../resources/compatibility";
|
import "../resources/compatibility";
|
||||||
import "../onboarding/ha-onboarding";
|
import "../onboarding/ha-onboarding";
|
||||||
|
import "../resources/safari-14-attachshadow-patch";
|
||||||
|
|
||||||
import("../resources/ha-style");
|
import("../resources/ha-style");
|
||||||
import("@polymer/polymer/lib/utils/settings").then(
|
import("@polymer/polymer/lib/utils/settings").then(
|
||||||
|
@@ -10,18 +10,6 @@ const now = () => new Date().toISOString();
|
|||||||
const randomTime = () =>
|
const randomTime = () =>
|
||||||
new Date(new Date().getTime() - Math.random() * 80 * 60 * 1000).toISOString();
|
new Date(new Date().getTime() - Math.random() * 80 * 60 * 1000).toISOString();
|
||||||
|
|
||||||
const CAPABILITY_ATTRIBUTES = [
|
|
||||||
"friendly_name",
|
|
||||||
"unit_of_measurement",
|
|
||||||
"icon",
|
|
||||||
"entity_picture",
|
|
||||||
"supported_features",
|
|
||||||
"hidden",
|
|
||||||
"assumed_state",
|
|
||||||
"device_class",
|
|
||||||
"state_class",
|
|
||||||
"restored",
|
|
||||||
];
|
|
||||||
export class Entity {
|
export class Entity {
|
||||||
public domain: string;
|
public domain: string;
|
||||||
|
|
||||||
@@ -41,28 +29,16 @@ export class Entity {
|
|||||||
|
|
||||||
public hass?: any;
|
public hass?: any;
|
||||||
|
|
||||||
static CAPABILITY_ATTRIBUTES = new Set(CAPABILITY_ATTRIBUTES);
|
constructor(domain, objectId, state, baseAttributes) {
|
||||||
|
|
||||||
constructor(domain, objectId, state, attributes) {
|
|
||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.objectId = objectId;
|
this.objectId = objectId;
|
||||||
this.entityId = `${domain}.${objectId}`;
|
this.entityId = `${domain}.${objectId}`;
|
||||||
this.lastChanged = randomTime();
|
this.lastChanged = randomTime();
|
||||||
this.lastUpdated = randomTime();
|
this.lastUpdated = randomTime();
|
||||||
this.state = String(state);
|
this.state = String(state);
|
||||||
|
|
||||||
// These are the attributes that we always write to the state machine
|
// These are the attributes that we always write to the state machine
|
||||||
const baseAttributes = {};
|
|
||||||
const capabilityAttributes =
|
|
||||||
TYPES[domain]?.CAPABILITY_ATTRIBUTES || Entity.CAPABILITY_ATTRIBUTES;
|
|
||||||
for (const key of Object.keys(attributes)) {
|
|
||||||
if (capabilityAttributes.has(key)) {
|
|
||||||
baseAttributes[key] = attributes[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.baseAttributes = baseAttributes;
|
this.baseAttributes = baseAttributes;
|
||||||
this.attributes = attributes;
|
this.attributes = baseAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handleService(domain, service, data: Record<string, any>) {
|
public async handleService(domain, service, data: Record<string, any>) {
|
||||||
@@ -78,7 +54,7 @@ export class Entity {
|
|||||||
this.lastUpdated = now();
|
this.lastUpdated = now();
|
||||||
this.lastChanged =
|
this.lastChanged =
|
||||||
state === this.state ? this.lastChanged : this.lastUpdated;
|
state === this.state ? this.lastChanged : this.lastUpdated;
|
||||||
this.attributes = { ...this.attributes, ...attributes };
|
this.attributes = { ...this.baseAttributes, ...attributes };
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.log("update", this.entityId, this);
|
console.log("update", this.entityId, this);
|
||||||
@@ -92,7 +68,7 @@ export class Entity {
|
|||||||
return {
|
return {
|
||||||
entity_id: this.entityId,
|
entity_id: this.entityId,
|
||||||
state: this.state,
|
state: this.state,
|
||||||
attributes: this.state === "off" ? this.baseAttributes : this.attributes,
|
attributes: this.attributes,
|
||||||
last_changed: this.lastChanged,
|
last_changed: this.lastChanged,
|
||||||
last_updated: this.lastUpdated,
|
last_updated: this.lastUpdated,
|
||||||
};
|
};
|
||||||
@@ -100,16 +76,6 @@ export class Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LightEntity extends Entity {
|
class LightEntity extends Entity {
|
||||||
static CAPABILITY_ATTRIBUTES = new Set([
|
|
||||||
...CAPABILITY_ATTRIBUTES,
|
|
||||||
"min_color_temp_kelvin",
|
|
||||||
"max_color_temp_kelvin",
|
|
||||||
"min_mireds",
|
|
||||||
"max_mireds",
|
|
||||||
"effect_list",
|
|
||||||
"supported_color_modes",
|
|
||||||
]);
|
|
||||||
|
|
||||||
public async handleService(domain, service, data) {
|
public async handleService(domain, service, data) {
|
||||||
if (!["homeassistant", this.domain].includes(domain)) {
|
if (!["homeassistant", this.domain].includes(domain)) {
|
||||||
return;
|
return;
|
||||||
@@ -222,12 +188,6 @@ class AlarmControlPanelEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MediaPlayerEntity extends Entity {
|
class MediaPlayerEntity extends Entity {
|
||||||
static CAPABILITY_ATTRIBUTES = new Set([
|
|
||||||
...CAPABILITY_ATTRIBUTES,
|
|
||||||
"source_list",
|
|
||||||
"sound_mode_list",
|
|
||||||
]);
|
|
||||||
|
|
||||||
public async handleService(
|
public async handleService(
|
||||||
domain,
|
domain,
|
||||||
service,
|
service,
|
||||||
@@ -263,11 +223,7 @@ class CoverEntity extends Entity {
|
|||||||
if (service === "open_cover") {
|
if (service === "open_cover") {
|
||||||
this.update("open");
|
this.update("open");
|
||||||
} else if (service === "close_cover") {
|
} else if (service === "close_cover") {
|
||||||
this.update("closed");
|
this.update("closing");
|
||||||
} else if (service === "set_cover_position") {
|
|
||||||
this.update(data.position > 0 ? "open" : "closed", {
|
|
||||||
current_position: data.position,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
super.handleService(domain, service, data);
|
super.handleService(domain, service, data);
|
||||||
}
|
}
|
||||||
@@ -332,19 +288,6 @@ class InputSelectEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ClimateEntity extends Entity {
|
class ClimateEntity extends Entity {
|
||||||
static CAPABILITY_ATTRIBUTES = new Set([
|
|
||||||
...CAPABILITY_ATTRIBUTES,
|
|
||||||
"hvac_modes",
|
|
||||||
"min_temp",
|
|
||||||
"max_temp",
|
|
||||||
"target_temp_step",
|
|
||||||
"fan_modes",
|
|
||||||
"preset_modes",
|
|
||||||
"swing_modes",
|
|
||||||
"min_humidity",
|
|
||||||
"max_humidity",
|
|
||||||
]);
|
|
||||||
|
|
||||||
public async handleService(domain, service, data) {
|
public async handleService(domain, service, data) {
|
||||||
if (domain !== this.domain) {
|
if (domain !== this.domain) {
|
||||||
return;
|
return;
|
||||||
@@ -414,14 +357,6 @@ class ClimateEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class WaterHeaterEntity extends Entity {
|
class WaterHeaterEntity extends Entity {
|
||||||
static CAPABILITY_ATTRIBUTES = new Set([
|
|
||||||
...CAPABILITY_ATTRIBUTES,
|
|
||||||
"current_temperature",
|
|
||||||
"min_temp",
|
|
||||||
"max_temp",
|
|
||||||
"operation_list",
|
|
||||||
]);
|
|
||||||
|
|
||||||
public async handleService(domain, service, data) {
|
public async handleService(domain, service, data) {
|
||||||
if (domain !== this.domain) {
|
if (domain !== this.domain) {
|
||||||
return;
|
return;
|
||||||
@@ -459,7 +394,6 @@ class GroupEntity extends Entity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TYPES = {
|
const TYPES = {
|
||||||
automation: ToggleEntity,
|
|
||||||
alarm_control_panel: AlarmControlPanelEntity,
|
alarm_control_panel: AlarmControlPanelEntity,
|
||||||
climate: ClimateEntity,
|
climate: ClimateEntity,
|
||||||
cover: CoverEntity,
|
cover: CoverEntity,
|
||||||
|
@@ -278,8 +278,6 @@ export const provideHass = (
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
async callService(domain, service, data) {
|
async callService(domain, service, data) {
|
||||||
if (data && "entity_id" in data) {
|
if (data && "entity_id" in data) {
|
||||||
// eslint-disable-next-line
|
|
||||||
console.log("Entity service call", domain, service, data);
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
ensureArray(data.entity_id).map((ent) =>
|
ensureArray(data.entity_id).map((ent) =>
|
||||||
entities[ent].handleService(domain, service, data)
|
entities[ent].handleService(domain, service, data)
|
||||||
|
@@ -6,7 +6,6 @@ import {
|
|||||||
mdiArrowDown,
|
mdiArrowDown,
|
||||||
mdiArrowUp,
|
mdiArrowUp,
|
||||||
mdiClose,
|
mdiClose,
|
||||||
mdiCog,
|
|
||||||
mdiFilterVariant,
|
mdiFilterVariant,
|
||||||
mdiFilterVariantRemove,
|
mdiFilterVariantRemove,
|
||||||
mdiFormatListChecks,
|
mdiFormatListChecks,
|
||||||
@@ -43,7 +42,6 @@ import "../components/search-input-outlined";
|
|||||||
import type { HomeAssistant, Route } from "../types";
|
import type { HomeAssistant, Route } from "../types";
|
||||||
import "./hass-tabs-subpage";
|
import "./hass-tabs-subpage";
|
||||||
import type { PageNavigation } from "./hass-tabs-subpage";
|
import type { PageNavigation } from "./hass-tabs-subpage";
|
||||||
import { showDataTableSettingsDialog } from "../components/data-table/show-dialog-data-table-settings";
|
|
||||||
|
|
||||||
@customElement("hass-tabs-subpage-data-table")
|
@customElement("hass-tabs-subpage-data-table")
|
||||||
export class HaTabsSubpageDataTable extends LitElement {
|
export class HaTabsSubpageDataTable extends LitElement {
|
||||||
@@ -173,10 +171,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public groupOrder?: string[];
|
@property({ attribute: false }) public groupOrder?: string[];
|
||||||
|
|
||||||
@property({ attribute: false }) public columnOrder?: string[];
|
|
||||||
|
|
||||||
@property({ attribute: false }) public hiddenColumns?: string[];
|
|
||||||
|
|
||||||
@state() private _sortColumn?: string;
|
@state() private _sortColumn?: string;
|
||||||
|
|
||||||
@state() private _sortDirection: SortingDirection = null;
|
@state() private _sortDirection: SortingDirection = null;
|
||||||
@@ -296,14 +290,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
`
|
`
|
||||||
: nothing;
|
: nothing;
|
||||||
|
|
||||||
const settingsButton = html`<ha-assist-chip
|
|
||||||
class="has-dropdown select-mode-chip"
|
|
||||||
@click=${this._openSettings}
|
|
||||||
.title=${localize("ui.components.subpage-data-table.settings")}
|
|
||||||
>
|
|
||||||
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
|
|
||||||
</ha-assist-chip>`;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-tabs-subpage
|
<hass-tabs-subpage
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -430,7 +416,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<ha-data-table
|
<ha-data-table
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.narrow=${this.narrow}
|
|
||||||
.columns=${this.columns}
|
.columns=${this.columns}
|
||||||
.data=${this.data}
|
.data=${this.data}
|
||||||
.noDataText=${this.noDataText}
|
.noDataText=${this.noDataText}
|
||||||
@@ -445,8 +430,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
.groupColumn=${this._groupColumn}
|
.groupColumn=${this._groupColumn}
|
||||||
.groupOrder=${this.groupOrder}
|
.groupOrder=${this.groupOrder}
|
||||||
.initialCollapsedGroups=${this.initialCollapsedGroups}
|
.initialCollapsedGroups=${this.initialCollapsedGroups}
|
||||||
.columnOrder=${this.columnOrder}
|
|
||||||
.hiddenColumns=${this.hiddenColumns}
|
|
||||||
>
|
>
|
||||||
${!this.narrow
|
${!this.narrow
|
||||||
? html`
|
? html`
|
||||||
@@ -455,7 +438,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
<div class="table-header">
|
<div class="table-header">
|
||||||
${this.hasFilters && !this.showFilters
|
${this.hasFilters && !this.showFilters
|
||||||
? html`${filterButton}`
|
? html`${filterButton}`
|
||||||
: nothing}${selectModeBtn}${searchBar}${groupByMenu}${sortByMenu}${settingsButton}
|
: nothing}${selectModeBtn}${searchBar}${groupByMenu}${sortByMenu}
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
@@ -465,7 +448,7 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
${this.hasFilters && !this.showFilters
|
${this.hasFilters && !this.showFilters
|
||||||
? html`${filterButton}`
|
? html`${filterButton}`
|
||||||
: nothing}
|
: nothing}
|
||||||
${selectModeBtn}${groupByMenu}${sortByMenu}${settingsButton}
|
${selectModeBtn}${groupByMenu}${sortByMenu}
|
||||||
</div>`}
|
</div>`}
|
||||||
</ha-data-table>`}
|
</ha-data-table>`}
|
||||||
<div slot="fab"><slot name="fab"></slot></div>
|
<div slot="fab"><slot name="fab"></slot></div>
|
||||||
@@ -625,22 +608,6 @@ export class HaTabsSubpageDataTable extends LitElement {
|
|||||||
fireEvent(this, "grouping-changed", { value: columnId });
|
fireEvent(this, "grouping-changed", { value: columnId });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openSettings() {
|
|
||||||
showDataTableSettingsDialog(this, {
|
|
||||||
columns: this.columns,
|
|
||||||
hiddenColumns: this.hiddenColumns,
|
|
||||||
columnOrder: this.columnOrder,
|
|
||||||
onUpdate: (
|
|
||||||
columnOrder: string[] | undefined,
|
|
||||||
hiddenColumns: string[] | undefined
|
|
||||||
) => {
|
|
||||||
this.columnOrder = columnOrder;
|
|
||||||
this.hiddenColumns = hiddenColumns;
|
|
||||||
fireEvent(this, "columns-changed", { columnOrder, hiddenColumns });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _collapseAllGroups() {
|
private _collapseAllGroups() {
|
||||||
this._dataTable.collapseAllGroups();
|
this._dataTable.collapseAllGroups();
|
||||||
}
|
}
|
||||||
@@ -907,10 +874,6 @@ declare global {
|
|||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"search-changed": { value: string };
|
"search-changed": { value: string };
|
||||||
"grouping-changed": { value: string };
|
"grouping-changed": { value: string };
|
||||||
"columns-changed": {
|
|
||||||
columnOrder: string[] | undefined;
|
|
||||||
hiddenColumns: string[] | undefined;
|
|
||||||
};
|
|
||||||
"clear-filter": undefined;
|
"clear-filter": undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -72,11 +72,11 @@ class DialogCommunity extends LitElement {
|
|||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
href="https://x.com/home_assistant"
|
href="https://twitter.com/home_assistant"
|
||||||
>
|
>
|
||||||
<ha-list-item hasMeta graphic="icon">
|
<ha-list-item hasMeta graphic="icon">
|
||||||
<img class="x" src="/static/images/logo_x.svg" slot="graphic" />
|
<img src="/static/images/logo_twitter.png" slot="graphic" />
|
||||||
${this.localize("ui.panel.page-onboarding.welcome.x")}
|
${this.localize("ui.panel.page-onboarding.welcome.twitter")}
|
||||||
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
<ha-svg-icon slot="meta" .path=${mdiOpenInNew}></ha-svg-icon>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
</a>
|
</a>
|
||||||
@@ -96,12 +96,6 @@ class DialogCommunity extends LitElement {
|
|||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
img.x {
|
|
||||||
filter: invert(1) hue-rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
import "@material/mwc-list/mwc-list-item";
|
import "@material/mwc-list/mwc-list-item";
|
||||||
import { mdiOpenInNew } from "@mdi/js";
|
import { mdiOpenInNew } from "@mdi/js";
|
||||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||||
@@ -10,7 +11,6 @@ import "../../../components/ha-combo-box";
|
|||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
import "../../../components/ha-markdown";
|
import "../../../components/ha-markdown";
|
||||||
import "../../../components/ha-textfield";
|
import "../../../components/ha-textfield";
|
||||||
import "../../../components/ha-button";
|
|
||||||
import {
|
import {
|
||||||
ApplicationCredential,
|
ApplicationCredential,
|
||||||
ApplicationCredentialsConfig,
|
ApplicationCredentialsConfig,
|
||||||
@@ -231,10 +231,10 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-button slot="secondaryAction" @click=${this._abortDialog}>
|
<mwc-button slot="primaryAction" @click=${this._abortDialog}>
|
||||||
${this.hass.localize("ui.common.cancel")}
|
${this.hass.localize("ui.common.cancel")}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
<ha-button
|
<mwc-button
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
.disabled=${!this._domain ||
|
.disabled=${!this._domain ||
|
||||||
!this._clientId ||
|
!this._clientId ||
|
||||||
@@ -244,7 +244,7 @@ export class DialogAddApplicationCredential extends LitElement {
|
|||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.application_credentials.editor.add"
|
"ui.panel.config.application_credentials.editor.add"
|
||||||
)}
|
)}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
`}
|
`}
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
|
@@ -7,12 +7,10 @@ import { LocalizeFunc } from "../../../common/translations/localize";
|
|||||||
import {
|
import {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
SelectionChangedEvent,
|
SelectionChangedEvent,
|
||||||
SortingChangedEvent,
|
|
||||||
} from "../../../components/data-table/ha-data-table";
|
} from "../../../components/data-table/ha-data-table";
|
||||||
import "../../../components/ha-fab";
|
import "../../../components/ha-fab";
|
||||||
import "../../../components/ha-help-tooltip";
|
import "../../../components/ha-help-tooltip";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "../../../components/ha-icon-overflow-menu";
|
|
||||||
import {
|
import {
|
||||||
ApplicationCredential,
|
ApplicationCredential,
|
||||||
deleteApplicationCredential,
|
deleteApplicationCredential,
|
||||||
@@ -28,7 +26,6 @@ import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-
|
|||||||
import { HomeAssistant, Route } from "../../../types";
|
import { HomeAssistant, Route } from "../../../types";
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import { showAddApplicationCredentialDialog } from "./show-dialog-add-application-credential";
|
import { showAddApplicationCredentialDialog } from "./show-dialog-add-application-credential";
|
||||||
import { storage } from "../../../common/decorators/storage";
|
|
||||||
|
|
||||||
@customElement("ha-config-application-credentials")
|
@customElement("ha-config-application-credentials")
|
||||||
export class HaConfigApplicationCredentials extends LitElement {
|
export class HaConfigApplicationCredentials extends LitElement {
|
||||||
@@ -47,45 +44,14 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
@query("hass-tabs-subpage-data-table", true)
|
@query("hass-tabs-subpage-data-table", true)
|
||||||
private _dataTable!: HaTabsSubpageDataTable;
|
private _dataTable!: HaTabsSubpageDataTable;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "application-credentials-table-sort",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeSorting?: SortingChangedEvent;
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "application-credentials-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "application-credentials-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
storage: "sessionStorage",
|
|
||||||
key: "application-credentials-table-search",
|
|
||||||
state: true,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _filter = "";
|
|
||||||
|
|
||||||
private _columns = memoizeOne(
|
private _columns = memoizeOne(
|
||||||
(localize: LocalizeFunc): DataTableColumnContainer => {
|
(narrow: boolean, localize: LocalizeFunc): DataTableColumnContainer => {
|
||||||
const columns: DataTableColumnContainer<ApplicationCredential> = {
|
const columns: DataTableColumnContainer<ApplicationCredential> = {
|
||||||
name: {
|
name: {
|
||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.application_credentials.picker.headers.name"
|
"ui.panel.config.application_credentials.picker.headers.name"
|
||||||
),
|
),
|
||||||
main: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
},
|
},
|
||||||
@@ -93,41 +59,17 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.application_credentials.picker.headers.client_id"
|
"ui.panel.config.application_credentials.picker.headers.client_id"
|
||||||
),
|
),
|
||||||
filterable: true,
|
|
||||||
width: "30%",
|
width: "30%",
|
||||||
|
hidden: narrow,
|
||||||
},
|
},
|
||||||
localizedDomain: {
|
localizedDomain: {
|
||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.application_credentials.picker.headers.application"
|
"ui.panel.config.application_credentials.picker.headers.application"
|
||||||
),
|
),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
|
||||||
width: "30%",
|
width: "30%",
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
title: "",
|
|
||||||
width: "64px",
|
|
||||||
type: "overflow-menu",
|
|
||||||
showNarrow: true,
|
|
||||||
hideable: false,
|
|
||||||
moveable: false,
|
|
||||||
template: (credential) => html`
|
|
||||||
<ha-icon-overflow-menu
|
|
||||||
.hass=${this.hass}
|
|
||||||
narrow
|
|
||||||
.items=${[
|
|
||||||
{
|
|
||||||
path: mdiDelete,
|
|
||||||
warning: true,
|
|
||||||
label: this.hass.localize("ui.common.delete"),
|
|
||||||
action: () => this._deleteCredential(credential),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
</ha-icon-overflow-menu>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
@@ -156,7 +98,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
back-path="/config"
|
back-path="/config"
|
||||||
.tabs=${configSections.devices}
|
.tabs=${configSections.devices}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
.data=${this._getApplicationCredentials(
|
.data=${this._getApplicationCredentials(
|
||||||
this._applicationCredentials,
|
this._applicationCredentials,
|
||||||
this.hass.localize
|
this.hass.localize
|
||||||
@@ -165,18 +107,11 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
selectable
|
selectable
|
||||||
.selected=${this._selected.length}
|
.selected=${this._selected.length}
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
.initialSorting=${this._activeSorting}
|
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
|
||||||
.filter=${this._filter}
|
|
||||||
@search-changed=${this._handleSearchChange}
|
|
||||||
>
|
>
|
||||||
<div class="header-btns" slot="selection-bar">
|
<div class="header-btns" slot="selection-bar">
|
||||||
${!this.narrow
|
${!this.narrow
|
||||||
? html`
|
? html`
|
||||||
<mwc-button @click=${this._deleteSelected} class="warning"
|
<mwc-button @click=${this._removeSelected} class="warning"
|
||||||
>${this.hass.localize(
|
>${this.hass.localize(
|
||||||
"ui.panel.config.application_credentials.picker.remove_selected.button"
|
"ui.panel.config.application_credentials.picker.remove_selected.button"
|
||||||
)}</mwc-button
|
)}</mwc-button
|
||||||
@@ -186,7 +121,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class="warning"
|
class="warning"
|
||||||
id="remove-btn"
|
id="remove-btn"
|
||||||
@click=${this._deleteSelected}
|
@click=${this._removeSelected}
|
||||||
.path=${mdiDelete}
|
.path=${mdiDelete}
|
||||||
.label=${this.hass.localize("ui.common.remove")}
|
.label=${this.hass.localize("ui.common.remove")}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
@@ -218,26 +153,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
this._selected = ev.detail.value;
|
this._selected = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _deleteCredential = async (credential) => {
|
private _removeSelected() {
|
||||||
const confirm = await showConfirmationDialog(this, {
|
|
||||||
title: this.hass.localize(
|
|
||||||
`ui.panel.config.application_credentials.picker.remove.confirm_title`
|
|
||||||
),
|
|
||||||
text: this.hass.localize(
|
|
||||||
"ui.panel.config.application_credentials.picker.remove_selected.confirm_text"
|
|
||||||
),
|
|
||||||
confirmText: this.hass.localize("ui.common.delete"),
|
|
||||||
dismissText: this.hass.localize("ui.common.cancel"),
|
|
||||||
destructive: true,
|
|
||||||
});
|
|
||||||
if (!confirm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await deleteApplicationCredential(this.hass, credential.id);
|
|
||||||
await this._fetchApplicationCredentials();
|
|
||||||
};
|
|
||||||
|
|
||||||
private _deleteSelected() {
|
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
`ui.panel.config.application_credentials.picker.remove_selected.confirm_title`,
|
`ui.panel.config.application_credentials.picker.remove_selected.confirm_title`,
|
||||||
@@ -246,9 +162,8 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.application_credentials.picker.remove_selected.confirm_text"
|
"ui.panel.config.application_credentials.picker.remove_selected.confirm_text"
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.delete"),
|
confirmText: this.hass.localize("ui.common.remove"),
|
||||||
dismissText: this.hass.localize("ui.common.cancel"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
destructive: true,
|
|
||||||
confirm: async () => {
|
confirm: async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -269,7 +184,7 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._dataTable.clearSelection();
|
this._dataTable.clearSelection();
|
||||||
await this._fetchApplicationCredentials();
|
this._fetchApplicationCredentials();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,19 +212,6 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleSortingChanged(ev: CustomEvent) {
|
|
||||||
this._activeSorting = ev.detail;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleSearchChange(ev: CustomEvent) {
|
|
||||||
this._filter = ev.detail.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.table-header {
|
.table-header {
|
||||||
@@ -359,9 +261,6 @@ export class HaConfigApplicationCredentials extends LitElement {
|
|||||||
margin-inline-start: 8px;
|
margin-inline-start: 8px;
|
||||||
margin-inline-end: initial;
|
margin-inline-end: initial;
|
||||||
}
|
}
|
||||||
.warning {
|
|
||||||
--mdc-theme-primary: var(--error-color);
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { stringCompare } from "../../../common/string/compare";
|
import { stringCompare } from "../../../common/string/compare";
|
||||||
import { stripDiacritics } from "../../../common/string/strip-diacritics";
|
|
||||||
import { LocalizeFunc } from "../../../common/translations/localize";
|
import { LocalizeFunc } from "../../../common/translations/localize";
|
||||||
import { deepEqual } from "../../../common/util/deep-equal";
|
import { deepEqual } from "../../../common/util/deep-equal";
|
||||||
import "../../../components/ha-dialog";
|
import "../../../components/ha-dialog";
|
||||||
@@ -51,7 +50,6 @@ import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger";
|
|||||||
import { HassDialog } from "../../../dialogs/make-dialog-manager";
|
import { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { getStripDiacriticsFn } from "../../../util/fuse";
|
|
||||||
import {
|
import {
|
||||||
AddAutomationElementDialogParams,
|
AddAutomationElementDialogParams,
|
||||||
PASTE_VALUE,
|
PASTE_VALUE,
|
||||||
@@ -210,10 +208,9 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(items, options);
|
const fuse = new Fuse(items, options);
|
||||||
return fuse.search(stripDiacritics(filter)).map((result) => result.item);
|
return fuse.search(filter).map((result) => result.item);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -3,10 +3,10 @@ import { HassEntity } from "home-assistant-js-websocket";
|
|||||||
import { html, nothing } from "lit";
|
import { html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../components/ha-alert";
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-markdown";
|
|
||||||
import { BlueprintAutomationConfig } from "../../../data/automation";
|
import { BlueprintAutomationConfig } from "../../../data/automation";
|
||||||
import { fetchBlueprints } from "../../../data/blueprint";
|
import { fetchBlueprints } from "../../../data/blueprint";
|
||||||
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
||||||
|
import "../../../components/ha-markdown";
|
||||||
|
|
||||||
@customElement("blueprint-automation-editor")
|
@customElement("blueprint-automation-editor")
|
||||||
export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
||||||
@@ -20,6 +20,14 @@ export class HaBlueprintAutomationEditor extends HaBlueprintGenericEditor {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
|
${this.disabled
|
||||||
|
? html`<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize("ui.panel.config.automation.editor.read_only")}
|
||||||
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
|
${this.hass.localize("ui.panel.config.automation.editor.migrate")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: nothing}
|
||||||
${this.stateObj?.state === "off"
|
${this.stateObj?.state === "off"
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="info">
|
<ha-alert alert-type="info">
|
||||||
|
@@ -6,7 +6,6 @@ import {
|
|||||||
mdiDebugStepOver,
|
mdiDebugStepOver,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiFileEdit,
|
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlayCircleOutline,
|
mdiPlayCircleOutline,
|
||||||
@@ -41,12 +40,10 @@ import "../../../components/ha-yaml-editor";
|
|||||||
import {
|
import {
|
||||||
AutomationConfig,
|
AutomationConfig,
|
||||||
AutomationEntity,
|
AutomationEntity,
|
||||||
BlueprintAutomationConfig,
|
|
||||||
deleteAutomation,
|
deleteAutomation,
|
||||||
fetchAutomationFileConfig,
|
fetchAutomationFileConfig,
|
||||||
getAutomationEditorInitData,
|
getAutomationEditorInitData,
|
||||||
getAutomationStateConfig,
|
getAutomationStateConfig,
|
||||||
normalizeAutomationConfig,
|
|
||||||
saveAutomationConfig,
|
saveAutomationConfig,
|
||||||
showAutomationEditor,
|
showAutomationEditor,
|
||||||
triggerAutomationActions,
|
triggerAutomationActions,
|
||||||
@@ -68,7 +65,6 @@ import { showAutomationModeDialog } from "./automation-mode-dialog/show-dialog-a
|
|||||||
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
import { showAutomationRenameDialog } from "./automation-rename-dialog/show-dialog-automation-rename";
|
||||||
import "./blueprint-automation-editor";
|
import "./blueprint-automation-editor";
|
||||||
import "./manual-automation-editor";
|
import "./manual-automation-editor";
|
||||||
import { substituteBlueprint } from "../../../data/blueprint";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -81,9 +77,9 @@ declare global {
|
|||||||
unsub?: UnsubscribeFunc;
|
unsub?: UnsubscribeFunc;
|
||||||
};
|
};
|
||||||
"ui-mode-not-available": Error;
|
"ui-mode-not-available": Error;
|
||||||
|
duplicate: undefined;
|
||||||
"move-down": undefined;
|
"move-down": undefined;
|
||||||
"move-up": undefined;
|
"move-up": undefined;
|
||||||
duplicate: undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +112,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _validationErrors?: (string | TemplateResult)[];
|
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||||
|
|
||||||
@state() private _blueprintConfig?: BlueprintAutomationConfig;
|
|
||||||
|
|
||||||
private _configSubscriptions: Record<
|
private _configSubscriptions: Record<
|
||||||
string,
|
string,
|
||||||
(config?: AutomationConfig) => void
|
(config?: AutomationConfig) => void
|
||||||
@@ -202,9 +196,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
<ha-list-item
|
<ha-list-item
|
||||||
graphic="icon"
|
graphic="icon"
|
||||||
@click=${this._promptAutomationAlias}
|
@click=${this._promptAutomationAlias}
|
||||||
.disabled=${this._readOnly ||
|
.disabled=${!this.automationId || this._mode === "yaml"}
|
||||||
!this.automationId ||
|
|
||||||
this._mode === "yaml"}
|
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.panel.config.automation.editor.rename")}
|
${this.hass.localize("ui.panel.config.automation.editor.rename")}
|
||||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||||
@@ -228,8 +220,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
|
|
||||||
<ha-list-item
|
<ha-list-item
|
||||||
.disabled=${this._blueprintConfig ||
|
.disabled=${!this._readOnly && !this.automationId}
|
||||||
(!this._readOnly && !this.automationId)}
|
|
||||||
graphic="icon"
|
graphic="icon"
|
||||||
@click=${this._duplicate}
|
@click=${this._duplicate}
|
||||||
>
|
>
|
||||||
@@ -244,24 +235,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
|
||||||
${useBlueprint
|
|
||||||
? html`
|
|
||||||
<ha-list-item
|
|
||||||
graphic="icon"
|
|
||||||
@click=${this._takeControl}
|
|
||||||
.disabled=${this._readOnly}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.take_control"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiFileEdit}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</ha-list-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<li divider role="separator"></li>
|
<li divider role="separator"></li>
|
||||||
|
|
||||||
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
|
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
|
||||||
@@ -342,32 +315,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._blueprintConfig
|
|
||||||
? html`<ha-alert alert-type="info">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.confirm_take_control"
|
|
||||||
)}
|
|
||||||
<div slot="action" style="display: flex;">
|
|
||||||
<mwc-button @click=${this._takeControlSave}
|
|
||||||
>${this.hass.localize("ui.common.yes")}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button @click=${this._revertBlueprint}
|
|
||||||
>${this.hass.localize("ui.common.no")}</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-alert>`
|
|
||||||
: this._readOnly
|
|
||||||
? html`<ha-alert alert-type="warning" dismissable
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.read_only"
|
|
||||||
)}
|
|
||||||
<mwc-button slot="action" @click=${this._duplicate}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.automation.editor.migrate"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</ha-alert>`
|
|
||||||
: nothing}
|
|
||||||
${this._mode === "gui"
|
${this._mode === "gui"
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
@@ -385,6 +332,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
.disabled=${Boolean(this._readOnly)}
|
.disabled=${Boolean(this._readOnly)}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
@duplicate=${this._duplicate}
|
||||||
></blueprint-automation-editor>
|
></blueprint-automation-editor>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
@@ -396,12 +344,25 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
.disabled=${Boolean(this._readOnly)}
|
.disabled=${Boolean(this._readOnly)}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
@duplicate=${this._duplicate}
|
||||||
></manual-automation-editor>
|
></manual-automation-editor>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: this._mode === "yaml"
|
: this._mode === "yaml"
|
||||||
? html`${stateObj?.state === "off"
|
? html` ${this._readOnly
|
||||||
|
? html`<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.read_only"
|
||||||
|
)}
|
||||||
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.migrate"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: nothing}
|
||||||
|
${stateObj?.state === "off"
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="info">
|
<ha-alert alert-type="info">
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@@ -426,7 +387,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
</div>
|
</div>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
class=${classMap({ dirty: !this._readOnly && this._dirty })}
|
class=${classMap({ dirty: this._dirty })}
|
||||||
.label=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
.label=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
||||||
extended
|
extended
|
||||||
@click=${this._saveAutomation}
|
@click=${this._saveAutomation}
|
||||||
@@ -471,7 +432,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
this._config = {
|
this._config = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
...(initData ? normalizeAutomationConfig(initData) : initData),
|
...initData,
|
||||||
} as AutomationConfig;
|
} as AutomationConfig;
|
||||||
this._entityId = undefined;
|
this._entityId = undefined;
|
||||||
this._readOnly = false;
|
this._readOnly = false;
|
||||||
@@ -480,7 +441,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
|
|
||||||
if (changedProps.has("entityId") && this.entityId) {
|
if (changedProps.has("entityId") && this.entityId) {
|
||||||
getAutomationStateConfig(this.hass, this.entityId).then((c) => {
|
getAutomationStateConfig(this.hass, this.entityId).then((c) => {
|
||||||
this._config = normalizeAutomationConfig(c.config);
|
this._config = this._normalizeConfig(c.config);
|
||||||
this._checkValidation();
|
this._checkValidation();
|
||||||
});
|
});
|
||||||
this._entityId = this.entityId;
|
this._entityId = this.entityId;
|
||||||
@@ -536,6 +497,18 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _normalizeConfig(config: AutomationConfig): AutomationConfig {
|
||||||
|
// Normalize data: ensure trigger, action and condition are lists
|
||||||
|
// Happens when people copy paste their automations into the config
|
||||||
|
for (const key of ["trigger", "condition", "action"]) {
|
||||||
|
const value = config[key];
|
||||||
|
if (value && !Array.isArray(value)) {
|
||||||
|
config[key] = [value];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
private async _loadConfig() {
|
private async _loadConfig() {
|
||||||
try {
|
try {
|
||||||
const config = await fetchAutomationFileConfig(
|
const config = await fetchAutomationFileConfig(
|
||||||
@@ -544,7 +517,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
this._dirty = false;
|
this._dirty = false;
|
||||||
this._readOnly = false;
|
this._readOnly = false;
|
||||||
this._config = normalizeAutomationConfig(config);
|
this._config = this._normalizeConfig(config);
|
||||||
this._checkValidation();
|
this._checkValidation();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
|
const entityRegistry = await fetchEntityRegistry(this.hass.connection);
|
||||||
@@ -665,51 +638,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private async _takeControl() {
|
|
||||||
const config = this._config as BlueprintAutomationConfig;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await substituteBlueprint(
|
|
||||||
this.hass,
|
|
||||||
"automation",
|
|
||||||
config.use_blueprint.path,
|
|
||||||
config.use_blueprint.input || {}
|
|
||||||
);
|
|
||||||
|
|
||||||
const newConfig = {
|
|
||||||
...normalizeAutomationConfig(result.substituted_config),
|
|
||||||
id: config.id,
|
|
||||||
alias: config.alias,
|
|
||||||
description: config.description,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._blueprintConfig = config;
|
|
||||||
this._config = newConfig;
|
|
||||||
if (this._mode === "yaml") {
|
|
||||||
this.renderRoot.querySelector("ha-yaml-editor")?.setValue(this._config);
|
|
||||||
}
|
|
||||||
this._readOnly = true;
|
|
||||||
this._errors = undefined;
|
|
||||||
} catch (err: any) {
|
|
||||||
this._errors = err.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _revertBlueprint() {
|
|
||||||
this._config = this._blueprintConfig;
|
|
||||||
if (this._mode === "yaml") {
|
|
||||||
this.renderRoot.querySelector("ha-yaml-editor")?.setValue(this._config);
|
|
||||||
}
|
|
||||||
this._blueprintConfig = undefined;
|
|
||||||
this._readOnly = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _takeControlSave() {
|
|
||||||
this._readOnly = false;
|
|
||||||
this._dirty = true;
|
|
||||||
this._blueprintConfig = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _duplicate() {
|
private async _duplicate() {
|
||||||
const result = this._readOnly
|
const result = this._readOnly
|
||||||
? await showConfirmationDialog(this, {
|
? await showConfirmationDialog(this, {
|
||||||
@@ -842,12 +770,10 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
manual-automation-editor,
|
manual-automation-editor,
|
||||||
blueprint-automation-editor,
|
blueprint-automation-editor {
|
||||||
:not(.yaml-mode) > ha-alert {
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 1040px;
|
max-width: 1040px;
|
||||||
padding: 28px 20px 0;
|
padding: 28px 20px 0;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
ha-yaml-editor {
|
ha-yaml-editor {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
@@ -192,20 +192,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "automation-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "automation-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@query("#overflow-menu") private _overflowMenu!: HaMenu;
|
@query("#overflow-menu") private _overflowMenu!: HaMenu;
|
||||||
|
|
||||||
private _sizeController = new ResizeController(this, {
|
private _sizeController = new ResizeController(this, {
|
||||||
@@ -265,10 +251,8 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
const columns: DataTableColumnContainer<AutomationItem> = {
|
const columns: DataTableColumnContainer<AutomationItem> = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize("ui.panel.config.automation.picker.headers.icon"),
|
label: localize("ui.panel.config.automation.picker.headers.state"),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
template: (automation) =>
|
template: (automation) =>
|
||||||
html`<ha-state-icon
|
html`<ha-state-icon
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -288,13 +272,30 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
extraTemplate: (automation) =>
|
template: (automation) => {
|
||||||
automation.labels.length
|
const date = new Date(automation.attributes.last_triggered);
|
||||||
? html`<ha-data-table-labels
|
const now = new Date();
|
||||||
@label-clicked=${this._labelClicked}
|
const dayDifference = differenceInDays(now, date);
|
||||||
.labels=${automation.labels}
|
return html`
|
||||||
></ha-data-table-labels>`
|
<div style="font-size: 14px;">${automation.name}</div>
|
||||||
: nothing,
|
${narrow
|
||||||
|
? html`<div class="secondary">
|
||||||
|
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||||
|
${automation.attributes.last_triggered
|
||||||
|
? dayDifference > 3
|
||||||
|
? formatShortDateTime(date, locale, this.hass.config)
|
||||||
|
: relativeTime(date, locale)
|
||||||
|
: localize("ui.components.relative_time.never")}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
${automation.labels.length
|
||||||
|
? html`<ha-data-table-labels
|
||||||
|
@label-clicked=${this._labelClicked}
|
||||||
|
.labels=${automation.labels}
|
||||||
|
></ha-data-table-labels>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
area: {
|
area: {
|
||||||
title: localize("ui.panel.config.automation.picker.headers.area"),
|
title: localize("ui.panel.config.automation.picker.headers.area"),
|
||||||
@@ -321,6 +322,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
width: "130px",
|
width: "130px",
|
||||||
title: localize("ui.card.automation.last_triggered"),
|
title: localize("ui.card.automation.last_triggered"),
|
||||||
|
hidden: narrow,
|
||||||
template: (automation) => {
|
template: (automation) => {
|
||||||
if (!automation.last_triggered) {
|
if (!automation.last_triggered) {
|
||||||
return this.hass.localize("ui.components.relative_time.never");
|
return this.hass.localize("ui.components.relative_time.never");
|
||||||
@@ -339,9 +341,9 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
width: "82px",
|
width: "82px",
|
||||||
sortable: true,
|
sortable: true,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
hidden: narrow,
|
|
||||||
title: "",
|
title: "",
|
||||||
type: "overflow",
|
type: "overflow",
|
||||||
|
hidden: narrow,
|
||||||
label: this.hass.localize("ui.panel.config.automation.picker.state"),
|
label: this.hass.localize("ui.panel.config.automation.picker.state"),
|
||||||
template: (automation) => html`
|
template: (automation) => html`
|
||||||
<ha-entity-toggle
|
<ha-entity-toggle
|
||||||
@@ -354,9 +356,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
title: "",
|
title: "",
|
||||||
width: "64px",
|
width: "64px",
|
||||||
type: "icon-button",
|
type: "icon-button",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
hideable: false,
|
|
||||||
template: (automation) => html`
|
template: (automation) => html`
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.automation=${automation}
|
.automation=${automation}
|
||||||
@@ -546,9 +545,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
|||||||
.initialGroupColumn=${this._activeGrouping || "category"}
|
.initialGroupColumn=${this._activeGrouping || "category"}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
@@ -1419,11 +1415,6 @@ ${rejected
|
|||||||
this._activeCollapsed = ev.detail.value;
|
this._activeCollapsed = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -38,6 +38,14 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
|
${this.disabled
|
||||||
|
? html`<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize("ui.panel.config.automation.editor.read_only")}
|
||||||
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
|
${this.hass.localize("ui.panel.config.automation.editor.migrate")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: nothing}
|
||||||
${this.stateObj?.state === "off"
|
${this.stateObj?.state === "off"
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="info">
|
<ha-alert alert-type="info">
|
||||||
@@ -230,6 +238,10 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _duplicate() {
|
||||||
|
fireEvent(this, "duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -268,6 +280,12 @@ export class HaManualAutomationEditor extends LitElement {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
}
|
}
|
||||||
|
ha-alert.re-order {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -60,19 +60,14 @@ class HaConfigBackup extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
grows: true,
|
||||||
template: narrow
|
template: (backup) =>
|
||||||
? undefined
|
html`${backup.name}
|
||||||
: (backup) =>
|
<div class="secondary">${backup.path}</div>`,
|
||||||
html`${backup.name}
|
|
||||||
<div class="secondary">${backup.path}</div>`,
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
title: localize("ui.panel.config.backup.path"),
|
|
||||||
hidden: !narrow,
|
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
title: localize("ui.panel.config.backup.size"),
|
title: localize("ui.panel.config.backup.size"),
|
||||||
width: "15%",
|
width: "15%",
|
||||||
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
|
template: (backup) => Math.ceil(backup.size * 10) / 10 + " MB",
|
||||||
@@ -81,6 +76,7 @@ class HaConfigBackup extends LitElement {
|
|||||||
title: localize("ui.panel.config.backup.created"),
|
title: localize("ui.panel.config.backup.created"),
|
||||||
width: "15%",
|
width: "15%",
|
||||||
direction: "desc",
|
direction: "desc",
|
||||||
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
template: (backup) =>
|
template: (backup) =>
|
||||||
@@ -91,9 +87,6 @@ class HaConfigBackup extends LitElement {
|
|||||||
title: "",
|
title: "",
|
||||||
width: "15%",
|
width: "15%",
|
||||||
type: "overflow-menu",
|
type: "overflow-menu",
|
||||||
showNarrow: true,
|
|
||||||
hideable: false,
|
|
||||||
moveable: false,
|
|
||||||
template: (backup) =>
|
template: (backup) =>
|
||||||
html`<ha-icon-overflow-menu
|
html`<ha-icon-overflow-menu
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@@ -3,6 +3,7 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { nestedArrayMove } from "../../../common/util/array-move";
|
import { nestedArrayMove } from "../../../common/util/array-move";
|
||||||
|
import "../../../components/ha-alert";
|
||||||
import "../../../components/ha-blueprint-picker";
|
import "../../../components/ha-blueprint-picker";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
@@ -125,14 +126,14 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
);
|
);
|
||||||
const expanded = !section.collapsed || anyRequired;
|
const expanded = !section.collapsed || anyRequired;
|
||||||
|
|
||||||
return html`<ha-expansion-panel
|
return html` <ha-expansion-panel
|
||||||
outlined
|
outlined
|
||||||
.expanded=${expanded}
|
.expanded=${expanded}
|
||||||
.noCollapse=${anyRequired}
|
.noCollapse=${anyRequired}
|
||||||
>
|
>
|
||||||
<div slot="header" role="heading" aria-level="3" class="section-header">
|
<div slot="header" role="heading" aria-level="3" class="section-header">
|
||||||
${section?.icon
|
${section?.icon
|
||||||
? html`<ha-icon
|
? html` <ha-icon
|
||||||
class="section-header"
|
class="section-header"
|
||||||
.icon=${section.icon}
|
.icon=${section.icon}
|
||||||
></ha-icon>`
|
></ha-icon>`
|
||||||
@@ -260,6 +261,10 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected _duplicate() {
|
||||||
|
fireEvent(this, "duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -313,6 +318,14 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
ha-alert {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
ha-alert.re-order {
|
||||||
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
div.section-header {
|
div.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@@ -320,10 +333,6 @@ export abstract class HaBlueprintGenericEditor extends LitElement {
|
|||||||
ha-icon.section-header {
|
ha-icon.section-header {
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
ha-alert {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -107,20 +107,6 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "blueprint-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "blueprint-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "blueprint-table-search",
|
key: "blueprint-table-search",
|
||||||
@@ -168,6 +154,8 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
|
|
||||||
private _columns = memoizeOne(
|
private _columns = memoizeOne(
|
||||||
(
|
(
|
||||||
|
narrow,
|
||||||
|
_language,
|
||||||
localize: LocalizeFunc
|
localize: LocalizeFunc
|
||||||
): DataTableColumnContainer<BlueprintMetaDataPath> => ({
|
): DataTableColumnContainer<BlueprintMetaDataPath> => ({
|
||||||
name: {
|
name: {
|
||||||
@@ -177,12 +165,19 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
|
template: narrow
|
||||||
|
? (blueprint) => html`
|
||||||
|
${blueprint.name}<br />
|
||||||
|
<div class="secondary">${blueprint.path}</div>
|
||||||
|
`
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
translated_type: {
|
translated_type: {
|
||||||
title: localize("ui.panel.config.blueprint.overview.headers.type"),
|
title: localize("ui.panel.config.blueprint.overview.headers.type"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
|
hidden: narrow,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
width: "10%",
|
width: "10%",
|
||||||
},
|
},
|
||||||
@@ -190,6 +185,7 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
title: localize("ui.panel.config.blueprint.overview.headers.file_name"),
|
title: localize("ui.panel.config.blueprint.overview.headers.file_name"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
hidden: narrow,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
width: "25%",
|
width: "25%",
|
||||||
},
|
},
|
||||||
@@ -201,9 +197,6 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
title: "",
|
title: "",
|
||||||
width: this.narrow ? undefined : "10%",
|
width: this.narrow ? undefined : "10%",
|
||||||
type: "overflow-menu",
|
type: "overflow-menu",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
hideable: false,
|
|
||||||
template: (blueprint) =>
|
template: (blueprint) =>
|
||||||
blueprint.error
|
blueprint.error
|
||||||
? html`<ha-svg-icon
|
? html`<ha-svg-icon
|
||||||
@@ -287,7 +280,11 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
back-path="/config"
|
back-path="/config"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.automations}
|
.tabs=${configSections.automations}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(
|
||||||
|
this.narrow,
|
||||||
|
this.hass.language,
|
||||||
|
this.hass.localize
|
||||||
|
)}
|
||||||
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
|
.data=${this._processedBlueprints(this.blueprints, this.hass.localize)}
|
||||||
id="fullpath"
|
id="fullpath"
|
||||||
.noDataText=${this.hass.localize(
|
.noDataText=${this.hass.localize(
|
||||||
@@ -316,9 +313,6 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
@@ -562,11 +556,6 @@ class HaBlueprintOverview extends LitElement {
|
|||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return haStyle;
|
return haStyle;
|
||||||
}
|
}
|
||||||
|
@@ -61,32 +61,32 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => {
|
|||||||
href="https://community.home-assistant.io"
|
href="https://community.home-assistant.io"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>${hass.localize("ui.panel.config.tips.join_forums")}</a
|
>Forums</a
|
||||||
>`,
|
>`,
|
||||||
twitter: html`<a
|
twitter: html`<a
|
||||||
href=${documentationUrl(hass, `/twitter`)}
|
href=${documentationUrl(hass, `/twitter`)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>${hass.localize("ui.panel.config.tips.join_x")}</a
|
>Twitter</a
|
||||||
>`,
|
>`,
|
||||||
discord: html`<a
|
discord: html`<a
|
||||||
href=${documentationUrl(hass, `/join-chat`)}
|
href=${documentationUrl(hass, `/join-chat`)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>${hass.localize("ui.panel.config.tips.join_chat")}</a
|
>Chat</a
|
||||||
>`,
|
>`,
|
||||||
blog: html`<a
|
blog: html`<a
|
||||||
href=${documentationUrl(hass, `/blog`)}
|
href=${documentationUrl(hass, `/blog`)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>${hass.localize("ui.panel.config.tips.join_blog")}</a
|
>Blog</a
|
||||||
>`,
|
>`,
|
||||||
newsletter: html`<span class="keep-together"
|
newsletter: html`<span class="keep-together"
|
||||||
><a
|
><a
|
||||||
href="https://newsletter.openhomefoundation.org/"
|
href="https://newsletter.openhomefoundation.org/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>${hass.localize("ui.panel.config.tips.join_newsletter")}</a
|
>Newsletter</a
|
||||||
>
|
>
|
||||||
</span>`,
|
</span>`,
|
||||||
}),
|
}),
|
||||||
|
@@ -154,20 +154,6 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
@storage({ key: "devices-table-collapsed", state: false, subscribe: false })
|
@storage({ key: "devices-table-collapsed", state: false, subscribe: false })
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "devices-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "devices-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
private _sizeController = new ResizeController(this, {
|
private _sizeController = new ResizeController(this, {
|
||||||
callback: (entries) => entries[0]?.contentRect.width,
|
callback: (entries) => entries[0]?.contentRect.width,
|
||||||
});
|
});
|
||||||
@@ -448,13 +434,10 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
typeof this._devicesAndFilterDomains
|
typeof this._devicesAndFilterDomains
|
||||||
>["devicesOutput"][number];
|
>["devicesOutput"][number];
|
||||||
|
|
||||||
return {
|
const columns: DataTableColumnContainer<DeviceItem> = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize("ui.panel.config.devices.data_table.icon"),
|
|
||||||
type: "icon",
|
type: "icon",
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
template: (device) =>
|
template: (device) =>
|
||||||
device.domains.length
|
device.domains.length
|
||||||
? html`<img
|
? html`<img
|
||||||
@@ -469,14 +452,19 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
/>`
|
/>`
|
||||||
: "",
|
: "",
|
||||||
},
|
},
|
||||||
name: {
|
};
|
||||||
|
|
||||||
|
if (narrow) {
|
||||||
|
columns.name = {
|
||||||
title: localize("ui.panel.config.devices.data_table.device"),
|
title: localize("ui.panel.config.devices.data_table.device"),
|
||||||
main: true,
|
main: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
extraTemplate: (device) => html`
|
template: (device) => html`
|
||||||
|
<div style="font-size: 14px;">${device.name}</div>
|
||||||
|
<div class="secondary">${device.area} | ${device.integration}</div>
|
||||||
${device.label_entries.length
|
${device.label_entries.length
|
||||||
? html`
|
? html`
|
||||||
<ha-data-table-labels
|
<ha-data-table-labels
|
||||||
@@ -485,89 +473,112 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
`,
|
`,
|
||||||
},
|
};
|
||||||
manufacturer: {
|
} else {
|
||||||
title: localize("ui.panel.config.devices.data_table.manufacturer"),
|
columns.name = {
|
||||||
|
title: localize("ui.panel.config.devices.data_table.device"),
|
||||||
|
main: true,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
groupable: true,
|
direction: "asc",
|
||||||
width: "15%",
|
grows: true,
|
||||||
},
|
template: (device) => html`
|
||||||
model: {
|
<div style="font-size: 14px;">${device.name}</div>
|
||||||
title: localize("ui.panel.config.devices.data_table.model"),
|
${device.label_entries.length
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
width: "15%",
|
|
||||||
},
|
|
||||||
area: {
|
|
||||||
title: localize("ui.panel.config.devices.data_table.area"),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
groupable: true,
|
|
||||||
width: "15%",
|
|
||||||
},
|
|
||||||
integration: {
|
|
||||||
title: localize("ui.panel.config.devices.data_table.integration"),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
groupable: true,
|
|
||||||
width: "15%",
|
|
||||||
},
|
|
||||||
battery_entity: {
|
|
||||||
title: localize("ui.panel.config.devices.data_table.battery"),
|
|
||||||
showNarrow: true,
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
type: "numeric",
|
|
||||||
width: narrow ? "105px" : "15%",
|
|
||||||
maxWidth: "105px",
|
|
||||||
valueColumn: "battery_level",
|
|
||||||
template: (device) => {
|
|
||||||
const batteryEntityPair = device.battery_entity;
|
|
||||||
const battery =
|
|
||||||
batteryEntityPair && batteryEntityPair[0]
|
|
||||||
? this.hass.states[batteryEntityPair[0]]
|
|
||||||
: undefined;
|
|
||||||
const batteryDomain = battery
|
|
||||||
? computeStateDomain(battery)
|
|
||||||
: undefined;
|
|
||||||
const batteryCharging =
|
|
||||||
batteryEntityPair && batteryEntityPair[1]
|
|
||||||
? this.hass.states[batteryEntityPair[1]]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return battery &&
|
|
||||||
(batteryDomain === "binary_sensor" || !isNaN(battery.state as any))
|
|
||||||
? html`
|
? html`
|
||||||
${batteryDomain === "sensor"
|
<ha-data-table-labels
|
||||||
? this.hass.formatEntityState(battery)
|
.labels=${device.label_entries}
|
||||||
: nothing}
|
></ha-data-table-labels>
|
||||||
<ha-battery-icon
|
|
||||||
.hass=${this.hass}
|
|
||||||
.batteryStateObj=${battery}
|
|
||||||
.batteryChargingStateObj=${batteryCharging}
|
|
||||||
></ha-battery-icon>
|
|
||||||
`
|
`
|
||||||
: html`—`;
|
: nothing}
|
||||||
},
|
`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.manufacturer = {
|
||||||
|
title: localize("ui.panel.config.devices.data_table.manufacturer"),
|
||||||
|
sortable: true,
|
||||||
|
hidden: narrow,
|
||||||
|
filterable: true,
|
||||||
|
groupable: true,
|
||||||
|
width: "15%",
|
||||||
|
};
|
||||||
|
columns.model = {
|
||||||
|
title: localize("ui.panel.config.devices.data_table.model"),
|
||||||
|
sortable: true,
|
||||||
|
hidden: narrow,
|
||||||
|
filterable: true,
|
||||||
|
width: "15%",
|
||||||
|
};
|
||||||
|
columns.area = {
|
||||||
|
title: localize("ui.panel.config.devices.data_table.area"),
|
||||||
|
sortable: true,
|
||||||
|
hidden: narrow,
|
||||||
|
filterable: true,
|
||||||
|
groupable: true,
|
||||||
|
width: "15%",
|
||||||
|
};
|
||||||
|
columns.integration = {
|
||||||
|
title: localize("ui.panel.config.devices.data_table.integration"),
|
||||||
|
sortable: true,
|
||||||
|
hidden: narrow,
|
||||||
|
filterable: true,
|
||||||
|
groupable: true,
|
||||||
|
width: "15%",
|
||||||
|
};
|
||||||
|
columns.battery_entity = {
|
||||||
|
title: localize("ui.panel.config.devices.data_table.battery"),
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
type: "numeric",
|
||||||
|
width: narrow ? "105px" : "15%",
|
||||||
|
maxWidth: "105px",
|
||||||
|
valueColumn: "battery_level",
|
||||||
|
template: (device) => {
|
||||||
|
const batteryEntityPair = device.battery_entity;
|
||||||
|
const battery =
|
||||||
|
batteryEntityPair && batteryEntityPair[0]
|
||||||
|
? this.hass.states[batteryEntityPair[0]]
|
||||||
|
: undefined;
|
||||||
|
const batteryDomain = battery ? computeStateDomain(battery) : undefined;
|
||||||
|
const batteryCharging =
|
||||||
|
batteryEntityPair && batteryEntityPair[1]
|
||||||
|
? this.hass.states[batteryEntityPair[1]]
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return battery &&
|
||||||
|
(batteryDomain === "binary_sensor" || !isNaN(battery.state as any))
|
||||||
|
? html`
|
||||||
|
${batteryDomain === "sensor"
|
||||||
|
? this.hass.formatEntityState(battery)
|
||||||
|
: nothing}
|
||||||
|
<ha-battery-icon
|
||||||
|
.hass=${this.hass}
|
||||||
|
.batteryStateObj=${battery}
|
||||||
|
.batteryChargingStateObj=${batteryCharging}
|
||||||
|
></ha-battery-icon>
|
||||||
|
`
|
||||||
|
: html`—`;
|
||||||
},
|
},
|
||||||
disabled_by: {
|
};
|
||||||
title: "",
|
columns.disabled_by = {
|
||||||
label: localize("ui.panel.config.devices.data_table.disabled_by"),
|
title: "",
|
||||||
hidden: true,
|
label: localize("ui.panel.config.devices.data_table.disabled_by"),
|
||||||
template: (device) =>
|
hidden: true,
|
||||||
device.disabled_by
|
template: (device) =>
|
||||||
? this.hass.localize("ui.panel.config.devices.disabled")
|
device.disabled_by
|
||||||
: "",
|
? this.hass.localize("ui.panel.config.devices.disabled")
|
||||||
},
|
: "",
|
||||||
labels: {
|
};
|
||||||
title: "",
|
columns.labels = {
|
||||||
hidden: true,
|
title: "",
|
||||||
filterable: true,
|
hidden: true,
|
||||||
template: (device) =>
|
filterable: true,
|
||||||
device.label_entries.map((lbl) => lbl.name).join(" "),
|
template: (device) =>
|
||||||
},
|
device.label_entries.map((lbl) => lbl.name).join(" "),
|
||||||
} as DataTableColumnContainer<DeviceItem>;
|
};
|
||||||
|
|
||||||
|
return columns;
|
||||||
});
|
});
|
||||||
|
|
||||||
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
protected hassSubscribe(): (UnsubscribeFunc | Promise<UnsubscribeFunc>)[] {
|
||||||
@@ -693,9 +704,6 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
|||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@clear-filter=${this._clearFilter}
|
@clear-filter=${this._clearFilter}
|
||||||
@search-changed=${this._handleSearchChange}
|
@search-changed=${this._handleSearchChange}
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@@ -1035,11 +1043,6 @@ ${rejected
|
|||||||
this._activeCollapsed = ev.detail.value;
|
this._activeCollapsed = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
|
@@ -186,20 +186,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "entities-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "entities-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@query("hass-tabs-subpage-data-table", true)
|
@query("hass-tabs-subpage-data-table", true)
|
||||||
private _dataTable!: HaTabsSubpageDataTable;
|
private _dataTable!: HaTabsSubpageDataTable;
|
||||||
|
|
||||||
@@ -265,13 +251,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
private _columns = memoize(
|
private _columns = memoize(
|
||||||
(localize: LocalizeFunc): DataTableColumnContainer<EntityRow> => ({
|
(
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
narrow,
|
||||||
|
_language
|
||||||
|
): DataTableColumnContainer<EntityRow> => ({
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize("ui.panel.config.entities.picker.headers.state_icon"),
|
label: localize("ui.panel.config.entities.picker.headers.state_icon"),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
template: (entry) =>
|
template: (entry) =>
|
||||||
entry.icon
|
entry.icon
|
||||||
? html`<ha-icon .icon=${entry.icon}></ha-icon>`
|
? html`<ha-icon .icon=${entry.icon}></ha-icon>`
|
||||||
@@ -295,23 +283,32 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
extraTemplate: (entry) =>
|
template: (entry) => html`
|
||||||
entry.label_entries.length
|
<div style="font-size: 14px;">${entry.name}</div>
|
||||||
|
${narrow
|
||||||
|
? html`<div class="secondary">
|
||||||
|
${entry.entity_id} | ${entry.localized_platform}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
${entry.label_entries.length
|
||||||
? html`
|
? html`
|
||||||
<ha-data-table-labels
|
<ha-data-table-labels
|
||||||
.labels=${entry.label_entries}
|
.labels=${entry.label_entries}
|
||||||
></ha-data-table-labels>
|
></ha-data-table-labels>
|
||||||
`
|
`
|
||||||
: nothing,
|
: nothing}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
entity_id: {
|
entity_id: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.entity_id"),
|
title: localize("ui.panel.config.entities.picker.headers.entity_id"),
|
||||||
|
hidden: narrow,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "25%",
|
width: "25%",
|
||||||
},
|
},
|
||||||
localized_platform: {
|
localized_platform: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.integration"),
|
title: localize("ui.panel.config.entities.picker.headers.integration"),
|
||||||
|
hidden: narrow,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
@@ -327,6 +324,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
area: {
|
area: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.area"),
|
title: localize("ui.panel.config.entities.picker.headers.area"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
width: "15%",
|
width: "15%",
|
||||||
@@ -345,7 +343,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
status: {
|
status: {
|
||||||
title: localize("ui.panel.config.entities.picker.headers.status"),
|
title: localize("ui.panel.config.entities.picker.headers.status"),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
showNarrow: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "68px",
|
width: "68px",
|
||||||
@@ -691,7 +688,11 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.devices}
|
.tabs=${configSections.devices}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(
|
||||||
|
this.hass.localize,
|
||||||
|
this.narrow,
|
||||||
|
this.hass.language
|
||||||
|
)}
|
||||||
.data=${filteredEntities}
|
.data=${filteredEntities}
|
||||||
.searchLabel=${this.hass.localize(
|
.searchLabel=${this.hass.localize(
|
||||||
"ui.panel.config.entities.picker.search",
|
"ui.panel.config.entities.picker.search",
|
||||||
@@ -713,9 +714,6 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
@@ -1337,11 +1335,6 @@ ${rejected
|
|||||||
this._activeCollapsed = ev.detail.value;
|
this._activeCollapsed = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -167,20 +167,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _filter = "";
|
private _filter = "";
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "helpers-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "helpers-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@state() private _stateItems: HassEntity[] = [];
|
@state() private _stateItems: HassEntity[] = [];
|
||||||
|
|
||||||
@state() private _entityEntries?: Record<string, EntityRegistryEntry>;
|
@state() private _entityEntries?: Record<string, EntityRegistryEntry>;
|
||||||
@@ -257,13 +243,14 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _columns = memoizeOne(
|
private _columns = memoizeOne(
|
||||||
(localize: LocalizeFunc): DataTableColumnContainer<HelperItem> => ({
|
(
|
||||||
|
narrow: boolean,
|
||||||
|
localize: LocalizeFunc
|
||||||
|
): DataTableColumnContainer<HelperItem> => ({
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize("ui.panel.config.helpers.picker.headers.icon"),
|
label: localize("ui.panel.config.helpers.picker.headers.icon"),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
template: (helper) =>
|
template: (helper) =>
|
||||||
helper.entity
|
helper.entity
|
||||||
? html`<ha-state-icon
|
? html`<ha-state-icon
|
||||||
@@ -282,17 +269,23 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
grows: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
extraTemplate: (helper) =>
|
template: (helper) => html`
|
||||||
helper.label_entries.length
|
<div style="font-size: 14px;">${helper.name}</div>
|
||||||
|
${narrow
|
||||||
|
? html`<div class="secondary">${helper.entity_id}</div> `
|
||||||
|
: nothing}
|
||||||
|
${helper.label_entries.length
|
||||||
? html`
|
? html`
|
||||||
<ha-data-table-labels
|
<ha-data-table-labels
|
||||||
.labels=${helper.label_entries}
|
.labels=${helper.label_entries}
|
||||||
></ha-data-table-labels>
|
></ha-data-table-labels>
|
||||||
`
|
`
|
||||||
: nothing,
|
: nothing}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
entity_id: {
|
entity_id: {
|
||||||
title: localize("ui.panel.config.helpers.picker.headers.entity_id"),
|
title: localize("ui.panel.config.helpers.picker.headers.entity_id"),
|
||||||
|
hidden: this.narrow,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "25%",
|
width: "25%",
|
||||||
@@ -320,9 +313,10 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
},
|
},
|
||||||
editable: {
|
editable: {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize("ui.panel.config.helpers.picker.headers.editable"),
|
label: this.hass.localize(
|
||||||
|
"ui.panel.config.helpers.picker.headers.editable"
|
||||||
|
),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
showNarrow: true,
|
|
||||||
template: (helper) => html`
|
template: (helper) => html`
|
||||||
${!helper.editable
|
${!helper.editable
|
||||||
? html`
|
? html`
|
||||||
@@ -343,12 +337,8 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
title: "",
|
title: "",
|
||||||
label: "Actions",
|
|
||||||
width: "64px",
|
width: "64px",
|
||||||
type: "overflow-menu",
|
type: "overflow-menu",
|
||||||
hideable: false,
|
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
template: (helper) => html`
|
template: (helper) => html`
|
||||||
<ha-icon-overflow-menu
|
<ha-icon-overflow-menu
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -566,14 +556,11 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
|||||||
Array.isArray(val) ? val.length : val
|
Array.isArray(val) ? val.length : val
|
||||||
)
|
)
|
||||||
).length}
|
).length}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
.data=${helpers}
|
.data=${helpers}
|
||||||
.initialGroupColumn=${this._activeGrouping || "category"}
|
.initialGroupColumn=${this._activeGrouping || "category"}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
@@ -1097,11 +1084,6 @@ ${rejected
|
|||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -55,8 +55,6 @@ import {
|
|||||||
showYamlIntegrationDialog,
|
showYamlIntegrationDialog,
|
||||||
} from "./show-add-integration-dialog";
|
} from "./show-add-integration-dialog";
|
||||||
import { getConfigEntries } from "../../../data/config_entries";
|
import { getConfigEntries } from "../../../data/config_entries";
|
||||||
import { stripDiacritics } from "../../../common/string/strip-diacritics";
|
|
||||||
import { getStripDiacriticsFn } from "../../../util/fuse";
|
|
||||||
|
|
||||||
export interface IntegrationListItem {
|
export interface IntegrationListItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -257,7 +255,6 @@ class AddIntegrationDialog extends LitElement {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
|
||||||
};
|
};
|
||||||
const helpers = Object.entries(h).map(([domain, integration]) => ({
|
const helpers = Object.entries(h).map(([domain, integration]) => ({
|
||||||
domain,
|
domain,
|
||||||
@@ -267,16 +264,15 @@ class AddIntegrationDialog extends LitElement {
|
|||||||
is_built_in: integration.is_built_in !== false,
|
is_built_in: integration.is_built_in !== false,
|
||||||
cloud: integration.iot_class?.startsWith("cloud_"),
|
cloud: integration.iot_class?.startsWith("cloud_"),
|
||||||
}));
|
}));
|
||||||
const normalizedFilter = stripDiacritics(filter);
|
|
||||||
return [
|
return [
|
||||||
...new Fuse(integrations, options)
|
...new Fuse(integrations, options)
|
||||||
.search(normalizedFilter)
|
.search(filter)
|
||||||
.map((result) => result.item),
|
.map((result) => result.item),
|
||||||
...new Fuse(yamlIntegrations, options)
|
...new Fuse(yamlIntegrations, options)
|
||||||
.search(normalizedFilter)
|
.search(filter)
|
||||||
.map((result) => result.item),
|
.map((result) => result.item),
|
||||||
...new Fuse(helpers, options)
|
...new Fuse(helpers, options)
|
||||||
.search(normalizedFilter)
|
.search(filter)
|
||||||
.map((result) => result.item),
|
.map((result) => result.item),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,25 @@
|
|||||||
import { ActionDetail } from "@material/mwc-list";
|
import { ActionDetail } from "@material/mwc-list";
|
||||||
import { mdiFilterVariant, mdiPlus } from "@mdi/js";
|
import { mdiFilterVariant, mdiPlus } from "@mdi/js";
|
||||||
import type { IFuseOptions } from "fuse.js";
|
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
import type { IFuseOptions } from "fuse.js";
|
||||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
css,
|
css,
|
||||||
|
CSSResultGroup,
|
||||||
html,
|
html,
|
||||||
|
LitElement,
|
||||||
nothing,
|
nothing,
|
||||||
|
PropertyValues,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||||
import {
|
import {
|
||||||
PROTOCOL_INTEGRATIONS,
|
|
||||||
protocolIntegrationPicked,
|
protocolIntegrationPicked,
|
||||||
|
PROTOCOL_INTEGRATIONS,
|
||||||
} from "../../../common/integrations/protocolIntegrationPicked";
|
} from "../../../common/integrations/protocolIntegrationPicked";
|
||||||
import { navigate } from "../../../common/navigate";
|
import { navigate } from "../../../common/navigate";
|
||||||
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
|
||||||
import { stripDiacritics } from "../../../common/string/strip-diacritics";
|
|
||||||
import { extractSearchParam } from "../../../common/url/search-params";
|
import { extractSearchParam } from "../../../common/url/search-params";
|
||||||
import { nextRender } from "../../../common/util/render-status";
|
import { nextRender } from "../../../common/util/render-status";
|
||||||
import "../../../components/ha-button-menu";
|
import "../../../components/ha-button-menu";
|
||||||
@@ -31,7 +29,6 @@ import "../../../components/ha-fab";
|
|||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-icon-button";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "../../../components/search-input";
|
import "../../../components/search-input";
|
||||||
import "../../../components/search-input-outlined";
|
|
||||||
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
import { ConfigEntry, getConfigEntries } from "../../../data/config_entries";
|
||||||
import { getConfigFlowInProgressCollection } from "../../../data/config_flow";
|
import { getConfigFlowInProgressCollection } from "../../../data/config_flow";
|
||||||
import { fetchDiagnosticHandlers } from "../../../data/diagnostics";
|
import { fetchDiagnosticHandlers } from "../../../data/diagnostics";
|
||||||
@@ -40,11 +37,11 @@ import {
|
|||||||
subscribeEntityRegistry,
|
subscribeEntityRegistry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
IntegrationLogInfo,
|
|
||||||
IntegrationManifest,
|
|
||||||
domainToName,
|
domainToName,
|
||||||
fetchIntegrationManifest,
|
fetchIntegrationManifest,
|
||||||
fetchIntegrationManifests,
|
fetchIntegrationManifests,
|
||||||
|
IntegrationLogInfo,
|
||||||
|
IntegrationManifest,
|
||||||
subscribeLogInfo,
|
subscribeLogInfo,
|
||||||
} from "../../../data/integration";
|
} from "../../../data/integration";
|
||||||
import {
|
import {
|
||||||
@@ -62,17 +59,18 @@ import "../../../layouts/hass-tabs-subpage";
|
|||||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import { getStripDiacriticsFn } from "../../../util/fuse";
|
|
||||||
import { configSections } from "../ha-panel-config";
|
import { configSections } from "../ha-panel-config";
|
||||||
import { isHelperDomain } from "../helpers/const";
|
import { isHelperDomain } from "../helpers/const";
|
||||||
import "./ha-config-flow-card";
|
import "./ha-config-flow-card";
|
||||||
import { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
import { DataEntryFlowProgressExtended } from "./ha-config-integrations";
|
||||||
import "./ha-disabled-config-entry-card";
|
|
||||||
import "./ha-ignored-config-entry-card";
|
import "./ha-ignored-config-entry-card";
|
||||||
import "./ha-integration-card";
|
import "./ha-integration-card";
|
||||||
import type { HaIntegrationCard } from "./ha-integration-card";
|
import type { HaIntegrationCard } from "./ha-integration-card";
|
||||||
import "./ha-integration-overflow-menu";
|
import "./ha-integration-overflow-menu";
|
||||||
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
import { showAddIntegrationDialog } from "./show-add-integration-dialog";
|
||||||
|
import "./ha-disabled-config-entry-card";
|
||||||
|
import { caseInsensitiveStringCompare } from "../../../common/string/compare";
|
||||||
|
import "../../../components/search-input-outlined";
|
||||||
|
|
||||||
export interface ConfigEntryExtended extends ConfigEntry {
|
export interface ConfigEntryExtended extends ConfigEntry {
|
||||||
localized_domain_name?: string;
|
localized_domain_name?: string;
|
||||||
@@ -210,12 +208,9 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(configEntriesInProgress, options);
|
const fuse = new Fuse(configEntriesInProgress, options);
|
||||||
filteredEntries = fuse
|
filteredEntries = fuse.search(filter).map((result) => result.item);
|
||||||
.search(stripDiacritics(filter))
|
|
||||||
.map((result) => result.item);
|
|
||||||
} else {
|
} else {
|
||||||
filteredEntries = configEntriesInProgress;
|
filteredEntries = configEntriesInProgress;
|
||||||
}
|
}
|
||||||
|
@@ -205,13 +205,14 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
Search device
|
Search device
|
||||||
</mwc-button>`
|
</mwc-button>`
|
||||||
: this._status === "qr_scan"
|
: this._status === "qr_scan"
|
||||||
? html` <ha-qr-scanner
|
? html`${this._error
|
||||||
.hass=${this.hass}
|
? html`<ha-alert alert-type="error"
|
||||||
|
>${this._error}</ha-alert
|
||||||
|
>`
|
||||||
|
: ""}
|
||||||
|
<ha-qr-scanner
|
||||||
.localize=${this.hass.localize}
|
.localize=${this.hass.localize}
|
||||||
.error=${this._error}
|
|
||||||
@qr-code-scanned=${this._qrCodeScanned}
|
@qr-code-scanned=${this._qrCodeScanned}
|
||||||
@qr-code-error=${this._qrCodeError}
|
|
||||||
@qr-code-closed=${this._startOver}
|
|
||||||
></ha-qr-scanner>
|
></ha-qr-scanner>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
slot="secondaryAction"
|
slot="secondaryAction"
|
||||||
@@ -360,7 +361,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
${this._supportsSmartStart
|
${this._supportsSmartStart
|
||||||
? html`<div class="outline">
|
? html` <div class="outline">
|
||||||
<h2>
|
<h2>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.zwave_js.add_node.qr_code"
|
"ui.panel.config.zwave_js.add_node.qr_code"
|
||||||
@@ -497,7 +498,9 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
<a
|
<a
|
||||||
href=${`/config/devices/device/${this._device?.id}`}
|
href=${`/config/devices/device/${
|
||||||
|
this._device?.id
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<mwc-button>
|
<mwc-button>
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
@@ -596,10 +599,6 @@ class DialogZWaveJSAddNode extends LitElement {
|
|||||||
this._handleQrCodeScanned(ev.detail.value);
|
this._handleQrCodeScanned(ev.detail.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _qrCodeError(ev: CustomEvent): void {
|
|
||||||
this._error = ev.detail.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleQrCodeScanned(qrCodeString: string): Promise<void> {
|
private async _handleQrCodeScanned(qrCodeString: string): Promise<void> {
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
if (this._status !== "qr_scan" || this._qrProcessing) {
|
if (this._status !== "qr_scan" || this._qrProcessing) {
|
||||||
|
@@ -66,26 +66,10 @@ export class HaConfigLabels extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeSorting?: SortingChangedEvent;
|
private _activeSorting?: SortingChangedEvent;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "labels-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "labels-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
private _columns = memoizeOne((localize: LocalizeFunc) => {
|
private _columns = memoizeOne((localize: LocalizeFunc) => {
|
||||||
const columns: DataTableColumnContainer<LabelRegistryEntry> = {
|
const columns: DataTableColumnContainer<LabelRegistryEntry> = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
label: localize("ui.panel.config.labels.headers.icon"),
|
label: localize("ui.panel.config.labels.headers.icon"),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (label) =>
|
template: (label) =>
|
||||||
@@ -93,7 +77,6 @@ export class HaConfigLabels extends LitElement {
|
|||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
title: "",
|
title: "",
|
||||||
showNarrow: true,
|
|
||||||
label: localize("ui.panel.config.labels.headers.color"),
|
label: localize("ui.panel.config.labels.headers.color"),
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (label) =>
|
template: (label) =>
|
||||||
@@ -122,9 +105,6 @@ export class HaConfigLabels extends LitElement {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
title: "",
|
title: "",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
hideable: false,
|
|
||||||
width: "64px",
|
width: "64px",
|
||||||
type: "overflow-menu",
|
type: "overflow-menu",
|
||||||
template: (label) => html`
|
template: (label) => html`
|
||||||
@@ -187,9 +167,6 @@ export class HaConfigLabels extends LitElement {
|
|||||||
.noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")}
|
.noDataText=${this.hass.localize("ui.panel.config.labels.no_labels")}
|
||||||
hasFab
|
hasFab
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@search-changed=${this._handleSearchChange}
|
@search-changed=${this._handleSearchChange}
|
||||||
@@ -320,11 +297,6 @@ export class HaConfigLabels extends LitElement {
|
|||||||
private _handleSearchChange(ev: CustomEvent) {
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -2,17 +2,21 @@ import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
|||||||
import {
|
import {
|
||||||
mdiCheck,
|
mdiCheck,
|
||||||
mdiCheckCircleOutline,
|
mdiCheckCircleOutline,
|
||||||
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiOpenInNew,
|
mdiPencil,
|
||||||
mdiPlus,
|
mdiPlus,
|
||||||
|
mdiStar,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import { LitElement, PropertyValues, html, nothing } from "lit";
|
import { LitElement, PropertyValues, html, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, query, state } from "lit/decorators";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import memoize from "memoize-one";
|
import memoize from "memoize-one";
|
||||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||||
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { navigate } from "../../../../common/navigate";
|
import { navigate } from "../../../../common/navigate";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
|
import { LocalizeFunc } from "../../../../common/translations/localize";
|
||||||
import {
|
import {
|
||||||
DataTableColumnContainer,
|
DataTableColumnContainer,
|
||||||
RowClickedEvent,
|
RowClickedEvent,
|
||||||
@@ -22,6 +26,9 @@ import "../../../../components/ha-clickable-list-item";
|
|||||||
import "../../../../components/ha-fab";
|
import "../../../../components/ha-fab";
|
||||||
import "../../../../components/ha-icon";
|
import "../../../../components/ha-icon";
|
||||||
import "../../../../components/ha-icon-button";
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "../../../../components/ha-menu";
|
||||||
|
import type { HaMenu } from "../../../../components/ha-menu";
|
||||||
|
import "../../../../components/ha-menu-item";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { LovelacePanelConfig } from "../../../../data/lovelace";
|
import { LovelacePanelConfig } from "../../../../data/lovelace";
|
||||||
import {
|
import {
|
||||||
@@ -41,13 +48,11 @@ import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-
|
|||||||
import "../../../../layouts/hass-loading-screen";
|
import "../../../../layouts/hass-loading-screen";
|
||||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import { HomeAssistant, Route } from "../../../../types";
|
import { HomeAssistant, Route } from "../../../../types";
|
||||||
import { LocalizeFunc } from "../../../../common/translations/localize";
|
|
||||||
import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy";
|
import { getLovelaceStrategy } from "../../../lovelace/strategies/get-strategy";
|
||||||
import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard";
|
import { showNewDashboardDialog } from "../../dashboard/show-dialog-new-dashboard";
|
||||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||||
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
import { showDashboardConfigureStrategyDialog } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||||
import { storage } from "../../../../common/decorators/storage";
|
|
||||||
|
|
||||||
type DataTableItem = Pick<
|
type DataTableItem = Pick<
|
||||||
LovelaceDashboard,
|
LovelaceDashboard,
|
||||||
@@ -85,19 +90,9 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeSorting?: SortingChangedEvent;
|
private _activeSorting?: SortingChangedEvent;
|
||||||
|
|
||||||
@storage({
|
@state() private _overflowDashboard?: LovelaceDashboard;
|
||||||
key: "lovelace-dashboards-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
@query("#overflow-menu") private _overflowMenu!: HaMenu;
|
||||||
key: "lovelace-dashboards-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
public willUpdate() {
|
public willUpdate() {
|
||||||
if (!this.hasUpdated) {
|
if (!this.hasUpdated) {
|
||||||
@@ -115,8 +110,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
const columns: DataTableColumnContainer<DataTableItem> = {
|
const columns: DataTableColumnContainer<DataTableItem> = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
label: localize(
|
label: localize(
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.icon"
|
"ui.panel.config.lovelace.dashboards.picker.headers.icon"
|
||||||
),
|
),
|
||||||
@@ -144,111 +137,118 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
grows: true,
|
grows: true,
|
||||||
template: narrow
|
template: (dashboard) => {
|
||||||
? undefined
|
const titleTemplate = html`
|
||||||
: (dashboard) => html`
|
${dashboard.title}
|
||||||
${dashboard.title}
|
${dashboard.default
|
||||||
${dashboard.default
|
? html`
|
||||||
? html`
|
<ha-svg-icon
|
||||||
<ha-svg-icon
|
style="padding-left: 10px; padding-inline-start: 10px; direction: var(--direction);"
|
||||||
style="padding-left: 10px; padding-inline-start: 10px; direction: var(--direction);"
|
.path=${mdiCheckCircleOutline}
|
||||||
.path=${mdiCheckCircleOutline}
|
></ha-svg-icon>
|
||||||
></ha-svg-icon>
|
<simple-tooltip animation-delay="0">
|
||||||
<simple-tooltip animation-delay="0">
|
${this.hass.localize(
|
||||||
${this.hass.localize(
|
`ui.panel.config.lovelace.dashboards.default_dashboard`
|
||||||
`ui.panel.config.lovelace.dashboards.default_dashboard`
|
)}
|
||||||
)}
|
</simple-tooltip>
|
||||||
</simple-tooltip>
|
`
|
||||||
`
|
: ""}
|
||||||
: ""}
|
`;
|
||||||
`,
|
return narrow
|
||||||
|
? html`
|
||||||
|
${titleTemplate}
|
||||||
|
<div class="secondary">
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
|
||||||
|
)}${dashboard.filename
|
||||||
|
? html` – ${dashboard.filename} `
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: titleTemplate;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
columns.mode = {
|
if (!narrow) {
|
||||||
title: localize(
|
columns.mode = {
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
width: "20%",
|
|
||||||
template: (dashboard) => html`
|
|
||||||
${this.hass.localize(
|
|
||||||
`ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
|
|
||||||
) || dashboard.mode}
|
|
||||||
`,
|
|
||||||
};
|
|
||||||
if (dashboards.some((dashboard) => dashboard.filename)) {
|
|
||||||
columns.filename = {
|
|
||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.filename"
|
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
|
||||||
),
|
),
|
||||||
width: "15%",
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
width: "20%",
|
||||||
|
template: (dashboard) => html`
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.lovelace.dashboards.conf_mode.${dashboard.mode}`
|
||||||
|
) || dashboard.mode}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
if (dashboards.some((dashboard) => dashboard.filename)) {
|
||||||
|
columns.filename = {
|
||||||
|
title: localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.filename"
|
||||||
|
),
|
||||||
|
width: "15%",
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
columns.require_admin = {
|
||||||
|
title: localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.require_admin"
|
||||||
|
),
|
||||||
|
sortable: true,
|
||||||
|
type: "icon",
|
||||||
|
width: "100px",
|
||||||
|
template: (dashboard) =>
|
||||||
|
dashboard.require_admin
|
||||||
|
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||||
|
: html`—`,
|
||||||
|
};
|
||||||
|
columns.show_in_sidebar = {
|
||||||
|
title: localize(
|
||||||
|
"ui.panel.config.lovelace.dashboards.picker.headers.sidebar"
|
||||||
|
),
|
||||||
|
type: "icon",
|
||||||
|
width: "121px",
|
||||||
|
template: (dashboard) =>
|
||||||
|
dashboard.show_in_sidebar
|
||||||
|
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
||||||
|
: html`—`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
columns.require_admin = {
|
|
||||||
title: localize(
|
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.require_admin"
|
|
||||||
),
|
|
||||||
sortable: true,
|
|
||||||
type: "icon",
|
|
||||||
hidden: narrow,
|
|
||||||
width: "100px",
|
|
||||||
template: (dashboard) =>
|
|
||||||
dashboard.require_admin
|
|
||||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
|
||||||
: html`—`,
|
|
||||||
};
|
|
||||||
columns.show_in_sidebar = {
|
|
||||||
title: localize(
|
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.sidebar"
|
|
||||||
),
|
|
||||||
type: "icon",
|
|
||||||
hidden: narrow,
|
|
||||||
width: "121px",
|
|
||||||
template: (dashboard) =>
|
|
||||||
dashboard.show_in_sidebar
|
|
||||||
? html`<ha-svg-icon .path=${mdiCheck}></ha-svg-icon>`
|
|
||||||
: html`—`,
|
|
||||||
};
|
|
||||||
|
|
||||||
columns.url_path = {
|
columns.actions = {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize(
|
width: "64px",
|
||||||
"ui.panel.config.lovelace.dashboards.picker.headers.url"
|
type: "icon-button",
|
||||||
),
|
template: (dashboard) => html`
|
||||||
filterable: true,
|
<ha-icon-button
|
||||||
showNarrow: true,
|
.dashboard=${dashboard}
|
||||||
width: "100px",
|
.label=${this.hass.localize("ui.common.overflow_menu")}
|
||||||
template: (dashboard) =>
|
.path=${mdiDotsVertical}
|
||||||
narrow
|
@click=${this._showOverflowMenu}
|
||||||
? html`
|
></ha-icon-button>
|
||||||
<ha-icon-button
|
`,
|
||||||
.path=${mdiOpenInNew}
|
|
||||||
.urlPath=${dashboard.url_path}
|
|
||||||
@click=${this._navigate}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.lovelace.dashboards.picker.open"
|
|
||||||
)}
|
|
||||||
></ha-icon-button>
|
|
||||||
`
|
|
||||||
: html`
|
|
||||||
<mwc-button
|
|
||||||
.urlPath=${dashboard.url_path}
|
|
||||||
@click=${this._navigate}
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.lovelace.dashboards.picker.open"
|
|
||||||
)}</mwc-button
|
|
||||||
>
|
|
||||||
`,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private _showOverflowMenu = (ev) => {
|
||||||
|
if (
|
||||||
|
this._overflowMenu.open &&
|
||||||
|
ev.target === this._overflowMenu.anchorElement
|
||||||
|
) {
|
||||||
|
this._overflowMenu.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._overflowDashboard = ev.target.dashboard;
|
||||||
|
this._overflowMenu.anchorElement = ev.target;
|
||||||
|
this._overflowMenu.show();
|
||||||
|
};
|
||||||
|
|
||||||
private _getItems = memoize((dashboards: LovelaceDashboard[]) => {
|
private _getItems = memoize((dashboards: LovelaceDashboard[]) => {
|
||||||
const defaultMode = (
|
const defaultMode = (
|
||||||
this.hass.panels?.lovelace?.config as LovelacePanelConfig
|
this.hass.panels?.lovelace?.config as LovelacePanelConfig
|
||||||
@@ -316,13 +316,10 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
)}
|
)}
|
||||||
.data=${this._getItems(this._dashboards)}
|
.data=${this._getItems(this._dashboards)}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@search-changed=${this._handleSearchChange}
|
@search-changed=${this._handleSearchChange}
|
||||||
@row-click=${this._editDashboard}
|
@row-click=${this._navigate}
|
||||||
id="url_path"
|
id="url_path"
|
||||||
hasFab
|
hasFab
|
||||||
clickable
|
clickable
|
||||||
@@ -354,6 +351,22 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
</ha-fab>
|
</ha-fab>
|
||||||
</hass-tabs-subpage-data-table>
|
</hass-tabs-subpage-data-table>
|
||||||
|
<ha-menu id="overflow-menu" positioning="fixed">
|
||||||
|
<ha-menu-item @click=${this._editDashboard}>
|
||||||
|
<ha-svg-icon .path=${mdiPencil} slot="start"></ha-svg-icon>
|
||||||
|
<div slot="headline">Edit</div>
|
||||||
|
</ha-menu-item>
|
||||||
|
|
||||||
|
<ha-menu-item>
|
||||||
|
<ha-svg-icon .path=${mdiStar} slot="start"></ha-svg-icon>
|
||||||
|
<div slot="headline">Set to default</div>
|
||||||
|
</ha-menu-item>
|
||||||
|
<md-divider role="separator" tabindex="-1"></md-divider>
|
||||||
|
<ha-menu-item class="warning">
|
||||||
|
<ha-svg-icon .path=${mdiDelete} slot="start"></ha-svg-icon>
|
||||||
|
<div slot="headline">Delete</div>
|
||||||
|
</ha-menu-item>
|
||||||
|
</ha-menu>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,21 +379,23 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
this._dashboards = await fetchDashboards(this.hass);
|
this._dashboards = await fetchDashboards(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _navigate(ev: Event) {
|
private _navigate(ev: CustomEvent) {
|
||||||
ev.stopPropagation();
|
const urlPath = (ev.detail as RowClickedEvent).id;
|
||||||
navigate(`/${(ev.target as any).urlPath}`);
|
navigate(`/${urlPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _editDashboard(ev: CustomEvent) {
|
private _editDashboard = (ev) => {
|
||||||
const urlPath = (ev.detail as RowClickedEvent).id;
|
ev.stopPropagation();
|
||||||
|
const dashboard = ev.currentTarget.parentElement.anchorElement.automation;
|
||||||
|
|
||||||
|
const urlPath = (ev.currentTarget as any).urlPath;
|
||||||
|
|
||||||
if (urlPath === "energy") {
|
if (urlPath === "energy") {
|
||||||
navigate("/config/energy");
|
navigate("/config/energy");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const dashboard = this._dashboards.find((res) => res.url_path === urlPath);
|
|
||||||
this._openDetailDialog(dashboard, urlPath);
|
this._openDetailDialog(dashboard, urlPath);
|
||||||
}
|
};
|
||||||
|
|
||||||
private async _addDashboard() {
|
private async _addDashboard() {
|
||||||
showNewDashboardDialog(this, {
|
showNewDashboardDialog(this, {
|
||||||
@@ -475,11 +490,6 @@ export class HaConfigLovelaceDashboards extends LitElement {
|
|||||||
private _handleSearchChange(ev: CustomEvent) {
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -67,27 +67,12 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeSorting?: SortingChangedEvent;
|
private _activeSorting?: SortingChangedEvent;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "lovelace-resources-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "lovelace-resources-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
private _columns = memoize(
|
private _columns = memoize(
|
||||||
(
|
(
|
||||||
_language,
|
_language,
|
||||||
localize: LocalizeFunc
|
localize: LocalizeFunc
|
||||||
): DataTableColumnContainer<LovelaceResource> => ({
|
): DataTableColumnContainer<LovelaceResource> => ({
|
||||||
url: {
|
url: {
|
||||||
main: true,
|
|
||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.lovelace.resources.picker.headers.url"
|
"ui.panel.config.lovelace.resources.picker.headers.url"
|
||||||
),
|
),
|
||||||
@@ -160,9 +145,6 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
"ui.panel.config.lovelace.resources.picker.no_resources"
|
"ui.panel.config.lovelace.resources.picker.no_resources"
|
||||||
)}
|
)}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
@search-changed=${this._handleSearchChange}
|
@search-changed=${this._handleSearchChange}
|
||||||
@@ -284,11 +266,6 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -1,15 +1,12 @@
|
|||||||
import { mdiPencil } from "@mdi/js";
|
import "@material/mwc-button";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators";
|
import { property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import "../../../components/entity/ha-entities-picker";
|
import "../../../components/entity/ha-entities-picker";
|
||||||
import "../../../components/ha-button";
|
|
||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
import "../../../components/ha-formfield";
|
import "../../../components/ha-formfield";
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "../../../components/ha-picture-upload";
|
import "../../../components/ha-picture-upload";
|
||||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||||
import "../../../components/ha-settings-row";
|
|
||||||
import "../../../components/ha-textfield";
|
import "../../../components/ha-textfield";
|
||||||
import { adminChangeUsername } from "../../../data/auth";
|
import { adminChangeUsername } from "../../../data/auth";
|
||||||
import { PersonMutableParams } from "../../../data/person";
|
import { PersonMutableParams } from "../../../data/person";
|
||||||
@@ -140,17 +137,11 @@ class DialogPersonDetail extends LitElement {
|
|||||||
@change=${this._pictureChanged}
|
@change=${this._pictureChanged}
|
||||||
></ha-picture-upload>
|
></ha-picture-upload>
|
||||||
|
|
||||||
<ha-settings-row>
|
<ha-formfield
|
||||||
<span slot="heading">
|
.label=${`${this.hass!.localize(
|
||||||
${this.hass!.localize(
|
"ui.panel.config.person.detail.allow_login"
|
||||||
"ui.panel.config.person.detail.allow_login"
|
)}${this._user ? ` (${this._user.username})` : ""}`}
|
||||||
)}
|
>
|
||||||
</span>
|
|
||||||
<span slot="description">
|
|
||||||
${this.hass!.localize(
|
|
||||||
"ui.panel.config.person.detail.allow_login_description"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<ha-switch
|
<ha-switch
|
||||||
@change=${this._allowLoginChanged}
|
@change=${this._allowLoginChanged}
|
||||||
.disabled=${this._user &&
|
.disabled=${this._user &&
|
||||||
@@ -159,9 +150,34 @@ class DialogPersonDetail extends LitElement {
|
|||||||
this._user.is_owner)}
|
this._user.is_owner)}
|
||||||
.checked=${this._userId}
|
.checked=${this._userId}
|
||||||
></ha-switch>
|
></ha-switch>
|
||||||
</ha-settings-row>
|
</ha-formfield>
|
||||||
|
|
||||||
${this._renderUserFields()}
|
${this._user
|
||||||
|
? html`<ha-formfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.person.detail.local_only"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-switch
|
||||||
|
.checked=${this._localOnly}
|
||||||
|
@change=${this._localOnlyChanged}
|
||||||
|
>
|
||||||
|
</ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.person.detail.admin"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-switch
|
||||||
|
.disabled=${this._user.system_generated ||
|
||||||
|
this._user.is_owner}
|
||||||
|
.checked=${this._isAdmin}
|
||||||
|
@change=${this._adminChanged}
|
||||||
|
>
|
||||||
|
</ha-switch>
|
||||||
|
</ha-formfield>`
|
||||||
|
: ""}
|
||||||
${this._deviceTrackersAvailable(this.hass)
|
${this._deviceTrackersAvailable(this.hass)
|
||||||
? html`
|
? html`
|
||||||
<p>
|
<p>
|
||||||
@@ -219,7 +235,7 @@ class DialogPersonDetail extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${this._params.entry
|
${this._params.entry
|
||||||
? html`
|
? html`
|
||||||
<ha-button
|
<mwc-button
|
||||||
slot="secondaryAction"
|
slot="secondaryAction"
|
||||||
class="warning"
|
class="warning"
|
||||||
@click=${this._deleteEntry}
|
@click=${this._deleteEntry}
|
||||||
@@ -227,10 +243,28 @@ class DialogPersonDetail extends LitElement {
|
|||||||
this._submitting}
|
this._submitting}
|
||||||
>
|
>
|
||||||
${this.hass!.localize("ui.panel.config.person.detail.delete")}
|
${this.hass!.localize("ui.panel.config.person.detail.delete")}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
|
${this._user && this.hass.user?.is_owner
|
||||||
|
? html`<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
@click=${this._changeUsername}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.change_username"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
@click=${this._changePassword}
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.change_password"
|
||||||
|
)}
|
||||||
|
</mwc-button>`
|
||||||
|
: ""}
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
<ha-button
|
<mwc-button
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
@click=${this._updateEntry}
|
@click=${this._updateEntry}
|
||||||
.disabled=${nameInvalid || this._submitting}
|
.disabled=${nameInvalid || this._submitting}
|
||||||
@@ -238,96 +272,11 @@ class DialogPersonDetail extends LitElement {
|
|||||||
${this._params.entry
|
${this._params.entry
|
||||||
? this.hass!.localize("ui.panel.config.person.detail.update")
|
? this.hass!.localize("ui.panel.config.person.detail.update")
|
||||||
: this.hass!.localize("ui.panel.config.person.detail.create")}
|
: this.hass!.localize("ui.panel.config.person.detail.create")}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderUserFields() {
|
|
||||||
const user = this._user;
|
|
||||||
if (!user) return nothing;
|
|
||||||
return html`
|
|
||||||
${!user.system_generated
|
|
||||||
? html`
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize("ui.panel.config.person.detail.username")}
|
|
||||||
</span>
|
|
||||||
<span slot="description">${user.username}</span>
|
|
||||||
${this.hass.user?.is_owner
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.path=${mdiPencil}
|
|
||||||
@click=${this._changeUsername}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.person.detail.change_username"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-icon-button>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</ha-settings-row>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${!user.system_generated && this.hass.user?.is_owner
|
|
||||||
? html`
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize("ui.panel.config.person.detail.password")}
|
|
||||||
</span>
|
|
||||||
<span slot="description">************</span>
|
|
||||||
${this.hass.user?.is_owner
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.path=${mdiPencil}
|
|
||||||
@click=${this._changePassword}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.person.detail.change_password"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-icon-button>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</ha-settings-row>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.person.detail.local_access_only"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="description">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.person.detail.local_access_only_description"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
.disabled=${user.system_generated}
|
|
||||||
.checked=${this._localOnly}
|
|
||||||
@change=${this._localOnlyChanged}
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
</ha-settings-row>
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize("ui.panel.config.person.detail.admin")}
|
|
||||||
</span>
|
|
||||||
<span slot="description">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.person.detail.admin_description"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
.disabled=${user.system_generated || user.is_owner}
|
|
||||||
.checked=${this._isAdmin}
|
|
||||||
@change=${this._adminChanged}
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
</ha-settings-row>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _closeDialog() {
|
private _closeDialog() {
|
||||||
this._params = undefined;
|
this._params = undefined;
|
||||||
}
|
}
|
||||||
@@ -368,16 +317,14 @@ class DialogPersonDetail extends LitElement {
|
|||||||
} else if (this._userId) {
|
} else if (this._userId) {
|
||||||
if (
|
if (
|
||||||
!(await showConfirmationDialog(this, {
|
!(await showConfirmationDialog(this, {
|
||||||
title: this.hass!.localize(
|
|
||||||
"ui.panel.config.person.detail.confirm_delete_user_title"
|
|
||||||
),
|
|
||||||
text: this.hass!.localize(
|
text: this.hass!.localize(
|
||||||
"ui.panel.config.person.detail.confirm_delete_user_text",
|
"ui.panel.config.person.detail.confirm_delete_user",
|
||||||
{ name: this._name }
|
{ name: this._name }
|
||||||
),
|
),
|
||||||
confirmText: this.hass!.localize("ui.common.delete"),
|
confirmText: this.hass!.localize(
|
||||||
|
"ui.panel.config.person.detail.delete"
|
||||||
|
),
|
||||||
dismissText: this.hass!.localize("ui.common.cancel"),
|
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||||
destructive: true,
|
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
target.checked = true;
|
target.checked = true;
|
||||||
@@ -541,8 +488,9 @@ class DialogPersonDetail extends LitElement {
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
--file-upload-image-border-radius: 50%;
|
--file-upload-image-border-radius: 50%;
|
||||||
}
|
}
|
||||||
ha-settings-row {
|
ha-formfield {
|
||||||
padding: 0;
|
display: block;
|
||||||
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
|
@@ -180,20 +180,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "scene-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "scene-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
private _sizeController = new ResizeController(this, {
|
private _sizeController = new ResizeController(this, {
|
||||||
callback: (entries) => entries[0]?.contentRect.width,
|
callback: (entries) => entries[0]?.contentRect.width,
|
||||||
});
|
});
|
||||||
@@ -239,13 +225,11 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private _columns = memoizeOne(
|
private _columns = memoizeOne(
|
||||||
(localize: LocalizeFunc): DataTableColumnContainer => {
|
(narrow, localize: LocalizeFunc): DataTableColumnContainer => {
|
||||||
const columns: DataTableColumnContainer<SceneItem> = {
|
const columns: DataTableColumnContainer<SceneItem> = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
label: localize("ui.panel.config.scene.picker.headers.icon"),
|
label: localize("ui.panel.config.scene.picker.headers.state"),
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (scene) => html`
|
template: (scene) => html`
|
||||||
<ha-state-icon
|
<ha-state-icon
|
||||||
@@ -261,13 +245,15 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
extraTemplate: (scene) =>
|
template: (scene) => html`
|
||||||
scene.labels.length
|
<div style="font-size: 14px;">${scene.name}</div>
|
||||||
|
${scene.labels.length
|
||||||
? html`<ha-data-table-labels
|
? html`<ha-data-table-labels
|
||||||
@label-clicked=${this._labelClicked}
|
@label-clicked=${this._labelClicked}
|
||||||
.labels=${scene.labels}
|
.labels=${scene.labels}
|
||||||
></ha-data-table-labels>`
|
></ha-data-table-labels>`
|
||||||
: nothing,
|
: nothing}
|
||||||
|
`,
|
||||||
},
|
},
|
||||||
area: {
|
area: {
|
||||||
title: localize("ui.panel.config.scene.picker.headers.area"),
|
title: localize("ui.panel.config.scene.picker.headers.area"),
|
||||||
@@ -295,6 +281,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
),
|
),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: "30%",
|
width: "30%",
|
||||||
|
hidden: narrow,
|
||||||
template: (scene) => {
|
template: (scene) => {
|
||||||
const lastActivated = scene.state;
|
const lastActivated = scene.state;
|
||||||
if (!lastActivated || isUnavailableState(lastActivated)) {
|
if (!lastActivated || isUnavailableState(lastActivated)) {
|
||||||
@@ -313,7 +300,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
only_editable: {
|
only_editable: {
|
||||||
title: "",
|
title: "",
|
||||||
width: "56px",
|
width: "56px",
|
||||||
showNarrow: true,
|
|
||||||
template: (scene) =>
|
template: (scene) =>
|
||||||
!scene.attributes.id
|
!scene.attributes.id
|
||||||
? html`
|
? html`
|
||||||
@@ -333,9 +319,6 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
title: "",
|
title: "",
|
||||||
width: "64px",
|
width: "64px",
|
||||||
type: "overflow-menu",
|
type: "overflow-menu",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
hideable: false,
|
|
||||||
template: (scene) => html`
|
template: (scene) => html`
|
||||||
<ha-icon-overflow-menu
|
<ha-icon-overflow-menu
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -553,14 +536,11 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
|||||||
Array.isArray(val) ? val.length : val
|
Array.isArray(val) ? val.length : val
|
||||||
)
|
)
|
||||||
).length}
|
).length}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
id="entity_id"
|
id="entity_id"
|
||||||
.initialGroupColumn=${this._activeGrouping || "category"}
|
.initialGroupColumn=${this._activeGrouping || "category"}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
@@ -1175,11 +1155,6 @@ ${rejected
|
|||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import { html, nothing } from "lit";
|
import { html, nothing } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import "../../../components/ha-markdown";
|
import "../../../components/ha-alert";
|
||||||
import { fetchBlueprints } from "../../../data/blueprint";
|
|
||||||
import { BlueprintScriptConfig } from "../../../data/script";
|
import { BlueprintScriptConfig } from "../../../data/script";
|
||||||
|
import { fetchBlueprints } from "../../../data/blueprint";
|
||||||
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
import { HaBlueprintGenericEditor } from "../blueprint/blueprint-generic-editor";
|
||||||
|
import "../../../components/ha-markdown";
|
||||||
|
|
||||||
@customElement("blueprint-script-editor")
|
@customElement("blueprint-script-editor")
|
||||||
export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
|
export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
|
||||||
@@ -16,6 +17,14 @@ export class HaBlueprintScriptEditor extends HaBlueprintGenericEditor {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
|
${this.disabled
|
||||||
|
? html`<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize("ui.panel.config.script.editor.read_only")}
|
||||||
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
|
${this.hass.localize("ui.panel.config.script.editor.migrate")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: nothing}
|
||||||
${this.config.description
|
${this.config.description
|
||||||
? html`<ha-markdown
|
? html`<ha-markdown
|
||||||
class="description"
|
class="description"
|
||||||
|
@@ -6,7 +6,6 @@ import {
|
|||||||
mdiDebugStepOver,
|
mdiDebugStepOver,
|
||||||
mdiDelete,
|
mdiDelete,
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiFileEdit,
|
|
||||||
mdiFormTextbox,
|
mdiFormTextbox,
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
@@ -41,7 +40,6 @@ import { validateConfig } from "../../../data/config";
|
|||||||
import { UNAVAILABLE } from "../../../data/entity";
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||||
import {
|
import {
|
||||||
BlueprintScriptConfig,
|
|
||||||
ScriptConfig,
|
ScriptConfig,
|
||||||
deleteScript,
|
deleteScript,
|
||||||
fetchScriptFileConfig,
|
fetchScriptFileConfig,
|
||||||
@@ -63,7 +61,6 @@ import { showAutomationRenameDialog } from "../automation/automation-rename-dial
|
|||||||
import "./blueprint-script-editor";
|
import "./blueprint-script-editor";
|
||||||
import "./manual-script-editor";
|
import "./manual-script-editor";
|
||||||
import type { HaManualScriptEditor } from "./manual-script-editor";
|
import type { HaManualScriptEditor } from "./manual-script-editor";
|
||||||
import { substituteBlueprint } from "../../../data/blueprint";
|
|
||||||
|
|
||||||
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -99,8 +96,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
|
|
||||||
@state() private _validationErrors?: (string | TemplateResult)[];
|
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||||
|
|
||||||
@state() private _blueprintConfig?: BlueprintScriptConfig;
|
|
||||||
|
|
||||||
protected render(): TemplateResult | typeof nothing {
|
protected render(): TemplateResult | typeof nothing {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return nothing;
|
return nothing;
|
||||||
@@ -218,8 +213,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
|
|
||||||
<ha-list-item
|
<ha-list-item
|
||||||
.disabled=${this._blueprintConfig ||
|
.disabled=${!this._readOnly && !this.scriptId}
|
||||||
(!this._readOnly && !this.scriptId)}
|
|
||||||
graphic="icon"
|
graphic="icon"
|
||||||
@click=${this._duplicate}
|
@click=${this._duplicate}
|
||||||
>
|
>
|
||||||
@@ -234,24 +228,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
</ha-list-item>
|
</ha-list-item>
|
||||||
|
|
||||||
${useBlueprint
|
|
||||||
? html`
|
|
||||||
<ha-list-item
|
|
||||||
graphic="icon"
|
|
||||||
@click=${this._takeControl}
|
|
||||||
.disabled=${this._readOnly}
|
|
||||||
>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.take_control"
|
|
||||||
)}
|
|
||||||
<ha-svg-icon
|
|
||||||
slot="graphic"
|
|
||||||
.path=${mdiFileEdit}
|
|
||||||
></ha-svg-icon>
|
|
||||||
</ha-list-item>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<li divider role="separator"></li>
|
<li divider role="separator"></li>
|
||||||
|
|
||||||
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
|
<ha-list-item graphic="icon" @click=${this._switchUiMode}>
|
||||||
@@ -315,32 +291,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
: nothing}
|
: nothing}
|
||||||
</ha-alert>`
|
</ha-alert>`
|
||||||
: ""}
|
: ""}
|
||||||
${this._blueprintConfig
|
|
||||||
? html`<ha-alert alert-type="info">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.confirm_take_control"
|
|
||||||
)}
|
|
||||||
<div slot="action" style="display: flex;">
|
|
||||||
<mwc-button @click=${this._takeControlSave}
|
|
||||||
>${this.hass.localize("ui.common.yes")}</mwc-button
|
|
||||||
>
|
|
||||||
<mwc-button @click=${this._revertBlueprint}
|
|
||||||
>${this.hass.localize("ui.common.no")}</mwc-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</ha-alert>`
|
|
||||||
: this._readOnly
|
|
||||||
? html`<ha-alert alert-type="warning" dismissable
|
|
||||||
>${this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.read_only"
|
|
||||||
)}
|
|
||||||
<mwc-button slot="action" @click=${this._duplicate}>
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.script.editor.migrate"
|
|
||||||
)}
|
|
||||||
</mwc-button>
|
|
||||||
</ha-alert>`
|
|
||||||
: nothing}
|
|
||||||
${this._mode === "gui"
|
${this._mode === "gui"
|
||||||
? html`
|
? html`
|
||||||
<div
|
<div
|
||||||
@@ -357,6 +307,7 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
.disabled=${this._readOnly}
|
.disabled=${this._readOnly}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
@duplicate=${this._duplicate}
|
||||||
></blueprint-script-editor>
|
></blueprint-script-editor>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
@@ -367,18 +318,31 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
.config=${this._config}
|
.config=${this._config}
|
||||||
.disabled=${this._readOnly}
|
.disabled=${this._readOnly}
|
||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
|
@duplicate=${this._duplicate}
|
||||||
></manual-script-editor>
|
></manual-script-editor>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: this._mode === "yaml"
|
: this._mode === "yaml"
|
||||||
? html`<ha-yaml-editor
|
? html` ${this._readOnly
|
||||||
copyClipboard
|
? html`<ha-alert alert-type="warning">
|
||||||
.hass=${this.hass}
|
${this.hass.localize(
|
||||||
.defaultValue=${this._preprocessYaml()}
|
"ui.panel.config.script.editor.read_only"
|
||||||
.readOnly=${this._readOnly}
|
)}
|
||||||
@value-changed=${this._yamlChanged}
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
></ha-yaml-editor>`
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.script.editor.migrate"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: nothing}
|
||||||
|
<ha-yaml-editor
|
||||||
|
copyClipboard
|
||||||
|
.hass=${this.hass}
|
||||||
|
.defaultValue=${this._preprocessYaml()}
|
||||||
|
.readOnly=${this._readOnly}
|
||||||
|
@value-changed=${this._yamlChanged}
|
||||||
|
></ha-yaml-editor>`
|
||||||
: nothing}
|
: nothing}
|
||||||
</div>
|
</div>
|
||||||
<ha-fab
|
<ha-fab
|
||||||
@@ -637,50 +601,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private async _takeControl() {
|
|
||||||
const config = this._config as BlueprintScriptConfig;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await substituteBlueprint(
|
|
||||||
this.hass,
|
|
||||||
"script",
|
|
||||||
config.use_blueprint.path,
|
|
||||||
config.use_blueprint.input || {}
|
|
||||||
);
|
|
||||||
|
|
||||||
const newConfig = {
|
|
||||||
...this._normalizeConfig(result.substituted_config),
|
|
||||||
alias: config.alias,
|
|
||||||
description: config.description,
|
|
||||||
};
|
|
||||||
|
|
||||||
this._blueprintConfig = config;
|
|
||||||
this._config = newConfig;
|
|
||||||
if (this._mode === "yaml") {
|
|
||||||
this.renderRoot.querySelector("ha-yaml-editor")?.setValue(this._config);
|
|
||||||
}
|
|
||||||
this._readOnly = true;
|
|
||||||
this._errors = undefined;
|
|
||||||
} catch (err: any) {
|
|
||||||
this._errors = err.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _revertBlueprint() {
|
|
||||||
this._config = this._blueprintConfig;
|
|
||||||
if (this._mode === "yaml") {
|
|
||||||
this.renderRoot.querySelector("ha-yaml-editor")?.setValue(this._config);
|
|
||||||
}
|
|
||||||
this._blueprintConfig = undefined;
|
|
||||||
this._readOnly = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _takeControlSave() {
|
|
||||||
this._readOnly = false;
|
|
||||||
this._dirty = true;
|
|
||||||
this._blueprintConfig = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _duplicate() {
|
private async _duplicate() {
|
||||||
const result = this._readOnly
|
const result = this._readOnly
|
||||||
? await showConfirmationDialog(this, {
|
? await showConfirmationDialog(this, {
|
||||||
@@ -832,12 +752,10 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
.config-container,
|
.config-container,
|
||||||
manual-script-editor,
|
manual-script-editor,
|
||||||
blueprint-script-editor,
|
blueprint-script-editor {
|
||||||
:not(.yaml-mode) > ha-alert {
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 1040px;
|
max-width: 1040px;
|
||||||
padding: 28px 20px 0;
|
padding: 28px 20px 0;
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
.config-container ha-alert {
|
.config-container ha-alert {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
@@ -184,20 +184,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "script-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "script-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
private _sizeController = new ResizeController(this, {
|
private _sizeController = new ResizeController(this, {
|
||||||
callback: (entries) => entries[0]?.contentRect.width,
|
callback: (entries) => entries[0]?.contentRect.width,
|
||||||
});
|
});
|
||||||
@@ -246,13 +232,15 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private _columns = memoizeOne(
|
private _columns = memoizeOne(
|
||||||
(localize: LocalizeFunc): DataTableColumnContainer<ScriptItem> => {
|
(
|
||||||
|
narrow,
|
||||||
|
localize: LocalizeFunc,
|
||||||
|
locale: HomeAssistant["locale"]
|
||||||
|
): DataTableColumnContainer<ScriptItem> => {
|
||||||
const columns: DataTableColumnContainer = {
|
const columns: DataTableColumnContainer = {
|
||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
showNarrow: true,
|
label: localize("ui.panel.config.script.picker.headers.state"),
|
||||||
moveable: false,
|
|
||||||
label: localize("ui.panel.config.script.picker.headers.icon"),
|
|
||||||
type: "icon",
|
type: "icon",
|
||||||
template: (script) =>
|
template: (script) =>
|
||||||
html`<ha-state-icon
|
html`<ha-state-icon
|
||||||
@@ -271,13 +259,30 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
extraTemplate: (script) =>
|
template: (script) => {
|
||||||
script.labels.length
|
const date = new Date(script.last_triggered);
|
||||||
? html`<ha-data-table-labels
|
const now = new Date();
|
||||||
@label-clicked=${this._labelClicked}
|
const dayDifference = differenceInDays(now, date);
|
||||||
.labels=${script.labels}
|
return html`
|
||||||
></ha-data-table-labels>`
|
<div style="font-size: 14px;">${script.name}</div>
|
||||||
: nothing,
|
${narrow
|
||||||
|
? html`<div class="secondary">
|
||||||
|
${this.hass.localize("ui.card.automation.last_triggered")}:
|
||||||
|
${script.attributes.last_triggered
|
||||||
|
? dayDifference > 3
|
||||||
|
? formatShortDateTime(date, locale, this.hass.config)
|
||||||
|
: relativeTime(date, locale)
|
||||||
|
: localize("ui.components.relative_time.never")}
|
||||||
|
</div>`
|
||||||
|
: nothing}
|
||||||
|
${script.labels.length
|
||||||
|
? html`<ha-data-table-labels
|
||||||
|
@label-clicked=${this._labelClicked}
|
||||||
|
.labels=${script.labels}
|
||||||
|
></ha-data-table-labels>`
|
||||||
|
: nothing}
|
||||||
|
`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
area: {
|
area: {
|
||||||
title: localize("ui.panel.config.script.picker.headers.area"),
|
title: localize("ui.panel.config.script.picker.headers.area"),
|
||||||
@@ -300,6 +305,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
template: (script) => script.labels.map((lbl) => lbl.name).join(" "),
|
template: (script) => script.labels.map((lbl) => lbl.name).join(" "),
|
||||||
},
|
},
|
||||||
last_triggered: {
|
last_triggered: {
|
||||||
|
hidden: narrow,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
width: "40%",
|
width: "40%",
|
||||||
title: localize("ui.card.automation.last_triggered"),
|
title: localize("ui.card.automation.last_triggered"),
|
||||||
@@ -324,9 +330,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
title: "",
|
title: "",
|
||||||
width: "64px",
|
width: "64px",
|
||||||
type: "overflow-menu",
|
type: "overflow-menu",
|
||||||
showNarrow: true,
|
|
||||||
moveable: false,
|
|
||||||
hideable: false,
|
|
||||||
template: (script) => html`
|
template: (script) => html`
|
||||||
<ha-icon-overflow-menu
|
<ha-icon-overflow-menu
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
@@ -536,9 +539,6 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
.initialGroupColumn=${this._activeGrouping || "category"}
|
.initialGroupColumn=${this._activeGrouping || "category"}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@collapsed-changed=${this._handleCollapseChanged}
|
@collapsed-changed=${this._handleCollapseChanged}
|
||||||
@@ -553,7 +553,11 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
|||||||
Array.isArray(val) ? val.length : val
|
Array.isArray(val) ? val.length : val
|
||||||
)
|
)
|
||||||
).length}
|
).length}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(
|
||||||
|
this.narrow,
|
||||||
|
this.hass.localize,
|
||||||
|
this.hass.locale
|
||||||
|
)}
|
||||||
.data=${scripts}
|
.data=${scripts}
|
||||||
.empty=${!this.scripts.length}
|
.empty=${!this.scripts.length}
|
||||||
.activeFilters=${this._activeFilters}
|
.activeFilters=${this._activeFilters}
|
||||||
@@ -1266,11 +1270,6 @@ ${rejected
|
|||||||
this._activeCollapsed = ev.detail.value;
|
this._activeCollapsed = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -60,6 +60,14 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
|
${this.disabled
|
||||||
|
? html`<ha-alert alert-type="warning">
|
||||||
|
${this.hass.localize("ui.panel.config.script.editor.read_only")}
|
||||||
|
<mwc-button slot="action" @click=${this._duplicate}>
|
||||||
|
${this.hass.localize("ui.panel.config.script.editor.migrate")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-alert>`
|
||||||
|
: nothing}
|
||||||
${this.config.description
|
${this.config.description
|
||||||
? html`<ha-markdown
|
? html`<ha-markdown
|
||||||
class="description"
|
class="description"
|
||||||
@@ -162,6 +170,10 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _duplicate() {
|
||||||
|
fireEvent(this, "duplicate");
|
||||||
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
@@ -193,6 +205,12 @@ export class HaManualScriptEditor extends LitElement {
|
|||||||
.header a {
|
.header a {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
ha-alert.re-order {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: var(--ha-card-border-radius, 12px);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -66,82 +66,93 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
|||||||
})
|
})
|
||||||
private _filter = "";
|
private _filter = "";
|
||||||
|
|
||||||
private _columns = memoizeOne((localize: LocalizeFunc) => {
|
private _columns = memoizeOne(
|
||||||
const columns: DataTableColumnContainer<TagRowData> = {
|
(narrow: boolean, _language, localize: LocalizeFunc) => {
|
||||||
icon: {
|
const columns: DataTableColumnContainer<TagRowData> = {
|
||||||
|
icon: {
|
||||||
|
title: "",
|
||||||
|
label: localize("ui.panel.config.tag.headers.icon"),
|
||||||
|
type: "icon",
|
||||||
|
template: (tag) => html`<tag-image .tag=${tag}></tag-image>`,
|
||||||
|
},
|
||||||
|
display_name: {
|
||||||
|
title: localize("ui.panel.config.tag.headers.name"),
|
||||||
|
main: true,
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
grows: true,
|
||||||
|
template: (tag) =>
|
||||||
|
html`${tag.display_name}
|
||||||
|
${narrow
|
||||||
|
? html`<div class="secondary">
|
||||||
|
${tag.last_scanned_datetime
|
||||||
|
? html`<ha-relative-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${tag.last_scanned_datetime}
|
||||||
|
capitalize
|
||||||
|
></ha-relative-time>`
|
||||||
|
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
||||||
|
</div>`
|
||||||
|
: ""}`,
|
||||||
|
},
|
||||||
|
last_scanned_datetime: {
|
||||||
|
title: localize("ui.panel.config.tag.headers.last_scanned"),
|
||||||
|
sortable: true,
|
||||||
|
hidden: narrow,
|
||||||
|
direction: "desc",
|
||||||
|
width: "20%",
|
||||||
|
template: (tag) => html`
|
||||||
|
${tag.last_scanned_datetime
|
||||||
|
? html`<ha-relative-time
|
||||||
|
.hass=${this.hass}
|
||||||
|
.datetime=${tag.last_scanned_datetime}
|
||||||
|
capitalize
|
||||||
|
></ha-relative-time>`
|
||||||
|
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (this._canWriteTags) {
|
||||||
|
columns.write = {
|
||||||
|
title: "",
|
||||||
|
label: localize("ui.panel.config.tag.headers.write"),
|
||||||
|
type: "icon-button",
|
||||||
|
template: (tag) =>
|
||||||
|
html` <ha-icon-button
|
||||||
|
.tag=${tag}
|
||||||
|
@click=${this._handleWriteClick}
|
||||||
|
.label=${this.hass.localize("ui.panel.config.tag.write")}
|
||||||
|
.path=${mdiContentDuplicate}
|
||||||
|
></ha-icon-button>`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
columns.automation = {
|
||||||
title: "",
|
title: "",
|
||||||
moveable: false,
|
|
||||||
showNarrow: true,
|
|
||||||
label: localize("ui.panel.config.tag.headers.icon"),
|
|
||||||
type: "icon",
|
|
||||||
template: (tag) => html`<tag-image .tag=${tag}></tag-image>`,
|
|
||||||
},
|
|
||||||
display_name: {
|
|
||||||
title: localize("ui.panel.config.tag.headers.name"),
|
|
||||||
main: true,
|
|
||||||
sortable: true,
|
|
||||||
filterable: true,
|
|
||||||
grows: true,
|
|
||||||
},
|
|
||||||
last_scanned_datetime: {
|
|
||||||
title: localize("ui.panel.config.tag.headers.last_scanned"),
|
|
||||||
sortable: true,
|
|
||||||
direction: "desc",
|
|
||||||
width: "20%",
|
|
||||||
template: (tag) => html`
|
|
||||||
${tag.last_scanned_datetime
|
|
||||||
? html`<ha-relative-time
|
|
||||||
.hass=${this.hass}
|
|
||||||
.datetime=${tag.last_scanned_datetime}
|
|
||||||
capitalize
|
|
||||||
></ha-relative-time>`
|
|
||||||
: this.hass.localize("ui.panel.config.tag.never_scanned")}
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
if (this._canWriteTags) {
|
|
||||||
columns.write = {
|
|
||||||
title: "",
|
|
||||||
label: localize("ui.panel.config.tag.headers.write"),
|
|
||||||
type: "icon-button",
|
type: "icon-button",
|
||||||
showNarrow: true,
|
|
||||||
template: (tag) =>
|
template: (tag) =>
|
||||||
html`<ha-icon-button
|
html` <ha-icon-button
|
||||||
.tag=${tag}
|
.tag=${tag}
|
||||||
@click=${this._handleWriteClick}
|
@click=${this._handleAutomationClick}
|
||||||
.label=${this.hass.localize("ui.panel.config.tag.write")}
|
.label=${this.hass.localize(
|
||||||
.path=${mdiContentDuplicate}
|
"ui.panel.config.tag.create_automation"
|
||||||
|
)}
|
||||||
|
.path=${mdiRobot}
|
||||||
></ha-icon-button>`,
|
></ha-icon-button>`,
|
||||||
};
|
};
|
||||||
|
columns.edit = {
|
||||||
|
title: "",
|
||||||
|
type: "icon-button",
|
||||||
|
template: (tag) =>
|
||||||
|
html` <ha-icon-button
|
||||||
|
.tag=${tag}
|
||||||
|
@click=${this._handleEditClick}
|
||||||
|
.label=${this.hass.localize("ui.panel.config.tag.edit")}
|
||||||
|
.path=${mdiCog}
|
||||||
|
></ha-icon-button>`,
|
||||||
|
};
|
||||||
|
return columns;
|
||||||
}
|
}
|
||||||
columns.automation = {
|
);
|
||||||
title: "",
|
|
||||||
type: "icon-button",
|
|
||||||
showNarrow: true,
|
|
||||||
template: (tag) =>
|
|
||||||
html`<ha-icon-button
|
|
||||||
.tag=${tag}
|
|
||||||
@click=${this._handleAutomationClick}
|
|
||||||
.label=${this.hass.localize("ui.panel.config.tag.create_automation")}
|
|
||||||
.path=${mdiRobot}
|
|
||||||
></ha-icon-button>`,
|
|
||||||
};
|
|
||||||
columns.edit = {
|
|
||||||
title: "",
|
|
||||||
type: "icon-button",
|
|
||||||
showNarrow: true,
|
|
||||||
hideable: false,
|
|
||||||
moveable: false,
|
|
||||||
template: (tag) =>
|
|
||||||
html`<ha-icon-button
|
|
||||||
.tag=${tag}
|
|
||||||
@click=${this._handleEditClick}
|
|
||||||
.label=${this.hass.localize("ui.panel.config.tag.edit")}
|
|
||||||
.path=${mdiCog}
|
|
||||||
></ha-icon-button>`,
|
|
||||||
};
|
|
||||||
return columns;
|
|
||||||
});
|
|
||||||
|
|
||||||
private _data = memoizeOne((tags: Tag[]): TagRowData[] =>
|
private _data = memoizeOne((tags: Tag[]): TagRowData[] =>
|
||||||
tags.map((tag) => ({
|
tags.map((tag) => ({
|
||||||
@@ -180,7 +191,11 @@ export class HaConfigTags extends SubscribeMixin(LitElement) {
|
|||||||
back-path="/config"
|
back-path="/config"
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.tabs=${configSections.tags}
|
.tabs=${configSections.tags}
|
||||||
.columns=${this._columns(this.hass.localize)}
|
.columns=${this._columns(
|
||||||
|
this.narrow,
|
||||||
|
this.hass.language,
|
||||||
|
this.hass.localize
|
||||||
|
)}
|
||||||
.data=${this._data(this._tags)}
|
.data=${this._data(this._tags)}
|
||||||
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
|
.noDataText=${this.hass.localize("ui.panel.config.tag.no_tags")}
|
||||||
.filter=${this._filter}
|
.filter=${this._filter}
|
||||||
|
@@ -1,34 +1,31 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
import {
|
import {
|
||||||
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-alert";
|
|
||||||
import "../../../components/ha-button";
|
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
import "../../../components/ha-formfield";
|
import "../../../components/ha-formfield";
|
||||||
import "../../../components/ha-icon-button";
|
|
||||||
import "../../../components/ha-settings-row";
|
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import type { HaSwitch } from "../../../components/ha-switch";
|
import type { HaSwitch } from "../../../components/ha-switch";
|
||||||
import "../../../components/ha-textfield";
|
|
||||||
import type { HaTextField } from "../../../components/ha-textfield";
|
|
||||||
import { createAuthForUser } from "../../../data/auth";
|
import { createAuthForUser } from "../../../data/auth";
|
||||||
import {
|
import {
|
||||||
|
createUser,
|
||||||
|
deleteUser,
|
||||||
SYSTEM_GROUP_ID_ADMIN,
|
SYSTEM_GROUP_ID_ADMIN,
|
||||||
SYSTEM_GROUP_ID_USER,
|
SYSTEM_GROUP_ID_USER,
|
||||||
User,
|
User,
|
||||||
createUser,
|
|
||||||
deleteUser,
|
|
||||||
} from "../../../data/user";
|
} from "../../../data/user";
|
||||||
|
import { ValueChangedEvent, HomeAssistant } from "../../../types";
|
||||||
import { haStyleDialog } from "../../../resources/styles";
|
import { haStyleDialog } from "../../../resources/styles";
|
||||||
import { HomeAssistant, ValueChangedEvent } from "../../../types";
|
|
||||||
import { AddUserDialogParams } from "./show-dialog-add-user";
|
import { AddUserDialogParams } from "./show-dialog-add-user";
|
||||||
|
import "../../../components/ha-textfield";
|
||||||
|
import type { HaTextField } from "../../../components/ha-textfield";
|
||||||
|
|
||||||
@customElement("dialog-add-user")
|
@customElement("dialog-add-user")
|
||||||
export class DialogAddUser extends LitElement {
|
export class DialogAddUser extends LitElement {
|
||||||
@@ -158,44 +155,38 @@ export class DialogAddUser extends LitElement {
|
|||||||
"ui.panel.config.users.add_user.password_not_match"
|
"ui.panel.config.users.add_user.password_not_match"
|
||||||
)}
|
)}
|
||||||
></ha-textfield>
|
></ha-textfield>
|
||||||
<ha-settings-row>
|
<div class="row">
|
||||||
<span slot="heading">
|
<ha-formfield
|
||||||
${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.users.editor.local_access_only"
|
"ui.panel.config.users.editor.local_only"
|
||||||
)}
|
)}
|
||||||
</span>
|
|
||||||
<span slot="description">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.local_access_only_description"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
.checked=${this._localOnly}
|
|
||||||
@change=${this._localOnlyChanged}
|
|
||||||
>
|
>
|
||||||
</ha-switch>
|
<ha-switch
|
||||||
</ha-settings-row>
|
.checked=${this._localOnly}
|
||||||
<ha-settings-row>
|
@change=${this._localOnlyChanged}
|
||||||
<span slot="heading">
|
>
|
||||||
${this.hass.localize("ui.panel.config.users.editor.admin")}
|
</ha-switch>
|
||||||
</span>
|
</ha-formfield>
|
||||||
<span slot="description">
|
</div>
|
||||||
${this.hass.localize(
|
<div class="row">
|
||||||
"ui.panel.config.users.editor.admin_description"
|
<ha-formfield
|
||||||
)}
|
.label=${this.hass.localize("ui.panel.config.users.editor.admin")}
|
||||||
</span>
|
>
|
||||||
<ha-switch .checked=${this._isAdmin} @change=${this._adminChanged}>
|
<ha-switch
|
||||||
</ha-switch>
|
.checked=${this._isAdmin}
|
||||||
</ha-settings-row>
|
@change=${this._adminChanged}
|
||||||
|
>
|
||||||
|
</ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
|
</div>
|
||||||
${!this._isAdmin
|
${!this._isAdmin
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="info">
|
<br />
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.users.users_privileges_note"
|
"ui.panel.config.users.users_privileges_note"
|
||||||
)}
|
)}
|
||||||
</ha-alert>
|
|
||||||
`
|
`
|
||||||
: nothing}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
${this._loading
|
${this._loading
|
||||||
? html`
|
? html`
|
||||||
@@ -204,7 +195,7 @@ export class DialogAddUser extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-button
|
<mwc-button
|
||||||
slot="primaryAction"
|
slot="primaryAction"
|
||||||
.disabled=${!this._name ||
|
.disabled=${!this._name ||
|
||||||
!this._username ||
|
!this._username ||
|
||||||
@@ -213,7 +204,7 @@ export class DialogAddUser extends LitElement {
|
|||||||
@click=${this._createUser}
|
@click=${this._createUser}
|
||||||
>
|
>
|
||||||
${this.hass.localize("ui.panel.config.users.add_user.create")}
|
${this.hass.localize("ui.panel.config.users.add_user.create")}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
`}
|
`}
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
@@ -290,11 +281,6 @@ export class DialogAddUser extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.username = this._username;
|
user.username = this._username;
|
||||||
user.credentials = [
|
|
||||||
{
|
|
||||||
type: "homeassistant",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
this._params!.userAddedCallback(user);
|
this._params!.userAddedCallback(user);
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
@@ -313,10 +299,7 @@ export class DialogAddUser extends LitElement {
|
|||||||
}
|
}
|
||||||
ha-textfield {
|
ha-textfield {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 16px;
|
||||||
}
|
|
||||||
ha-settings-row {
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -132,7 +132,7 @@ class DialogAdminChangePassword extends LitElement {
|
|||||||
@value-changed=${this._valueChanged}
|
@value-changed=${this._valueChanged}
|
||||||
.disabled=${this._submitting}
|
.disabled=${this._submitting}
|
||||||
></ha-form>
|
></ha-form>
|
||||||
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
|
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||||
${this.hass.localize("ui.common.cancel")}
|
${this.hass.localize("ui.common.cancel")}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
<mwc-button
|
<mwc-button
|
||||||
|
@@ -1,13 +1,12 @@
|
|||||||
import { mdiPencil } from "@mdi/js";
|
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||||
|
import "@material/mwc-button";
|
||||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import "../../../components/ha-alert";
|
|
||||||
import "../../../components/ha-button";
|
|
||||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||||
import "../../../components/ha-formfield";
|
import "../../../components/ha-formfield";
|
||||||
import "../../../components/ha-icon-button";
|
import "../../../components/ha-help-tooltip";
|
||||||
import "../../../components/ha-label";
|
import "../../../components/ha-label";
|
||||||
import "../../../components/ha-settings-row";
|
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import "../../../components/ha-switch";
|
import "../../../components/ha-switch";
|
||||||
import "../../../components/ha-textfield";
|
import "../../../components/ha-textfield";
|
||||||
@@ -69,15 +68,15 @@ class DialogUserDetail extends LitElement {
|
|||||||
.heading=${createCloseHeading(this.hass, user.name)}
|
.heading=${createCloseHeading(this.hass, user.name)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
${this._error
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
? html`<div class="error">${this._error}</div>`
|
|
||||||
: nothing}
|
|
||||||
<div class="secondary">
|
<div class="secondary">
|
||||||
${this.hass.localize("ui.panel.config.users.editor.id")}:
|
${this.hass.localize("ui.panel.config.users.editor.id")}:
|
||||||
${user.id}<br />
|
${user.id}<br />
|
||||||
|
${this.hass.localize("ui.panel.config.users.editor.username")}:
|
||||||
|
${user.username}
|
||||||
</div>
|
</div>
|
||||||
${badges.length === 0
|
${badges.length === 0
|
||||||
? nothing
|
? ""
|
||||||
: html`
|
: html`
|
||||||
<div class="badge-container">
|
<div class="badge-container">
|
||||||
${badges.map(
|
${badges.map(
|
||||||
@@ -91,136 +90,74 @@ class DialogUserDetail extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
<div class="form">
|
<div class="form">
|
||||||
${!user.system_generated
|
<ha-textfield
|
||||||
? html`
|
dialogInitialFocus
|
||||||
<ha-textfield
|
.value=${this._name}
|
||||||
dialogInitialFocus
|
.disabled=${user.system_generated}
|
||||||
.value=${this._name}
|
@input=${this._nameChanged}
|
||||||
@input=${this._nameChanged}
|
.label=${this.hass!.localize("ui.panel.config.users.editor.name")}
|
||||||
.label=${this.hass!.localize(
|
></ha-textfield>
|
||||||
"ui.panel.config.users.editor.name"
|
<div class="row">
|
||||||
)}
|
<ha-formfield
|
||||||
></ha-textfield>
|
.label=${this.hass.localize(
|
||||||
<ha-settings-row>
|
"ui.panel.config.users.editor.local_only"
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.username"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="description">${user.username}</span>
|
|
||||||
${this.hass.user?.is_owner
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.path=${mdiPencil}
|
|
||||||
@click=${this._changeUsername}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.change_username"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-icon-button>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</ha-settings-row>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
${!user.system_generated && this.hass.user?.is_owner
|
|
||||||
? html`
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.password"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<span slot="description">************</span>
|
|
||||||
${this.hass.user?.is_owner
|
|
||||||
? html`
|
|
||||||
<ha-icon-button
|
|
||||||
.path=${mdiPencil}
|
|
||||||
@click=${this._changePassword}
|
|
||||||
.label=${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.change_password"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</ha-icon-button>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</ha-settings-row>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
|
|
||||||
<ha-settings-row>
|
|
||||||
<span slot="heading">
|
|
||||||
${this.hass.localize("ui.panel.config.users.editor.active")}
|
|
||||||
</span>
|
|
||||||
<span slot="description">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.active_description"
|
|
||||||
)}
|
)}
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
.disabled=${user.system_generated || user.is_owner}
|
|
||||||
.checked=${this._isActive}
|
|
||||||
@change=${this._activeChanged}
|
|
||||||
>
|
>
|
||||||
</ha-switch>
|
<ha-switch
|
||||||
</ha-settings-row>
|
.disabled=${user.system_generated}
|
||||||
<ha-settings-row>
|
.checked=${this._localOnly}
|
||||||
<span slot="heading">
|
@change=${this._localOnlyChanged}
|
||||||
${this.hass.localize(
|
>
|
||||||
"ui.panel.config.users.editor.local_access_only"
|
</ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.admin"
|
||||||
)}
|
)}
|
||||||
</span>
|
|
||||||
<span slot="description">
|
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.users.editor.local_access_only_description"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
.disabled=${user.system_generated}
|
|
||||||
.checked=${this._localOnly}
|
|
||||||
@change=${this._localOnlyChanged}
|
|
||||||
>
|
>
|
||||||
</ha-switch>
|
<ha-switch
|
||||||
</ha-settings-row>
|
.disabled=${user.system_generated || user.is_owner}
|
||||||
<ha-settings-row>
|
.checked=${this._isAdmin}
|
||||||
<span slot="heading">
|
@change=${this._adminChanged}
|
||||||
${this.hass.localize("ui.panel.config.users.editor.admin")}
|
>
|
||||||
</span>
|
</ha-switch>
|
||||||
<span slot="description">
|
</ha-formfield>
|
||||||
${this.hass.localize(
|
</div>
|
||||||
"ui.panel.config.users.editor.admin_description"
|
${!this._isAdmin
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
<ha-switch
|
|
||||||
.disabled=${user.system_generated || user.is_owner}
|
|
||||||
.checked=${this._isAdmin}
|
|
||||||
@change=${this._adminChanged}
|
|
||||||
>
|
|
||||||
</ha-switch>
|
|
||||||
</ha-settings-row>
|
|
||||||
${!this._isAdmin && !user.system_generated
|
|
||||||
? html`
|
? html`
|
||||||
<ha-alert alert-type="info">
|
<br />
|
||||||
${this.hass.localize(
|
|
||||||
"ui.panel.config.users.users_privileges_note"
|
|
||||||
)}
|
|
||||||
</ha-alert>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</div>
|
|
||||||
${user.system_generated
|
|
||||||
? html`
|
|
||||||
<ha-alert alert-type="info">
|
|
||||||
${this.hass.localize(
|
${this.hass.localize(
|
||||||
"ui.panel.config.users.editor.system_generated_read_only_users"
|
"ui.panel.config.users.users_privileges_note"
|
||||||
)}
|
)}
|
||||||
</ha-alert>
|
`
|
||||||
`
|
: ""}
|
||||||
: nothing}
|
<div class="row">
|
||||||
|
<ha-formfield
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.active"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ha-switch
|
||||||
|
.disabled=${user.system_generated || user.is_owner}
|
||||||
|
.checked=${this._isActive}
|
||||||
|
@change=${this._activeChanged}
|
||||||
|
>
|
||||||
|
</ha-switch>
|
||||||
|
</ha-formfield>
|
||||||
|
<ha-help-tooltip
|
||||||
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.active_tooltip"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</ha-help-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div slot="secondaryAction">
|
<div slot="secondaryAction">
|
||||||
<ha-button
|
<mwc-button
|
||||||
class="warning"
|
class="warning"
|
||||||
@click=${this._deleteEntry}
|
@click=${this._deleteEntry}
|
||||||
.disabled=${this._submitting ||
|
.disabled=${this._submitting ||
|
||||||
@@ -228,18 +165,47 @@ class DialogUserDetail extends LitElement {
|
|||||||
user.is_owner}
|
user.is_owner}
|
||||||
>
|
>
|
||||||
${this.hass!.localize("ui.panel.config.users.editor.delete_user")}
|
${this.hass!.localize("ui.panel.config.users.editor.delete_user")}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
|
${user.system_generated
|
||||||
|
? html`
|
||||||
|
<simple-tooltip animation-delay="0" position="right">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.system_generated_users_not_removable"
|
||||||
|
)}
|
||||||
|
</simple-tooltip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${!user.system_generated && this.hass.user?.is_owner
|
||||||
|
? html`<mwc-button @click=${this._changeUsername}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.change_username"
|
||||||
|
)} </mwc-button
|
||||||
|
><mwc-button @click=${this._changePassword}>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.change_password"
|
||||||
|
)}
|
||||||
|
</mwc-button>`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div slot="primaryAction">
|
<div slot="primaryAction">
|
||||||
<ha-button
|
<mwc-button
|
||||||
@click=${this._updateEntry}
|
@click=${this._updateEntry}
|
||||||
.disabled=${!this._name ||
|
.disabled=${!this._name ||
|
||||||
this._submitting ||
|
this._submitting ||
|
||||||
user.system_generated}
|
user.system_generated}
|
||||||
>
|
>
|
||||||
${this.hass!.localize("ui.panel.config.users.editor.update_user")}
|
${this.hass!.localize("ui.panel.config.users.editor.update_user")}
|
||||||
</ha-button>
|
</mwc-button>
|
||||||
|
${user.system_generated
|
||||||
|
? html`
|
||||||
|
<simple-tooltip animation-delay="0" position="left">
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.users.editor.system_generated_users_not_editable"
|
||||||
|
)}
|
||||||
|
</simple-tooltip>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
@@ -387,8 +353,27 @@ class DialogUserDetail extends LitElement {
|
|||||||
margin-inline-end: 4px;
|
margin-inline-end: 4px;
|
||||||
margin-inline-start: 0;
|
margin-inline-start: 0;
|
||||||
}
|
}
|
||||||
ha-settings-row {
|
.state {
|
||||||
padding: 0;
|
background-color: rgba(var(--rgb-primary-text-color), 0.15);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.state:not(:first-child) {
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-inline-start: 8px;
|
||||||
|
margin-inline-end: initial;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
ha-help-tooltip {
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-inline-start: 4px;
|
||||||
|
margin-inline-end: initial;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -46,20 +46,6 @@ export class HaConfigUsers extends LitElement {
|
|||||||
@storage({ key: "users-table-grouping", state: false, subscribe: false })
|
@storage({ key: "users-table-grouping", state: false, subscribe: false })
|
||||||
private _activeGrouping?: string;
|
private _activeGrouping?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "users-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "users-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@storage({
|
@storage({
|
||||||
storage: "sessionStorage",
|
storage: "sessionStorage",
|
||||||
key: "users-table-search",
|
key: "users-table-search",
|
||||||
@@ -86,6 +72,17 @@ export class HaConfigUsers extends LitElement {
|
|||||||
width: "25%",
|
width: "25%",
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
|
template: (user) =>
|
||||||
|
narrow
|
||||||
|
? html` ${user.name}<br />
|
||||||
|
<div class="secondary">
|
||||||
|
${user.username ? `${user.username} |` : ""}
|
||||||
|
${localize(`groups.${user.group_ids[0]}`)}
|
||||||
|
</div>`
|
||||||
|
: html` ${user.name ||
|
||||||
|
this.hass!.localize(
|
||||||
|
"ui.panel.config.users.editor.unnamed_user"
|
||||||
|
)}`,
|
||||||
},
|
},
|
||||||
username: {
|
username: {
|
||||||
title: localize("ui.panel.config.users.picker.headers.username"),
|
title: localize("ui.panel.config.users.picker.headers.username"),
|
||||||
@@ -93,6 +90,7 @@ export class HaConfigUsers extends LitElement {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
width: "20%",
|
width: "20%",
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
|
hidden: narrow,
|
||||||
template: (user) => html`${user.username || "—"}`,
|
template: (user) => html`${user.username || "—"}`,
|
||||||
},
|
},
|
||||||
group: {
|
group: {
|
||||||
@@ -102,6 +100,7 @@ export class HaConfigUsers extends LitElement {
|
|||||||
groupable: true,
|
groupable: true,
|
||||||
width: "20%",
|
width: "20%",
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
|
hidden: narrow,
|
||||||
},
|
},
|
||||||
is_active: {
|
is_active: {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
@@ -155,7 +154,6 @@ export class HaConfigUsers extends LitElement {
|
|||||||
filterable: false,
|
filterable: false,
|
||||||
width: "104px",
|
width: "104px",
|
||||||
hidden: !narrow,
|
hidden: !narrow,
|
||||||
showNarrow: true,
|
|
||||||
template: (user) => {
|
template: (user) => {
|
||||||
const badges = computeUserBadges(this.hass, user, false);
|
const badges = computeUserBadges(this.hass, user, false);
|
||||||
return html`${badges.map(
|
return html`${badges.map(
|
||||||
@@ -188,9 +186,6 @@ export class HaConfigUsers extends LitElement {
|
|||||||
.tabs=${configSections.persons}
|
.tabs=${configSections.persons}
|
||||||
.columns=${this._columns(this.narrow, this.hass.localize)}
|
.columns=${this._columns(this.narrow, this.hass.localize)}
|
||||||
.data=${this._userData(this._users, this.hass.localize)}
|
.data=${this._userData(this._users, this.hass.localize)}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
@@ -218,7 +213,6 @@ export class HaConfigUsers extends LitElement {
|
|||||||
private _userData = memoizeOne((users: User[], localize: LocalizeFunc) =>
|
private _userData = memoizeOne((users: User[], localize: LocalizeFunc) =>
|
||||||
users.map((user) => ({
|
users.map((user) => ({
|
||||||
...user,
|
...user,
|
||||||
name: user.name || localize("ui.panel.config.users.editor.unnamed_user"),
|
|
||||||
group: localize(`groups.${user.group_ids[0]}`),
|
group: localize(`groups.${user.group_ids[0]}`),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@@ -308,11 +302,6 @@ export class HaConfigUsers extends LitElement {
|
|||||||
private _handleSearchChange(ev: CustomEvent) {
|
private _handleSearchChange(ev: CustomEvent) {
|
||||||
this._filter = ev.detail.value;
|
this._filter = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@@ -118,20 +118,6 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
})
|
})
|
||||||
private _activeCollapsed?: string;
|
private _activeCollapsed?: string;
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "voice-expose-table-column-order",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeColumnOrder?: string[];
|
|
||||||
|
|
||||||
@storage({
|
|
||||||
key: "voice-expose-table-hidden-columns",
|
|
||||||
state: false,
|
|
||||||
subscribe: false,
|
|
||||||
})
|
|
||||||
private _activeHiddenColumns?: string[];
|
|
||||||
|
|
||||||
@query("hass-tabs-subpage-data-table", true)
|
@query("hass-tabs-subpage-data-table", true)
|
||||||
private _dataTable!: HaTabsSubpageDataTable;
|
private _dataTable!: HaTabsSubpageDataTable;
|
||||||
|
|
||||||
@@ -151,7 +137,6 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
icon: {
|
icon: {
|
||||||
title: "",
|
title: "",
|
||||||
type: "icon",
|
type: "icon",
|
||||||
moveable: false,
|
|
||||||
hidden: narrow,
|
hidden: narrow,
|
||||||
template: (entry) => html`
|
template: (entry) => html`
|
||||||
<ha-state-icon
|
<ha-state-icon
|
||||||
@@ -168,20 +153,10 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
filterable: true,
|
filterable: true,
|
||||||
direction: "asc",
|
direction: "asc",
|
||||||
grows: true,
|
grows: true,
|
||||||
template: narrow
|
template: (entry) => html`
|
||||||
? undefined
|
${entry.name}<br />
|
||||||
: (entry) => html`
|
<div class="secondary">${entry.entity_id}</div>
|
||||||
${entry.name}<br />
|
`,
|
||||||
<div class="secondary">${entry.entity_id}</div>
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
// For search & narrow
|
|
||||||
entity_id: {
|
|
||||||
title: localize(
|
|
||||||
"ui.panel.config.voice_assistants.expose.headers.entity_id"
|
|
||||||
),
|
|
||||||
hidden: !narrow,
|
|
||||||
filterable: true,
|
|
||||||
},
|
},
|
||||||
domain: {
|
domain: {
|
||||||
title: localize(
|
title: localize(
|
||||||
@@ -196,6 +171,7 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
title: localize("ui.panel.config.voice_assistants.expose.headers.area"),
|
title: localize("ui.panel.config.voice_assistants.expose.headers.area"),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
groupable: true,
|
groupable: true,
|
||||||
|
hidden: narrow,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "15%",
|
width: "15%",
|
||||||
},
|
},
|
||||||
@@ -203,7 +179,6 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
title: localize(
|
title: localize(
|
||||||
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
"ui.panel.config.voice_assistants.expose.headers.assistants"
|
||||||
),
|
),
|
||||||
showNarrow: true,
|
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
width: "160px",
|
width: "160px",
|
||||||
@@ -233,6 +208,7 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
),
|
),
|
||||||
sortable: true,
|
sortable: true,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
|
hidden: narrow,
|
||||||
width: "15%",
|
width: "15%",
|
||||||
template: (entry) =>
|
template: (entry) =>
|
||||||
entry.aliases.length === 0
|
entry.aliases.length === 0
|
||||||
@@ -254,6 +230,12 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
.path=${mdiCloseCircleOutline}
|
.path=${mdiCloseCircleOutline}
|
||||||
></ha-icon-button>`,
|
></ha-icon-button>`,
|
||||||
},
|
},
|
||||||
|
// For search
|
||||||
|
entity_id: {
|
||||||
|
title: "",
|
||||||
|
hidden: true,
|
||||||
|
filterable: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -570,9 +552,6 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
.initialSorting=${this._activeSorting}
|
.initialSorting=${this._activeSorting}
|
||||||
.initialGroupColumn=${this._activeGrouping}
|
.initialGroupColumn=${this._activeGrouping}
|
||||||
.initialCollapsedGroups=${this._activeCollapsed}
|
.initialCollapsedGroups=${this._activeCollapsed}
|
||||||
.columnOrder=${this._activeColumnOrder}
|
|
||||||
.hiddenColumns=${this._activeHiddenColumns}
|
|
||||||
@columns-changed=${this._handleColumnsChanged}
|
|
||||||
@sorting-changed=${this._handleSortingChanged}
|
@sorting-changed=${this._handleSortingChanged}
|
||||||
@selection-changed=${this._handleSelectionChanged}
|
@selection-changed=${this._handleSelectionChanged}
|
||||||
@grouping-changed=${this._handleGroupingChanged}
|
@grouping-changed=${this._handleGroupingChanged}
|
||||||
@@ -778,11 +757,6 @@ export class VoiceAssistantsExpose extends LitElement {
|
|||||||
this._activeCollapsed = ev.detail.value;
|
this._activeCollapsed = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleColumnsChanged(ev: CustomEvent) {
|
|
||||||
this._activeColumnOrder = ev.detail.columnOrder;
|
|
||||||
this._activeHiddenColumns = ev.detail.hiddenColumns;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
haStyle,
|
haStyle,
|
||||||
|
@@ -139,7 +139,7 @@ export class HaLogbook extends LitElement {
|
|||||||
this._throttleGetLogbookEntries.cancel();
|
this._throttleGetLogbookEntries.cancel();
|
||||||
this._updateTraceContexts.cancel();
|
this._updateTraceContexts.cancel();
|
||||||
this._updateUsers.cancel();
|
this._updateUsers.cancel();
|
||||||
this._unsubscribeSetLoading();
|
await this._unsubscribeSetLoading();
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
this._getLogBookData();
|
this._getLogBookData();
|
||||||
@@ -206,9 +206,18 @@ export class HaLogbook extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _unsubscribe() {
|
private async _unsubscribe(): Promise<void> {
|
||||||
if (this._subscribed) {
|
if (this._subscribed) {
|
||||||
this._subscribed.then((unsub) => unsub?.());
|
const unsub = await this._subscribed;
|
||||||
|
if (unsub) {
|
||||||
|
try {
|
||||||
|
await unsub();
|
||||||
|
} catch (e) {
|
||||||
|
// The backend will cancel the subscription if
|
||||||
|
// we subscribe to entities that will all be
|
||||||
|
// filtered away
|
||||||
|
}
|
||||||
|
}
|
||||||
this._subscribed = undefined;
|
this._subscribed = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,8 +239,8 @@ export class HaLogbook extends LitElement {
|
|||||||
* Setting this._logbookEntries to undefined
|
* Setting this._logbookEntries to undefined
|
||||||
* will put the page in a loading state.
|
* will put the page in a loading state.
|
||||||
*/
|
*/
|
||||||
private _unsubscribeSetLoading() {
|
private async _unsubscribeSetLoading() {
|
||||||
this._unsubscribe();
|
await this._unsubscribe();
|
||||||
this._logbookEntries = undefined;
|
this._logbookEntries = undefined;
|
||||||
this._pendingStreamMessages = [];
|
this._pendingStreamMessages = [];
|
||||||
}
|
}
|
||||||
@@ -240,8 +249,8 @@ export class HaLogbook extends LitElement {
|
|||||||
* Setting this._logbookEntries to an empty
|
* Setting this._logbookEntries to an empty
|
||||||
* list will show a no results message.
|
* list will show a no results message.
|
||||||
*/
|
*/
|
||||||
private _unsubscribeNoResults() {
|
private async _unsubscribeNoResults() {
|
||||||
this._unsubscribe();
|
await this._unsubscribe();
|
||||||
this._logbookEntries = [];
|
this._logbookEntries = [];
|
||||||
this._pendingStreamMessages = [];
|
this._pendingStreamMessages = [];
|
||||||
}
|
}
|
||||||
|
@@ -55,11 +55,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
|||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "../components/hui-image";
|
import "../components/hui-image";
|
||||||
import "../components/hui-warning";
|
import "../components/hui-warning";
|
||||||
import {
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
LovelaceCard,
|
|
||||||
LovelaceCardEditor,
|
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
|
||||||
import { AreaCardConfig } from "./types";
|
import { AreaCardConfig } from "./types";
|
||||||
|
|
||||||
export const DEFAULT_ASPECT_RATIO = "16:9";
|
export const DEFAULT_ASPECT_RATIO = "16:9";
|
||||||
@@ -106,9 +102,6 @@ export class HuiAreaCard
|
|||||||
|
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
public layout?: string;
|
|
||||||
|
|
||||||
@state() private _config?: AreaCardConfig;
|
@state() private _config?: AreaCardConfig;
|
||||||
|
|
||||||
@state() private _entities?: EntityRegistryEntry[];
|
@state() private _entities?: EntityRegistryEntry[];
|
||||||
@@ -414,17 +407,13 @@ export class HuiAreaCard
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imageClass = area.picture || cameraEntityId;
|
const imageClass = area.picture || cameraEntityId;
|
||||||
|
|
||||||
const ignoreAspectRatio = this.layout === "grid";
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
class=${imageClass ? "image" : ""}
|
class=${imageClass ? "image" : ""}
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
paddingBottom:
|
paddingBottom: imageClass
|
||||||
ignoreAspectRatio || imageClass
|
? "0"
|
||||||
? "0"
|
: `${((100 * this._ratio!.h) / this._ratio!.w).toFixed(2)}%`,
|
||||||
: `${((100 * this._ratio!.h) / this._ratio!.w).toFixed(2)}%`,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
${area.picture || cameraEntityId
|
${area.picture || cameraEntityId
|
||||||
@@ -435,10 +424,8 @@ export class HuiAreaCard
|
|||||||
.image=${area.picture ? area.picture : undefined}
|
.image=${area.picture ? area.picture : undefined}
|
||||||
.cameraImage=${cameraEntityId}
|
.cameraImage=${cameraEntityId}
|
||||||
.cameraView=${this._config.camera_view}
|
.cameraView=${this._config.camera_view}
|
||||||
.aspectRatio=${ignoreAspectRatio
|
.aspectRatio=${this._config.aspect_ratio ||
|
||||||
? undefined
|
DEFAULT_ASPECT_RATIO}
|
||||||
: this._config.aspect_ratio || DEFAULT_ASPECT_RATIO}
|
|
||||||
fitMode="cover"
|
|
||||||
></hui-image>
|
></hui-image>
|
||||||
`
|
`
|
||||||
: area.icon
|
: area.icon
|
||||||
@@ -547,20 +534,12 @@ export class HuiAreaCard
|
|||||||
forwardHaptic("light");
|
forwardHaptic("light");
|
||||||
}
|
}
|
||||||
|
|
||||||
getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
return {
|
|
||||||
grid_columns: 4,
|
|
||||||
grid_rows: 3,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@@ -588,10 +567,6 @@ export class HuiAreaCard
|
|||||||
opacity: 0.12;
|
opacity: 0.12;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image hui-image {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
.icon-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@@ -145,16 +145,9 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
|||||||
this._config?.show_icon &&
|
this._config?.show_icon &&
|
||||||
(this._config?.show_name || this._config?.show_state)
|
(this._config?.show_name || this._config?.show_state)
|
||||||
) {
|
) {
|
||||||
return {
|
return { grid_rows: 2, grid_columns: 2 };
|
||||||
grid_rows: 2,
|
|
||||||
grid_columns: 2,
|
|
||||||
grid_min_rows: 2,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return {
|
return { grid_rows: 1, grid_columns: 1 };
|
||||||
grid_rows: 1,
|
|
||||||
grid_columns: 1,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setConfig(config: ButtonCardConfig): void {
|
public setConfig(config: ButtonCardConfig): void {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { PropertyValues, ReactiveElement } from "lit";
|
import { PropertyValueMap, PropertyValues, ReactiveElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import { fireEvent } from "../../../common/dom/fire_event";
|
import { fireEvent } from "../../../common/dom/fire_event";
|
||||||
import { MediaQueriesListener } from "../../../common/dom/media_query";
|
import { MediaQueriesListener } from "../../../common/dom/media_query";
|
||||||
@@ -23,25 +23,30 @@ declare global {
|
|||||||
|
|
||||||
@customElement("hui-card")
|
@customElement("hui-card")
|
||||||
export class HuiCard extends ReactiveElement {
|
export class HuiCard extends ReactiveElement {
|
||||||
@property({ attribute: false }) public preview = false;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public isPanel = false;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public config?: LovelaceCardConfig;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public layout?: string;
|
@property({ type: Boolean }) public preview = false;
|
||||||
|
|
||||||
private _elementConfig?: LovelaceCardConfig;
|
@property({ type: Boolean }) public isPanel = false;
|
||||||
|
|
||||||
public load() {
|
set config(config: LovelaceCardConfig | undefined) {
|
||||||
if (!this.config) {
|
if (!config) return;
|
||||||
throw new Error("Cannot build card without config");
|
if (config.type !== this._config?.type) {
|
||||||
|
this._buildElement(config);
|
||||||
|
} else if (config !== this.config) {
|
||||||
|
this._element?.setConfig(config);
|
||||||
|
fireEvent(this, "card-updated");
|
||||||
}
|
}
|
||||||
this._loadElement(this.config);
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public get config() {
|
||||||
|
return this._config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _config?: LovelaceCardConfig;
|
||||||
|
|
||||||
private _element?: LovelaceCard;
|
private _element?: LovelaceCard;
|
||||||
|
|
||||||
private _listeners: MediaQueriesListener[] = [];
|
private _listeners: MediaQueriesListener[] = [];
|
||||||
@@ -85,86 +90,55 @@ export class HuiCard extends ReactiveElement {
|
|||||||
return this._element?.getLayoutOptions?.() ?? {};
|
return this._element?.getLayoutOptions?.() ?? {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _updateElement(config: LovelaceCardConfig) {
|
private _createElement(config: LovelaceCardConfig) {
|
||||||
if (!this._element) {
|
const element = createCardElement(config);
|
||||||
return;
|
element.hass = this.hass;
|
||||||
}
|
element.preview = this.preview;
|
||||||
this._element.setConfig(config);
|
|
||||||
this._elementConfig = config;
|
|
||||||
fireEvent(this, "card-updated");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _loadElement(config: LovelaceCardConfig) {
|
|
||||||
this._element = createCardElement(config);
|
|
||||||
this._elementConfig = config;
|
|
||||||
if (this.hass) {
|
|
||||||
this._element.hass = this.hass;
|
|
||||||
}
|
|
||||||
this._element.layout = this.layout;
|
|
||||||
this._element.preview = this.preview;
|
|
||||||
// For backwards compatibility
|
// For backwards compatibility
|
||||||
(this._element as any).editMode = this.preview;
|
(element as any).editMode = this.preview;
|
||||||
// Update element when the visibility of the card changes (e.g. conditional card or filter card)
|
// Update element when the visibility of the card changes (e.g. conditional card or filter card)
|
||||||
this._element.addEventListener("card-visibility-changed", (ev: Event) => {
|
element.addEventListener("card-visibility-changed", (ev: Event) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
});
|
});
|
||||||
this._element.addEventListener(
|
element.addEventListener(
|
||||||
"ll-upgrade",
|
"ll-upgrade",
|
||||||
(ev: Event) => {
|
(ev: Event) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
if (this.hass) {
|
|
||||||
this._element!.hass = this.hass;
|
|
||||||
}
|
|
||||||
fireEvent(this, "card-updated");
|
fireEvent(this, "card-updated");
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
this._element.addEventListener(
|
element.addEventListener(
|
||||||
"ll-rebuild",
|
"ll-rebuild",
|
||||||
(ev: Event) => {
|
(ev: Event) => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._loadElement(config);
|
this._buildElement(config);
|
||||||
fireEvent(this, "card-updated");
|
fireEvent(this, "card-updated");
|
||||||
},
|
},
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _buildElement(config: LovelaceCardConfig) {
|
||||||
|
this._element = this._createElement(config);
|
||||||
|
|
||||||
while (this.lastChild) {
|
while (this.lastChild) {
|
||||||
this.removeChild(this.lastChild);
|
this.removeChild(this.lastChild);
|
||||||
}
|
}
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected willUpdate(changedProps: PropertyValues<typeof this>): void {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
|
|
||||||
if (!this._element) {
|
|
||||||
this.load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected update(changedProps: PropertyValues<typeof this>) {
|
protected update(changedProps: PropertyValues<typeof this>) {
|
||||||
super.update(changedProps);
|
super.update(changedProps);
|
||||||
|
|
||||||
if (this._element) {
|
if (this._element) {
|
||||||
if (changedProps.has("config")) {
|
|
||||||
const elementConfig = this._elementConfig;
|
|
||||||
if (this.config !== elementConfig && this.config) {
|
|
||||||
const typeChanged = this.config?.type !== elementConfig?.type;
|
|
||||||
if (typeChanged) {
|
|
||||||
this._loadElement(this.config);
|
|
||||||
} else {
|
|
||||||
this._updateElement(this.config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changedProps.has("hass")) {
|
if (changedProps.has("hass")) {
|
||||||
try {
|
try {
|
||||||
if (this.hass) {
|
this._element.hass = this.hass;
|
||||||
this._element.hass = this.hass;
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this._loadElement(createErrorCardConfig(e.message, null));
|
this._buildElement(createErrorCardConfig(e.message, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changedProps.has("preview")) {
|
if (changedProps.has("preview")) {
|
||||||
@@ -173,17 +147,18 @@ export class HuiCard extends ReactiveElement {
|
|||||||
// For backwards compatibility
|
// For backwards compatibility
|
||||||
(this._element as any).editMode = this.preview;
|
(this._element as any).editMode = this.preview;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this._loadElement(createErrorCardConfig(e.message, null));
|
this._buildElement(createErrorCardConfig(e.message, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changedProps.has("isPanel")) {
|
if (changedProps.has("isPanel")) {
|
||||||
this._element.isPanel = this.isPanel;
|
this._element.isPanel = this.isPanel;
|
||||||
}
|
}
|
||||||
if (changedProps.has("layout")) {
|
|
||||||
this._element.layout = this.layout;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(
|
||||||
|
changedProps: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||||
|
): void {
|
||||||
if (changedProps.has("hass") || changedProps.has("preview")) {
|
if (changedProps.has("hass") || changedProps.has("preview")) {
|
||||||
this._updateVisibility();
|
this._updateVisibility();
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,6 @@ class HuiConditionalCard extends HuiConditionalBase implements LovelaceCard {
|
|||||||
element.hass = this.hass;
|
element.hass = this.hass;
|
||||||
element.preview = this.preview;
|
element.preview = this.preview;
|
||||||
element.config = cardConfig;
|
element.config = cardConfig;
|
||||||
element.load();
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,11 +36,7 @@ import { findEntities } from "../common/find-entities";
|
|||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
||||||
import {
|
import { LovelaceCard, LovelaceHeaderFooter } from "../types";
|
||||||
LovelaceCard,
|
|
||||||
LovelaceHeaderFooter,
|
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
|
||||||
import { HuiErrorCard } from "./hui-error-card";
|
import { HuiErrorCard } from "./hui-error-card";
|
||||||
import { EntityCardConfig } from "./types";
|
import { EntityCardConfig } from "./types";
|
||||||
|
|
||||||
@@ -245,15 +241,6 @@ export class HuiEntityCard extends LitElement implements LovelaceCard {
|
|||||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
return {
|
|
||||||
grid_columns: 2,
|
|
||||||
grid_rows: 2,
|
|
||||||
grid_min_columns: 2,
|
|
||||||
grid_min_rows: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
iconColorCSS,
|
iconColorCSS,
|
||||||
|
@@ -249,7 +249,6 @@ export class HuiEntityFilterCard
|
|||||||
element.hass = this.hass;
|
element.hass = this.hass;
|
||||||
element.preview = this.preview;
|
element.preview = this.preview;
|
||||||
element.config = cardConfig;
|
element.config = cardConfig;
|
||||||
element.load();
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -92,7 +92,6 @@ class HuiGridCard extends HuiStackCard<GridCardConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([square]) #root > *:not([hidden]) {
|
:host([square]) #root > *:not([hidden]) {
|
||||||
display: block;
|
|
||||||
grid-row: 1 / 1;
|
grid-row: 1 / 1;
|
||||||
grid-column: 1 / 1;
|
grid-column: 1 / 1;
|
||||||
}
|
}
|
||||||
|
@@ -30,10 +30,7 @@ export class HuiHorizontalStackCard extends HuiStackCard {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px));
|
gap: var(--horizontal-stack-card-gap, var(--stack-card-gap, 8px));
|
||||||
}
|
}
|
||||||
#root > hui-card {
|
#root > * {
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
#root > hui-card > * {
|
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -22,24 +21,11 @@ import { HomeAssistant } from "../../../types";
|
|||||||
import "../card-features/hui-card-features";
|
import "../card-features/hui-card-features";
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import {
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
LovelaceCard,
|
|
||||||
LovelaceCardEditor,
|
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
|
||||||
import { HumidifierCardConfig } from "./types";
|
import { HumidifierCardConfig } from "./types";
|
||||||
|
|
||||||
@customElement("hui-humidifier-card")
|
@customElement("hui-humidifier-card")
|
||||||
export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
||||||
private _resizeController = new ResizeController(this, {
|
|
||||||
callback: (entries) => {
|
|
||||||
const container = entries[0]?.target.shadowRoot?.querySelector(
|
|
||||||
".container"
|
|
||||||
) as HTMLElement | undefined;
|
|
||||||
return container?.clientHeight;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import("../editor/config-elements/hui-humidifier-card-editor");
|
await import("../editor/config-elements/hui-humidifier-card-editor");
|
||||||
return document.createElement("hui-humidifier-card-editor");
|
return document.createElement("hui-humidifier-card-editor");
|
||||||
@@ -137,25 +123,16 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
const color = stateColorCss(stateObj);
|
const color = stateColorCss(stateObj);
|
||||||
|
|
||||||
const controlMaxWidth = this._resizeController.value
|
|
||||||
? `${this._resizeController.value}px`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<p class="title">${name}</p>
|
<p class="title">${name}</p>
|
||||||
<div class="container">
|
<ha-state-control-humidifier-humidity
|
||||||
<ha-state-control-humidifier-humidity
|
prevent-interaction-on-scroll
|
||||||
style=${styleMap({
|
.showCurrentAsPrimary=${this._config.show_current_as_primary}
|
||||||
maxWidth: controlMaxWidth,
|
show-secondary
|
||||||
})}
|
.hass=${this.hass}
|
||||||
prevent-interaction-on-scroll
|
.stateObj=${stateObj}
|
||||||
.showCurrentAsPrimary=${this._config.show_current_as_primary}
|
></ha-state-control-humidifier-humidity>
|
||||||
show-secondary
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateObj=${stateObj}
|
|
||||||
></ha-state-control-humidifier-humidity>
|
|
||||||
</div>
|
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class="more-info"
|
class="more-info"
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
@@ -177,35 +154,12 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
const grid_columns = 4;
|
|
||||||
let grid_rows = 5;
|
|
||||||
let grid_min_rows = 2;
|
|
||||||
const grid_min_columns = 2;
|
|
||||||
if (this._config?.features?.length) {
|
|
||||||
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
|
|
||||||
grid_rows += featureHeight;
|
|
||||||
grid_min_rows += featureHeight;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
grid_columns,
|
|
||||||
grid_rows,
|
|
||||||
grid_min_rows,
|
|
||||||
grid_min_columns,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
ha-card {
|
ha-card {
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -224,28 +178,13 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
ha-state-control-humidifier-humidity {
|
||||||
display: flex;
|
width: 100%;
|
||||||
align-items: center;
|
max-width: 344px; /* 12px + 12px + 320px */
|
||||||
justify-content: center;
|
padding: 0 12px 12px 12px;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container:before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container > * {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-info {
|
.more-info {
|
||||||
@@ -262,7 +201,6 @@ export class HuiHumidifierCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
hui-card-features {
|
hui-card-features {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: none;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -7,11 +7,7 @@ import "../../../components/ha-alert";
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
import { IFRAME_SANDBOX } from "../../../util/iframe";
|
import { IFRAME_SANDBOX } from "../../../util/iframe";
|
||||||
import {
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
LovelaceCard,
|
|
||||||
LovelaceCardEditor,
|
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
|
||||||
import { IframeCardConfig } from "./types";
|
import { IframeCardConfig } from "./types";
|
||||||
|
|
||||||
@customElement("hui-iframe-card")
|
@customElement("hui-iframe-card")
|
||||||
@@ -32,9 +28,6 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public isPanel = false;
|
public isPanel = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
public layout?: string;
|
|
||||||
|
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@state() protected _config?: IframeCardConfig;
|
@state() protected _config?: IframeCardConfig;
|
||||||
@@ -63,16 +56,13 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let padding = "";
|
let padding = "";
|
||||||
const ignoreAspectRatio = this.isPanel || this.layout === "grid";
|
if (!this.isPanel && this._config.aspect_ratio) {
|
||||||
if (!ignoreAspectRatio) {
|
const ratio = parseAspectRatio(this._config.aspect_ratio);
|
||||||
if (this._config.aspect_ratio) {
|
if (ratio && ratio.w > 0 && ratio.h > 0) {
|
||||||
const ratio = parseAspectRatio(this._config.aspect_ratio);
|
padding = `${((100 * ratio.h) / ratio.w).toFixed(2)}%`;
|
||||||
if (ratio && ratio.w > 0 && ratio.h > 0) {
|
|
||||||
padding = `${((100 * ratio.h) / ratio.w).toFixed(2)}%`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
padding = "50%";
|
|
||||||
}
|
}
|
||||||
|
} else if (!this.isPanel) {
|
||||||
|
padding = "50%";
|
||||||
}
|
}
|
||||||
|
|
||||||
const target_protocol = new URL(this._config.url, location.toString())
|
const target_protocol = new URL(this._config.url, location.toString())
|
||||||
@@ -115,28 +105,26 @@ export class HuiIframeCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
return {
|
|
||||||
grid_columns: 4,
|
|
||||||
grid_rows: 4,
|
|
||||||
grid_min_rows: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
:host([ispanel]) ha-card {
|
||||||
overflow: hidden;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-card {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host([ispanel]) #root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
iframe {
|
iframe {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border: none;
|
border: none;
|
||||||
|
@@ -39,7 +39,7 @@ import { HomeAssistant } from "../../../types";
|
|||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { processConfigEntities } from "../common/process-config-entities";
|
import { processConfigEntities } from "../common/process-config-entities";
|
||||||
import { EntityConfig } from "../entity-rows/types";
|
import { EntityConfig } from "../entity-rows/types";
|
||||||
import { LovelaceCard, LovelaceLayoutOptions } from "../types";
|
import { LovelaceCard } from "../types";
|
||||||
import { MapCardConfig } from "./types";
|
import { MapCardConfig } from "./types";
|
||||||
|
|
||||||
export const DEFAULT_HOURS_TO_SHOW = 0;
|
export const DEFAULT_HOURS_TO_SHOW = 0;
|
||||||
@@ -57,9 +57,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
@property({ type: Boolean, reflect: true })
|
@property({ type: Boolean, reflect: true })
|
||||||
public isPanel = false;
|
public isPanel = false;
|
||||||
|
|
||||||
@property({ attribute: false })
|
|
||||||
public layout?: string;
|
|
||||||
|
|
||||||
@state() private _stateHistory?: HistoryStates;
|
@state() private _stateHistory?: HistoryStates;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@@ -300,9 +297,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
private _computePadding(): void {
|
private _computePadding(): void {
|
||||||
const root = this.shadowRoot!.getElementById("root");
|
const root = this.shadowRoot!.getElementById("root");
|
||||||
|
if (!this._config || this.isPanel || !root) {
|
||||||
const ignoreAspectRatio = this.isPanel || this.layout === "grid";
|
|
||||||
if (!this._config || ignoreAspectRatio || !root) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,15 +423,6 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
return {
|
|
||||||
grid_columns: 4,
|
|
||||||
grid_rows: 4,
|
|
||||||
grid_min_columns: 2,
|
|
||||||
grid_min_rows: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
|
@@ -76,8 +76,6 @@ class HuiSensorCard extends HuiEntityCard {
|
|||||||
return {
|
return {
|
||||||
grid_columns: 2,
|
grid_columns: 2,
|
||||||
grid_rows: 2,
|
grid_rows: 2,
|
||||||
grid_min_columns: 2,
|
|
||||||
grid_min_rows: 2,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -56,7 +56,7 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
|||||||
card.hass = this.hass;
|
card.hass = this.hass;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (changedProperties.has("preview")) {
|
if (changedProperties.has("editMode")) {
|
||||||
this._cards.forEach((card) => {
|
this._cards.forEach((card) => {
|
||||||
card.preview = this.preview;
|
card.preview = this.preview;
|
||||||
});
|
});
|
||||||
@@ -69,7 +69,6 @@ export abstract class HuiStackCard<T extends StackCardConfig = StackCardConfig>
|
|||||||
element.hass = this.hass;
|
element.hass = this.hass;
|
||||||
element.preview = this.preview;
|
element.preview = this.preview;
|
||||||
element.config = cardConfig;
|
element.config = cardConfig;
|
||||||
element.load();
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
|
html,
|
||||||
LitElement,
|
LitElement,
|
||||||
PropertyValues,
|
PropertyValues,
|
||||||
css,
|
|
||||||
html,
|
|
||||||
nothing,
|
nothing,
|
||||||
} from "lit";
|
} from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
@@ -16,12 +16,12 @@ import "../../../components/ha-alert";
|
|||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-state-icon";
|
import "../../../components/ha-state-icon";
|
||||||
import {
|
import {
|
||||||
StatisticsMetaData,
|
|
||||||
fetchStatistic,
|
fetchStatistic,
|
||||||
getDisplayUnit,
|
getDisplayUnit,
|
||||||
getStatisticLabel,
|
getStatisticLabel,
|
||||||
getStatisticMetadata,
|
getStatisticMetadata,
|
||||||
isExternalStatistic,
|
isExternalStatistic,
|
||||||
|
StatisticsMetaData,
|
||||||
} from "../../../data/recorder";
|
} from "../../../data/recorder";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { computeCardSize } from "../common/compute-card-size";
|
import { computeCardSize } from "../common/compute-card-size";
|
||||||
@@ -32,7 +32,6 @@ import {
|
|||||||
LovelaceCard,
|
LovelaceCard,
|
||||||
LovelaceCardEditor,
|
LovelaceCardEditor,
|
||||||
LovelaceHeaderFooter,
|
LovelaceHeaderFooter,
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { HuiErrorCard } from "./hui-error-card";
|
import { HuiErrorCard } from "./hui-error-card";
|
||||||
import { EntityCardConfig, StatisticCardConfig } from "./types";
|
import { EntityCardConfig, StatisticCardConfig } from "./types";
|
||||||
@@ -255,15 +254,6 @@ export class HuiStatisticCard extends LitElement implements LovelaceCard {
|
|||||||
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
fireEvent(this, "hass-more-info", { entityId: this._config!.entity });
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
return {
|
|
||||||
grid_columns: 2,
|
|
||||||
grid_rows: 2,
|
|
||||||
grid_min_columns: 2,
|
|
||||||
grid_min_rows: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
css`
|
css`
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
|
||||||
import { mdiDotsVertical } from "@mdi/js";
|
import { mdiDotsVertical } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@@ -19,27 +18,14 @@ import "../../../components/ha-icon-button";
|
|||||||
import { ClimateEntity } from "../../../data/climate";
|
import { ClimateEntity } from "../../../data/climate";
|
||||||
import "../../../state-control/climate/ha-state-control-climate-temperature";
|
import "../../../state-control/climate/ha-state-control-climate-temperature";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import "../card-features/hui-card-features";
|
|
||||||
import { findEntities } from "../common/find-entities";
|
import { findEntities } from "../common/find-entities";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import {
|
import "../card-features/hui-card-features";
|
||||||
LovelaceCard,
|
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
LovelaceCardEditor,
|
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
|
||||||
import { ThermostatCardConfig } from "./types";
|
import { ThermostatCardConfig } from "./types";
|
||||||
|
|
||||||
@customElement("hui-thermostat-card")
|
@customElement("hui-thermostat-card")
|
||||||
export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||||
private _resizeController = new ResizeController(this, {
|
|
||||||
callback: (entries) => {
|
|
||||||
const container = entries[0]?.target.shadowRoot?.querySelector(
|
|
||||||
".container"
|
|
||||||
) as HTMLElement | undefined;
|
|
||||||
return container?.clientHeight;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||||
await import("../editor/config-elements/hui-thermostat-card-editor");
|
await import("../editor/config-elements/hui-thermostat-card-editor");
|
||||||
return document.createElement("hui-thermostat-card-editor");
|
return document.createElement("hui-thermostat-card-editor");
|
||||||
@@ -129,25 +115,16 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
const color = stateColorCss(stateObj);
|
const color = stateColorCss(stateObj);
|
||||||
|
|
||||||
const controlMaxWidth = this._resizeController.value
|
|
||||||
? `${this._resizeController.value}px`
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
<p class="title">${name}</p>
|
<p class="title">${name}</p>
|
||||||
<div class="container">
|
<ha-state-control-climate-temperature
|
||||||
<ha-state-control-climate-temperature
|
prevent-interaction-on-scroll
|
||||||
style=${styleMap({
|
.showCurrentAsPrimary=${this._config.show_current_as_primary}
|
||||||
maxWidth: controlMaxWidth,
|
show-secondary
|
||||||
})}
|
.hass=${this.hass}
|
||||||
prevent-interaction-on-scroll
|
.stateObj=${stateObj}
|
||||||
.showCurrentAsPrimary=${this._config.show_current_as_primary}
|
></ha-state-control-climate-temperature>
|
||||||
show-secondary
|
|
||||||
.hass=${this.hass}
|
|
||||||
.stateObj=${stateObj}
|
|
||||||
></ha-state-control-climate-temperature>
|
|
||||||
</div>
|
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
class="more-info"
|
class="more-info"
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
@@ -169,35 +146,12 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
|
||||||
const grid_columns = 4;
|
|
||||||
let grid_rows = 5;
|
|
||||||
let grid_min_rows = 2;
|
|
||||||
const grid_min_columns = 2;
|
|
||||||
if (this._config?.features?.length) {
|
|
||||||
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
|
|
||||||
grid_rows += featureHeight;
|
|
||||||
grid_min_rows += featureHeight;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
grid_columns,
|
|
||||||
grid_rows,
|
|
||||||
grid_min_rows,
|
|
||||||
grid_min_columns,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
:host {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
ha-card {
|
ha-card {
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -216,28 +170,13 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
ha-state-control-climate-temperature {
|
||||||
display: flex;
|
width: 100%;
|
||||||
align-items: center;
|
max-width: 344px; /* 12px + 12px + 320px */
|
||||||
justify-content: center;
|
padding: 0 12px 12px 12px;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container:before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
padding-top: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container > * {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-info {
|
.more-info {
|
||||||
@@ -254,7 +193,6 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
hui-card-features {
|
hui-card-features {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: none;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -122,21 +122,17 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
public getLayoutOptions(): LovelaceLayoutOptions {
|
||||||
const grid_columns = 2;
|
const options = {
|
||||||
let grid_rows = 1;
|
grid_columns: 2,
|
||||||
|
grid_rows: 1,
|
||||||
|
};
|
||||||
if (this._config?.features?.length) {
|
if (this._config?.features?.length) {
|
||||||
const featureHeight = Math.ceil((this._config.features.length * 2) / 3);
|
options.grid_rows += Math.ceil((this._config.features.length * 2) / 3);
|
||||||
grid_rows += featureHeight;
|
|
||||||
}
|
}
|
||||||
if (this._config?.vertical) {
|
if (this._config?.vertical) {
|
||||||
grid_rows!++;
|
options.grid_rows++;
|
||||||
}
|
}
|
||||||
return {
|
return options;
|
||||||
grid_columns,
|
|
||||||
grid_rows,
|
|
||||||
grid_min_rows: grid_rows,
|
|
||||||
grid_min_columns: grid_columns,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleAction(ev: ActionHandlerEvent) {
|
private _handleAction(ev: ActionHandlerEvent) {
|
||||||
@@ -242,14 +238,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
></ha-relative-time>
|
></ha-relative-time>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
if (content === "last-updated") {
|
|
||||||
return html`
|
|
||||||
<ha-relative-time
|
|
||||||
.hass=${this.hass}
|
|
||||||
.datetime=${stateObj.last_updated}
|
|
||||||
></ha-relative-time>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
if (content === "last_triggered") {
|
if (content === "last_triggered") {
|
||||||
return html`
|
return html`
|
||||||
<ha-relative-time
|
<ha-relative-time
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
|
||||||
import {
|
import {
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
LitElement,
|
LitElement,
|
||||||
@@ -15,6 +14,7 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
|
|||||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||||
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
|
||||||
import { formatNumber } from "../../../common/number/format_number";
|
import { formatNumber } from "../../../common/number/format_number";
|
||||||
|
import { debounce } from "../../../common/util/debounce";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-svg-icon";
|
import "../../../components/ha-svg-icon";
|
||||||
import { UNAVAILABLE } from "../../../data/entity";
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
@@ -38,11 +38,7 @@ import { handleAction } from "../common/handle-action";
|
|||||||
import { hasAction } from "../common/has-action";
|
import { hasAction } from "../common/has-action";
|
||||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||||
import type {
|
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||||
LovelaceCard,
|
|
||||||
LovelaceCardEditor,
|
|
||||||
LovelaceLayoutOptions,
|
|
||||||
} from "../types";
|
|
||||||
import type { WeatherForecastCardConfig } from "./types";
|
import type { WeatherForecastCardConfig } from "./types";
|
||||||
|
|
||||||
@customElement("hui-weather-forecast-card")
|
@customElement("hui-weather-forecast-card")
|
||||||
@@ -78,21 +74,10 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@state() private _subscribed?: Promise<() => void>;
|
@state() private _subscribed?: Promise<() => void>;
|
||||||
|
|
||||||
private _sizeController = new ResizeController(this, {
|
// @todo Consider reworking to eliminate need for attribute since it is manipulated internally
|
||||||
callback: (entries) => {
|
@property({ type: Boolean, reflect: true }) public veryVeryNarrow = false;
|
||||||
const width = entries[0]?.contentRect.width;
|
|
||||||
if (width < 245) {
|
private _resizeObserver?: ResizeObserver;
|
||||||
return "very-very-narrow";
|
|
||||||
}
|
|
||||||
if (width < 300) {
|
|
||||||
return "very-narrow";
|
|
||||||
}
|
|
||||||
if (width < 375) {
|
|
||||||
return "narrow";
|
|
||||||
}
|
|
||||||
return "regular";
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
private _needForecastSubscription() {
|
private _needForecastSubscription() {
|
||||||
return (
|
return (
|
||||||
@@ -133,10 +118,14 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
if (this.hasUpdated && this._config && this.hass) {
|
if (this.hasUpdated && this._config && this.hass) {
|
||||||
this._subscribeForecastEvents();
|
this._subscribeForecastEvents();
|
||||||
}
|
}
|
||||||
|
this.updateComplete.then(() => this._attachObserver());
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnectedCallback(): void {
|
public disconnectedCallback(): void {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
|
if (this._resizeObserver) {
|
||||||
|
this._resizeObserver.disconnect();
|
||||||
|
}
|
||||||
this._unsubscribeForecastEvents();
|
this._unsubscribeForecastEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +159,16 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public willUpdate(): void {
|
||||||
|
if (!this.hasUpdated) {
|
||||||
|
this._measureCard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(): void {
|
||||||
|
this._attachObserver();
|
||||||
|
}
|
||||||
|
|
||||||
protected updated(changedProps: PropertyValues): void {
|
protected updated(changedProps: PropertyValues): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
@@ -227,10 +226,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
);
|
);
|
||||||
const forecast =
|
const forecast =
|
||||||
this._config?.show_forecast !== false && forecastData?.forecast?.length
|
this._config?.show_forecast !== false && forecastData?.forecast?.length
|
||||||
? forecastData.forecast.slice(
|
? forecastData.forecast.slice(0, this.veryVeryNarrow ? 3 : 5)
|
||||||
0,
|
|
||||||
this._sizeController.value === "very-very-narrow" ? 3 : 5
|
|
||||||
)
|
|
||||||
: undefined;
|
: undefined;
|
||||||
const weather = !forecast || this._config?.show_current !== false;
|
const weather = !forecast || this._config?.show_current !== false;
|
||||||
|
|
||||||
@@ -242,7 +238,6 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card
|
<ha-card
|
||||||
class=${ifDefined(this._sizeController.value)}
|
|
||||||
@action=${this._handleAction}
|
@action=${this._handleAction}
|
||||||
.actionHandler=${actionHandler({
|
.actionHandler=${actionHandler({
|
||||||
hasHold: hasAction(this._config!.hold_action),
|
hasHold: hasAction(this._config!.hold_action),
|
||||||
@@ -421,39 +416,52 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _showValue(item?: any): boolean {
|
private async _attachObserver(): Promise<void> {
|
||||||
return typeof item !== "undefined" && item !== null;
|
if (!this._resizeObserver) {
|
||||||
|
this._resizeObserver = new ResizeObserver(
|
||||||
|
debounce(() => this._measureCard(), 250, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const card = this.shadowRoot!.querySelector("ha-card");
|
||||||
|
// If we show an error or warning there is no ha-card
|
||||||
|
if (!card) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._resizeObserver.observe(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLayoutOptions(): LovelaceLayoutOptions {
|
private _measureCard() {
|
||||||
if (
|
if (!this.isConnected) {
|
||||||
this._config?.show_current !== false &&
|
return;
|
||||||
this._config?.show_forecast !== false
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
grid_columns: 4,
|
|
||||||
grid_min_columns: 2,
|
|
||||||
grid_rows: 3,
|
|
||||||
grid_min_rows: 3,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
grid_columns: 4,
|
const card = this.shadowRoot!.querySelector("ha-card");
|
||||||
grid_min_columns: 2,
|
// If we show an error or warning there is no ha-card
|
||||||
grid_rows: 2,
|
if (!card) {
|
||||||
grid_min_rows: 1,
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
if (card.offsetWidth < 375) {
|
||||||
|
this.setAttribute("narrow", "");
|
||||||
|
} else {
|
||||||
|
this.removeAttribute("narrow");
|
||||||
|
}
|
||||||
|
if (card.offsetWidth < 300) {
|
||||||
|
this.setAttribute("verynarrow", "");
|
||||||
|
} else {
|
||||||
|
this.removeAttribute("verynarrow");
|
||||||
|
}
|
||||||
|
this.veryVeryNarrow = card.offsetWidth < 245;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showValue(item?: any): boolean {
|
||||||
|
return typeof item !== "undefined" && item !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return [
|
return [
|
||||||
weatherSVGStyles,
|
weatherSVGStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
ha-card {
|
ha-card {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -604,48 +612,48 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
/* ============= NARROW ============= */
|
/* ============= NARROW ============= */
|
||||||
|
|
||||||
[class*="narrow"] .icon-image {
|
:host([narrow]) .icon-image {
|
||||||
min-width: 52px;
|
min-width: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="narrow"] .weather-image {
|
:host([narrow]) .weather-image {
|
||||||
flex: 0 0 52px;
|
flex: 0 0 52px;
|
||||||
width: 52px;
|
width: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="narrow"] .icon-image .weather-icon {
|
:host([narrow]) .icon-image .weather-icon {
|
||||||
--mdc-icon-size: 52px;
|
--mdc-icon-size: 52px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="narrow"] .state,
|
:host([narrow]) .state,
|
||||||
[class*="narrow"] .temp-attribute .temp {
|
:host([narrow]) .temp-attribute .temp {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="narrow"] .temp-attribute .temp {
|
:host([narrow]) .temp-attribute .temp {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
margin-inline-end: 16px;
|
margin-inline-end: 16px;
|
||||||
margin-inline-start: initial;
|
margin-inline-start: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="narrow"] .temp span {
|
:host([narrow]) .temp span {
|
||||||
top: 1px;
|
top: 1px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============= VERY NARROW ============= */
|
/* ============= VERY NARROW ============= */
|
||||||
|
|
||||||
[class*="very-narrow"] .name,
|
:host([veryNarrow]) .name,
|
||||||
[class*="very-narrow"] .attribute {
|
:host([veryNarrow]) .attribute {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="very-narrow"] .info {
|
:host([veryNarrow]) .info {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="very-narrow"] .name-state {
|
:host([veryNarrow]) .name-state {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
padding-inline-end: 0;
|
padding-inline-end: 0;
|
||||||
padding-inline-start: initial;
|
padding-inline-start: initial;
|
||||||
@@ -653,18 +661,18 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
/* ============= VERY VERY NARROW ============= */
|
/* ============= VERY VERY NARROW ============= */
|
||||||
|
|
||||||
[class*="very-very-narrow"] .info {
|
:host([veryVeryNarrow]) .info {
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="very-very-narrow"] .content {
|
:host([veryVeryNarrow]) .content {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*="very-very-narrow"] .icon-image {
|
:host([veryVeryNarrow]) .icon-image {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
margin-inline-end: 0;
|
margin-inline-end: 0;
|
||||||
margin-inline-start: initial;
|
margin-inline-start: initial;
|
||||||
|
@@ -255,14 +255,15 @@ export class HaGridLayoutSlider extends LitElement {
|
|||||||
>
|
>
|
||||||
<div id="slider" class="slider">
|
<div id="slider" class="slider">
|
||||||
<div class="track">
|
<div class="track">
|
||||||
<div class="background"></div>
|
<div class="background">
|
||||||
<div
|
<div
|
||||||
class="active"
|
class="active"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
"--min": `${this.min / this._range}`,
|
"--min": `${this.min / this._range}`,
|
||||||
"--max": `${1 - this.max / this._range}`,
|
"--max": `${1 - this.max / this._range}`,
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${this.value !== undefined
|
${this.value !== undefined
|
||||||
? html`<div class="handle"></div>`
|
? html`<div class="handle"></div>`
|
||||||
@@ -322,12 +323,11 @@ export class HaGridLayoutSlider extends LitElement {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: var(--disabled-color);
|
background: var(--disabled-color);
|
||||||
opacity: 0.2;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
.active {
|
.active {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: grey;
|
background: grey;
|
||||||
opacity: 0.7;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
right: calc(var(--max) * 100%);
|
right: calc(var(--max) * 100%);
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -375,9 +375,6 @@ export class HaGridLayoutSlider extends LitElement {
|
|||||||
:host(:disabled) .slider {
|
:host(:disabled) .slider {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
:host(:disabled) .handle:after {
|
|
||||||
background: var(--disabled-color);
|
|
||||||
}
|
|
||||||
.pressed .handle {
|
.pressed .handle {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
|||||||
import { haStyle } from "../../../../resources/styles";
|
import { haStyle } from "../../../../resources/styles";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { HuiCard } from "../../cards/hui-card";
|
import { HuiCard } from "../../cards/hui-card";
|
||||||
import { computeSizeOnGrid } from "../../sections/hui-grid-section";
|
import { DEFAULT_GRID_OPTIONS } from "../../sections/hui-grid-section";
|
||||||
import { LovelaceLayoutOptions } from "../../types";
|
import { LovelaceLayoutOptions } from "../../types";
|
||||||
|
|
||||||
@customElement("hui-card-layout-editor")
|
@customElement("hui-card-layout-editor")
|
||||||
@@ -38,29 +38,28 @@ export class HuiCardLayoutEditor extends LitElement {
|
|||||||
|
|
||||||
private _cardElement?: HuiCard;
|
private _cardElement?: HuiCard;
|
||||||
|
|
||||||
private _mergedOptions = memoizeOne(
|
private _gridSizeValue = memoizeOne(
|
||||||
(
|
(
|
||||||
options?: LovelaceLayoutOptions,
|
options?: LovelaceLayoutOptions,
|
||||||
defaultOptions?: LovelaceLayoutOptions
|
defaultOptions?: LovelaceLayoutOptions
|
||||||
) => ({
|
) => ({
|
||||||
...defaultOptions,
|
rows:
|
||||||
...options,
|
options?.grid_rows ??
|
||||||
|
defaultOptions?.grid_rows ??
|
||||||
|
DEFAULT_GRID_OPTIONS.grid_rows,
|
||||||
|
columns:
|
||||||
|
options?.grid_columns ??
|
||||||
|
defaultOptions?.grid_columns ??
|
||||||
|
DEFAULT_GRID_OPTIONS.grid_columns,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
private _gridSizeValue = memoizeOne(computeSizeOnGrid);
|
|
||||||
|
|
||||||
private _isDefault = memoizeOne(
|
private _isDefault = memoizeOne(
|
||||||
(options?: LovelaceLayoutOptions) =>
|
(options?: LovelaceLayoutOptions) =>
|
||||||
options?.grid_columns === undefined && options?.grid_rows === undefined
|
options?.grid_columns === undefined && options?.grid_rows === undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const options = this._mergedOptions(
|
|
||||||
this.config.layout_options,
|
|
||||||
this._defaultLayoutOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p class="intro">
|
<p class="intro">
|
||||||
@@ -124,13 +123,12 @@ export class HuiCardLayoutEditor extends LitElement {
|
|||||||
: html`
|
: html`
|
||||||
<ha-grid-size-picker
|
<ha-grid-size-picker
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.value=${this._gridSizeValue(options)}
|
.value=${this._gridSizeValue(
|
||||||
|
this.config.layout_options,
|
||||||
|
this._defaultLayoutOptions
|
||||||
|
)}
|
||||||
.isDefault=${this._isDefault(this.config.layout_options)}
|
.isDefault=${this._isDefault(this.config.layout_options)}
|
||||||
@value-changed=${this._gridSizeChanged}
|
@value-changed=${this._gridSizeChanged}
|
||||||
.rowMin=${options.grid_min_rows}
|
|
||||||
.rowMax=${options.grid_max_rows}
|
|
||||||
.columnMin=${options.grid_min_columns}
|
|
||||||
.columnMax=${options.grid_max_columns}
|
|
||||||
></ha-grid-size-picker>
|
></ha-grid-size-picker>
|
||||||
`}
|
`}
|
||||||
`;
|
`;
|
||||||
@@ -148,7 +146,6 @@ export class HuiCardLayoutEditor extends LitElement {
|
|||||||
this._defaultLayoutOptions =
|
this._defaultLayoutOptions =
|
||||||
this._cardElement?.getElementLayoutOptions();
|
this._cardElement?.getElementLayoutOptions();
|
||||||
});
|
});
|
||||||
this._cardElement.load();
|
|
||||||
this._defaultLayoutOptions = this._cardElement.getElementLayoutOptions();
|
this._defaultLayoutOptions = this._cardElement.getElementLayoutOptions();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@@ -16,7 +16,6 @@ import memoizeOne from "memoize-one";
|
|||||||
import { storage } from "../../../../common/decorators/storage";
|
import { storage } from "../../../../common/decorators/storage";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stringCompare } from "../../../../common/string/compare";
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
import { stripDiacritics } from "../../../../common/string/strip-diacritics";
|
|
||||||
import "../../../../components/ha-circular-progress";
|
import "../../../../components/ha-circular-progress";
|
||||||
import "../../../../components/search-input";
|
import "../../../../components/search-input";
|
||||||
import { isUnavailableState } from "../../../../data/entity";
|
import { isUnavailableState } from "../../../../data/entity";
|
||||||
@@ -29,7 +28,6 @@ import {
|
|||||||
getCustomCardEntry,
|
getCustomCardEntry,
|
||||||
} from "../../../../data/lovelace_custom_cards";
|
} from "../../../../data/lovelace_custom_cards";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { getStripDiacriticsFn } from "../../../../util/fuse";
|
|
||||||
import {
|
import {
|
||||||
calcUnusedEntities,
|
calcUnusedEntities,
|
||||||
computeUsedEntities,
|
computeUsedEntities,
|
||||||
@@ -88,10 +86,9 @@ export class HuiCardPicker extends LitElement {
|
|||||||
isCaseSensitive: false,
|
isCaseSensitive: false,
|
||||||
minMatchCharLength: Math.min(filter.length, 2),
|
minMatchCharLength: Math.min(filter.length, 2),
|
||||||
threshold: 0.2,
|
threshold: 0.2,
|
||||||
getFn: getStripDiacriticsFn,
|
|
||||||
};
|
};
|
||||||
const fuse = new Fuse(cards, options);
|
const fuse = new Fuse(cards, options);
|
||||||
cards = fuse.search(stripDiacritics(filter)).map((result) => result.item);
|
cards = fuse.search(filter).map((result) => result.item);
|
||||||
return cardElements.filter((cardElement: CardElement) =>
|
return cardElements.filter((cardElement: CardElement) =>
|
||||||
cards.includes(cardElement.card)
|
cards.includes(cardElement.card)
|
||||||
);
|
);
|
||||||
|
@@ -201,12 +201,6 @@ export class HuiTileCardEditor
|
|||||||
),
|
),
|
||||||
value: "last-changed",
|
value: "last-changed",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: localize(
|
|
||||||
`ui.panel.lovelace.editor.card.tile.state_content_options.last-updated`
|
|
||||||
),
|
|
||||||
value: "last-updated",
|
|
||||||
},
|
|
||||||
...Object.keys(stateObj?.attributes ?? {})
|
...Object.keys(stateObj?.attributes ?? {})
|
||||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||||
.map((attribute) => ({
|
.map((attribute) => ({
|
||||||
|
@@ -15,7 +15,6 @@ import { HuiCard } from "../cards/hui-card";
|
|||||||
import "../components/hui-card-edit-mode";
|
import "../components/hui-card-edit-mode";
|
||||||
import { moveCard } from "../editor/config-util";
|
import { moveCard } from "../editor/config-util";
|
||||||
import type { Lovelace, LovelaceLayoutOptions } from "../types";
|
import type { Lovelace, LovelaceLayoutOptions } from "../types";
|
||||||
import { conditionalClamp } from "../../../common/number/clamp";
|
|
||||||
|
|
||||||
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
||||||
delay: 100,
|
delay: 100,
|
||||||
@@ -24,41 +23,9 @@ const CARD_SORTABLE_OPTIONS: HaSortableOptions = {
|
|||||||
invertedSwapThreshold: 0.7,
|
invertedSwapThreshold: 0.7,
|
||||||
} as HaSortableOptions;
|
} as HaSortableOptions;
|
||||||
|
|
||||||
export const DEFAULT_GRID_OPTIONS = {
|
export const DEFAULT_GRID_OPTIONS: LovelaceLayoutOptions = {
|
||||||
grid_columns: 4,
|
grid_columns: 4,
|
||||||
grid_rows: 1,
|
grid_rows: 1,
|
||||||
} as const satisfies LovelaceLayoutOptions;
|
|
||||||
|
|
||||||
type GridSizeValue = {
|
|
||||||
rows?: number;
|
|
||||||
columns?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const computeSizeOnGrid = (
|
|
||||||
options: LovelaceLayoutOptions
|
|
||||||
): GridSizeValue => {
|
|
||||||
const rows =
|
|
||||||
typeof options.grid_rows === "number"
|
|
||||||
? conditionalClamp(
|
|
||||||
options.grid_rows,
|
|
||||||
options.grid_min_rows,
|
|
||||||
options.grid_max_rows
|
|
||||||
)
|
|
||||||
: DEFAULT_GRID_OPTIONS.grid_rows;
|
|
||||||
|
|
||||||
const columns =
|
|
||||||
typeof options.grid_columns === "number"
|
|
||||||
? conditionalClamp(
|
|
||||||
options.grid_columns,
|
|
||||||
options.grid_min_columns,
|
|
||||||
options.grid_max_columns
|
|
||||||
)
|
|
||||||
: DEFAULT_GRID_OPTIONS.grid_columns;
|
|
||||||
|
|
||||||
return {
|
|
||||||
rows,
|
|
||||||
columns,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class GridSection extends LitElement implements LovelaceSectionElement {
|
export class GridSection extends LitElement implements LovelaceSectionElement {
|
||||||
@@ -131,16 +98,17 @@ export class GridSection extends LitElement implements LovelaceSectionElement {
|
|||||||
(cardConfig) => this._getKey(cardConfig),
|
(cardConfig) => this._getKey(cardConfig),
|
||||||
(_cardConfig, idx) => {
|
(_cardConfig, idx) => {
|
||||||
const card = this.cards![idx];
|
const card = this.cards![idx];
|
||||||
card.layout = "grid";
|
|
||||||
const layoutOptions = card.getLayoutOptions();
|
const layoutOptions = card.getLayoutOptions();
|
||||||
|
|
||||||
const { rows, columns } = computeSizeOnGrid(layoutOptions);
|
const columnSize =
|
||||||
|
layoutOptions.grid_columns ?? DEFAULT_GRID_OPTIONS.grid_columns;
|
||||||
|
const rowSize =
|
||||||
|
layoutOptions.grid_rows ?? DEFAULT_GRID_OPTIONS.grid_rows;
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
"--column-size": columns,
|
"--column-size": columnSize,
|
||||||
"--row-size": rows,
|
"--row-size": rowSize,
|
||||||
})}
|
})}
|
||||||
class="card ${classMap({
|
class="card ${classMap({
|
||||||
"fit-rows": typeof layoutOptions?.grid_rows === "number",
|
"fit-rows": typeof layoutOptions?.grid_rows === "number",
|
||||||
|
@@ -64,7 +64,6 @@ export class HuiSection extends ReactiveElement {
|
|||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this._cards = [...this._cards];
|
this._cards = [...this._cards];
|
||||||
});
|
});
|
||||||
element.load();
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user