mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
Merge branch 'dev' into break-out-assist-chat
This commit is contained in:
commit
199b7d9bc3
4
.github/workflows/ci.yaml
vendored
4
.github/workflows/ci.yaml
vendored
@ -89,7 +89,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: frontend-bundle-stats
|
||||
path: build/stats/*.json
|
||||
@ -113,7 +113,7 @@ jobs:
|
||||
env:
|
||||
IS_TEST: "true"
|
||||
- name: Upload bundle stats
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: supervisor-bundle-stats
|
||||
path: build/stats/*.json
|
||||
|
4
.github/workflows/nightly.yaml
vendored
4
.github/workflows/nightly.yaml
vendored
@ -57,14 +57,14 @@ jobs:
|
||||
run: tar -czvf translations.tar.gz translations
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: wheels
|
||||
path: dist/home_assistant_frontend*.whl
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload translations
|
||||
uses: actions/upload-artifact@v4.3.4
|
||||
uses: actions/upload-artifact@v4.3.6
|
||||
with:
|
||||
name: translations
|
||||
path: translations.tar.gz
|
||||
|
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@v2.1.11
|
||||
uses: relative-ci/agent-action@v2.1.12
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
894
.yarn/releases/yarn-4.3.1.cjs
vendored
894
.yarn/releases/yarn-4.3.1.cjs
vendored
File diff suppressed because one or more lines are too long
925
.yarn/releases/yarn-4.4.0.cjs
vendored
Executable file
925
.yarn/releases/yarn-4.4.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
@ -6,4 +6,4 @@ enableGlobalCache: false
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.4.0.cjs
|
||||
|
@ -29,7 +29,7 @@ const compressDistZopfli = (rootDir, modernDir) =>
|
||||
[
|
||||
`${rootDir}/**/${filesGlob}`,
|
||||
`!${modernDir}/**/${filesGlob}`,
|
||||
`!${rootDir}/sw-modern.js`,
|
||||
`!${rootDir}/{sw-modern,service_worker}.js`,
|
||||
`${rootDir}/{authorize,onboarding}.html`,
|
||||
],
|
||||
{ base: rootDir }
|
||||
|
@ -1,35 +1,76 @@
|
||||
// Tasks to generate entry HTML
|
||||
|
||||
import { getUserAgentRegex } from "browserslist-useragent-regexp";
|
||||
import {
|
||||
applyVersionsToRegexes,
|
||||
compileRegex,
|
||||
getPreUserAgentRegexes,
|
||||
} from "browserslist-useragent-regexp";
|
||||
import fs from "fs-extra";
|
||||
import gulp from "gulp";
|
||||
import { minify } from "html-minifier-terser";
|
||||
import template from "lodash.template";
|
||||
import path from "path";
|
||||
import { dirname, extname, resolve } from "node:path";
|
||||
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
|
||||
import env from "../env.cjs";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
// macOS companion app has no way to obtain the Safari version used by WKWebView,
|
||||
// and it is not in the default user agent string. So we add an additional regex
|
||||
// to serve modern based on a minimum macOS version. We take the minimum Safari
|
||||
// major version from browserslist and manually map that to a supported macOS
|
||||
// version. Note this assumes the user has kept Safari updated.
|
||||
const HA_MACOS_REGEX =
|
||||
/Home Assistant\/[\d.]+ \(.+; macOS (\d+)\.(\d+)(?:\.(\d+))?\)/;
|
||||
const SAFARI_TO_MACOS = {
|
||||
15: [10, 15, 0],
|
||||
16: [11, 0, 0],
|
||||
17: [12, 0, 0],
|
||||
18: [13, 0, 0],
|
||||
};
|
||||
|
||||
const getCommonTemplateVars = () => {
|
||||
const browserRegexes = getPreUserAgentRegexes({
|
||||
env: "modern",
|
||||
allowHigherVersions: true,
|
||||
mobileToDesktop: true,
|
||||
throwOnMissing: true,
|
||||
});
|
||||
const minSafariVersion = browserRegexes.find(
|
||||
(regex) => regex.family === "safari"
|
||||
)?.matchedVersions[0][0];
|
||||
const minMacOSVersion = SAFARI_TO_MACOS[minSafariVersion];
|
||||
if (!minMacOSVersion) {
|
||||
throw Error(
|
||||
`Could not find minimum MacOS version for Safari ${minSafariVersion}.`
|
||||
);
|
||||
}
|
||||
const haMacOSRegex = applyVersionsToRegexes(
|
||||
[
|
||||
{
|
||||
family: "ha_macos",
|
||||
regex: HA_MACOS_REGEX,
|
||||
matchedVersions: [minMacOSVersion],
|
||||
requestVersions: [minMacOSVersion],
|
||||
},
|
||||
],
|
||||
{ ignorePatch: true, allowHigherVersions: true }
|
||||
);
|
||||
return {
|
||||
useRollup: env.useRollup(),
|
||||
useWDS: env.useWDS(),
|
||||
modernRegex: compileRegex(browserRegexes.concat(haMacOSRegex)).toString(),
|
||||
};
|
||||
};
|
||||
|
||||
const renderTemplate = (templateFile, data = {}) => {
|
||||
const compiled = template(
|
||||
fs.readFileSync(templateFile, { encoding: "utf-8" })
|
||||
);
|
||||
return compiled({
|
||||
...data,
|
||||
useRollup: env.useRollup(),
|
||||
useWDS: env.useWDS(),
|
||||
modernRegex: getUserAgentRegex({
|
||||
env: "modern",
|
||||
allowHigherVersions: true,
|
||||
mobileToDesktop: true,
|
||||
throwOnMissing: true,
|
||||
}).toString(),
|
||||
// Resolve any child/nested templates relative to the parent and pass the same data
|
||||
renderTemplate: (childTemplate) =>
|
||||
renderTemplate(
|
||||
path.resolve(path.dirname(templateFile), childTemplate),
|
||||
data
|
||||
),
|
||||
renderTemplate(resolve(dirname(templateFile), childTemplate), data),
|
||||
});
|
||||
};
|
||||
|
||||
@ -63,10 +104,12 @@ const genPagesDevTask =
|
||||
publicRoot = ""
|
||||
) =>
|
||||
async () => {
|
||||
const commonVars = getCommonTemplateVars();
|
||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||
const content = renderTemplate(
|
||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map((entry) =>
|
||||
useWDS
|
||||
? `http://localhost:8000/src/entrypoints/${entry}.ts`
|
||||
@ -81,7 +124,7 @@ const genPagesDevTask =
|
||||
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
|
||||
}
|
||||
);
|
||||
fs.outputFileSync(path.resolve(outputRoot, page), content);
|
||||
fs.outputFileSync(resolve(outputRoot, page), content);
|
||||
}
|
||||
};
|
||||
|
||||
@ -98,16 +141,18 @@ const genPagesProdTask =
|
||||
) =>
|
||||
async () => {
|
||||
const latestManifest = fs.readJsonSync(
|
||||
path.resolve(outputLatest, "manifest.json")
|
||||
resolve(outputLatest, "manifest.json")
|
||||
);
|
||||
const es5Manifest = outputES5
|
||||
? fs.readJsonSync(path.resolve(outputES5, "manifest.json"))
|
||||
? fs.readJsonSync(resolve(outputES5, "manifest.json"))
|
||||
: {};
|
||||
const commonVars = getCommonTemplateVars();
|
||||
const minifiedHTML = [];
|
||||
for (const [page, entries] of Object.entries(pageEntries)) {
|
||||
const content = renderTemplate(
|
||||
path.resolve(inputRoot, inputSub, `${page}.template`),
|
||||
resolve(inputRoot, inputSub, `${page}.template`),
|
||||
{
|
||||
...commonVars,
|
||||
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
|
||||
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
@ -115,8 +160,8 @@ const genPagesProdTask =
|
||||
}
|
||||
);
|
||||
minifiedHTML.push(
|
||||
minifyHtml(content, path.extname(page)).then((minified) =>
|
||||
fs.outputFileSync(path.resolve(outputRoot, page), minified)
|
||||
minifyHtml(content, extname(page)).then((minified) =>
|
||||
fs.outputFileSync(resolve(outputRoot, page), minified)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
import { deleteAsync } from "del";
|
||||
import gulp from "gulp";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { join, relative } from "node:path";
|
||||
import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
|
||||
import { basename, join, relative } from "node:path";
|
||||
import { injectManifest } from "workbox-build";
|
||||
import paths from "../paths.cjs";
|
||||
|
||||
@ -41,10 +41,11 @@ gulp.task("gen-service-worker-app-prod", () =>
|
||||
await readFile(join(outPath, "manifest.json"), "utf-8")
|
||||
);
|
||||
const swSrc = join(paths.app_output_root, manifest["service-worker.js"]);
|
||||
const swDest = join(paths.app_output_root, `sw-${build}.js`);
|
||||
const buildDir = relative(paths.app_output_root, outPath);
|
||||
const { warnings } = await injectManifest({
|
||||
swSrc,
|
||||
swDest: join(paths.app_output_root, `sw-${build}.js`),
|
||||
swDest,
|
||||
injectionPoint: "__WB_MANIFEST__",
|
||||
// Files that mach this pattern will be considered unique and skip revision check
|
||||
// ignore JS files + translation files
|
||||
@ -76,6 +77,11 @@ gulp.task("gen-service-worker-app-prod", () =>
|
||||
);
|
||||
}
|
||||
await deleteAsync(`${swSrc}?(.map)`);
|
||||
// Needed to install new SW from a cached HTML
|
||||
if (build === "modern") {
|
||||
const swOld = join(paths.app_output_root, "service_worker.js");
|
||||
await symlink(basename(swDest), swOld);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
@ -63,33 +63,42 @@
|
||||
align-items: center;
|
||||
}
|
||||
#ha-launch-screen svg {
|
||||
width: 170px;
|
||||
width: 112px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer {
|
||||
#ha-launch-screen .ha-launch-screen-spacer-top {
|
||||
flex: 1;
|
||||
margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px );
|
||||
padding-top: 48px;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
||||
flex: 1;
|
||||
padding-top: 48px;
|
||||
}
|
||||
.ohf-logo {
|
||||
color: grey;
|
||||
font-size: 12px;
|
||||
margin-bottom: 16px;
|
||||
margin: max(env(safe-area-inset-bottom), 48px) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: .66;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ohf-logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ha-launch-screen">
|
||||
<div class="ha-launch-screen-spacer"></div>
|
||||
<div class="ha-launch-screen-spacer-top"></div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
||||
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
|
||||
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
|
||||
</svg>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer-bottom"></div>
|
||||
<div class="ohf-logo">
|
||||
a project from
|
||||
<img src="/static/icons/ohf.svg" alt="Open Home Foundation" height="32">
|
||||
<img src="/static/images/ohf-badge.svg" alt="Home Assistant is a project by the Open Home Foundation" height="46">
|
||||
</div>
|
||||
</div>
|
||||
<ha-demo></ha-demo>
|
||||
|
@ -532,15 +532,6 @@ export default {
|
||||
last_changed: "2018-07-19T10:44:46.200946+00:00",
|
||||
last_updated: "2018-07-19T10:44:46.200946+00:00",
|
||||
},
|
||||
"mailbox.demomailbox": {
|
||||
entity_id: "mailbox.demomailbox",
|
||||
state: "10",
|
||||
attributes: {
|
||||
friendly_name: "DemoMailbox",
|
||||
},
|
||||
last_changed: "2018-07-19T10:45:16.555210+00:00",
|
||||
last_updated: "2018-07-19T10:45:16.555210+00:00",
|
||||
},
|
||||
"input_select.living_room_preset": {
|
||||
entity_id: "input_select.living_room_preset",
|
||||
state: "Visitors",
|
||||
|
@ -8,6 +8,7 @@ import { getEntity } from "../../../../src/fake_data/entity";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import "../../components/demo-cards";
|
||||
import { mockIcons } from "../../../../demo/src/stubs/icons";
|
||||
import { ClimateEntityFeature } from "../../../../src/data/climate";
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("switch", "tv_outlet", "on", {
|
||||
@ -60,6 +61,36 @@ const ENTITIES = [
|
||||
CoverEntityFeature.OPEN_TILT +
|
||||
CoverEntityFeature.STOP_TILT,
|
||||
}),
|
||||
getEntity("input_number", "counter", "1.0", {
|
||||
friendly_name: "Counter",
|
||||
initial: 0,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
mode: "slider",
|
||||
}),
|
||||
getEntity("climate", "dual_thermostat", "heat/cool", {
|
||||
friendly_name: "Dual thermostat",
|
||||
hvac_modes: ["off", "cool", "heat_cool", "auto", "dry", "fan_only"],
|
||||
min_temp: 7,
|
||||
max_temp: 35,
|
||||
fan_modes: ["on_low", "on_high", "auto_low", "auto_high", "off"],
|
||||
preset_modes: ["home", "eco", "away"],
|
||||
swing_modes: ["auto", "1", "2", "3", "off"],
|
||||
current_temperature: 23,
|
||||
target_temp_high: 24,
|
||||
target_temp_low: 21,
|
||||
fan_mode: "auto_low",
|
||||
preset_mode: "home",
|
||||
swing_mode: "auto",
|
||||
supported_features:
|
||||
ClimateEntityFeature.TURN_ON +
|
||||
ClimateEntityFeature.TURN_OFF +
|
||||
ClimateEntityFeature.SWING_MODE +
|
||||
ClimateEntityFeature.PRESET_MODE +
|
||||
ClimateEntityFeature.FAN_MODE +
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE,
|
||||
}),
|
||||
];
|
||||
|
||||
const CONFIGS = [
|
||||
@ -193,6 +224,25 @@ const CONFIGS = [
|
||||
- type: "cover-tilt"
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Number buttons feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: input_number.counter
|
||||
features:
|
||||
- type: numeric-input
|
||||
style: buttons
|
||||
`,
|
||||
},
|
||||
{
|
||||
heading: "Dual thermostat feature",
|
||||
config: `
|
||||
- type: tile
|
||||
entity: climate.dual_thermostat
|
||||
features:
|
||||
- type: target-temperature
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("demo-lovelace-tile-card")
|
||||
|
58
package.json
58
package.json
@ -25,15 +25,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.25.0",
|
||||
"@babel/runtime": "7.25.4",
|
||||
"@braintree/sanitize-url": "7.1.0",
|
||||
"@codemirror/autocomplete": "6.17.0",
|
||||
"@codemirror/autocomplete": "6.18.0",
|
||||
"@codemirror/commands": "6.6.0",
|
||||
"@codemirror/language": "6.10.2",
|
||||
"@codemirror/legacy-modes": "6.4.0",
|
||||
"@codemirror/legacy-modes": "6.4.1",
|
||||
"@codemirror/search": "6.5.6",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.29.1",
|
||||
"@codemirror/view": "6.32.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.5",
|
||||
"@formatjs/intl-displaynames": "6.6.8",
|
||||
@ -49,11 +49,11 @@
|
||||
"@fullcalendar/list": "6.1.15",
|
||||
"@fullcalendar/luxon3": "6.1.15",
|
||||
"@fullcalendar/timegrid": "6.1.15",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.7",
|
||||
"@lit-labs/observers": "2.0.2",
|
||||
"@lit-labs/virtualizer": "2.0.13",
|
||||
"@lit-labs/virtualizer": "2.0.14",
|
||||
"@lrnwebcomponents/simple-tooltip": "8.0.2",
|
||||
"@material/chips": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/data-table": "=14.0.0-canary.53b3cad2f.0",
|
||||
@ -80,7 +80,7 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "2.0.0",
|
||||
"@material/web": "2.1.0",
|
||||
"@mdi/js": "7.4.47",
|
||||
"@mdi/svg": "7.4.47",
|
||||
"@polymer/paper-item": "3.0.1",
|
||||
@ -88,8 +88,8 @@
|
||||
"@polymer/paper-tabs": "3.1.0",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.4.4",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.4",
|
||||
"@vaadin/combo-box": "24.4.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.5",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@ -97,10 +97,10 @@
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "4.4.3",
|
||||
"chart.js": "4.4.4",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.37.1",
|
||||
"core-js": "3.38.1",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "3.6.0",
|
||||
"date-fns-tz": "3.1.3",
|
||||
@ -117,20 +117,20 @@
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.4",
|
||||
"marked": "13.0.3",
|
||||
"luxon": "3.5.0",
|
||||
"marked": "14.0.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
"punycode": "2.3.1",
|
||||
"qr-scanner": "1.4.2",
|
||||
"qrcode": "1.5.3",
|
||||
"qrcode": "1.5.4",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.2",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "2.1.0",
|
||||
"tinykeys": "3.0.0",
|
||||
"tsparticles-engine": "2.12.0",
|
||||
"tsparticles-preset-links": "2.12.0",
|
||||
"ua-parser-js": "1.0.38",
|
||||
@ -149,18 +149,18 @@
|
||||
"xss": "1.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.24.9",
|
||||
"@babel/core": "7.25.2",
|
||||
"@babel/helper-define-polyfill-provider": "0.6.2",
|
||||
"@babel/plugin-proposal-decorators": "7.24.7",
|
||||
"@babel/plugin-transform-runtime": "7.24.7",
|
||||
"@babel/preset-env": "7.25.0",
|
||||
"@babel/plugin-transform-runtime": "7.25.4",
|
||||
"@babel/preset-env": "7.25.4",
|
||||
"@babel/preset-typescript": "7.24.7",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.13.4",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.14.2",
|
||||
"@koa/cors": "5.0.0",
|
||||
"@lokalise/node-api": "12.7.0",
|
||||
"@octokit/auth-oauth-device": "7.1.1",
|
||||
"@octokit/plugin-retry": "7.1.1",
|
||||
"@octokit/rest": "21.0.1",
|
||||
"@octokit/rest": "21.0.2",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.4",
|
||||
"@rollup/plugin-commonjs": "26.0.1",
|
||||
@ -168,7 +168,7 @@
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.16",
|
||||
"@types/chromecast-caf-receiver": "6.0.17",
|
||||
"@types/chromecast-caf-sender": "1.0.10",
|
||||
"@types/color-name": "1.1.4",
|
||||
"@types/glob": "8.1.0",
|
||||
@ -185,8 +185,8 @@
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||
"@typescript-eslint/parser": "7.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "7.18.0",
|
||||
"@typescript-eslint/parser": "7.18.0",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
@ -202,8 +202,8 @@
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-lit": "1.14.0",
|
||||
"eslint-plugin-lit-a11y": "4.1.4",
|
||||
"eslint-plugin-unused-imports": "4.0.1",
|
||||
"eslint-plugin-wc": "2.1.0",
|
||||
"eslint-plugin-unused-imports": "4.1.3",
|
||||
"eslint-plugin-wc": "2.1.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "11.0.0",
|
||||
@ -213,10 +213,10 @@
|
||||
"gulp-rename": "2.0.0",
|
||||
"gulp-zopfli-green": "6.0.2",
|
||||
"html-minifier-terser": "7.2.0",
|
||||
"husky": "9.1.4",
|
||||
"husky": "9.1.5",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.2.7",
|
||||
"lint-staged": "15.2.9",
|
||||
"lit-analyzer": "2.0.3",
|
||||
"lodash.merge": "4.6.2",
|
||||
"lodash.template": "4.5.0",
|
||||
@ -239,7 +239,7 @@
|
||||
"transform-async-modules-webpack-plugin": "1.1.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.5.4",
|
||||
"webpack": "5.93.0",
|
||||
"webpack": "5.94.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
"webpack-dev-server": "5.0.4",
|
||||
"webpack-manifest-plugin": "5.0.0",
|
||||
@ -258,5 +258,5 @@
|
||||
"sortablejs@1.15.2": "patch:sortablejs@npm%3A1.15.2#~/.yarn/patches/sortablejs-npm-1.15.2-73347ae85a.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
},
|
||||
"packageManager": "yarn@4.3.1"
|
||||
"packageManager": "yarn@4.4.0"
|
||||
}
|
||||
|
66
public/static/images/ohf-badge.svg
Normal file
66
public/static/images/ohf-badge.svg
Normal file
@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="b" data-name="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 760.69 138.69">
|
||||
<g id="c" data-name="Layer_1">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M136.22,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM136.27,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M184.16,80.53c0,3.47-1.06,6.27-3.18,8.41s-4.98,3.21-8.59,3.21h-7.45v12h-6.56v-35.18h14.06c3.64,0,6.5,1.04,8.59,3.11s3.13,4.89,3.13,8.45ZM177.25,80.39c0-1.64-.52-2.98-1.56-4.03s-2.52-1.57-4.44-1.57h-6.3v11.65h6.26c1.95,0,3.45-.55,4.49-1.65s1.56-2.57,1.56-4.39Z"/>
|
||||
<path d="M210.82,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
|
||||
<path d="M246.95,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||
<path d="M266.45,68.98h6.56v14.44l14.7.05v-14.48h6.63v35.18h-6.63v-14.84l-14.7-.09v14.93h-6.56v-35.18Z"/>
|
||||
<path d="M316.41,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM316.46,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M373.66,68.98v35.18h-6.45v-20.55l-8.11,20.55h-6.23l-8.02-20.39v20.39h-6.28v-35.18h6.28l11.13,27.54,11.23-27.54h6.45Z"/>
|
||||
<path d="M402.87,98.02v6.14h-22.03v-35.18h21.98v6.19h-15.42v8.3h13.78v5.81h-13.78v8.74h15.47Z"/>
|
||||
<path d="M427.83,75.12v8.93h13.01l-.05,5.91h-12.96v14.2h-6.52v-35.18h21.98l-.05,6.14h-15.42Z"/>
|
||||
<path d="M463.16,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM463.21,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M485,68.98h6.56v22.12c0,2.31.72,4.12,2.16,5.43s3.3,1.96,5.58,1.96,4.08-.67,5.58-2.02,2.25-3.13,2.25-5.37v-22.12h6.52v22.31c0,2.08-.38,3.98-1.14,5.7s-1.79,3.14-3.09,4.25-2.82,1.98-4.56,2.59-3.59.91-5.55.91c-2.59,0-4.96-.52-7.1-1.55s-3.88-2.58-5.2-4.65-1.99-4.49-1.99-7.25v-22.31Z"/>
|
||||
<path d="M549.63,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||
<path d="M586.9,86.58c.05,3.34-.71,6.37-2.27,9.08s-3.7,4.82-6.42,6.32-5.73,2.23-9.02,2.18h-12.42v-35.18h12.42c2.45-.03,4.78.39,6.98,1.28s4.1,2.1,5.68,3.66,2.84,3.43,3.75,5.64,1.35,4.55,1.3,7.03ZM579.99,86.58c0-3.39-1-6.16-3.01-8.3s-4.62-3.21-7.84-3.21h-5.81v23.04h5.81c3.27,0,5.89-1.06,7.88-3.19s2.98-4.91,2.98-8.34Z"/>
|
||||
<path d="M609.16,96.19h-12.73l-2.79,7.97h-6.82l12.68-35.18h6.63l12.66,35.18h-6.96l-2.67-7.97ZM607.24,90.73l-4.43-12.87-4.45,12.87h8.88Z"/>
|
||||
<path d="M642.87,75.17h-9.89v28.99h-6.56v-28.99h-9.94v-6.19h26.39v6.19Z"/>
|
||||
<path d="M647.06,104.16v-35.18h6.56v35.18h-6.56Z"/>
|
||||
<path d="M675.71,68.35c3.31-.05,6.29.72,8.92,2.31s4.67,3.77,6.11,6.55,2.14,5.89,2.11,9.33c.03,3.44-.68,6.55-2.12,9.34s-3.49,4.98-6.13,6.56-5.62,2.36-8.93,2.31c-3.33,0-6.29-.78-8.88-2.33s-4.6-3.71-6.02-6.48-2.13-5.9-2.13-9.4c-.03-2.56.38-4.98,1.22-7.24s2.01-4.21,3.5-5.82,3.31-2.88,5.45-3.8,4.45-1.36,6.91-1.32ZM675.76,98.35c3.09,0,5.56-1.07,7.41-3.2s2.77-5,2.77-8.61-.91-6.52-2.73-8.65-4.3-3.19-7.44-3.19-5.63,1.06-7.46,3.19-2.75,5.01-2.75,8.65.91,6.52,2.74,8.64,4.32,3.18,7.48,3.18Z"/>
|
||||
<path d="M726.96,68.98v35.18h-6.49l-16.08-24.77v24.77h-6.52v-35.18h6.52l16.08,24.77v-24.77h6.49Z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M94.34,79.34c0,2.75-2.25,5-5,5h-50c-2.75,0-5-2.25-5-5v-20c0-2.75,1.59-6.59,3.54-8.54l22.93-22.93c1.94-1.94,5.13-1.94,7.07,0l22.93,22.93c1.94,1.94,3.54,5.79,3.54,8.54v20Z"/>
|
||||
<g>
|
||||
<rect x="34.34" y="94.34" width="60" height="10" rx="2.5" ry="2.5"/>
|
||||
<rect x="34.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
|
||||
<rect x="84.34" y="94.34" width="10" height="20" rx="1.56" ry="1.56"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M735.34,3c12.32,0,22.34,10.02,22.34,22.34v88c0,12.32-10.02,22.34-22.34,22.34H25.34c-12.32,0-22.34-10.02-22.34-22.34V25.34C3,13.02,13.02,3,25.34,3h710M735.34,0H25.34C11.37,0,0,11.37,0,25.34v88c0,13.98,11.37,25.34,25.34,25.34h710c13.97,0,25.34-11.37,25.34-25.34V25.34c0-13.98-11.37-25.34-25.34-25.34h0Z"/>
|
||||
<g>
|
||||
<path d="M120.98,36.79h2.95v7.26l7.66.02v-7.29h2.97v17.37h-2.97v-7.47l-7.66-.02v7.49h-2.95v-17.37Z"/>
|
||||
<path d="M146.97,36.47c1.63,0,3.09.39,4.37,1.16s2.28,1.84,2.99,3.2,1.06,2.9,1.06,4.61c.02,1.7-.32,3.24-1.04,4.62s-1.72,2.47-3.02,3.25-2.75,1.16-4.36,1.14c-1.62.02-3.08-.36-4.37-1.14s-2.29-1.86-3-3.24-1.05-2.91-1.03-4.61c0-1.27.2-2.47.61-3.58s.99-2.08,1.72-2.88,1.63-1.42,2.68-1.88,2.18-.67,3.39-.66ZM146.99,51.57c1.6,0,2.89-.56,3.85-1.67s1.45-2.6,1.45-4.45-.48-3.32-1.45-4.43-2.25-1.66-3.85-1.66-2.89.55-3.86,1.66-1.45,2.58-1.45,4.43.48,3.34,1.44,4.46,2.25,1.67,3.88,1.67Z"/>
|
||||
<path d="M176.51,36.79v17.37h-2.89v-10.78l-4.29,10.78h-2.81l-4.25-10.71v10.71h-2.84v-17.37h2.84l5.66,13.92,5.69-13.92h2.89Z"/>
|
||||
<path d="M192.41,51.37v2.79h-10.78v-17.37h10.78v2.81h-7.83v4.5h7v2.61h-7v4.66h7.83Z"/>
|
||||
<path d="M213.93,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM213.07,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
|
||||
<path d="M226.96,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||
<path d="M242.38,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||
<path d="M252.68,54.16v-17.37h2.95v17.37h-2.95Z"/>
|
||||
<path d="M265.82,36.47c1.59,0,2.91.39,3.96,1.16s1.7,1.81,1.94,3.1l-2.78.76c-.16-.74-.52-1.32-1.09-1.72s-1.27-.6-2.11-.6c-.9,0-1.61.21-2.14.64s-.79,1-.79,1.71c0,1.12.7,1.85,2.09,2.18l2.84.71c1.46.38,2.56.98,3.29,1.81s1.09,1.84,1.09,3.05c0,1.55-.56,2.8-1.68,3.76s-2.63,1.44-4.51,1.44c-1.7,0-3.13-.4-4.3-1.2-1.15-.83-1.84-1.92-2.05-3.28l2.78-.72c.1.77.48,1.37,1.14,1.8s1.5.65,2.53.65,1.76-.21,2.32-.62.84-.98.84-1.69c0-1.12-.7-1.85-2.09-2.21l-2.84-.69c-1.46-.33-2.55-.92-3.28-1.77s-1.1-1.88-1.1-3.11c0-1.53.54-2.78,1.63-3.74s2.52-1.44,4.29-1.44Z"/>
|
||||
<path d="M287.47,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
|
||||
<path d="M298.87,50.11h-6.61l-1.43,4.04h-3.04l6.27-17.37h3.04l6.29,17.37h-3.11l-1.41-4.04ZM298.01,47.62l-2.43-6.95-2.45,6.95h4.88Z"/>
|
||||
<path d="M320.89,36.79v17.37h-2.93l-8.25-12.67v12.67h-2.93v-17.37h2.93l8.25,12.65v-12.65h2.93Z"/>
|
||||
<path d="M337.31,39.57h-4.97v14.58h-2.95v-14.58h-4.97v-2.79h12.9v2.79Z"/>
|
||||
<path d="M348.75,54.16v-17.14h2.05v17.14h-2.05Z"/>
|
||||
<path d="M360.95,36.72c1.55,0,2.82.38,3.81,1.14,1,.74,1.61,1.72,1.82,2.95l-1.95.52c-.16-.87-.56-1.54-1.23-2.02s-1.5-.71-2.5-.71c-1.08,0-1.95.27-2.6.8s-.97,1.24-.97,2.13c0,1.36.84,2.26,2.52,2.71l2.9.73c1.37.34,2.41.9,3.11,1.68s1.05,1.74,1.05,2.9c0,1.46-.53,2.64-1.6,3.54s-2.49,1.36-4.28,1.36c-1.61,0-2.95-.37-4.03-1.12s-1.72-1.76-1.95-3.06l1.98-.55c.13.88.55,1.56,1.25,2.06s1.63.75,2.77.75,2.12-.26,2.79-.77,1.02-1.22,1.02-2.14c0-1.44-.84-2.36-2.52-2.77l-2.88-.71c-1.38-.34-2.42-.9-3.12-1.69s-1.05-1.75-1.05-2.9c0-1.44.52-2.61,1.56-3.51s2.4-1.35,4.09-1.35Z"/>
|
||||
<path d="M388.35,49.75h-7.54l-1.59,4.4h-2.07l6.25-17.14h2.36l6.31,17.14h-2.15l-1.57-4.4ZM387.73,48.05l-3.09-8.71-3.2,8.71h6.29Z"/>
|
||||
<path d="M415.46,42.47c0,1.6-.5,2.91-1.5,3.95s-2.32,1.56-3.97,1.56h-4.53v6.18h-2.05v-17.14h6.6c1.67,0,3,.49,3.98,1.47s1.47,2.31,1.47,3.98ZM413.31,42.42c0-1.07-.32-1.92-.95-2.56s-1.51-.96-2.64-.96h-4.26v7.24h4.17c1.15,0,2.06-.34,2.71-1.02s.98-1.58.98-2.7Z"/>
|
||||
<path d="M428.37,46.9l3.43,7.26h-2.31l-3.18-6.95h-4.76v6.95h-2.05v-17.14h6.54c1.81,0,3.22.45,4.24,1.35s1.53,2.14,1.53,3.72c0,1.22-.3,2.26-.9,3.1s-1.44,1.41-2.53,1.7ZM429.64,42.12c0-1.01-.32-1.81-.95-2.38s-1.52-.86-2.66-.86h-4.5v6.47h4.53c1.15,0,2.03-.28,2.64-.85s.92-1.36.92-2.38Z"/>
|
||||
<path d="M443.34,36.74c1.18-.02,2.28.2,3.31.65s1.9,1.07,2.62,1.85,1.28,1.73,1.69,2.83.6,2.27.59,3.52c.02,1.67-.33,3.19-1.03,4.54s-1.68,2.42-2.95,3.19-2.68,1.14-4.25,1.12c-1.59,0-3-.38-4.25-1.13s-2.21-1.81-2.89-3.15-1.03-2.87-1.03-4.57c-.02-1.67.32-3.18,1.02-4.53s1.67-2.42,2.93-3.2,2.68-1.15,4.24-1.13ZM443.34,52.45c1.8,0,3.26-.64,4.38-1.91s1.68-2.92,1.68-4.95-.56-3.71-1.68-4.98-2.58-1.9-4.38-1.9-3.28.63-4.4,1.9-1.68,2.93-1.68,4.98.56,3.69,1.68,4.96,2.59,1.9,4.39,1.9Z"/>
|
||||
<path d="M464.3,37.02v12.42c0,1.49-.47,2.71-1.41,3.64s-2.17,1.39-3.71,1.39c-1.56,0-2.76-.46-3.61-1.37s-1.27-2.13-1.27-3.69v-.55h1.98v.55c0,1.18.29,1.99.86,2.45.59.45,1.26.67,2.02.67.93,0,1.68-.26,2.24-.78.57-.52.85-1.32.85-2.39v-12.35h2.05Z"/>
|
||||
<path d="M479.86,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
|
||||
<path d="M496.97,42.42c-.36-1.15-1.01-2.06-1.93-2.71s-2.02-.98-3.3-.98c-1.79,0-3.24.63-4.35,1.89s-1.66,2.92-1.66,4.96.55,3.72,1.66,4.96,2.55,1.86,4.33,1.86c1.27,0,2.39-.31,3.35-.94.95-.63,1.61-1.44,1.98-2.45l1.93.83c-.55,1.41-1.48,2.53-2.78,3.36-1.31.81-2.81,1.22-4.51,1.22-2.4,0-4.35-.81-5.84-2.43s-2.24-3.75-2.24-6.4c0-1.74.34-3.28,1.02-4.62s1.64-2.39,2.88-3.13,2.65-1.11,4.25-1.11c1.8,0,3.33.46,4.59,1.38,1.26.91,2.12,2.1,2.57,3.59l-1.93.71Z"/>
|
||||
<path d="M512.92,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
|
||||
<path d="M536.4,49.66c0,1.39-.49,2.48-1.46,3.29s-2.27,1.21-3.9,1.21h-6.72v-17.12h6.58c1.65,0,2.95.41,3.89,1.24s1.41,1.93,1.41,3.32c0,.92-.21,1.72-.64,2.4s-1.02,1.2-1.79,1.57c.81.37,1.45.91,1.92,1.62s.7,1.53.7,2.47ZM526.3,38.81v5.97h4.54c1.03,0,1.85-.27,2.46-.81s.92-1.24.92-2.09c0-.91-.31-1.65-.93-2.22s-1.45-.85-2.5-.85h-4.5ZM534.45,49.39c0-.91-.32-1.64-.95-2.17s-1.44-.8-2.46-.8h-4.74v5.95h4.74c1.04,0,1.86-.27,2.48-.81s.92-1.26.92-2.16Z"/>
|
||||
<path d="M541.07,37.02l4.54,8.1,4.54-8.1h2.24l-5.76,10.05v7.09h-2.05v-7.09l-5.83-10.05h2.31Z"/>
|
||||
<path d="M574.27,38.95h-5.12v15.21h-2.05v-15.21h-5.16v-1.93h12.33v1.93Z"/>
|
||||
<path d="M577.95,37.02h2.05v7.55l8.74.02v-7.58h2.05v17.14h-2.05v-7.76l-8.74-.02v7.79h-2.05v-17.14Z"/>
|
||||
<path d="M606.55,52.23v1.93h-10.35v-17.14h10.33v1.95h-8.31v5.67h7.53v1.8h-7.53v5.79h8.33Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20240731.0"
|
||||
version = "20240809.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@ -37,8 +37,7 @@
|
||||
{
|
||||
"description": "Group tsparticles engine and presets",
|
||||
"groupName": "tsparticles",
|
||||
"matchPackageNames": ["tsparticles-engine"],
|
||||
"matchPackagePrefixes": ["tsparticles-preset-"]
|
||||
"matchPackageNames": ["tsparticles-engine", "tsparticles-preset-{/,}**"]
|
||||
},
|
||||
{
|
||||
"description": "Group date-fns with dependent timezone package",
|
||||
@ -48,8 +47,8 @@
|
||||
{
|
||||
"description": "Group and temporarily disable WDS packages",
|
||||
"groupName": "Web Dev Server",
|
||||
"matchPackagePrefixes": ["@web/dev-server"],
|
||||
"enabled": false
|
||||
"enabled": false,
|
||||
"matchPackageNames": ["@web/dev-server{/,}**"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ import {
|
||||
mdiImageFilterFrames,
|
||||
mdiLightbulb,
|
||||
mdiLightningBolt,
|
||||
mdiMailbox,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMeterGas,
|
||||
mdiMicrophoneMessage,
|
||||
@ -119,7 +118,6 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
input_text: mdiFormTextbox,
|
||||
lawn_mower: mdiRobotMower,
|
||||
light: mdiLightbulb,
|
||||
mailbox: mdiMailbox,
|
||||
notify: mdiCommentAlert,
|
||||
number: mdiRayVertex,
|
||||
persistent_notification: mdiBell,
|
||||
|
@ -26,7 +26,7 @@ export const FIXED_DOMAIN_STATES = {
|
||||
humidifier: ["on", "off"],
|
||||
input_boolean: ["on", "off"],
|
||||
input_button: [],
|
||||
lawn_mower: ["error", "paused", "mowing", "docked"],
|
||||
lawn_mower: ["error", "paused", "mowing", "returning", "docked"],
|
||||
light: ["on", "off"],
|
||||
lock: [
|
||||
"jammed",
|
||||
|
@ -81,6 +81,9 @@ class HaEntityStatePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "allow-name" }) public allowName =
|
||||
false;
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@property() public value?: string[] | string;
|
||||
@ -95,43 +98,55 @@ class HaEntityStatePicker extends LitElement {
|
||||
return !(!changedProps.has("_opened") && this._opened);
|
||||
}
|
||||
|
||||
private options = memoizeOne((entityId?: string, stateObj?: HassEntity) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
return [
|
||||
{
|
||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||
value: "state",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
},
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map((content) => ({
|
||||
label: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
}))
|
||||
: []),
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map((attribute) => ({
|
||||
value: attribute,
|
||||
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
})),
|
||||
];
|
||||
});
|
||||
private options = memoizeOne(
|
||||
(entityId?: string, stateObj?: HassEntity, allowName?: boolean) => {
|
||||
const domain = entityId ? computeDomain(entityId) : undefined;
|
||||
return [
|
||||
{
|
||||
label: this.hass.localize("ui.components.state-content-picker.state"),
|
||||
value: "state",
|
||||
},
|
||||
...(allowName
|
||||
? [
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.name"
|
||||
),
|
||||
value: "name",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_changed"
|
||||
),
|
||||
value: "last_changed",
|
||||
},
|
||||
{
|
||||
label: this.hass.localize(
|
||||
"ui.components.state-content-picker.last_updated"
|
||||
),
|
||||
value: "last_updated",
|
||||
},
|
||||
...(domain
|
||||
? STATE_DISPLAY_SPECIAL_CONTENT.filter((content) =>
|
||||
STATE_DISPLAY_SPECIAL_CONTENT_DOMAINS[domain]?.includes(content)
|
||||
).map((content) => ({
|
||||
label: this.hass.localize(
|
||||
`ui.components.state-content-picker.${content}`
|
||||
),
|
||||
value: content,
|
||||
}))
|
||||
: []),
|
||||
...Object.keys(stateObj?.attributes ?? {})
|
||||
.filter((a) => !HIDDEN_ATTRIBUTES.includes(a))
|
||||
.map((attribute) => ({
|
||||
value: attribute,
|
||||
label: this.hass.formatEntityAttributeName(stateObj!, attribute),
|
||||
})),
|
||||
];
|
||||
}
|
||||
);
|
||||
|
||||
private _filter = "";
|
||||
|
||||
@ -146,7 +161,7 @@ class HaEntityStatePicker extends LitElement {
|
||||
? this.hass.states[this.entityId]
|
||||
: undefined;
|
||||
|
||||
const options = this.options(this.entityId, stateObj);
|
||||
const options = this.options(this.entityId, stateObj, this.allowName);
|
||||
const optionItems = options.filter(
|
||||
(option) => !this._value.includes(option.value)
|
||||
);
|
||||
|
@ -28,6 +28,11 @@ const LAWN_MOWER_ACTIONS: Partial<
|
||||
service: "start_mowing",
|
||||
feature: LawnMowerEntityFeature.START_MOWING,
|
||||
},
|
||||
returning: {
|
||||
action: "pause",
|
||||
service: "pause",
|
||||
feature: LawnMowerEntityFeature.PAUSE,
|
||||
},
|
||||
paused: {
|
||||
action: "resume_mowing",
|
||||
service: "start_mowing",
|
||||
|
@ -81,15 +81,16 @@ export class HaTargetSelector extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
.createDomains=${this._createDomains}
|
||||
></ha-target-picker>`;
|
||||
return html` ${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
<ha-target-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.helper=${this.helper}
|
||||
.deviceFilter=${this._filterDevices}
|
||||
.entityFilter=${this._filterEntities}
|
||||
.disabled=${this.disabled}
|
||||
.createDomains=${this._createDomains}
|
||||
></ha-target-picker>`;
|
||||
}
|
||||
|
||||
private _filterEntities = (entity: HassEntity): boolean => {
|
||||
|
@ -36,6 +36,7 @@ export class HaSelectorUiStateContent extends SubscribeMixin(LitElement) {
|
||||
.helper=${this.helper}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
.allowName=${this.selector.ui_state_content?.allow_name}
|
||||
></ha-entity-state-content-picker>
|
||||
`;
|
||||
}
|
||||
|
@ -366,7 +366,9 @@ export const normalizeAutomationConfig = <
|
||||
}
|
||||
}
|
||||
|
||||
config.action = migrateAutomationAction(config.action || []);
|
||||
if (config.action) {
|
||||
config.action = migrateAutomationAction(config.action);
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
@ -4,7 +4,12 @@ import {
|
||||
} from "home-assistant-js-websocket";
|
||||
import { UNAVAILABLE } from "./entity";
|
||||
|
||||
export type LawnMowerEntityState = "paused" | "mowing" | "docked" | "error";
|
||||
export type LawnMowerEntityState =
|
||||
| "paused"
|
||||
| "mowing"
|
||||
| "returning"
|
||||
| "docked"
|
||||
| "error";
|
||||
|
||||
export const enum LawnMowerEntityFeature {
|
||||
START_MOWING = 1,
|
||||
|
@ -13,6 +13,7 @@ export const ensureBadgeConfig = (
|
||||
return {
|
||||
type: "entity",
|
||||
entity: config,
|
||||
display_type: "complete",
|
||||
};
|
||||
}
|
||||
if ("type" in config && config.type) {
|
||||
|
@ -461,6 +461,7 @@ export interface UiStateContentSelector {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
ui_state_content: {
|
||||
entity_id?: string;
|
||||
allow_name?: boolean;
|
||||
} | null;
|
||||
}
|
||||
|
||||
|
@ -101,10 +101,12 @@ export class HaVoiceCommandDialog extends LitElement {
|
||||
|
||||
const controlHA = !this._pipeline
|
||||
? false
|
||||
: supportsFeature(
|
||||
this.hass.states[this._pipeline?.conversation_engine],
|
||||
ConversationEntityFeature.CONTROL
|
||||
);
|
||||
: this.hass.states[this._pipeline?.conversation_engine]
|
||||
? supportsFeature(
|
||||
this.hass.states[this._pipeline?.conversation_engine],
|
||||
ConversationEntityFeature.CONTROL
|
||||
)
|
||||
: true;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
|
@ -42,30 +42,39 @@
|
||||
width: 112px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer {
|
||||
#ha-launch-screen .ha-launch-screen-spacer-top {
|
||||
flex: 1;
|
||||
margin-top: calc( 2 * max(env(safe-area-inset-bottom), 48px) + 46px );
|
||||
padding-top: 48px;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer-bottom {
|
||||
flex: 1;
|
||||
padding-top: 48px;
|
||||
}
|
||||
.ohf-logo {
|
||||
color: grey;
|
||||
font-size: 12px;
|
||||
margin-bottom: 16px;
|
||||
margin: max(env(safe-area-inset-bottom), 48px) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
opacity: .66;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ohf-logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ha-launch-screen">
|
||||
<div class="ha-launch-screen-spacer"></div>
|
||||
<div class="ha-launch-screen-spacer-top"></div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
||||
<path fill="#18BCF2" d="M240 224.762a15 15 0 0 1-15 15H15a15 15 0 0 1-15-15v-90c0-8.25 4.77-19.769 10.61-25.609l98.78-98.7805c5.83-5.83 15.38-5.83 21.21 0l98.79 98.7895c5.83 5.83 10.61 17.36 10.61 25.61v90-.01Z"/>
|
||||
<path fill="#F2F4F9" d="m107.27 239.762-40.63-40.63c-2.09.72-4.32 1.13-6.64 1.13-11.3 0-20.5-9.2-20.5-20.5s9.2-20.5 20.5-20.5 20.5 9.2 20.5 20.5c0 2.33-.41 4.56-1.13 6.65l31.63 31.63v-115.88c-6.8-3.3395-11.5-10.3195-11.5-18.3895 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5c0 8.07-4.7 15.05-11.5 18.3895v81.27l31.46-31.46c-.62-1.96-.96-4.04-.96-6.2 0-11.3 9.2-20.5 20.5-20.5s20.5 9.2 20.5 20.5-9.2 20.5-20.5 20.5c-2.5 0-4.88-.47-7.09-1.29L129 208.892v30.88z"/>
|
||||
</svg>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer-bottom"></div>
|
||||
<div class="ohf-logo">
|
||||
a project from
|
||||
<img src="/static/icons/ohf.svg" alt="Open Home Foundation" height="32">
|
||||
<img src="/static/images/ohf-badge.svg" alt="Home Assistant is a project by the Open Home Foundation" height="46">
|
||||
</div>
|
||||
</div>
|
||||
<home-assistant></home-assistant>
|
||||
|
@ -29,7 +29,6 @@ const COMPONENTS = {
|
||||
history: () => import("../panels/history/ha-panel-history"),
|
||||
iframe: () => import("../panels/iframe/ha-panel-iframe"),
|
||||
logbook: () => import("../panels/logbook/ha-panel-logbook"),
|
||||
mailbox: () => import("../panels/mailbox/ha-panel-mailbox"),
|
||||
map: () => import("../panels/map/ha-panel-map"),
|
||||
my: () => import("../panels/my/ha-panel-my"),
|
||||
profile: () => import("../panels/profile/ha-panel-profile"),
|
||||
|
@ -53,7 +53,7 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
);
|
||||
|
||||
public static get defaultConfig() {
|
||||
return { service: "", data: {} };
|
||||
return { action: "", data: {} };
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
|
@ -353,6 +353,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: this.hass.localize("ui.panel.config.generic.headers.actions"),
|
||||
type: "icon-button",
|
||||
showNarrow: true,
|
||||
moveable: false,
|
||||
|
@ -217,6 +217,7 @@ export class HaWebhookTrigger extends LitElement {
|
||||
ha-textfield > ha-icon-button {
|
||||
--mdc-icon-button-size: 24px;
|
||||
--mdc-icon-size: 18px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-button-menu {
|
||||
|
@ -198,6 +198,7 @@ class HaBlueprintOverview extends LitElement {
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: this.hass.localize("ui.panel.config.generic.headers.actions"),
|
||||
type: "overflow-menu",
|
||||
showNarrow: true,
|
||||
moveable: false,
|
||||
|
@ -1,29 +0,0 @@
|
||||
const documentContainer = document.createElement("template");
|
||||
documentContainer.setAttribute("style", "display: none;");
|
||||
|
||||
documentContainer.innerHTML = `<dom-module id="ha-form-style">
|
||||
<template>
|
||||
<style>
|
||||
.form-group {
|
||||
@apply --layout-horizontal;
|
||||
@apply --layout-center;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
@apply --layout-flex-2;
|
||||
}
|
||||
|
||||
.form-group .form-control {
|
||||
@apply --layout-flex;
|
||||
}
|
||||
|
||||
.form-group.vertical {
|
||||
@apply --layout-vertical;
|
||||
@apply --layout-start;
|
||||
}
|
||||
</style>
|
||||
</template>
|
||||
</dom-module>`;
|
||||
|
||||
document.head.appendChild(documentContainer.content);
|
@ -123,13 +123,10 @@ export class DialogHelperDetail extends LitElement {
|
||||
this._opened = true;
|
||||
await this.updateComplete;
|
||||
this.hass.loadFragmentTranslation("config");
|
||||
Promise.all([
|
||||
getConfigFlowHandlers(this.hass, ["helper"]),
|
||||
// Ensure the titles are loaded before we render the flows.
|
||||
this.hass.loadBackendTranslation("title", undefined, true),
|
||||
]).then(([flows]) => {
|
||||
this._helperFlows = flows;
|
||||
});
|
||||
const flows = await getConfigFlowHandlers(this.hass, ["helper"]);
|
||||
await this.hass.loadBackendTranslation("title", flows, true);
|
||||
// Ensure the titles are loaded before we render the flows.
|
||||
this._helperFlows = flows;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
|
@ -349,7 +349,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: "Actions",
|
||||
label: this.hass.localize("ui.panel.config.generic.headers.actions"),
|
||||
type: "overflow-menu",
|
||||
hideable: false,
|
||||
moveable: false,
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
mdiFileCodeOutline,
|
||||
mdiHandExtendedOutline,
|
||||
mdiOpenInNew,
|
||||
mdiPackageVariant,
|
||||
@ -329,6 +330,22 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
|
||||
)}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
${normalEntries.length === 0 &&
|
||||
this._manifest &&
|
||||
!this._manifest.config_flow &&
|
||||
this.hass.config.components.find(
|
||||
(comp) => comp.split(".")[0] === this.domain
|
||||
)
|
||||
? html`<ha-alert alert-type="info"
|
||||
><ha-svg-icon
|
||||
slot="icon"
|
||||
path=${mdiFileCodeOutline}
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.no_config_flow"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: ""}
|
||||
</div>
|
||||
|
||||
<div class="card-actions">
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
|
||||
import { mdiCloud, mdiCodeBraces, mdiPackageVariant } from "@mdi/js";
|
||||
import { mdiCloud, mdiFileCodeOutline, mdiPackageVariant } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
@ -184,7 +184,7 @@ export class HaIntegrationCard extends LitElement {
|
||||
: nothing}
|
||||
${this.manifest && !this.manifest?.config_flow
|
||||
? html`<div class="icon yaml">
|
||||
<ha-svg-icon .path=${mdiCodeBraces}></ha-svg-icon>
|
||||
<ha-svg-icon .path=${mdiFileCodeOutline}></ha-svg-icon>
|
||||
<simple-tooltip
|
||||
animation-delay="0"
|
||||
.position=${computeRTL(this.hass) ? "right" : "left"}
|
||||
|
@ -311,6 +311,9 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
only_editable: {
|
||||
title: "",
|
||||
label: this.hass.localize(
|
||||
"ui.panel.config.scene.picker.headers.editable"
|
||||
),
|
||||
type: "icon",
|
||||
showNarrow: true,
|
||||
template: (scene) =>
|
||||
@ -330,6 +333,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: this.hass.localize("ui.panel.config.generic.headers.actions"),
|
||||
type: "overflow-menu",
|
||||
showNarrow: true,
|
||||
moveable: false,
|
||||
|
@ -488,7 +488,9 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
if (value && !Array.isArray(value)) {
|
||||
config.sequence = [value];
|
||||
}
|
||||
config.sequence = migrateAutomationAction(config.sequence);
|
||||
if (config.sequence) {
|
||||
config.sequence = migrateAutomationAction(config.sequence);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
|
@ -321,6 +321,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
actions: {
|
||||
title: "",
|
||||
label: this.hass.localize("ui.panel.config.generic.headers.actions"),
|
||||
type: "overflow-menu",
|
||||
showNarrow: true,
|
||||
moveable: false,
|
||||
|
@ -38,7 +38,7 @@ export class AssistPipelineEvents extends LitElement {
|
||||
</ha-card>`;
|
||||
}
|
||||
return html`<ha-alert alert-type="warning"
|
||||
>There where no events in this run.</ha-alert
|
||||
>There were no events in this run.</ha-alert
|
||||
>`;
|
||||
}
|
||||
return html`
|
||||
|
@ -308,7 +308,7 @@ class HaPanelDevAction extends LitElement {
|
||||
|
||||
private async _copyTemplate(): Promise<void> {
|
||||
await copyToClipboard(
|
||||
`{% set action_response = ${JSON.stringify(this._response)} %}`
|
||||
`{% set ${this._serviceData?.response_variable || "action_response"} = ${JSON.stringify(this._response)} %}`
|
||||
);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
|
@ -88,13 +88,8 @@ class HaPanelDevTemplate extends LitElement {
|
||||
: "dict"
|
||||
: type;
|
||||
return html`
|
||||
<div
|
||||
class="content ${classMap({
|
||||
layout: !this.narrow,
|
||||
horizontal: !this.narrow,
|
||||
})}"
|
||||
>
|
||||
<div class="edit-pane">
|
||||
<div class="content">
|
||||
<div class="description">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.description"
|
||||
@ -126,123 +121,143 @@ class HaPanelDevTemplate extends LitElement {
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.editor"
|
||||
)}
|
||||
</p>
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.hass=${this.hass}
|
||||
.value=${this._template}
|
||||
.error=${this._error}
|
||||
autofocus
|
||||
autocomplete-entities
|
||||
autocomplete-icons
|
||||
@value-changed=${this._templateChanged}
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
<mwc-button @click=${this._restoreDemo}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.reset"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._clear}>
|
||||
${this.hass.localize("ui.common.clear")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="content ${classMap({
|
||||
layout: !this.narrow,
|
||||
horizontal: !this.narrow,
|
||||
})}"
|
||||
>
|
||||
<ha-card
|
||||
class="edit-pane"
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.editor"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.hass=${this.hass}
|
||||
.value=${this._template}
|
||||
.error=${this._error}
|
||||
autofocus
|
||||
autocomplete-entities
|
||||
autocomplete-icons
|
||||
@value-changed=${this._templateChanged}
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._restoreDemo}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.reset"
|
||||
)}
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._clear}>
|
||||
${this.hass.localize("ui.common.clear")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
|
||||
<div class="render-pane">
|
||||
${this._rendering
|
||||
? html`<ha-circular-progress
|
||||
class="render-spinner"
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this._error
|
||||
? html`<ha-alert
|
||||
alert-type=${this._errorLevel?.toLowerCase() || "error"}
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
${this._templateResult
|
||||
? html`${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.result_type"
|
||||
)}:
|
||||
${resultType}
|
||||
<!-- prettier-ignore -->
|
||||
<pre class="rendered ${classMap({
|
||||
[resultType]: resultType,
|
||||
})}"
|
||||
>${type === "object"
|
||||
? JSON.stringify(this._templateResult.result, null, 2)
|
||||
: this._templateResult.result}</pre>
|
||||
${this._templateResult.listeners.time
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.time"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: ""}
|
||||
${!this._templateResult.listeners
|
||||
? nothing
|
||||
: this._templateResult.listeners.all
|
||||
<ha-card
|
||||
class="render-pane"
|
||||
header=${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.result"
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._rendering
|
||||
? html`<ha-circular-progress
|
||||
class="render-spinner"
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this._error
|
||||
? html`<ha-alert
|
||||
alert-type=${this._errorLevel?.toLowerCase() || "error"}
|
||||
>${this._error}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
${this._templateResult
|
||||
? html`${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.result_type"
|
||||
)}:
|
||||
${resultType}
|
||||
<!-- prettier-ignore -->
|
||||
<pre class="rendered ${classMap({
|
||||
[resultType]: resultType,
|
||||
})}"
|
||||
>${type === "object"
|
||||
? JSON.stringify(this._templateResult.result, null, 2)
|
||||
: this._templateResult.result}</pre>
|
||||
${this._templateResult.listeners.time
|
||||
? html`
|
||||
<p class="all_listeners">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
||||
"ui.panel.developer-tools.tabs.templates.time"
|
||||
)}
|
||||
</p>
|
||||
`
|
||||
: this._templateResult.listeners.domains.length ||
|
||||
this._templateResult.listeners.entities.length
|
||||
: ""}
|
||||
${!this._templateResult.listeners
|
||||
? nothing
|
||||
: this._templateResult.listeners.all
|
||||
? html`
|
||||
<p>
|
||||
<p class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.listeners"
|
||||
"ui.panel.developer-tools.tabs.templates.all_listeners"
|
||||
)}
|
||||
</p>
|
||||
<ul>
|
||||
${this._templateResult.listeners.domains
|
||||
.sort()
|
||||
.map(
|
||||
(domain) => html`
|
||||
<li>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.domain"
|
||||
)}</b
|
||||
>: ${domain}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
${this._templateResult.listeners.entities
|
||||
.sort()
|
||||
.map(
|
||||
(entity_id) => html`
|
||||
<li>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.entity"
|
||||
)}</b
|
||||
>: ${entity_id}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
`
|
||||
: !this._templateResult.listeners.time
|
||||
? html`<span class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
||||
)}
|
||||
</span>`
|
||||
: nothing}`
|
||||
: nothing}
|
||||
</div>
|
||||
: this._templateResult.listeners.domains.length ||
|
||||
this._templateResult.listeners.entities.length
|
||||
? html`
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.listeners"
|
||||
)}
|
||||
</p>
|
||||
<ul>
|
||||
${this._templateResult.listeners.domains
|
||||
.sort()
|
||||
.map(
|
||||
(domain) => html`
|
||||
<li>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.domain"
|
||||
)}</b
|
||||
>: ${domain}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
${this._templateResult.listeners.entities
|
||||
.sort()
|
||||
.map(
|
||||
(entity_id) => html`
|
||||
<li>
|
||||
<b
|
||||
>${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.entity"
|
||||
)}</b
|
||||
>: ${entity_id}
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
`
|
||||
: !this._templateResult.listeners.time
|
||||
? html`<span class="all_listeners">
|
||||
${this.hass.localize(
|
||||
"ui.panel.developer-tools.tabs.templates.no_listeners"
|
||||
)}
|
||||
</span>`
|
||||
: nothing}`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -258,6 +273,7 @@ class HaPanelDevTemplate extends LitElement {
|
||||
}
|
||||
|
||||
.content {
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
padding: max(16px, env(safe-area-inset-top))
|
||||
max(16px, env(safe-area-inset-right))
|
||||
@ -265,10 +281,11 @@ class HaPanelDevTemplate extends LitElement {
|
||||
max(16px, env(safe-area-inset-left));
|
||||
}
|
||||
|
||||
ha-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.edit-pane {
|
||||
margin-right: 16px;
|
||||
margin-inline-start: initial;
|
||||
margin-inline-end: 16px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
@ -280,12 +297,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
.render-pane {
|
||||
position: relative;
|
||||
max-width: 50%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.render-spinner {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
|
@ -57,9 +57,6 @@ export class HuiBadge extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _updateElement(config: LovelaceBadgeConfig) {
|
||||
if (config.type === "state-label") {
|
||||
config = { ...config, type: "entity" };
|
||||
}
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
@ -69,9 +66,6 @@ export class HuiBadge extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _loadElement(config: LovelaceBadgeConfig) {
|
||||
if (config.type === "state-label") {
|
||||
config = { ...config, type: "entity" };
|
||||
}
|
||||
this._element = createBadgeElement(config);
|
||||
this._elementConfig = config;
|
||||
if (this.hass) {
|
||||
|
@ -5,6 +5,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { mdiAlertCircle } from "@mdi/js";
|
||||
import { computeCssColor } from "../../../common/color/compute-color";
|
||||
import { hsv2rgb, rgb2hex, rgb2hsv } from "../../../common/color/convert-color";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
@ -12,6 +13,7 @@ import { stateActive } from "../../../common/entity/state_active";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-ripple";
|
||||
import "../../../components/ha-state-icon";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
@ -129,7 +131,17 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||
|
||||
if (!stateObj) {
|
||||
return nothing;
|
||||
return html`
|
||||
<div class="badge error">
|
||||
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
|
||||
<span class="content">
|
||||
<span class="name">${entityId}</span>
|
||||
<span class="state">
|
||||
${this.hass.localize("ui.badge.entity.not_found")}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const active = stateActive(stateObj);
|
||||
@ -144,6 +156,7 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
.content=${this._config.state_content}
|
||||
.name=${this._config.name}
|
||||
>
|
||||
</state-display>
|
||||
`;
|
||||
@ -205,6 +218,9 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
--badge-color: var(--state-inactive-color);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.badge.error {
|
||||
--badge-color: var(--red-color);
|
||||
}
|
||||
.badge {
|
||||
position: relative;
|
||||
--ha-ripple-color: var(--badge-color);
|
||||
@ -224,8 +240,14 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
box-sizing: border-box;
|
||||
width: auto;
|
||||
border-radius: 18px;
|
||||
background-color: var(--card-background-color, white);
|
||||
background: var(
|
||||
--ha-card-background,
|
||||
var(--card-background-color, white)
|
||||
);
|
||||
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||
backdrop-filter: var(--ha-card-backdrop-filter, none);
|
||||
border-width: var(--ha-card-border-width, 1px);
|
||||
box-shadow: var(--ha-card-box-shadow, none);
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
@ -276,7 +298,8 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
|
||||
letter-spacing: 0.1px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-state-icon {
|
||||
ha-state-icon,
|
||||
ha-svg-icon {
|
||||
color: var(--badge-color);
|
||||
line-height: 0;
|
||||
}
|
||||
|
@ -1,71 +1,25 @@
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../components/entity/ha-state-label-badge";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { LovelaceBadge } from "../types";
|
||||
import { StateLabelBadgeConfig } from "./types";
|
||||
import { HuiStateLabelBadgeEditor } from "../editor/config-elements/hui-state-label-badge-editor";
|
||||
import { HuiEntityBadge } from "./hui-entity-badge";
|
||||
import { EntityBadgeConfig, StateLabelBadgeConfig } from "./types";
|
||||
|
||||
@customElement("hui-state-label-badge")
|
||||
export class HuiStateLabelBadge extends LitElement implements LovelaceBadge {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() protected _config?: StateLabelBadgeConfig;
|
||||
|
||||
public setConfig(config: StateLabelBadgeConfig): void {
|
||||
this._config = config;
|
||||
export class HuiStateLabelBadge extends HuiEntityBadge {
|
||||
public static async getConfigElement(): Promise<HuiStateLabelBadgeEditor> {
|
||||
await import("../editor/config-elements/hui-state-label-badge-editor");
|
||||
return document.createElement("hui-state-label-badge-editor");
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
// @ts-ignore
|
||||
public override setConfig(config: StateLabelBadgeConfig): void {
|
||||
const entityBadgeConfig: EntityBadgeConfig = {
|
||||
type: "entity",
|
||||
entity: config.entity,
|
||||
display_type: config.show_name === false ? "standard" : "complete",
|
||||
};
|
||||
|
||||
const stateObj = this.hass.states[this._config.entity!];
|
||||
|
||||
return html`
|
||||
<ha-state-label-badge
|
||||
.hass=${this.hass}
|
||||
.state=${stateObj}
|
||||
.name=${this._config.name}
|
||||
.icon=${this._config.icon}
|
||||
.image=${this._config.image}
|
||||
.showName=${this._config.show_name ?? true}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
tabindex=${ifDefined(
|
||||
hasAction(this._config.tap_action) || this._config.entity
|
||||
? "0"
|
||||
: undefined
|
||||
)}
|
||||
></ha-state-label-badge>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleAction(ev: ActionHandlerEvent) {
|
||||
handleAction(this, this.hass!, this._config!, ev.detail.action!);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-state-label-badge:focus {
|
||||
outline: none;
|
||||
background: var(--divider-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
ha-state-label-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 2px 4px 2px;
|
||||
margin: -4px -2px -4px -2px;
|
||||
}
|
||||
`;
|
||||
this._config = entityBadgeConfig;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,10 +150,10 @@ export class HuiViewBadges extends LitElement {
|
||||
class="add"
|
||||
@click=${this._addBadge}
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.add_card"
|
||||
"ui.panel.lovelace.editor.section.add_badge"
|
||||
)}
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.lovelace.editor.section.add_card"
|
||||
"ui.panel.lovelace.editor.section.add_badge"
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus}></ha-svg-icon>
|
||||
|
@ -96,7 +96,12 @@ class HuiAlarmModeCardFeature
|
||||
}
|
||||
|
||||
private async _setMode(mode: AlarmMode) {
|
||||
setProtectedAlarmControlPanelMode(this, this.hass!, this.stateObj!, mode);
|
||||
await setProtectedAlarmControlPanelMode(
|
||||
this,
|
||||
this.hass!,
|
||||
this.stateObj!,
|
||||
mode
|
||||
);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | null {
|
||||
|
@ -23,7 +23,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasAction, hasAnyAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
@ -128,24 +128,20 @@ class HuiGaugeCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const name = this._config.name ?? computeStateName(stateObj);
|
||||
|
||||
const hasAnyAction =
|
||||
!this._config.tap_action ||
|
||||
hasAction(this._config.tap_action) ||
|
||||
hasAction(this._config.hold_action) ||
|
||||
hasAction(this._config.double_tap_action);
|
||||
|
||||
// Use `stateObj.state` as value to keep formatting (e.g trailing zeros)
|
||||
// for consistent value display across gauge, entity, entity-row, etc.
|
||||
return html`
|
||||
<ha-card
|
||||
class=${classMap({ action: hasAnyAction })}
|
||||
class=${classMap({ action: hasAnyAction(this._config) })}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this._config.hold_action),
|
||||
hasDoubleClick: hasAction(this._config.double_tap_action),
|
||||
})}
|
||||
tabindex=${ifDefined(
|
||||
hasAction(this._config.tap_action) ? "0" : undefined
|
||||
!this._config.tap_action || hasAction(this._config.tap_action)
|
||||
? "0"
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
<ha-gauge
|
||||
|
@ -28,7 +28,7 @@ import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasAction, hasAnyAction } from "../common/has-action";
|
||||
import { hasConfigOrEntitiesChanged } from "../common/has-changed";
|
||||
import { processConfigEntities } from "../common/process-config-entities";
|
||||
import "../components/hui-timestamp-display";
|
||||
@ -263,15 +263,9 @@ export class HuiGlanceCard extends LitElement implements LovelaceCard {
|
||||
|
||||
const name = entityConf.name ?? computeStateName(stateObj);
|
||||
|
||||
const hasAnyAction =
|
||||
!entityConf.tap_action ||
|
||||
hasAction(entityConf.tap_action) ||
|
||||
hasAction(entityConf.hold_action) ||
|
||||
hasAction(entityConf.double_tap_action);
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classMap({ entity: true, action: hasAnyAction })}
|
||||
class=${classMap({ entity: true, action: hasAnyAction(entityConf) })}
|
||||
.config=${entityConf}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
|
@ -251,6 +251,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
||||
.stateObj=${stateObj}
|
||||
.hass=${this.hass}
|
||||
.content=${this._config.state_content}
|
||||
.name=${this._config.name}
|
||||
>
|
||||
</state-display>
|
||||
`;
|
||||
|
@ -430,6 +430,14 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
this._config?.show_current !== false &&
|
||||
this._config?.show_forecast !== false
|
||||
) {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_rows: 4,
|
||||
grid_min_rows: 4,
|
||||
};
|
||||
}
|
||||
if (this._config?.show_forecast !== false) {
|
||||
return {
|
||||
grid_columns: 4,
|
||||
grid_min_columns: 2,
|
||||
@ -441,7 +449,7 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
grid_columns: 4,
|
||||
grid_min_columns: 2,
|
||||
grid_rows: 2,
|
||||
grid_min_rows: 1,
|
||||
grid_min_rows: 2,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ export function createStyledHuiElement(
|
||||
|
||||
if (elementConfig.style) {
|
||||
Object.keys(elementConfig.style).forEach((prop) => {
|
||||
element.style.setProperty(prop, elementConfig.style[prop]);
|
||||
element.style.setProperty(prop, elementConfig.style![prop]);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,7 @@ const HIDE_DOMAIN = new Set([
|
||||
"persistent_notification",
|
||||
"script",
|
||||
"sun",
|
||||
"tag",
|
||||
"todo",
|
||||
"zone",
|
||||
...ASSIST_ENTITIES,
|
||||
|
@ -1,5 +1,15 @@
|
||||
import { ActionConfig } from "../../../data/lovelace/config/action";
|
||||
import { ConfigEntity } from "../cards/types";
|
||||
|
||||
export function hasAction(config?: ActionConfig): boolean {
|
||||
return config !== undefined && config.action !== "none";
|
||||
}
|
||||
|
||||
export function hasAnyAction(config: ConfigEntity): boolean {
|
||||
return (
|
||||
!config.tap_action ||
|
||||
hasAction(config.tap_action) ||
|
||||
hasAction(config.hold_action) ||
|
||||
hasAction(config.double_tap_action)
|
||||
);
|
||||
}
|
||||
|
@ -226,8 +226,15 @@ export class HuiActionEditor extends LitElement {
|
||||
if (!this.hass) {
|
||||
return;
|
||||
}
|
||||
let action = this.config?.action;
|
||||
|
||||
if (action === "call-service") {
|
||||
action = "perform-action";
|
||||
}
|
||||
|
||||
const value = ev.target.value;
|
||||
if (this.config?.action === value) {
|
||||
|
||||
if (action === value) {
|
||||
return;
|
||||
}
|
||||
if (value === "default") {
|
||||
@ -292,7 +299,8 @@ export class HuiActionEditor extends LitElement {
|
||||
ev.stopPropagation();
|
||||
const value = {
|
||||
...this.config!,
|
||||
perform_action: ev.detail.value.service || "",
|
||||
action: "perform-action",
|
||||
perform_action: ev.detail.value.action || "",
|
||||
data: ev.detail.value.data,
|
||||
target: ev.detail.value.target || {},
|
||||
};
|
||||
|
@ -20,7 +20,7 @@ import { HomeAssistant } from "../../../types";
|
||||
import { EntitiesCardEntityConfig } from "../cards/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasAction, hasAnyAction } from "../common/has-action";
|
||||
import { createEntityNotFoundWarning } from "./hui-warning";
|
||||
|
||||
@customElement("hui-generic-entity-row")
|
||||
@ -60,9 +60,7 @@ export class HuiGenericEntityRow extends LitElement {
|
||||
// By default, we always show a pointer, since if there is no explicit configuration provided,
|
||||
// the frontend always assumes "more-info" in the action handler. We only need to hide the pointer
|
||||
// if the tap action is explicitly set to "none".
|
||||
const pointer = !(
|
||||
this.config.tap_action && this.config.tap_action.action === "none"
|
||||
);
|
||||
const pointer = hasAnyAction(this.config);
|
||||
|
||||
const hasSecondary = this.secondaryText || this.config.secondary_info;
|
||||
const name = this.config.name ?? computeStateName(stateObj);
|
||||
@ -82,7 +80,11 @@ export class HuiGenericEntityRow extends LitElement {
|
||||
hasHold: hasAction(this.config!.hold_action),
|
||||
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
||||
})}
|
||||
tabindex=${ifDefined(pointer ? "0" : undefined)}
|
||||
tabindex=${ifDefined(
|
||||
!this.config.tap_action || hasAction(this.config.tap_action)
|
||||
? "0"
|
||||
: undefined
|
||||
)}
|
||||
></state-badge>
|
||||
${!this.hideName
|
||||
? html`<div
|
||||
|
@ -35,7 +35,7 @@ export const createPictureElementElement = (config: LovelaceElementConfig) =>
|
||||
|
||||
export const getPictureElementClass = (type: string) =>
|
||||
getLovelaceElementClass(
|
||||
type,
|
||||
type === "action-button" ? "service-button" : type,
|
||||
"element",
|
||||
ALWAYS_LOADED_TYPES,
|
||||
LAZY_LOAD_TYPES
|
||||
|
@ -1,3 +1,5 @@
|
||||
import "@polymer/paper-tabs/paper-tab";
|
||||
import "@polymer/paper-tabs/paper-tabs";
|
||||
import { css, CSSResultGroup, html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { LovelaceBadgeConfig } from "../../../../data/lovelace/config/badge";
|
||||
|
@ -109,10 +109,6 @@ export class HuiDialogEditBadge
|
||||
this._badgeConfig = badge != null ? ensureBadgeConfig(badge) : badge;
|
||||
}
|
||||
|
||||
if (this._badgeConfig?.type === "state-label") {
|
||||
this._badgeConfig = { ...this._badgeConfig, type: "entity" };
|
||||
}
|
||||
|
||||
this.large = false;
|
||||
if (this._badgeConfig && !Object.isFrozen(this._badgeConfig)) {
|
||||
this._badgeConfig = deepFreeze(this._badgeConfig);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "@polymer/paper-tabs/paper-tab";
|
||||
import "@polymer/paper-tabs/paper-tabs";
|
||||
import "@material/mwc-tab-bar/mwc-tab-bar";
|
||||
import "@material/mwc-tab/mwc-tab";
|
||||
import { CSSResultGroup, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { LovelaceCardConfig } from "../../../../data/lovelace/config/card";
|
||||
|
@ -235,9 +235,11 @@ export class HaCardConditionEditor extends LitElement {
|
||||
? this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_pass"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_error"
|
||||
)
|
||||
: this._testingResult === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.lovelace.editor.condition-editor.testing_error"
|
||||
)
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
</ha-card>
|
||||
|
@ -102,7 +102,7 @@ export class HaCardConditionState extends LitElement {
|
||||
const data: StateConditionData = {
|
||||
...content,
|
||||
entity: this.condition.entity,
|
||||
invert: this.condition.state_not ? "true" : "false",
|
||||
invert: this.condition.state_not !== undefined ? "true" : "false",
|
||||
state: this.condition.state_not ?? this.condition.state,
|
||||
};
|
||||
|
||||
|
@ -106,12 +106,23 @@ export class HuiConditionalElementEditor
|
||||
private _elementsChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
const oldLength = this._config?.elements?.length || 0;
|
||||
const config = {
|
||||
...this._config,
|
||||
elements: ev.detail.elements as LovelaceElementConfig[],
|
||||
} as LovelaceCardConfig;
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
|
||||
const newLength = ev.detail.elements?.length || 0;
|
||||
if (newLength === oldLength + 1) {
|
||||
const index = newLength - 1;
|
||||
this._subElementEditorConfig = {
|
||||
index,
|
||||
type: "element",
|
||||
elementConfig: { ...ev.detail.elements[index] },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _handleSubElementChanged(ev: CustomEvent): void {
|
||||
|
@ -122,7 +122,9 @@ export class HuiEntityBadgeEditor
|
||||
{
|
||||
name: "state_content",
|
||||
selector: {
|
||||
ui_state_content: {},
|
||||
ui_state_content: {
|
||||
allow_name: true,
|
||||
},
|
||||
},
|
||||
context: {
|
||||
filter_entity: "entity",
|
||||
|
@ -23,7 +23,7 @@ import { baseLovelaceCardConfig } from "../structs/base-card-struct";
|
||||
import { DEFAULT_MIN, DEFAULT_MAX } from "../../cards/hui-gauge-card";
|
||||
import { UiAction } from "../../components/hui-action-editor";
|
||||
|
||||
const TAP_ACTIONS: UiAction[] = ["navigate", "url", "call-service", "none"];
|
||||
const TAP_ACTIONS: UiAction[] = ["navigate", "url", "perform-action", "none"];
|
||||
|
||||
const gaugeSegmentStruct = object({
|
||||
from: number(),
|
||||
|
@ -138,12 +138,23 @@ export class HuiPictureElementsCardEditor
|
||||
private _elementsChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
|
||||
const oldLength = this._config?.elements?.length || 0;
|
||||
const config = {
|
||||
...this._config,
|
||||
elements: ev.detail.elements as LovelaceElementConfig[],
|
||||
} as LovelaceCardConfig;
|
||||
|
||||
fireEvent(this, "config-changed", { config });
|
||||
|
||||
const newLength = ev.detail.elements?.length || 0;
|
||||
if (newLength === oldLength + 1) {
|
||||
const index = newLength - 1;
|
||||
this._subElementEditorConfig = {
|
||||
index,
|
||||
type: "element",
|
||||
elementConfig: { ...ev.detail.elements[index] },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _handleSubElementChanged(ev: CustomEvent): void {
|
||||
|
@ -0,0 +1,45 @@
|
||||
import { customElement } from "lit/decorators";
|
||||
import { assert, assign, boolean, object, optional, string } from "superstruct";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
import { EntityBadgeConfig } from "../../badges/types";
|
||||
import "../hui-sub-element-editor";
|
||||
import { actionConfigStruct } from "../structs/action-struct";
|
||||
import { baseLovelaceBadgeConfig } from "../structs/base-badge-struct";
|
||||
import "./hui-card-features-editor";
|
||||
import { HuiEntityBadgeEditor } from "./hui-entity-badge-editor";
|
||||
|
||||
const badgeConfigStruct = assign(
|
||||
baseLovelaceBadgeConfig,
|
||||
object({
|
||||
entity: optional(string()),
|
||||
name: optional(string()),
|
||||
icon: optional(string()),
|
||||
show_entity_picture: optional(boolean()),
|
||||
tap_action: optional(actionConfigStruct),
|
||||
show_name: optional(boolean()),
|
||||
image: optional(string()),
|
||||
})
|
||||
);
|
||||
|
||||
@customElement("hui-state-label-badge-editor")
|
||||
export class HuiStateLabelBadgeEditor extends HuiEntityBadgeEditor {
|
||||
// @ts-ignore
|
||||
public override setConfig(config: StateLabelBadgeConfig): void {
|
||||
assert(config, badgeConfigStruct);
|
||||
|
||||
const entityBadgeConfig: EntityBadgeConfig = {
|
||||
type: "entity",
|
||||
entity: config.entity,
|
||||
display_type: config.show_name === false ? "standard" : "complete",
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
this._config = entityBadgeConfig;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-state-label-badge-editor": HuiStateLabelBadgeEditor;
|
||||
}
|
||||
}
|
30
src/panels/lovelace/editor/get-element-stub-config.ts
Normal file
30
src/panels/lovelace/editor/get-element-stub-config.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { LovelaceElementConfig } from "../elements/types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { getPictureElementClass } from "../create-element/create-picture-element";
|
||||
|
||||
export const getElementStubConfig = async (
|
||||
hass: HomeAssistant,
|
||||
type: string,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
): Promise<LovelaceElementConfig> => {
|
||||
let elementConfig: LovelaceElementConfig = { type };
|
||||
|
||||
if (type !== "conditional") {
|
||||
elementConfig.style = { left: "50%", top: "50%" };
|
||||
}
|
||||
|
||||
const elClass = await getPictureElementClass(type);
|
||||
|
||||
if (elClass && elClass.getStubConfig) {
|
||||
const classStubConfig = await elClass.getStubConfig(
|
||||
hass,
|
||||
entities,
|
||||
entitiesFallback
|
||||
);
|
||||
|
||||
elementConfig = { ...elementConfig, ...classStubConfig };
|
||||
}
|
||||
|
||||
return elementConfig;
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
import deepClone from "deep-clone-simple";
|
||||
import { mdiClose, mdiPencil, mdiContentDuplicate } from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
@ -9,6 +10,7 @@ import { HomeAssistant } from "../../../types";
|
||||
import "../../../components/ha-select";
|
||||
import type { HaSelect } from "../../../components/ha-select";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { getElementStubConfig } from "./get-element-stub-config";
|
||||
import {
|
||||
ConditionalElementConfig,
|
||||
IconElementConfig,
|
||||
@ -171,17 +173,14 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
|
||||
if (value === "") {
|
||||
return;
|
||||
}
|
||||
const newElements = this.elements!.concat({
|
||||
type: value! as string,
|
||||
...(value !== "conditional"
|
||||
? {
|
||||
style: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
} as LovelaceElementConfig);
|
||||
const newElements = this.elements!.concat(
|
||||
await getElementStubConfig(
|
||||
this.hass!,
|
||||
value,
|
||||
Object.keys(this.hass!.entities),
|
||||
[]
|
||||
)
|
||||
);
|
||||
fireEvent(this, "elements-changed", { elements: newElements });
|
||||
this._select.select(-1);
|
||||
}
|
||||
@ -225,7 +224,7 @@ export class HuiPictureElementsCardRowEditor extends LitElement {
|
||||
|
||||
private _duplicateRow(ev: CustomEvent): void {
|
||||
const index = (ev.currentTarget as any).index;
|
||||
const newElements = [...this.elements!, this.elements![index]];
|
||||
const newElements = [...this.elements!, deepClone(this.elements![index])];
|
||||
|
||||
fireEvent(this, "elements-changed", { elements: newElements });
|
||||
}
|
||||
|
@ -53,9 +53,19 @@ export class HuiSubElementEditor extends LitElement {
|
||||
@click=${this._goBack}
|
||||
></ha-icon-button-prev>
|
||||
<span slot="title"
|
||||
>${this.hass.localize(
|
||||
`ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}`
|
||||
)}</span
|
||||
>${this.config?.type === "element"
|
||||
? this.hass.localize(
|
||||
`ui.panel.lovelace.editor.sub-element-editor.types.element_type`,
|
||||
{
|
||||
type:
|
||||
this.hass.localize(
|
||||
`ui.panel.lovelace.editor.card.picture-elements.element_types.${this.config?.elementConfig?.type}`
|
||||
) || this.config?.elementConfig?.type,
|
||||
}
|
||||
)
|
||||
: this.hass.localize(
|
||||
`ui.panel.lovelace.editor.sub-element-editor.types.${this.config?.type}`
|
||||
)}</span
|
||||
>
|
||||
</div>
|
||||
<ha-icon-button
|
||||
|
@ -41,6 +41,8 @@ const actionConfigStructService = object({
|
||||
entity_id: optional(union([string(), array(string())])),
|
||||
device_id: optional(union([string(), array(string())])),
|
||||
area_id: optional(union([string(), array(string())])),
|
||||
floor_id: optional(union([string(), array(string())])),
|
||||
label_id: optional(union([string(), array(string())])),
|
||||
})
|
||||
),
|
||||
confirmation: optional(actionConfigStructConfirmation),
|
||||
|
@ -18,6 +18,10 @@ export class HuiIconElement extends LitElement implements LovelaceElement {
|
||||
return document.createElement("hui-icon-element-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): IconElementConfig {
|
||||
return { type: "icon", icon: "mdi:alert-circle" };
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: IconElementConfig;
|
||||
|
@ -17,6 +17,14 @@ export class HuiServiceButtonElement
|
||||
return document.createElement("hui-service-button-element-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(hass: HomeAssistant): ServiceButtonElementConfig {
|
||||
return {
|
||||
type: "action-button",
|
||||
action: "homeassistant.turn_on",
|
||||
title: hass.localize("ui.card.common.turn_on"),
|
||||
};
|
||||
}
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: ServiceButtonElementConfig;
|
||||
|
@ -1,6 +1,8 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { LitElement, PropertyValues, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/entity/ha-state-label-badge";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
@ -8,6 +10,7 @@ import { HomeAssistant } from "../../../types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
@ -26,6 +29,27 @@ export class HuiStateBadgeElement
|
||||
return document.createElement("hui-state-badge-element-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
): StateBadgeElementConfig {
|
||||
const includeDomains = ["light", "switch", "sensor"];
|
||||
const maxEntities = 1;
|
||||
const entityFilter = (stateObj: HassEntity): boolean =>
|
||||
!isUnavailableState(stateObj.state);
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
entitiesFallback,
|
||||
includeDomains,
|
||||
entityFilter
|
||||
);
|
||||
|
||||
return { type: "state-badge", entity: foundEntities[0] || "" };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: StateBadgeElementConfig;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@ -8,12 +9,14 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import "../../../components/entity/state-badge";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeTooltip } from "../common/compute-tooltip";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
@ -30,6 +33,27 @@ export class HuiStateIconElement extends LitElement implements LovelaceElement {
|
||||
return document.createElement("hui-state-icon-element-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
): StateIconElementConfig {
|
||||
const includeDomains = ["light", "switch", "sensor"];
|
||||
const maxEntities = 1;
|
||||
const entityFilter = (stateObj: HassEntity): boolean =>
|
||||
!isUnavailableState(stateObj.state);
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
entitiesFallback,
|
||||
includeDomains,
|
||||
entityFilter
|
||||
);
|
||||
|
||||
return { type: "state-icon", entity: foundEntities[0] || "" };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: StateIconElementConfig;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
@ -8,12 +9,14 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { findEntities } from "../common/find-entities";
|
||||
import { ActionHandlerEvent } from "../../../data/lovelace/action_handler";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { computeTooltip } from "../common/compute-tooltip";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { isUnavailableState } from "../../../data/entity";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import "../components/hui-warning-element";
|
||||
@ -29,6 +32,27 @@ class HuiStateLabelElement extends LitElement implements LovelaceElement {
|
||||
return document.createElement("hui-state-label-element-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
): StateLabelElementConfig {
|
||||
const includeDomains = ["light", "switch", "sensor"];
|
||||
const maxEntities = 1;
|
||||
const entityFilter = (stateObj: HassEntity): boolean =>
|
||||
!isUnavailableState(stateObj.state);
|
||||
const foundEntities = findEntities(
|
||||
hass,
|
||||
maxEntities,
|
||||
entities,
|
||||
entitiesFallback,
|
||||
includeDomains,
|
||||
entityFilter
|
||||
);
|
||||
|
||||
return { type: "state-label", entity: foundEntities[0] || "" };
|
||||
}
|
||||
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _config?: StateLabelElementConfig;
|
||||
|
@ -6,7 +6,7 @@ import { HuiImage } from "../components/hui-image";
|
||||
|
||||
interface LovelaceElementConfigBase {
|
||||
type: string;
|
||||
style: Record<string, string>;
|
||||
style?: Record<string, string>;
|
||||
}
|
||||
|
||||
export type LovelaceElementConfig =
|
||||
|
@ -123,7 +123,6 @@ class HuiNumberEntityRow extends LitElement implements LovelaceRow {
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
.flex {
|
||||
|
@ -70,9 +70,6 @@ class HuiSceneEntityRow extends LitElement implements LovelaceRow {
|
||||
margin-inline-end: -0.57em;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
:host {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -58,9 +58,6 @@ class HuiSimpleEntityRow extends LitElement implements LovelaceRow {
|
||||
div {
|
||||
text-align: right;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -61,9 +61,6 @@ class HuiUpdateEntityRow extends LitElement implements LovelaceRow {
|
||||
div {
|
||||
text-align: right;
|
||||
}
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
import type { EntitiesCardEntityConfig } from "../cards/types";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { handleAction } from "../common/handle-action";
|
||||
import { hasAction } from "../common/has-action";
|
||||
import { hasAction, hasAnyAction } from "../common/has-action";
|
||||
import { hasConfigOrEntityChanged } from "../common/has-changed";
|
||||
import "../components/hui-generic-entity-row";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
@ -118,9 +118,7 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
|
||||
`;
|
||||
}
|
||||
|
||||
const pointer = !(
|
||||
this._config.tap_action && this._config.tap_action.action !== "none"
|
||||
);
|
||||
const pointer = hasAnyAction(this._config);
|
||||
|
||||
const hasSecondary = this._config.secondary_info;
|
||||
const weatherStateIcon = getWeatherStateIcon(stateObj.state, this);
|
||||
@ -138,7 +136,11 @@ class HuiWeatherEntityRow extends LitElement implements LovelaceRow {
|
||||
hasHold: hasAction(this._config!.hold_action),
|
||||
hasDoubleClick: hasAction(this._config!.double_tap_action),
|
||||
})}
|
||||
tabindex=${ifDefined(pointer ? "0" : undefined)}
|
||||
tabindex=${ifDefined(
|
||||
!this._config.tap_action || hasAction(this._config.tap_action)
|
||||
? "0"
|
||||
: undefined
|
||||
)}
|
||||
>
|
||||
${weatherStateIcon ||
|
||||
html`
|
||||
|
@ -231,14 +231,15 @@ class LovelaceFullConfigEditor extends LitElement {
|
||||
if (!value) {
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_remove_config_title"
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_delete_config_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_remove_config_text"
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_delete_config_text"
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.remove"),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
confirm: () => this._removeConfig(),
|
||||
destructive: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -109,6 +109,11 @@ export interface LovelaceRowConstructor extends Constructor<LovelaceRow> {
|
||||
export interface LovelaceElementConstructor
|
||||
extends Constructor<LovelaceElement> {
|
||||
getConfigElement?: () => LovelacePictureElementEditor;
|
||||
getStubConfig?: (
|
||||
hass: HomeAssistant,
|
||||
entities: string[],
|
||||
entitiesFallback: string[]
|
||||
) => LovelaceElementConfig;
|
||||
}
|
||||
|
||||
export interface LovelaceHeaderFooter extends HTMLElement {
|
||||
|
@ -12,6 +12,8 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { LovelaceViewElement } from "../../../data/lovelace";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { HuiBadge } from "../badges/hui-badge";
|
||||
import "../badges/hui-view-badges";
|
||||
import { HuiCard } from "../cards/hui-card";
|
||||
import { HuiCardOptions } from "../components/hui-card-options";
|
||||
import { replaceCard } from "../editor/config-util";
|
||||
@ -28,6 +30,8 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
@property({ attribute: false }) public cards: HuiCard[] = [];
|
||||
|
||||
@property({ attribute: false }) public badges: HuiBadge[] = [];
|
||||
|
||||
@state() private _config?: LovelaceViewConfig;
|
||||
|
||||
private _mqlListenerRef?: () => void;
|
||||
@ -85,6 +89,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<hui-view-badges
|
||||
.hass=${this.hass}
|
||||
.badges=${this.badges}
|
||||
.lovelace=${this.lovelace}
|
||||
.viewIndex=${this.index}
|
||||
></hui-view-badges>
|
||||
<div
|
||||
class="container ${this.lovelace?.editMode ? "edit-mode" : ""}"
|
||||
></div>
|
||||
@ -191,6 +201,12 @@ export class SideBarView extends LitElement implements LovelaceViewElement {
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
hui-view-badges {
|
||||
display: block;
|
||||
margin: 12px 8px 20px 8px;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -1,153 +0,0 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-icon-button";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
|
||||
@customElement("ha-dialog-show-audio-message")
|
||||
class HaDialogShowAudioMessage extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _currentMessage?: any;
|
||||
|
||||
@state() private _errorMsg?: string;
|
||||
|
||||
@state() private _loading: boolean = false;
|
||||
|
||||
@state() private _opened: boolean = false;
|
||||
|
||||
@state() private _blobUrl?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closed=${this._closeDialog}
|
||||
heading=${this.hass.localize("ui.panel.mailbox.playback_title")}
|
||||
>
|
||||
${this._loading
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
: html`<div class="icon">
|
||||
<ha-icon-button id="delicon" @click=${this._openDeleteDialog}>
|
||||
<ha-icon icon="hass:delete"></ha-icon>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
${
|
||||
this._currentMessage
|
||||
? html`<div id="transcribe">
|
||||
${this._currentMessage?.message}
|
||||
</div>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._errorMsg
|
||||
? html`<div class="error">${this._errorMsg}</div>`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
this._blobUrl
|
||||
? html` <audio id="mp3" preload="none" controls autoplay>
|
||||
<source
|
||||
id="mp3src"
|
||||
src=${this._blobUrl}
|
||||
type="audio/mpeg"
|
||||
/>
|
||||
</audio>`
|
||||
: nothing
|
||||
}
|
||||
</div>`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
showDialog({ hass, message }) {
|
||||
this.hass = hass;
|
||||
this._errorMsg = undefined;
|
||||
this._currentMessage = message;
|
||||
this._opened = true;
|
||||
const platform = message.platform;
|
||||
if (platform.has_media) {
|
||||
this._loading = true;
|
||||
const url = `/api/mailbox/media/${platform.name}/${message.sha}`;
|
||||
this.hass
|
||||
.fetchWithAuth(url)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.blob();
|
||||
}
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
});
|
||||
})
|
||||
.then((blob) => {
|
||||
this._loading = false;
|
||||
this._blobUrl = window.URL.createObjectURL(blob);
|
||||
})
|
||||
.catch((err) => {
|
||||
this._loading = false;
|
||||
this._errorMsg = `Error loading audio: ${err.statusText}`;
|
||||
});
|
||||
} else {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _openDeleteDialog() {
|
||||
if (confirm(this.hass.localize("ui.panel.mailbox.delete_prompt"))) {
|
||||
this._deleteSelected();
|
||||
}
|
||||
}
|
||||
|
||||
private _deleteSelected() {
|
||||
const msg = this._currentMessage;
|
||||
this.hass.callApi(
|
||||
"DELETE",
|
||||
`mailbox/delete/${msg.platform.name}/${msg.sha}`
|
||||
);
|
||||
this._closeDialog();
|
||||
}
|
||||
|
||||
private _closeDialog() {
|
||||
const mp3 = this.shadowRoot!.querySelector("#mp3")! as any;
|
||||
mp3.pause();
|
||||
this._currentMessage = undefined;
|
||||
this._errorMsg = undefined;
|
||||
this._loading = false;
|
||||
this._opened = false;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.icon {
|
||||
text-align: var(--float-end);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-dialog-show-audio-message": HaDialogShowAudioMessage;
|
||||
}
|
||||
}
|
@ -1,275 +0,0 @@
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "@material/mwc-button";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/ha-tabs";
|
||||
import "@polymer/paper-tabs/paper-tab";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import "../../components/ha-top-app-bar-fixed";
|
||||
import { formatDuration } from "../../common/datetime/format_duration";
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
interface MailboxMessage {
|
||||
info: {
|
||||
origtime: number;
|
||||
callerid: string;
|
||||
duration: string;
|
||||
};
|
||||
text: string;
|
||||
sha: string;
|
||||
}
|
||||
|
||||
@customElement("ha-panel-mailbox")
|
||||
class HaPanelMailbox extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public narrow!: boolean;
|
||||
|
||||
@property({ attribute: false }) public platforms?: any[];
|
||||
|
||||
@state() private _messages?: any[];
|
||||
|
||||
@state() private _currentPlatform: number = 0;
|
||||
|
||||
private _unsubEvents?;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-top-app-bar-fixed>
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div slot="title">${this.hass.localize("panel.mailbox")}</div>
|
||||
${!this._areTabsHidden(this.platforms)
|
||||
? html`<div sticky>
|
||||
<ha-tabs
|
||||
scrollable
|
||||
.selected=${this._currentPlatform}
|
||||
@iron-activate=${this._handlePlatformSelected}
|
||||
>
|
||||
${this.platforms?.map(
|
||||
(platform) =>
|
||||
html` <paper-tab data-entity=${platform}>
|
||||
${this._getPlatformName(platform)}
|
||||
</paper-tab>`
|
||||
)}
|
||||
</ha-tabs>
|
||||
</div>`
|
||||
: ""}
|
||||
</ha-top-app-bar-fixed>
|
||||
<div class="content">
|
||||
<ha-card>
|
||||
${!this._messages?.length
|
||||
? html`<div class="card-content empty">
|
||||
${this.hass.localize("ui.panel.mailbox.empty")}
|
||||
</div>`
|
||||
: nothing}
|
||||
${this._messages?.map(
|
||||
(message) =>
|
||||
html` <ha-list-item
|
||||
.message=${message}
|
||||
@click=${this._openMP3Dialog}
|
||||
twoline
|
||||
>
|
||||
<span>
|
||||
<span>${message.caller}</span>
|
||||
<span class="tip">
|
||||
${formatDuration(this.hass.locale, {
|
||||
seconds: message.duration,
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
<span slot="secondary">
|
||||
<span class="date">${message.timestamp}</span> -
|
||||
${message.message}
|
||||
</span>
|
||||
</ha-list-item>`
|
||||
)}
|
||||
</ha-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
fireEvent(this, "register-dialog", {
|
||||
dialogShowEvent: "show-audio-message-dialog",
|
||||
dialogTag: "ha-dialog-show-audio-message",
|
||||
dialogImport: () => import("./ha-dialog-show-audio-message"),
|
||||
});
|
||||
}
|
||||
this.hassChanged = this.hassChanged.bind(this);
|
||||
this.hass.connection
|
||||
.subscribeEvents(this.hassChanged, "mailbox_updated")
|
||||
.then((unsub) => {
|
||||
this._unsubEvents = unsub;
|
||||
});
|
||||
this._computePlatforms().then((platforms) => {
|
||||
this.platforms = platforms;
|
||||
this.hassChanged();
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubEvents) this._unsubEvents();
|
||||
}
|
||||
|
||||
hassChanged() {
|
||||
if (!this._messages) {
|
||||
this._messages = [];
|
||||
}
|
||||
this._getMessages().then((items) => {
|
||||
this._messages = items;
|
||||
});
|
||||
}
|
||||
|
||||
private _openMP3Dialog(ev) {
|
||||
const message: any = (ev.currentTarget! as any).message;
|
||||
fireEvent(this, "show-audio-message-dialog", {
|
||||
hass: this.hass,
|
||||
message: message,
|
||||
});
|
||||
}
|
||||
|
||||
private _getMessages() {
|
||||
const platform = this.platforms![this._currentPlatform];
|
||||
return this.hass
|
||||
.callApi<MailboxMessage[]>("GET", `mailbox/messages/${platform.name}`)
|
||||
.then((values) => {
|
||||
const platformItems: any[] = [];
|
||||
const arrayLength = values.length;
|
||||
for (let i = 0; i < arrayLength; i++) {
|
||||
const datetime = formatDateTime(
|
||||
new Date(values[i].info.origtime * 1000),
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
platformItems.push({
|
||||
timestamp: datetime,
|
||||
caller: values[i].info.callerid,
|
||||
message: values[i].text,
|
||||
sha: values[i].sha,
|
||||
duration: values[i].info.duration,
|
||||
platform: platform,
|
||||
});
|
||||
}
|
||||
return platformItems.sort((a, b) => b.timestamp - a.timestamp);
|
||||
});
|
||||
}
|
||||
|
||||
private _computePlatforms(): Promise<any[]> {
|
||||
return this.hass.callApi<any[]>("GET", "mailbox/platforms");
|
||||
}
|
||||
|
||||
private _handlePlatformSelected(ev) {
|
||||
const newPlatform = ev.detail.selected;
|
||||
if (newPlatform !== this._currentPlatform) {
|
||||
this._currentPlatform = newPlatform;
|
||||
this.hassChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _areTabsHidden(platforms) {
|
||||
return !platforms || platforms.length < 2;
|
||||
}
|
||||
|
||||
private _getPlatformName(item) {
|
||||
const entity = `mailbox.${item.name}`;
|
||||
const stateObj = this.hass.states[entity.toLowerCase()];
|
||||
return stateObj.attributes.friendly_name;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
-ms-user-select: initial;
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ha-tabs {
|
||||
margin-left: max(env(safe-area-inset-left), 24px);
|
||||
margin-right: max(env(safe-area-inset-right), 24px);
|
||||
margin-inline-start: max(env(safe-area-inset-left), 24px);
|
||||
margin-inline-end: max(env(safe-area-inset-right), 24px);
|
||||
--paper-tabs-selection-bar-color: #fff;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
@apply --paper-font-title;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
.content {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tip {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
}
|
||||
.date {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-panel-mailbox": HaPanelMailbox;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"show-audio-message-dialog": {
|
||||
hass: HomeAssistant;
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
}
|
@ -222,6 +222,7 @@ class HaProfileSectionGeneral extends LitElement {
|
||||
text: this.hass.localize("ui.panel.profile.logout_text"),
|
||||
confirmText: this.hass.localize("ui.panel.profile.logout"),
|
||||
confirm: () => fireEvent(this, "hass-logout"),
|
||||
destructive: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -364,6 +364,9 @@ class DialogTodoItemEditor extends LitElement {
|
||||
"ui.components.todo.item.confirm_delete.delete"
|
||||
),
|
||||
text: this.hass.localize("ui.components.todo.item.confirm_delete.prompt"),
|
||||
destructive: true,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
});
|
||||
if (!confirm) {
|
||||
// Cancel
|
||||
|
@ -55,6 +55,8 @@ class StateDisplay extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public content?: StateContent;
|
||||
|
||||
@property({ attribute: false }) public name?: string;
|
||||
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
@ -88,6 +90,9 @@ class StateDisplay extends LitElement {
|
||||
|
||||
return this.hass!.formatEntityState(stateObj);
|
||||
}
|
||||
if (content === "name") {
|
||||
return html`${this.name || stateObj.attributes.friendly_name}`;
|
||||
}
|
||||
// Check last-changed for backwards compatibility
|
||||
if (content === "last_changed" || content === "last-changed") {
|
||||
return html`
|
||||
|
@ -7,7 +7,6 @@
|
||||
"map": "Map",
|
||||
"logbook": "Logbook",
|
||||
"history": "History",
|
||||
"mailbox": "Mailbox",
|
||||
"todo": "To-do lists",
|
||||
"developer_tools": "Developer tools",
|
||||
"media_browser": "Media",
|
||||
@ -71,6 +70,11 @@
|
||||
"backup": {
|
||||
"upload_backup": "Upload backup"
|
||||
},
|
||||
"badge": {
|
||||
"entity": {
|
||||
"not_found": "[%key:ui::card::tile::not_found%]"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"common": {
|
||||
"turn_on": "Turn on",
|
||||
@ -171,6 +175,7 @@
|
||||
"actions": {
|
||||
"resume_mowing": "Resume mowing",
|
||||
"start_mowing": "Start mowing",
|
||||
"pause": "Pause",
|
||||
"dock": "Return to dock"
|
||||
}
|
||||
},
|
||||
@ -1026,6 +1031,7 @@
|
||||
},
|
||||
"state-content-picker": {
|
||||
"state": "State",
|
||||
"name": "Name",
|
||||
"last_changed": "Last changed",
|
||||
"last_updated": "Last updated",
|
||||
"remaining_time": "Remaining time",
|
||||
@ -3743,6 +3749,7 @@
|
||||
"name": "Name",
|
||||
"last_activated": "Last activated",
|
||||
"category": "Category",
|
||||
"editable": "[%key:ui::panel::config::helpers::picker::headers::editable%]",
|
||||
"area": "Area",
|
||||
"icon": "Icon"
|
||||
},
|
||||
@ -5490,8 +5497,8 @@
|
||||
"saved": "Saved",
|
||||
"reload": "Reload",
|
||||
"lovelace_changed": "Your dashboard was updated, do you want to load the updated config in the editor and lose your current changes?",
|
||||
"confirm_remove_config_title": "Are you sure you want to remove your dashboard configuration?",
|
||||
"confirm_remove_config_text": "We will automatically generate your dashboard views with your areas and devices if you remove your dashboard configuration.",
|
||||
"confirm_delete_config_title": "Delete dashboard configuration?",
|
||||
"confirm_delete_config_text": "This dashboard will be permanently deleted. The dashboard will be automatically regenerated to display your areas, devices and entities.",
|
||||
"confirm_unsaved_changes": "You have unsaved changes, are you sure you want to exit?",
|
||||
"confirm_unsaved_comments": "Your configuration might contains comment(s), these will not be saved. Do you want to continue?",
|
||||
"error_parse_yaml": "Unable to parse YAML: {error}",
|
||||
@ -5625,6 +5632,7 @@
|
||||
},
|
||||
"section": {
|
||||
"unnamed_section": "Unnamed section",
|
||||
"add_badge": "Add badge",
|
||||
"add_card": "[%key:ui::panel::lovelace::editor::edit_card::add%]",
|
||||
"create_section": "Create section"
|
||||
},
|
||||
@ -6323,7 +6331,8 @@
|
||||
"footer": "Footer editor",
|
||||
"row": "Entity row editor",
|
||||
"feature": "Feature editor",
|
||||
"element": "Element editor"
|
||||
"element": "Element editor",
|
||||
"element_type": "{type} element editor"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -6351,12 +6360,6 @@
|
||||
},
|
||||
"reload_lovelace": "Reload UI"
|
||||
},
|
||||
"mailbox": {
|
||||
"empty": "You do not have any messages",
|
||||
"playback_title": "Message playback",
|
||||
"delete_prompt": "Delete this message?",
|
||||
"delete_button": "Delete"
|
||||
},
|
||||
"media-browser": {
|
||||
"error": {
|
||||
"player_not_exist": "Media player {name} does not exist"
|
||||
@ -6853,6 +6856,7 @@
|
||||
"title": "Template",
|
||||
"description": "Templates are rendered using the Jinja2 template engine with some Home Assistant specific extensions.",
|
||||
"editor": "Template editor",
|
||||
"result": "Result",
|
||||
"reset": "Reset to demo template",
|
||||
"confirm_reset": "Do you want to reset your current template back to the demo template?",
|
||||
"confirm_clear": "Do you want to clear your current template?",
|
||||
|
Loading…
x
Reference in New Issue
Block a user