mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-13 15:09:41 +00:00
Compare commits
175 Commits
tile-templ
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d64acca598 | ||
![]() |
59571d03a6 | ||
![]() |
28c515bbac | ||
![]() |
27db5b3b02 | ||
![]() |
1922db0474 | ||
![]() |
c8c74a9744 | ||
![]() |
2c676baa99 | ||
![]() |
3e41474faa | ||
![]() |
5f9c69ac21 | ||
![]() |
8b45ccaaba | ||
![]() |
455925f637 | ||
![]() |
9fba7427f8 | ||
![]() |
21aae02652 | ||
![]() |
24e3fbf622 | ||
![]() |
2cbcf1a689 | ||
![]() |
1c1c0d70c5 | ||
![]() |
a66f5fb573 | ||
![]() |
9affeab755 | ||
![]() |
2bfaf77908 | ||
![]() |
bc4caae796 | ||
![]() |
8746acd329 | ||
![]() |
96ecf16da2 | ||
![]() |
1e95a0f3ef | ||
![]() |
a164d793b1 | ||
![]() |
510fc71b40 | ||
![]() |
2a6a3edb77 | ||
![]() |
c7a8796a47 | ||
![]() |
9d40fa5f2b | ||
![]() |
8f2a023775 | ||
![]() |
989b0b34fe | ||
![]() |
cf94e71215 | ||
![]() |
49896f3fa6 | ||
![]() |
fc4b7674b1 | ||
![]() |
04c9f32539 | ||
![]() |
21e3fc9bb9 | ||
![]() |
4b78eb7656 | ||
![]() |
e6f91aef8e | ||
![]() |
8f99f86c8b | ||
![]() |
b7eff547c7 | ||
![]() |
ceb6b64152 | ||
![]() |
d253041376 | ||
![]() |
cb0aa81f89 | ||
![]() |
42061b2f8c | ||
![]() |
69bfb89a65 | ||
![]() |
e0307f9688 | ||
![]() |
1cf353461f | ||
![]() |
1786235c86 | ||
![]() |
645ba3f9c1 | ||
![]() |
b65f6f46e1 | ||
![]() |
84ad521b3d | ||
![]() |
dfb9c662e7 | ||
![]() |
5ac42e17b0 | ||
![]() |
be2f19637e | ||
![]() |
b7a6ee3792 | ||
![]() |
1fb2f0c989 | ||
![]() |
b4ad411e6f | ||
![]() |
5d76a92f73 | ||
![]() |
beee09491a | ||
![]() |
ee5aabdddf | ||
![]() |
ec80f6a6f1 | ||
![]() |
9845f0b47c | ||
![]() |
cd294ba619 | ||
![]() |
61e27cb1ea | ||
![]() |
8d6295e8e8 | ||
![]() |
b0e95699f7 | ||
![]() |
c8e1e7b8a8 | ||
![]() |
d2cea159af | ||
![]() |
eb5d1c79c8 | ||
![]() |
65ab6848ab | ||
![]() |
7a1d934e8d | ||
![]() |
cbacde12fa | ||
![]() |
4c33618e05 | ||
![]() |
3837b3e630 | ||
![]() |
7c15633f6d | ||
![]() |
f7ec8650eb | ||
![]() |
7674eee0fb | ||
![]() |
f494a6453a | ||
![]() |
37f3682ffa | ||
![]() |
8055286a1f | ||
![]() |
0bdd213761 | ||
![]() |
810b43760e | ||
![]() |
424d71c55a | ||
![]() |
176924241c | ||
![]() |
da08aa7fb0 | ||
![]() |
6047227648 | ||
![]() |
fc71fd6bc3 | ||
![]() |
90a1b135e1 | ||
![]() |
e19413b6ca | ||
![]() |
0dfc10af5f | ||
![]() |
bbbc419bea | ||
![]() |
50ad5e376f | ||
![]() |
a9f2254bbc | ||
![]() |
a8836404d4 | ||
![]() |
954e0a5f63 | ||
![]() |
4dbd4eebaa | ||
![]() |
09b01df366 | ||
![]() |
a76539c732 | ||
![]() |
c7babe884c | ||
![]() |
ce83feec93 | ||
![]() |
150ee3fb12 | ||
![]() |
8fd3fcd323 | ||
![]() |
6e3b3a53e4 | ||
![]() |
22966485c7 | ||
![]() |
673ca8ba4b | ||
![]() |
c8be25dfc2 | ||
![]() |
edaaa00038 | ||
![]() |
2de605d97a | ||
![]() |
0b11302b1d | ||
![]() |
ddb224e145 | ||
![]() |
317149e51e | ||
![]() |
51840b88b3 | ||
![]() |
319a1ad8c6 | ||
![]() |
d75c84750d | ||
![]() |
492a73e345 | ||
![]() |
64bf101c95 | ||
![]() |
876ced25f1 | ||
![]() |
72e3b72854 | ||
![]() |
6ccd3d3b95 | ||
![]() |
5709cb6aa4 | ||
![]() |
1fe7282b0e | ||
![]() |
6d29063b35 | ||
![]() |
d3e0b94d27 | ||
![]() |
f4f1f98433 | ||
![]() |
c172f0c486 | ||
![]() |
1244ed73a2 | ||
![]() |
e2aef205cc | ||
![]() |
7434c9345a | ||
![]() |
287ff17107 | ||
![]() |
21309944e5 | ||
![]() |
c0e39ffd67 | ||
![]() |
3da2cb3123 | ||
![]() |
7957bd1f25 | ||
![]() |
64e00e559f | ||
![]() |
b407bd4c4f | ||
![]() |
d466abf9c4 | ||
![]() |
4d98230145 | ||
![]() |
8a5bca0eb0 | ||
![]() |
1638da858c | ||
![]() |
bfb11102cc | ||
![]() |
a3d3539e82 | ||
![]() |
1fc6cff857 | ||
![]() |
c5d7eb5384 | ||
![]() |
759e6eba35 | ||
![]() |
153129e066 | ||
![]() |
7bf3c7273e | ||
![]() |
08765e6ce2 | ||
![]() |
550e4cd4aa | ||
![]() |
f6041c5cbb | ||
![]() |
42f65c2ca1 | ||
![]() |
588f171f7f | ||
![]() |
bd1445840d | ||
![]() |
97a0903cec | ||
![]() |
e2525a3d07 | ||
![]() |
8bc0f5a42c | ||
![]() |
bf7e8ffd24 | ||
![]() |
255e598c65 | ||
![]() |
5e22178225 | ||
![]() |
56b7a6abec | ||
![]() |
f34c4a11af | ||
![]() |
102689b711 | ||
![]() |
b16d769192 | ||
![]() |
29bcacc64e | ||
![]() |
c7f3331373 | ||
![]() |
c32444b70c | ||
![]() |
11c6b90eb0 | ||
![]() |
f7a17598f0 | ||
![]() |
56967bc0c1 | ||
![]() |
b01ab9234b | ||
![]() |
ad39228dea | ||
![]() |
8cc48cdecb | ||
![]() |
524e89acf0 | ||
![]() |
48f6b34882 | ||
![]() |
44d9185574 | ||
![]() |
51ff6c6564 | ||
![]() |
b49b8e3db8 |
@@ -18,7 +18,6 @@ import { HaDeviceAction } from "../../../../src/panels/config/automation/action/
|
||||
import { HaEventAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-event";
|
||||
import { HaIfAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-if";
|
||||
import { HaParallelAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-parallel";
|
||||
import { HaPlayMediaAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-play_media";
|
||||
import { HaRepeatAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-repeat";
|
||||
import { HaSequenceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-sequence";
|
||||
import { HaServiceAction } from "../../../../src/panels/config/automation/action/types/ha-automation-action-service";
|
||||
@@ -32,7 +31,6 @@ const SCHEMAS: { name: string; actions: Action[] }[] = [
|
||||
{ name: "Service", actions: [HaServiceAction.defaultConfig] },
|
||||
{ name: "Condition", actions: [HaConditionAction.defaultConfig] },
|
||||
{ name: "Delay", actions: [HaDelayAction.defaultConfig] },
|
||||
{ name: "Play media", actions: [HaPlayMediaAction.defaultConfig] },
|
||||
{ name: "Wait", actions: [HaWaitAction.defaultConfig] },
|
||||
{ name: "WaitForTrigger", actions: [HaWaitForTriggerAction.defaultConfig] },
|
||||
{ name: "Repeat", actions: [HaRepeatAction.defaultConfig] },
|
||||
|
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Template
|
||||
---
|
@@ -1,65 +0,0 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-template";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
import { getEntity } from "../../../../src/fake_data/entity";
|
||||
|
||||
interface TemplateContent {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const templates: TemplateContent[] = [
|
||||
{ content: "{{ states('sensor.temperature') }}" },
|
||||
{
|
||||
content: "{{ 'Day' if is_state('sun.sun', 'above_horizon') else 'Night' }}",
|
||||
},
|
||||
];
|
||||
|
||||
const ENTITIES = [
|
||||
getEntity("sensor", "temperature", "25", {
|
||||
friendly_name: "Temperature",
|
||||
}),
|
||||
getEntity("sun", "sun", "above_horizon", {
|
||||
friendly_name: "Controller 2",
|
||||
}),
|
||||
];
|
||||
|
||||
@customElement("demo-misc-ha-template")
|
||||
export class DemoMiscTemplate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
protected firstUpdated() {
|
||||
const hass = provideHass(this);
|
||||
hass.addEntities(ENTITIES);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="container">
|
||||
${templates.map(
|
||||
(t) =>
|
||||
html`<ha-card>
|
||||
<pre>Template: ${t.content}</pre>
|
||||
<pre>Result: <ha-template
|
||||
.hass=${this.hass} .content=${t.content}></ha-template></pre>
|
||||
</ha-card>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-card {
|
||||
margin: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-misc-ha-template": DemoMiscTemplate;
|
||||
}
|
||||
}
|
@@ -126,7 +126,6 @@
|
||||
"marked": "16.2.0",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.3",
|
||||
"nunjucks": "3.2.4",
|
||||
"object-hash": "3.0.0",
|
||||
"punycode": "2.3.1",
|
||||
"qr-scanner": "1.4.2",
|
||||
@@ -154,7 +153,7 @@
|
||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||
"@babel/plugin-transform-runtime": "7.28.3",
|
||||
"@babel/preset-env": "7.28.3",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.2",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.3",
|
||||
"@lokalise/node-api": "15.2.1",
|
||||
"@octokit/auth-oauth-device": "8.0.1",
|
||||
"@octokit/plugin-retry": "8.0.1",
|
||||
@@ -175,7 +174,6 @@
|
||||
"@types/lodash.merge": "4.6.9",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/mocha": "10.0.10",
|
||||
"@types/nunjucks": "^3",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@types/tar": "6.1.13",
|
||||
|
@@ -0,0 +1,76 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4744_40067)">
|
||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H28C31.3137 0 34 2.68629 34 6C34 9.31371 31.3137 12 28 12H6C2.68629 12 0 9.31371 0 6Z" fill="white" fill-opacity="0.48"/>
|
||||
<path d="M0 28C0 23.5817 3.58172 20 8 20H42.6667C47.0849 20 50.6667 23.5817 50.6667 28V36C50.6667 40.4183 47.0849 44 42.6667 44H8.00001C3.58173 44 0 40.4183 0 36V28Z" fill="#1C1C1C"/>
|
||||
<path d="M8 20.5H42.667C46.809 20.5002 50.167 23.858 50.167 28V36C50.167 40.142 46.809 43.4998 42.667 43.5H8C3.85787 43.5 0.5 40.1421 0.5 36V28C0.5 23.8579 3.85786 20.5 8 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M6 32C6 28.6863 8.68629 26 12 26C15.3137 26 18 28.6863 18 32C18 35.3137 15.3137 38 12 38C8.68629 38 6 35.3137 6 32Z" fill="white" fill-opacity="0.24"/>
|
||||
<path d="M24 31C24 29.3431 25.3431 28 27 28H39.6667C41.3235 28 42.6667 29.3431 42.6667 31V33C42.6667 34.6569 41.3235 36 39.6667 36H27C25.3431 36 24 34.6569 24 33V31Z" fill="white" fill-opacity="0.24"/>
|
||||
<path d="M54.6666 28C54.6666 23.5817 58.2483 20 62.6666 20H97.3333C101.752 20 105.333 23.5817 105.333 28V36C105.333 40.4183 101.752 44 97.3333 44H62.6666C58.2484 44 54.6666 40.4183 54.6666 36V28Z" fill="#1C1C1C"/>
|
||||
<path d="M62.6666 20.5H97.3336C101.476 20.5002 104.834 23.858 104.834 28V36C104.834 40.142 101.476 43.4998 97.3336 43.5H62.6666C58.5245 43.5 55.1666 40.1421 55.1666 36V28C55.1666 23.8579 58.5245 20.5 62.6666 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M60.6666 32C60.6666 28.6863 63.3529 26 66.6666 26C69.9803 26 72.6666 28.6863 72.6666 32C72.6666 35.3137 69.9803 38 66.6666 38C63.3529 38 60.6666 35.3137 60.6666 32Z" fill="white" fill-opacity="0.24"/>
|
||||
<path d="M78.6666 31C78.6666 29.3431 80.0098 28 81.6666 28H94.3333C95.9901 28 97.3333 29.3431 97.3333 31V33C97.3333 34.6569 95.9901 36 94.3333 36H81.6666C80.0098 36 78.6666 34.6569 78.6666 33V31Z" fill="white" fill-opacity="0.24"/>
|
||||
<path d="M109.333 28C109.333 23.5817 112.915 20 117.333 20H152C156.418 20 160 23.5817 160 28V36C160 40.4183 156.418 44 152 44H117.333C112.915 44 109.333 40.4183 109.333 36V28Z" fill="#1C1C1C"/>
|
||||
<path d="M117.333 20.5H152C156.142 20.5002 159.5 23.858 159.5 28V36C159.5 40.142 156.142 43.4998 152 43.5H117.333C113.191 43.5 109.833 40.1421 109.833 36V28C109.833 23.8579 113.191 20.5 117.333 20.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<path d="M115.333 32C115.333 28.6863 118.02 26 121.333 26C124.647 26 127.333 28.6863 127.333 32C127.333 35.3137 124.647 38 121.333 38C118.02 38 115.333 35.3137 115.333 32Z" fill="white" fill-opacity="0.24"/>
|
||||
<path d="M133.333 31C133.333 29.3431 134.677 28 136.333 28H149C150.657 28 152 29.3431 152 31V33C152 34.6569 150.657 36 149 36H136.333C134.677 36 133.333 34.6569 133.333 33V31Z" fill="white" fill-opacity="0.24"/>
|
||||
<path d="M0 56C0 53.7909 1.79086 52 4 52H29C31.2091 52 33 53.7909 33 56C33 58.2091 31.2091 60 29 60H4C1.79086 60 0 58.2091 0 56Z" fill="white" fill-opacity="0.48"/>
|
||||
<path d="M0 72C0 67.5817 3.58172 64 8 64H29C33.4183 64 37 67.5817 37 72V96C37 100.418 33.4183 104 29 104H8C3.58172 104 0 100.418 0 96V72Z" fill="#1C1C1C"/>
|
||||
<path d="M8 64.5H29C33.1421 64.5 36.5 67.8579 36.5 72V96C36.5 100.142 33.1421 103.5 29 103.5H8C3.85786 103.5 0.5 100.142 0.5 96V72C0.5 67.8579 3.85786 64.5 8 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<mask id="mask0_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="72" width="25" height="24">
|
||||
<path d="M18.5 74C16.6435 74 14.863 74.7375 13.5503 76.0503C12.2375 77.363 11.5 79.1435 11.5 81C11.5 83.38 12.69 85.47 14.5 86.74V89C14.5 89.2652 14.6054 89.5196 14.7929 89.7071C14.9804 89.8946 15.2348 90 15.5 90H21.5C21.7652 90 22.0196 89.8946 22.2071 89.7071C22.3946 89.5196 22.5 89.2652 22.5 89V86.74C24.31 85.47 25.5 83.38 25.5 81C25.5 79.1435 24.7625 77.363 23.4497 76.0503C22.137 74.7375 20.3565 74 18.5 74ZM15.5 93C15.5 93.2652 15.6054 93.5196 15.7929 93.7071C15.9804 93.8946 16.2348 94 16.5 94H20.5C20.7652 94 21.0196 93.8946 21.2071 93.7071C21.3946 93.5196 21.5 93.2652 21.5 93V92H15.5V93Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_4744_40067)">
|
||||
<rect x="6.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M41 72C41 67.5817 44.5817 64 49 64H70C74.4183 64 78 67.5817 78 72V96C78 100.418 74.4183 104 70 104H49C44.5817 104 41 100.418 41 96V72Z" fill="#1C1C1C"/>
|
||||
<path d="M49 64.5H70C74.1421 64.5 77.5 67.8579 77.5 72V96C77.5 100.142 74.1421 103.5 70 103.5H49C44.8579 103.5 41.5 100.142 41.5 96V72C41.5 67.8579 44.8579 64.5 49 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<mask id="mask1_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="72" width="25" height="24">
|
||||
<path d="M66.5 80C67.61 80 68.5 80.9 68.5 82V88.76C69.11 89.31 69.5 90.11 69.5 91C69.5 92.66 68.16 94 66.5 94C64.84 94 63.5 92.66 63.5 91C63.5 90.11 63.89 89.31 64.5 88.76V82C64.5 80.9 65.4 80 66.5 80ZM66.5 81C65.95 81 65.5 81.45 65.5 82V83H67.5V82C67.5 81.45 67.05 81 66.5 81ZM52.5 92V84H49.5L59.5 75L63.9 78.96C63.04 79.69 62.5 80.78 62.5 82V88C61.87 88.83 61.5 89.87 61.5 91L61.6 92H52.5Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_4744_40067)">
|
||||
<rect x="47.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M82 72C82 67.5817 85.5817 64 90 64H111C115.418 64 119 67.5817 119 72V96C119 100.418 115.418 104 111 104H90C85.5817 104 82 100.418 82 96V72Z" fill="#1C1C1C"/>
|
||||
<path d="M90 64.5H111C115.142 64.5 118.5 67.8579 118.5 72V96C118.5 100.142 115.142 103.5 111 103.5H90C85.8579 103.5 82.5 100.142 82.5 96V72C82.5 67.8579 85.8579 64.5 90 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<mask id="mask2_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="88" y="72" width="25" height="24">
|
||||
<path d="M100.5 84H107.5C106.97 88.11 104.22 91.78 100.5 92.92V84H93.5V78.3L100.5 75.19M100.5 73L91.5 77V83C91.5 88.55 95.34 93.73 100.5 95C105.66 93.73 109.5 88.55 109.5 83V77L100.5 73Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask2_4744_40067)">
|
||||
<rect x="88.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M123 72C123 67.5817 126.582 64 131 64H152C156.418 64 160 67.5817 160 72V96C160 100.418 156.418 104 152 104H131C126.582 104 123 100.418 123 96V72Z" fill="#1C1C1C"/>
|
||||
<path d="M131 64.5H152C156.142 64.5 159.5 67.8579 159.5 72V96C159.5 100.142 156.142 103.5 152 103.5H131C126.858 103.5 123.5 100.142 123.5 96V72C123.5 67.8579 126.858 64.5 131 64.5Z" stroke="white" stroke-opacity="0.24"/>
|
||||
<mask id="mask3_4744_40067" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="129" y="72" width="25" height="24">
|
||||
<path d="M145.5 84C145.5 83.4696 145.711 82.9609 146.086 82.5858C146.461 82.2107 146.97 82 147.5 82C148.03 82 148.539 82.2107 148.914 82.5858C149.289 82.9609 149.5 83.4696 149.5 84C149.5 84.5304 149.289 85.0391 148.914 85.4142C148.539 85.7893 148.03 86 147.5 86C146.97 86 146.461 85.7893 146.086 85.4142C145.711 85.0391 145.5 84.5304 145.5 84ZM139.5 84C139.5 83.4696 139.711 82.9609 140.086 82.5858C140.461 82.2107 140.97 82 141.5 82C142.03 82 142.539 82.2107 142.914 82.5858C143.289 82.9609 143.5 83.4696 143.5 84C143.5 84.5304 143.289 85.0391 142.914 85.4142C142.539 85.7893 142.03 86 141.5 86C140.97 86 140.461 85.7893 140.086 85.4142C139.711 85.0391 139.5 84.5304 139.5 84ZM133.5 84C133.5 83.4696 133.711 82.9609 134.086 82.5858C134.461 82.2107 134.97 82 135.5 82C136.03 82 136.539 82.2107 136.914 82.5858C137.289 82.9609 137.5 83.4696 137.5 84C137.5 84.5304 137.289 85.0391 136.914 85.4142C136.539 85.7893 136.03 86 135.5 86C134.97 86 134.461 85.7893 134.086 85.4142C133.711 85.0391 133.5 84.5304 133.5 84Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask3_4744_40067)">
|
||||
<rect x="129.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M0 116C0 113.791 1.79086 112 4 112H29C31.2091 112 33 113.791 33 116C33 118.209 31.2091 120 29 120H4C1.79086 120 0 118.209 0 116Z" fill="white" fill-opacity="0.48"/>
|
||||
<path d="M0 132C0 127.582 3.58172 124 8 124H70C74.4183 124 78 127.582 78 132V160H0V132Z" fill="url(#paint0_linear_4744_40067)"/>
|
||||
<path d="M8 124.5H70C74.1421 124.5 77.5 127.858 77.5 132V159.5H0.5V132C0.5 127.858 3.85786 124.5 8 124.5Z" stroke="url(#paint1_linear_4744_40067)" stroke-opacity="0.12"/>
|
||||
<path d="M82 132C82 127.582 85.5817 124 90 124H152C156.418 124 160 127.582 160 132V160H82V132Z" fill="url(#paint2_linear_4744_40067)"/>
|
||||
<path d="M90 124.5H152C156.142 124.5 159.5 127.858 159.5 132V159.5H82.5V132C82.5 127.858 85.8579 124.5 90 124.5Z" stroke="url(#paint3_linear_4744_40067)" stroke-opacity="0.12"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_4744_40067" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-color="#1C1C1C"/>
|
||||
<stop offset="1" stop-color="#1C1C1C" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_4744_40067" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-color="white" stop-opacity="0.24"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_4744_40067" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-color="#1C1C1C"/>
|
||||
<stop offset="1" stop-color="#1C1C1C" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_4744_40067" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-color="white" stop-opacity="0.24"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_4744_40067">
|
||||
<rect width="160" height="160" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,76 @@
|
||||
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4744_39984)">
|
||||
<path d="M0 6C0 2.68629 2.68629 0 6 0H28C31.3137 0 34 2.68629 34 6C34 9.31371 31.3137 12 28 12H6C2.68629 12 0 9.31371 0 6Z" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M0 28C0 23.5817 3.58172 20 8 20H42.6667C47.0849 20 50.6667 23.5817 50.6667 28V36C50.6667 40.4183 47.0849 44 42.6667 44H8.00001C3.58173 44 0 40.4183 0 36V28Z" fill="white"/>
|
||||
<path d="M8 20.5H42.667C46.809 20.5002 50.167 23.858 50.167 28V36C50.167 40.142 46.809 43.4998 42.667 43.5H8C3.85787 43.5 0.5 40.1421 0.5 36V28C0.5 23.8579 3.85786 20.5 8 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<rect x="6" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
||||
<path d="M24 31C24 29.3431 25.3431 28 27 28H39.6667C41.3235 28 42.6667 29.3431 42.6667 31V33C42.6667 34.6569 41.3235 36 39.6667 36H27C25.3431 36 24 34.6569 24 33V31Z" fill="black" fill-opacity="0.12"/>
|
||||
<path d="M54.6667 28C54.6667 23.5817 58.2484 20 62.6667 20H97.3333C101.752 20 105.333 23.5817 105.333 28V36C105.333 40.4183 101.752 44 97.3334 44H62.6667C58.2484 44 54.6667 40.4183 54.6667 36V28Z" fill="white"/>
|
||||
<path d="M62.6667 20.5H97.3337C101.476 20.5002 104.834 23.858 104.834 28V36C104.834 40.142 101.476 43.4998 97.3337 43.5H62.6667C58.5246 43.5 55.1667 40.1421 55.1667 36V28C55.1667 23.8579 58.5246 20.5 62.6667 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<rect x="60.6667" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
||||
<path d="M78.6667 31C78.6667 29.3431 80.0098 28 81.6667 28H94.3334C95.9902 28 97.3334 29.3431 97.3334 31V33C97.3334 34.6569 95.9902 36 94.3334 36H81.6667C80.0098 36 78.6667 34.6569 78.6667 33V31Z" fill="black" fill-opacity="0.12"/>
|
||||
<path d="M109.333 28C109.333 23.5817 112.915 20 117.333 20H152C156.418 20 160 23.5817 160 28V36C160 40.4183 156.418 44 152 44H117.333C112.915 44 109.333 40.4183 109.333 36V28Z" fill="white"/>
|
||||
<path d="M117.333 20.5H152C156.142 20.5002 159.5 23.858 159.5 28V36C159.5 40.142 156.142 43.4998 152 43.5H117.333C113.191 43.5 109.833 40.1421 109.833 36V28C109.833 23.8579 113.191 20.5 117.333 20.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<rect x="115.333" y="26" width="12" height="12" rx="6" fill="black" fill-opacity="0.12"/>
|
||||
<path d="M133.333 31C133.333 29.3431 134.676 28 136.333 28H149C150.657 28 152 29.3431 152 31V33C152 34.6569 150.657 36 149 36H136.333C134.676 36 133.333 34.6569 133.333 33V31Z" fill="black" fill-opacity="0.12"/>
|
||||
<path d="M0 56C0 53.7909 1.79086 52 4 52H29C31.2091 52 33 53.7909 33 56C33 58.2091 31.2091 60 29 60H4C1.79086 60 0 58.2091 0 56Z" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M0 72C0 67.5817 3.58172 64 8 64H29C33.4183 64 37 67.5817 37 72V96C37 100.418 33.4183 104 29 104H8C3.58172 104 0 100.418 0 96V72Z" fill="white"/>
|
||||
<path d="M8 64.5H29C33.1421 64.5 36.5 67.8579 36.5 72V96C36.5 100.142 33.1421 103.5 29 103.5H8C3.85786 103.5 0.5 100.142 0.5 96V72C0.5 67.8579 3.85786 64.5 8 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<mask id="mask0_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="72" width="25" height="24">
|
||||
<path d="M18.5 74C16.6435 74 14.863 74.7375 13.5503 76.0503C12.2375 77.363 11.5 79.1435 11.5 81C11.5 83.38 12.69 85.47 14.5 86.74V89C14.5 89.2652 14.6054 89.5196 14.7929 89.7071C14.9804 89.8946 15.2348 90 15.5 90H21.5C21.7652 90 22.0196 89.8946 22.2071 89.7071C22.3946 89.5196 22.5 89.2652 22.5 89V86.74C24.31 85.47 25.5 83.38 25.5 81C25.5 79.1435 24.7625 77.363 23.4497 76.0503C22.137 74.7375 20.3565 74 18.5 74ZM15.5 93C15.5 93.2652 15.6054 93.5196 15.7929 93.7071C15.9804 93.8946 16.2348 94 16.5 94H20.5C20.7652 94 21.0196 93.8946 21.2071 93.7071C21.3946 93.5196 21.5 93.2652 21.5 93V92H15.5V93Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_4744_39984)">
|
||||
<rect x="6.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M41 72C41 67.5817 44.5817 64 49 64H70C74.4183 64 78 67.5817 78 72V96C78 100.418 74.4183 104 70 104H49C44.5817 104 41 100.418 41 96V72Z" fill="white"/>
|
||||
<path d="M49 64.5H70C74.1421 64.5 77.5 67.8579 77.5 72V96C77.5 100.142 74.1421 103.5 70 103.5H49C44.8579 103.5 41.5 100.142 41.5 96V72C41.5 67.8579 44.8579 64.5 49 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<mask id="mask1_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="47" y="72" width="25" height="24">
|
||||
<path d="M66.5 80C67.61 80 68.5 80.9 68.5 82V88.76C69.11 89.31 69.5 90.11 69.5 91C69.5 92.66 68.16 94 66.5 94C64.84 94 63.5 92.66 63.5 91C63.5 90.11 63.89 89.31 64.5 88.76V82C64.5 80.9 65.4 80 66.5 80ZM66.5 81C65.95 81 65.5 81.45 65.5 82V83H67.5V82C67.5 81.45 67.05 81 66.5 81ZM52.5 92V84H49.5L59.5 75L63.9 78.96C63.04 79.69 62.5 80.78 62.5 82V88C61.87 88.83 61.5 89.87 61.5 91L61.6 92H52.5Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask1_4744_39984)">
|
||||
<rect x="47.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M82 72C82 67.5817 85.5817 64 90 64H111C115.418 64 119 67.5817 119 72V96C119 100.418 115.418 104 111 104H90C85.5817 104 82 100.418 82 96V72Z" fill="white"/>
|
||||
<path d="M90 64.5H111C115.142 64.5 118.5 67.8579 118.5 72V96C118.5 100.142 115.142 103.5 111 103.5H90C85.8579 103.5 82.5 100.142 82.5 96V72C82.5 67.8579 85.8579 64.5 90 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<mask id="mask2_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="88" y="72" width="25" height="24">
|
||||
<path d="M100.5 84H107.5C106.97 88.11 104.22 91.78 100.5 92.92V84H93.5V78.3L100.5 75.19M100.5 73L91.5 77V83C91.5 88.55 95.34 93.73 100.5 95C105.66 93.73 109.5 88.55 109.5 83V77L100.5 73Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask2_4744_39984)">
|
||||
<rect x="88.5" y="72" width="24" height="24" fill="#03A9F4"/>
|
||||
</g>
|
||||
<path d="M123 72C123 67.5817 126.582 64 131 64H152C156.418 64 160 67.5817 160 72V96C160 100.418 156.418 104 152 104H131C126.582 104 123 100.418 123 96V72Z" fill="white"/>
|
||||
<path d="M131 64.5H152C156.142 64.5 159.5 67.8579 159.5 72V96C159.5 100.142 156.142 103.5 152 103.5H131C126.858 103.5 123.5 100.142 123.5 96V72C123.5 67.8579 126.858 64.5 131 64.5Z" stroke="black" stroke-opacity="0.12"/>
|
||||
<mask id="mask3_4744_39984" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="129" y="72" width="25" height="24">
|
||||
<path d="M145.5 84C145.5 83.4696 145.711 82.9609 146.086 82.5858C146.461 82.2107 146.97 82 147.5 82C148.03 82 148.539 82.2107 148.914 82.5858C149.289 82.9609 149.5 83.4696 149.5 84C149.5 84.5304 149.289 85.0391 148.914 85.4142C148.539 85.7893 148.03 86 147.5 86C146.97 86 146.461 85.7893 146.086 85.4142C145.711 85.0391 145.5 84.5304 145.5 84ZM139.5 84C139.5 83.4696 139.711 82.9609 140.086 82.5858C140.461 82.2107 140.97 82 141.5 82C142.03 82 142.539 82.2107 142.914 82.5858C143.289 82.9609 143.5 83.4696 143.5 84C143.5 84.5304 143.289 85.0391 142.914 85.4142C142.539 85.7893 142.03 86 141.5 86C140.97 86 140.461 85.7893 140.086 85.4142C139.711 85.0391 139.5 84.5304 139.5 84ZM133.5 84C133.5 83.4696 133.711 82.9609 134.086 82.5858C134.461 82.2107 134.97 82 135.5 82C136.03 82 136.539 82.2107 136.914 82.5858C137.289 82.9609 137.5 83.4696 137.5 84C137.5 84.5304 137.289 85.0391 136.914 85.4142C136.539 85.7893 136.03 86 135.5 86C134.97 86 134.461 85.7893 134.086 85.4142C133.711 85.0391 133.5 84.5304 133.5 84Z" fill="black"/>
|
||||
</mask>
|
||||
<g mask="url(#mask3_4744_39984)">
|
||||
<rect x="129.5" y="72" width="24" height="24" fill="#18BCF2"/>
|
||||
</g>
|
||||
<path d="M0 116C0 113.791 1.79086 112 4 112H29C31.2091 112 33 113.791 33 116C33 118.209 31.2091 120 29 120H4C1.79086 120 0 118.209 0 116Z" fill="black" fill-opacity="0.32"/>
|
||||
<path d="M0 132C0 127.582 3.58172 124 8 124H70C74.4183 124 78 127.582 78 132V160H0V132Z" fill="url(#paint0_linear_4744_39984)"/>
|
||||
<path d="M8 124.5H70C74.1421 124.5 77.5 127.858 77.5 132V159.5H0.5V132C0.5 127.858 3.85786 124.5 8 124.5Z" stroke="url(#paint1_linear_4744_39984)" stroke-opacity="0.12"/>
|
||||
<path d="M82 132C82 127.582 85.5817 124 90 124H152C156.418 124 160 127.582 160 132V160H82V132Z" fill="url(#paint2_linear_4744_39984)"/>
|
||||
<path d="M90 124.5H152C156.142 124.5 159.5 127.858 159.5 132V159.5H82.5V132C82.5 127.858 85.8579 124.5 90 124.5Z" stroke="url(#paint3_linear_4744_39984)" stroke-opacity="0.12"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_4744_39984" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_4744_39984" x1="39" y1="124" x2="39" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-opacity="0.12"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_4744_39984" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_4744_39984" x1="121" y1="124" x2="121" y2="160" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.5" stop-opacity="0.12"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_4744_39984">
|
||||
<rect width="160" height="160" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250730.0"
|
||||
version = "20250903.5"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
@@ -1,137 +0,0 @@
|
||||
import nunjucks, { Environment, Template as NunjucksTemplate } from "nunjucks";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
nunjucks.installJinjaCompat();
|
||||
|
||||
function createEnv() {
|
||||
const env = new Environment();
|
||||
// Add filters and globals that don't use hass
|
||||
env.addFilter("min", (numbers: number[]) => Math.min(...numbers));
|
||||
env.addFilter("max", (numbers: number[]) => Math.max(...numbers));
|
||||
|
||||
env.addGlobal(
|
||||
"states",
|
||||
(
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
round = false,
|
||||
withUnit = false
|
||||
): string => {
|
||||
if (!hass?.states[id]) {
|
||||
return "unknown";
|
||||
}
|
||||
const state = hass?.states[id]?.state;
|
||||
if (state == null) {
|
||||
return "unavailable";
|
||||
}
|
||||
if (round) {
|
||||
return String(Math.round(Number(state)));
|
||||
}
|
||||
if (withUnit) {
|
||||
return `${state} ${hass?.states[id]?.attributes.unit_of_measurement}`;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
);
|
||||
env.addGlobal(
|
||||
"state_attr",
|
||||
(hass: HomeAssistant, id: string, attr: string) =>
|
||||
hass?.states[id]?.attributes[attr]
|
||||
);
|
||||
env.addGlobal(
|
||||
"is_state",
|
||||
(hass: HomeAssistant, id: string, value: string) =>
|
||||
hass?.states[id]?.state === value
|
||||
);
|
||||
env.addGlobal(
|
||||
"is_state_attr",
|
||||
(hass: HomeAssistant, id: string, attr: string, value: string) =>
|
||||
hass?.states[id]?.attributes[attr] === value
|
||||
);
|
||||
env.addGlobal(
|
||||
"has_value",
|
||||
(hass: HomeAssistant, id: string) => hass?.states[id]?.state != null
|
||||
);
|
||||
env.addGlobal("state_translated", (hass: HomeAssistant, id: string) => {
|
||||
try {
|
||||
return hass?.formatEntityState(hass?.states[id], hass?.states[id]?.state);
|
||||
} catch {
|
||||
return hass?.states[id]?.state ?? undefined;
|
||||
}
|
||||
});
|
||||
return env;
|
||||
}
|
||||
|
||||
export class HaTemplate {
|
||||
private _njTemplate?: NunjucksTemplate;
|
||||
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
private _content?: string;
|
||||
|
||||
private _context?: Record<string, any>;
|
||||
|
||||
private _value = "";
|
||||
|
||||
public entityIds = new Set<string>();
|
||||
|
||||
public shouldUpdate = false;
|
||||
|
||||
private _env = createEnv();
|
||||
|
||||
constructor() {
|
||||
// functions that access the hass state have to be dynamic
|
||||
// in order to track which entities are used in the template
|
||||
[
|
||||
"states",
|
||||
"state_attr",
|
||||
"is_state",
|
||||
"is_state_attr",
|
||||
"has_value",
|
||||
"state_translated",
|
||||
].forEach((func) => {
|
||||
const original = this._env.getGlobal(func);
|
||||
this._env.addGlobal(func, (id: string, ...args: any[]): string => {
|
||||
this.entityIds.add(id);
|
||||
return original(this._hass, id, ...args);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public render(): string {
|
||||
if (this.shouldUpdate) {
|
||||
this.shouldUpdate = false;
|
||||
this.entityIds.clear();
|
||||
this._value = this._njTemplate!.render(this._context);
|
||||
}
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set content(content: string) {
|
||||
if (this._content !== content) {
|
||||
this._content = content;
|
||||
this._njTemplate = new NunjucksTemplate(content, this._env);
|
||||
this.shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public set context(context: Record<string, any>) {
|
||||
if (this._context !== context) {
|
||||
this._context = context;
|
||||
this.shouldUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public set hass(hass: HomeAssistant) {
|
||||
if (this._hass !== hass) {
|
||||
if (!this.shouldUpdate) {
|
||||
this.shouldUpdate =
|
||||
!this._hass !== !hass ||
|
||||
Array.from(this.entityIds).some(
|
||||
(id) => this._hass?.states[id]?.state !== hass.states[id]?.state
|
||||
);
|
||||
}
|
||||
this._hass = hass;
|
||||
}
|
||||
}
|
||||
}
|
@@ -119,10 +119,11 @@ class HaAlert extends LitElement {
|
||||
.main-content {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
line-height: normal;
|
||||
margin-left: 8px;
|
||||
margin-right: 0;
|
||||
margin-inline-start: 8px;
|
||||
margin-inline-end: 0;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
.title {
|
||||
margin-top: 2px;
|
||||
|
@@ -3,11 +3,15 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeAttributeNameDisplay } from "../common/entity/compute_attribute_display";
|
||||
import { STATE_ATTRIBUTES } from "../data/entity_attributes";
|
||||
import {
|
||||
STATE_ATTRIBUTES,
|
||||
STATE_ATTRIBUTES_DOMAIN_CLASS,
|
||||
} from "../data/entity_attributes";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-attribute-value";
|
||||
import "./ha-expansion-panel";
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
|
||||
@customElement("ha-attributes")
|
||||
class HaAttributes extends LitElement {
|
||||
@@ -22,7 +26,12 @@ class HaAttributes extends LitElement {
|
||||
private get _filteredAttributes() {
|
||||
return this._computeDisplayAttributes(
|
||||
STATE_ATTRIBUTES.concat(
|
||||
this.extraFilters ? this.extraFilters.split(",") : []
|
||||
this.extraFilters ? this.extraFilters.split(",") : [],
|
||||
(this.stateObj &&
|
||||
STATE_ATTRIBUTES_DOMAIN_CLASS[computeStateDomain(this.stateObj)]?.[
|
||||
this.stateObj.attributes?.device_class
|
||||
]) ||
|
||||
[]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { mdiChevronUp } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import "./ha-icon-button";
|
||||
|
||||
@@ -16,12 +16,20 @@ export class HaAutomationRow extends LitElement {
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public selected = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public disabled = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true, attribute: "building-block" })
|
||||
public buildingBlock = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public highlight?: boolean;
|
||||
|
||||
@query(".row")
|
||||
private _rowElement?: HTMLDivElement;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div
|
||||
@@ -66,15 +74,44 @@ export class HaAutomationRow extends LitElement {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
if (ev.key !== "Enter" && ev.key !== " ") {
|
||||
|
||||
if (
|
||||
ev.key !== "Enter" &&
|
||||
ev.key !== " " &&
|
||||
!(
|
||||
(this.sortSelected || ev.altKey) &&
|
||||
!(ev.ctrlKey || ev.metaKey) &&
|
||||
!ev.shiftKey &&
|
||||
(ev.key === "ArrowUp" || ev.key === "ArrowDown")
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
|
||||
if (ev.key === "ArrowUp") {
|
||||
fireEvent(this, "move-up");
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "move-down");
|
||||
return;
|
||||
}
|
||||
if (this.sortSelected && (ev.key === "Enter" || ev.key === " ")) {
|
||||
fireEvent(this, "stop-sort-selection");
|
||||
return;
|
||||
}
|
||||
|
||||
this.click();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
requestAnimationFrame(() => {
|
||||
this._rowElement?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
@@ -97,6 +134,7 @@ export class HaAutomationRow extends LitElement {
|
||||
.expand-button {
|
||||
transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
margin-left: -8px;
|
||||
}
|
||||
:host([building-block]) .leading-icon-wrapper {
|
||||
background-color: var(--ha-color-fill-neutral-loud-resting);
|
||||
@@ -134,6 +172,22 @@ export class HaAutomationRow extends LitElement {
|
||||
overflow-wrap: anywhere;
|
||||
margin: 0 12px;
|
||||
}
|
||||
:host([sort-selected]) .row {
|
||||
outline: solid;
|
||||
outline-color: rgba(var(--rgb-accent-color), 0.6);
|
||||
outline-offset: -2px;
|
||||
outline-width: 2px;
|
||||
background-color: rgba(var(--rgb-accent-color), 0.08);
|
||||
}
|
||||
.row:hover {
|
||||
background-color: rgba(var(--rgb-primary-text-color), 0.04);
|
||||
}
|
||||
:host([highlight]) .row {
|
||||
background-color: rgba(var(--rgb-primary-color), 0.08);
|
||||
}
|
||||
:host([highlight]) .row:hover {
|
||||
background-color: rgba(var(--rgb-primary-color), 0.16);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -144,5 +198,9 @@ declare global {
|
||||
|
||||
interface HASSDomEvents {
|
||||
"toggle-collapsed": undefined;
|
||||
"stop-sort-selection": undefined;
|
||||
"copy-row": undefined;
|
||||
"cut-row": undefined;
|
||||
"delete-row": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -30,6 +30,8 @@ export class HaBottomSheet extends LitElement {
|
||||
|
||||
@state() private _dialogMaxViewpointHeight = 70;
|
||||
|
||||
@state() private _dialogMinViewpointHeight = 55;
|
||||
|
||||
@state() private _dialogViewportHeight?: number;
|
||||
|
||||
render() {
|
||||
@@ -41,6 +43,7 @@ export class HaBottomSheet extends LitElement {
|
||||
? `${this._dialogViewportHeight}vh`
|
||||
: "auto",
|
||||
maxHeight: `${this._dialogMaxViewpointHeight}vh`,
|
||||
minHeight: `${this._dialogMinViewpointHeight}vh`,
|
||||
})}
|
||||
>
|
||||
<div class="handle-wrapper">
|
||||
@@ -80,6 +83,7 @@ export class HaBottomSheet extends LitElement {
|
||||
this._dialogViewportHeight =
|
||||
(this._dialog.offsetHeight / window.innerHeight) * 100;
|
||||
this._dialogMaxViewpointHeight = 90;
|
||||
this._dialogMinViewpointHeight = 20;
|
||||
} else {
|
||||
// after close animation is done close dialog element and fire closed event
|
||||
this._dialog.close();
|
||||
@@ -197,6 +201,7 @@ export class HaBottomSheet extends LitElement {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 7;
|
||||
padding-bottom: 76px;
|
||||
}
|
||||
.handle-wrapper .handle::after {
|
||||
content: "";
|
||||
@@ -227,7 +232,6 @@ export class HaBottomSheet extends LitElement {
|
||||
box-shadow: var(--wa-shadow-l);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
top: auto;
|
||||
inset-inline-end: auto;
|
||||
bottom: 0;
|
||||
|
@@ -148,6 +148,10 @@ export class HaDialog extends DialogBase {
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
margin-right: 12px;
|
||||
margin-inline-end: 12px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
.header_button {
|
||||
text-decoration: none;
|
||||
|
@@ -6,8 +6,9 @@ import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { IntegrationManifest } from "../data/integration";
|
||||
import { fetchIntegrationManifests } from "../data/integration";
|
||||
import { fetchIntegrationManifests, domainToName } from "../data/integration";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-check-list-item";
|
||||
@@ -63,7 +64,12 @@ export class HaFilterIntegrations extends LitElement {
|
||||
multi
|
||||
>
|
||||
${repeat(
|
||||
this._integrations(this._manifests, this._filter, this.value),
|
||||
this._integrations(
|
||||
this.hass.localize,
|
||||
this._manifests,
|
||||
this._filter,
|
||||
this.value
|
||||
),
|
||||
(i) => i.domain,
|
||||
(integration) =>
|
||||
html`<ha-check-list-item
|
||||
@@ -79,7 +85,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
.domain=${integration.domain}
|
||||
brand-fallback
|
||||
></ha-domain-icon>
|
||||
${integration.name || integration.domain}
|
||||
${integration.name}
|
||||
</ha-check-list-item>`
|
||||
)}
|
||||
</ha-list> `
|
||||
@@ -108,11 +114,21 @@ export class HaFilterIntegrations extends LitElement {
|
||||
|
||||
protected async firstUpdated() {
|
||||
this._manifests = await fetchIntegrationManifests(this.hass);
|
||||
this.hass.loadBackendTranslation("title");
|
||||
}
|
||||
|
||||
private _integrations = memoizeOne(
|
||||
(manifest: IntegrationManifest[], filter: string | undefined, _value) =>
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
manifest: IntegrationManifest[],
|
||||
filter: string | undefined,
|
||||
_value
|
||||
) =>
|
||||
manifest
|
||||
.map((mnfst) => ({
|
||||
...mnfst,
|
||||
name: domainToName(localize, mnfst.domain, mnfst),
|
||||
}))
|
||||
.filter(
|
||||
(mnfst) =>
|
||||
(!mnfst.integration_type ||
|
||||
@@ -124,11 +140,7 @@ export class HaFilterIntegrations extends LitElement {
|
||||
mnfst.domain.toLowerCase().includes(filter))
|
||||
)
|
||||
.sort((a, b) =>
|
||||
stringCompare(
|
||||
a.name || a.domain,
|
||||
b.name || b.domain,
|
||||
this.hass.locale.language
|
||||
)
|
||||
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||
)
|
||||
);
|
||||
|
||||
|
@@ -393,10 +393,13 @@ export class HaItemDisplayEditor extends LitElement {
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
}
|
||||
ha-md-list-item.drag-selected {
|
||||
box-shadow:
|
||||
0px 0px 8px 4px rgba(var(--rgb-accent-color), 0.8),
|
||||
inset 0px 2px 8px 4px rgba(var(--rgb-accent-color), 0.4);
|
||||
--md-focus-ring-color: rgba(var(--rgb-accent-color), 0.6);
|
||||
border-radius: 8px;
|
||||
outline: solid;
|
||||
outline-color: rgba(var(--rgb-accent-color), 0.6);
|
||||
outline-offset: -2px;
|
||||
outline-width: 2px;
|
||||
background-color: rgba(var(--rgb-accent-color), 0.08);
|
||||
}
|
||||
ha-md-list-item ha-icon-button {
|
||||
margin-left: -12px;
|
||||
|
@@ -16,9 +16,23 @@ export class HaMdButtonMenu extends LitElement {
|
||||
|
||||
@property() public positioning?: "fixed" | "absolute" | "popover";
|
||||
|
||||
@property({ attribute: "anchor-corner" }) public anchorCorner:
|
||||
| "start-start"
|
||||
| "start-end"
|
||||
| "end-start"
|
||||
| "end-end" = "end-start";
|
||||
|
||||
@property({ attribute: "menu-corner" }) public menuCorner:
|
||||
| "start-start"
|
||||
| "start-end"
|
||||
| "end-start"
|
||||
| "end-end" = "start-start";
|
||||
|
||||
@property({ type: Boolean, attribute: "has-overflow" }) public hasOverflow =
|
||||
false;
|
||||
|
||||
@property({ type: Boolean }) public quick = false;
|
||||
|
||||
@query("ha-md-menu", true) private _menu!: HaMdMenu;
|
||||
|
||||
public get items() {
|
||||
@@ -39,8 +53,11 @@ export class HaMdButtonMenu extends LitElement {
|
||||
<slot name="trigger" @slotchange=${this._setTriggerAria}></slot>
|
||||
</div>
|
||||
<ha-md-menu
|
||||
.quick=${this.quick}
|
||||
.positioning=${this.positioning}
|
||||
.hasOverflow=${this.hasOverflow}
|
||||
.anchorCorner=${this.anchorCorner}
|
||||
.menuCorner=${this.menuCorner}
|
||||
@opening=${this._handleOpening}
|
||||
@closing=${this._handleClosing}
|
||||
>
|
||||
|
@@ -159,6 +159,7 @@ export class HaMdDialog extends Dialog {
|
||||
--md-dialog-headline-size: var(--ha-font-size-xl);
|
||||
--md-dialog-supporting-text-size: var(--ha-font-size-m);
|
||||
--md-dialog-supporting-text-line-height: var(--ha-line-height-normal);
|
||||
--md-divider-color: var(--divider-color);
|
||||
}
|
||||
|
||||
:host([type="alert"]) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { mdiStar } from "@mdi/js";
|
||||
import { mdiInformationOutline, mdiStar } from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -71,6 +71,17 @@ export class HaNetwork extends LitElement {
|
||||
<span slot="description" data-for="auto_configure">
|
||||
${this.hass.localize("ui.panel.config.network.adapter.detected")}:
|
||||
${format_auto_detected_interfaces(this.networkConfig.adapters)}
|
||||
${!configured_adapters.length
|
||||
? html`<div class="info-text">
|
||||
<ha-svg-icon
|
||||
.path=${mdiInformationOutline}
|
||||
class="info-icon"
|
||||
></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.adapter.auto_configure_manual_hint"
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
</span>
|
||||
</ha-settings-row>
|
||||
${configured_adapters.length || this._expanded
|
||||
@@ -171,6 +182,21 @@ export class HaNetwork extends LitElement {
|
||||
span[slot="description"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--info-color, var(--primary-color));
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import {
|
||||
mdiDevices,
|
||||
mdiPaletteSwatch,
|
||||
mdiTextureBox,
|
||||
mdiTransitConnectionVariant,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -266,7 +267,9 @@ export class HaRelatedItems extends LitElement {
|
||||
<a href="/config/devices/device/${relatedDeviceId}">
|
||||
<ha-list-item hasMeta graphic="icon">
|
||||
<ha-svg-icon
|
||||
.path=${mdiDevices}
|
||||
.path=${device.entry_type === "service"
|
||||
? mdiTransitConnectionVariant
|
||||
: mdiDevices}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${device.name_by_user || device.name}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import "./ha-tooltip";
|
||||
@@ -7,7 +7,7 @@ import "./ha-tooltip";
|
||||
export interface Segment {
|
||||
value: number;
|
||||
color: string;
|
||||
label: TemplateResult | string;
|
||||
label?: TemplateResult | string;
|
||||
}
|
||||
|
||||
@customElement("ha-segmented-bar")
|
||||
@@ -18,6 +18,12 @@ class HaSegmentedBar extends LitElement {
|
||||
|
||||
@property({ type: String }) public description?: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-legend" })
|
||||
public hideLegend = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "hide-tooltip" })
|
||||
public hideTooltip = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const totalValue = this.segments.reduce(
|
||||
(acc, segment) => acc + segment.value,
|
||||
@@ -26,39 +32,51 @@ class HaSegmentedBar extends LitElement {
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="heading">
|
||||
<span>${this.heading}</span>
|
||||
<span>${this.description}</span>
|
||||
<div class="title">
|
||||
<span>${this.heading}</span>
|
||||
<span>${this.description}</span>
|
||||
</div>
|
||||
<slot name="extra"></slot>
|
||||
</div>
|
||||
<div class="bar">
|
||||
${this.segments.map(
|
||||
(segment) => html`
|
||||
<ha-tooltip>
|
||||
<span slot="content">${segment.label}</span>
|
||||
<div
|
||||
style=${styleMap({
|
||||
width: `${(segment.value / totalValue) * 100}%`,
|
||||
backgroundColor: segment.color,
|
||||
})}
|
||||
></div>
|
||||
</ha-tooltip>
|
||||
`
|
||||
)}
|
||||
${this.segments.map((segment) => {
|
||||
const bar = html`<div
|
||||
style=${styleMap({
|
||||
width: `${(segment.value / totalValue) * 100}%`,
|
||||
backgroundColor: segment.color,
|
||||
})}
|
||||
></div>`;
|
||||
return this.hideTooltip && !segment.label
|
||||
? bar
|
||||
: html`
|
||||
<ha-tooltip>
|
||||
<span slot="content">${segment.label}</span>
|
||||
${bar}
|
||||
</ha-tooltip>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
<ul class="legend">
|
||||
${this.segments.map(
|
||||
(segment) => html`
|
||||
<li>
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
backgroundColor: segment.color,
|
||||
})}
|
||||
></div>
|
||||
<span class="label">${segment.label}</span>
|
||||
</li>
|
||||
`
|
||||
)}
|
||||
</ul>
|
||||
${this.hideLegend
|
||||
? nothing
|
||||
: html`
|
||||
<ul class="legend">
|
||||
${this.segments.map((segment) =>
|
||||
segment.label
|
||||
? html`
|
||||
<li>
|
||||
<div
|
||||
class="bullet"
|
||||
style=${styleMap({
|
||||
backgroundColor: segment.color,
|
||||
})}
|
||||
></div>
|
||||
<span class="label">${segment.label}</span>
|
||||
</li>
|
||||
`
|
||||
: nothing
|
||||
)}
|
||||
</ul>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -67,12 +85,20 @@ class HaSegmentedBar extends LitElement {
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
.heading span {
|
||||
.heading {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
.heading .title {
|
||||
flex: 1;
|
||||
}
|
||||
.heading .title span {
|
||||
color: var(--secondary-text-color);
|
||||
line-height: var(--ha-line-height-expanded);
|
||||
margin-right: 8px;
|
||||
}
|
||||
.heading span:first-child {
|
||||
.heading .title span:first-child {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.bar {
|
||||
@@ -113,6 +139,9 @@ class HaSegmentedBar extends LitElement {
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { consume, ContextProvider } from "@lit/context";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fullEntitiesContext } from "../../data/context";
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import { migrateAutomationAction } from "../../data/script";
|
||||
import type { ActionSelector } from "../../data/selector";
|
||||
import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
||||
import "../../panels/config/automation/action/ha-automation-action";
|
||||
import type HaAutomationAction from "../../panels/config/automation/action/ha-automation-action";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-selector-action")
|
||||
@@ -35,6 +36,9 @@ export class HaActionSelector extends SubscribeMixin(LitElement) {
|
||||
|
||||
@state() private _entitiesContext;
|
||||
|
||||
@query("ha-automation-action")
|
||||
private _actionElement?: HaAutomationAction;
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_entitiesContext"];
|
||||
|
||||
private _actions = memoizeOne((action: Action | undefined) => {
|
||||
@@ -61,6 +65,14 @@ export class HaActionSelector extends SubscribeMixin(LitElement) {
|
||||
];
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._actionElement?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._actionElement?.collapseAll();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
@@ -77,12 +89,12 @@ export class HaActionSelector extends SubscribeMixin(LitElement) {
|
||||
static styles = css`
|
||||
ha-automation-action {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import type { Condition } from "../../data/automation";
|
||||
import type { ConditionSelector } from "../../data/selector";
|
||||
import "../../panels/config/automation/condition/ha-automation-condition";
|
||||
import type HaAutomationCondition from "../../panels/config/automation/condition/ha-automation-condition";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-selector-condition")
|
||||
@@ -19,6 +20,9 @@ export class HaConditionSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public disabled = false;
|
||||
|
||||
@query("ha-automation-condition")
|
||||
private _conditionElement?: HaAutomationCondition;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
${this.label ? html`<label>${this.label}</label>` : nothing}
|
||||
@@ -32,6 +36,14 @@ export class HaConditionSelector extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._conditionElement?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._conditionElement?.collapseAll();
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-automation-condition {
|
||||
display: block;
|
||||
@@ -41,6 +53,7 @@ export class HaConditionSelector extends LitElement {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import "../ha-alert";
|
||||
import "../ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../ha-form/types";
|
||||
import { showMediaBrowserDialog } from "../media-player/show-media-browser-dialog";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
|
||||
const MANUAL_SCHEMA = [
|
||||
{ name: "media_content_id", required: false, selector: { text: {} } },
|
||||
@@ -44,9 +45,25 @@ export class HaMediaSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public required = true;
|
||||
|
||||
@property({ attribute: false }) public context?: {
|
||||
filter_entity?: string | string[];
|
||||
};
|
||||
|
||||
@state() private _thumbnailUrl?: string | null;
|
||||
|
||||
private _contextEntities: string[] | undefined;
|
||||
|
||||
private get _hasAccept(): boolean {
|
||||
return !!this.selector?.media?.accept?.length;
|
||||
}
|
||||
|
||||
willUpdate(changedProps: PropertyValues<this>) {
|
||||
if (changedProps.has("context")) {
|
||||
if (!this._hasAccept) {
|
||||
this._contextEntities = ensureArray(this.context?.filter_entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("value")) {
|
||||
const thumbnail = this.value?.metadata?.thumbnail;
|
||||
const oldThumbnail = (changedProps.get("value") as this["value"])
|
||||
@@ -79,24 +96,23 @@ export class HaMediaSelector extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const stateObj = this.value?.entity_id
|
||||
? this.hass.states[this.value.entity_id]
|
||||
: undefined;
|
||||
const entityId = this._getActiveEntityId();
|
||||
|
||||
const stateObj = entityId ? this.hass.states[entityId] : undefined;
|
||||
|
||||
const supportsBrowse =
|
||||
!this.value?.entity_id ||
|
||||
!entityId ||
|
||||
(stateObj &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.BROWSE_MEDIA));
|
||||
|
||||
const hasAccept = this.selector?.media?.accept?.length;
|
||||
|
||||
return html`
|
||||
${hasAccept
|
||||
${this._hasAccept ||
|
||||
(this._contextEntities && this._contextEntities.length <= 1)
|
||||
? nothing
|
||||
: html`
|
||||
<ha-entity-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this.value?.entity_id}
|
||||
.value=${entityId}
|
||||
.label=${this.label ||
|
||||
this.hass.localize(
|
||||
"ui.components.selectors.media.pick_media_player"
|
||||
@@ -104,8 +120,10 @@ export class HaMediaSelector extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.required=${this.required}
|
||||
.hideClearIcon=${!!this._contextEntities}
|
||||
.includeDomains=${INCLUDE_DOMAINS}
|
||||
allow-custom-entity
|
||||
.includeEntities=${this._contextEntities}
|
||||
.allowCustomEntity=${!this._contextEntities}
|
||||
@value-changed=${this._entityChanged}
|
||||
></ha-entity-picker>
|
||||
`}
|
||||
@@ -121,6 +139,7 @@ export class HaMediaSelector extends LitElement {
|
||||
.data=${this.value || EMPTY_FORM}
|
||||
.schema=${MANUAL_SCHEMA}
|
||||
.computeLabel=${this._computeLabelCallback}
|
||||
.computeHelper=${this._computeHelperCallback}
|
||||
></ha-form>
|
||||
`
|
||||
: html`
|
||||
@@ -133,7 +152,7 @@ export class HaMediaSelector extends LitElement {
|
||||
: this.value.metadata?.title || this.value.media_content_id}
|
||||
@click=${this._pickMedia}
|
||||
@keydown=${this._handleKeyDown}
|
||||
class=${this.disabled || (!this.value?.entity_id && !hasAccept)
|
||||
class=${this.disabled || (!entityId && !this._hasAccept)
|
||||
? "disabled"
|
||||
: ""}
|
||||
>
|
||||
@@ -193,21 +212,38 @@ export class HaMediaSelector extends LitElement {
|
||||
): string =>
|
||||
this.hass.localize(`ui.components.selectors.media.${schema.name}`);
|
||||
|
||||
private _computeHelperCallback = (
|
||||
schema: SchemaUnion<typeof MANUAL_SCHEMA>
|
||||
): string =>
|
||||
this.hass.localize(`ui.components.selectors.media.${schema.name}_detail`);
|
||||
|
||||
private _entityChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
entity_id: ev.detail.value,
|
||||
media_content_id: "",
|
||||
media_content_type: "",
|
||||
},
|
||||
});
|
||||
if (!this._hasAccept && this.context?.filter_entity) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
media_content_id: "",
|
||||
media_content_type: "",
|
||||
metadata: {
|
||||
browse_entity_id: ev.detail.value,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
entity_id: ev.detail.value,
|
||||
media_content_id: "",
|
||||
media_content_type: "",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _pickMedia() {
|
||||
showMediaBrowserDialog(this, {
|
||||
action: "pick",
|
||||
entityId: this.value?.entity_id,
|
||||
entityId: this._getActiveEntityId(),
|
||||
navigateIds: this.value?.metadata?.navigateIds,
|
||||
accept: this.selector.media?.accept,
|
||||
mediaPickedCallback: (pickedMedia: MediaPickedEvent) => {
|
||||
@@ -225,6 +261,9 @@ export class HaMediaSelector extends LitElement {
|
||||
media_content_type: id.media_content_type,
|
||||
media_content_id: id.media_content_id,
|
||||
})),
|
||||
...(!this._hasAccept && this.context?.filter_entity
|
||||
? { browse_entity_id: this._getActiveEntityId() }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -232,6 +271,15 @@ export class HaMediaSelector extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _getActiveEntityId(): string | undefined {
|
||||
const metaId = this.value?.metadata?.browse_entity_id;
|
||||
return (
|
||||
this.value?.entity_id ||
|
||||
(metaId && this._contextEntities?.includes(metaId) && metaId) ||
|
||||
this._contextEntities?.[0]
|
||||
);
|
||||
}
|
||||
|
||||
private _handleKeyDown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.preventDefault();
|
||||
|
@@ -15,6 +15,7 @@ declare global {
|
||||
"item-added": {
|
||||
index: number;
|
||||
data: any;
|
||||
item: any;
|
||||
};
|
||||
"item-removed": {
|
||||
index: number;
|
||||
@@ -180,6 +181,7 @@ export class HaSortable extends LitElement {
|
||||
fireEvent(this, "item-added", {
|
||||
index: evt.newIndex,
|
||||
data: evt.item.sortableData,
|
||||
item: evt.item,
|
||||
});
|
||||
};
|
||||
|
||||
|
@@ -1,40 +0,0 @@
|
||||
import { LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { HaTemplate } from "../common/template";
|
||||
|
||||
@customElement("ha-template")
|
||||
export class HaTemplateElement extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public content!: string;
|
||||
|
||||
@property({ attribute: false }) public context: Record<string, any> = {};
|
||||
|
||||
private _template = new HaTemplate();
|
||||
|
||||
protected shouldUpdate(): boolean {
|
||||
if (this.hass) {
|
||||
this._template.hass = this.hass;
|
||||
this._template.content = this.content;
|
||||
this._template.context = this.context;
|
||||
}
|
||||
return this._template.shouldUpdate;
|
||||
}
|
||||
|
||||
public render() {
|
||||
try {
|
||||
return this._template.render();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(`Error rendering template: ${error}`);
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-template": HaTemplateElement;
|
||||
}
|
||||
}
|
@@ -91,6 +91,15 @@ export const isService = (key: string | undefined): boolean | undefined =>
|
||||
export const getService = (key: string): string =>
|
||||
key.substring(SERVICE_PREFIX.length);
|
||||
|
||||
export const COLLAPSIBLE_ACTION_ELEMENTS = [
|
||||
"ha-automation-action-choose",
|
||||
"ha-automation-action-condition",
|
||||
"ha-automation-action-if",
|
||||
"ha-automation-action-parallel",
|
||||
"ha-automation-action-repeat",
|
||||
"ha-automation-action-sequence",
|
||||
];
|
||||
|
||||
export const ACTION_BUILDING_BLOCKS = [
|
||||
"choose",
|
||||
"if",
|
||||
|
@@ -556,15 +556,18 @@ export interface AutomationClipboard {
|
||||
}
|
||||
|
||||
export interface BaseSidebarConfig {
|
||||
toggleYamlMode: () => boolean;
|
||||
delete: () => void;
|
||||
close: (focus?: boolean) => void;
|
||||
}
|
||||
|
||||
export interface TriggerSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Trigger) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
disable: () => void;
|
||||
duplicate: () => void;
|
||||
cut: () => void;
|
||||
copy: () => void;
|
||||
toggleYamlMode: () => void;
|
||||
config: Trigger;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
@@ -572,9 +575,13 @@ export interface TriggerSidebarConfig extends BaseSidebarConfig {
|
||||
|
||||
export interface ConditionSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Condition) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
disable: () => void;
|
||||
test: () => void;
|
||||
duplicate: () => void;
|
||||
cut: () => void;
|
||||
copy: () => void;
|
||||
toggleYamlMode: () => void;
|
||||
config: Condition;
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
@@ -582,28 +589,35 @@ export interface ConditionSidebarConfig extends BaseSidebarConfig {
|
||||
|
||||
export interface ActionSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Action) => void;
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
disable: () => void;
|
||||
config: Action;
|
||||
duplicate: () => void;
|
||||
cut: () => void;
|
||||
copy: () => void;
|
||||
run: () => void;
|
||||
toggleYamlMode: () => void;
|
||||
config: {
|
||||
action: Action;
|
||||
};
|
||||
yamlMode: boolean;
|
||||
uiSupported: boolean;
|
||||
}
|
||||
|
||||
export interface OptionSidebarConfig extends BaseSidebarConfig {
|
||||
close: () => void;
|
||||
rename: () => void;
|
||||
duplicate: () => void;
|
||||
defaultOption?: boolean;
|
||||
}
|
||||
|
||||
export interface ScriptFieldSidebarConfig extends BaseSidebarConfig {
|
||||
save: (value: Field) => void;
|
||||
close: () => void;
|
||||
config: {
|
||||
field: Field;
|
||||
selector: boolean;
|
||||
key: string;
|
||||
excludeKeys: string[];
|
||||
};
|
||||
toggleYamlMode: () => void;
|
||||
yamlMode: boolean;
|
||||
}
|
||||
|
||||
|
@@ -378,7 +378,17 @@ const tryDescribeTrigger = (
|
||||
|
||||
// Tag Trigger
|
||||
if (trigger.trigger === "tag") {
|
||||
return hass.localize(`${triggerTranslationBaseKey}.tag.description.full`);
|
||||
const entity = Object.values(hass.states).find(
|
||||
(state) =>
|
||||
state.entity_id.startsWith("tag.") &&
|
||||
state.attributes.tag_id === trigger.tag_id
|
||||
);
|
||||
return entity
|
||||
? hass.localize(
|
||||
`${triggerTranslationBaseKey}.tag.description.known_tag`,
|
||||
{ tag_name: computeStateName(entity) }
|
||||
)
|
||||
: hass.localize(`${triggerTranslationBaseKey}.tag.description.full`);
|
||||
}
|
||||
|
||||
// Time Trigger
|
||||
|
@@ -52,3 +52,9 @@ export const CONDITION_GROUPS: AutomationElementGroup = {
|
||||
} as const;
|
||||
|
||||
export const CONDITION_BUILDING_BLOCKS = ["and", "or", "not"];
|
||||
|
||||
export const COLLAPSIBLE_CONDITION_ELEMENTS = [
|
||||
"ha-automation-condition-and",
|
||||
"ha-automation-condition-not",
|
||||
"ha-automation-condition-or",
|
||||
];
|
||||
|
@@ -42,6 +42,19 @@ export const getSubEntries = (hass: HomeAssistant, entry_id: string) =>
|
||||
entry_id,
|
||||
});
|
||||
|
||||
export const updateSubEntry = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
subentry_id: string,
|
||||
updatedValues: SubEntryMutableParams
|
||||
) =>
|
||||
hass.callWS({
|
||||
type: "config_entries/subentries/update",
|
||||
entry_id,
|
||||
subentry_id,
|
||||
...updatedValues,
|
||||
});
|
||||
|
||||
export const deleteSubEntry = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
@@ -60,6 +73,8 @@ export type ConfigEntryMutableParams = Partial<
|
||||
>
|
||||
>;
|
||||
|
||||
export type SubEntryMutableParams = Partial<Pick<SubEntry, "title">>;
|
||||
|
||||
// https://github.com/home-assistant/core/blob/2286dea636fda001f03433ba14d7adbda43979e5/homeassistant/config_entries.py#L81
|
||||
export const ERROR_STATES: ConfigEntry["state"][] = [
|
||||
"migration_error",
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { formatDurationDigital } from "../common/datetime/format_duration";
|
||||
import type { FrontendLocaleData } from "./translation";
|
||||
|
||||
// These attributes are hidden from the more-info window for all entities.
|
||||
export const STATE_ATTRIBUTES = [
|
||||
"entity_id",
|
||||
"assumed_state",
|
||||
@@ -26,6 +27,14 @@ export const STATE_ATTRIBUTES = [
|
||||
"available_tones",
|
||||
];
|
||||
|
||||
// These attributes are hidden from the more-info window for entities of the
|
||||
// matching domain and device_class.
|
||||
export const STATE_ATTRIBUTES_DOMAIN_CLASS = {
|
||||
sensor: {
|
||||
enum: ["options"],
|
||||
},
|
||||
};
|
||||
|
||||
export const TEMPERATURE_ATTRIBUTES = new Set([
|
||||
"temperature",
|
||||
"current_temperature",
|
||||
|
@@ -1 +1 @@
|
||||
export const strokeWidth = 5;
|
||||
export const strokeWidth = 2;
|
||||
|
@@ -195,6 +195,7 @@ export const fetchHostDisksUsage = async (hass: HomeAssistant) => {
|
||||
type: "supervisor/api",
|
||||
endpoint: "/host/disks/default/usage",
|
||||
method: "get",
|
||||
timeout: 3600, // seconds. This can take a while
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -2,16 +2,10 @@ import type { Condition } from "../../../panels/lovelace/common/validate-conditi
|
||||
import type { LovelaceCardConfig } from "./card";
|
||||
import type { LovelaceStrategyConfig } from "./strategy";
|
||||
|
||||
export interface LovelaceSectionStyleConfig {
|
||||
background_color?: string;
|
||||
background_opacity?: number;
|
||||
}
|
||||
|
||||
export interface LovelaceBaseSectionConfig {
|
||||
visibility?: Condition[];
|
||||
column_span?: number;
|
||||
row_span?: number;
|
||||
style?: LovelaceSectionStyleConfig;
|
||||
/**
|
||||
* @deprecated Use heading card instead.
|
||||
*/
|
||||
|
@@ -11,8 +11,6 @@ import {
|
||||
union,
|
||||
array,
|
||||
assign,
|
||||
literal,
|
||||
is,
|
||||
boolean,
|
||||
refine,
|
||||
} from "superstruct";
|
||||
@@ -68,17 +66,6 @@ export const serviceActionStruct: Describe<ServiceActionWithTemplate> = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const playMediaActionStruct: Describe<PlayMediaAction> = assign(
|
||||
baseActionStruct,
|
||||
object({
|
||||
action: literal("media_player.play_media"),
|
||||
target: optional(object({ entity_id: optional(string()) })),
|
||||
entity_id: optional(string()),
|
||||
data: object({ media_content_id: string(), media_content_type: string() }),
|
||||
metadata: object(),
|
||||
})
|
||||
);
|
||||
|
||||
export interface ScriptEntity extends HassEntityBase {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
last_triggered: string;
|
||||
@@ -182,14 +169,6 @@ export interface WaitForTriggerAction extends BaseAction {
|
||||
continue_on_timeout?: boolean;
|
||||
}
|
||||
|
||||
export interface PlayMediaAction extends BaseAction {
|
||||
action: "media_player.play_media";
|
||||
target?: { entity_id?: string };
|
||||
entity_id?: string;
|
||||
data: { media_content_id: string; media_content_type: string };
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface RepeatAction extends BaseAction {
|
||||
repeat: CountRepeat | WhileRepeat | UntilRepeat | ForEachRepeat;
|
||||
}
|
||||
@@ -266,7 +245,6 @@ export type NonConditionAction =
|
||||
| ChooseAction
|
||||
| IfAction
|
||||
| VariablesAction
|
||||
| PlayMediaAction
|
||||
| StopAction
|
||||
| SequenceAction
|
||||
| ParallelAction
|
||||
@@ -291,7 +269,6 @@ export interface ActionTypes {
|
||||
wait_for_trigger: WaitForTriggerAction;
|
||||
variables: VariablesAction;
|
||||
service: ServiceAction;
|
||||
play_media: PlayMediaAction;
|
||||
stop: StopAction;
|
||||
sequence: SequenceAction;
|
||||
parallel: ParallelAction;
|
||||
@@ -398,11 +375,6 @@ export const getActionType = (action: Action): ActionType => {
|
||||
return "set_conversation_response";
|
||||
}
|
||||
if ("action" in action || "service" in action) {
|
||||
if ("metadata" in action) {
|
||||
if (is(action, playMediaActionStruct)) {
|
||||
return "play_media";
|
||||
}
|
||||
}
|
||||
return "service";
|
||||
}
|
||||
return "unknown";
|
||||
@@ -443,6 +415,31 @@ export const migrateAutomationAction = (
|
||||
delete action.scene;
|
||||
}
|
||||
|
||||
// legacy play media
|
||||
if (
|
||||
typeof action === "object" &&
|
||||
action !== null &&
|
||||
"action" in action &&
|
||||
action.action === "media_player.play_media" &&
|
||||
"data" in action &&
|
||||
((action.data as any)?.media_content_id ||
|
||||
(action.data as any)?.media_content_type)
|
||||
) {
|
||||
const oldData = { ...(action.data as any) };
|
||||
const media = {
|
||||
media_content_id: oldData.media_content_id,
|
||||
media_content_type: oldData.media_content_type,
|
||||
metadata: { ...(action.metadata || {}) },
|
||||
};
|
||||
delete action.metadata;
|
||||
delete oldData.media_content_id;
|
||||
delete oldData.media_content_type;
|
||||
action.data = {
|
||||
...oldData,
|
||||
media,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof action === "object" && action !== null && "sequence" in action) {
|
||||
for (const sequenceAction of (action as SequenceAction).sequence) {
|
||||
migrateAutomationAction(sequenceAction);
|
||||
|
@@ -26,7 +26,6 @@ import type {
|
||||
EventAction,
|
||||
IfAction,
|
||||
ParallelAction,
|
||||
PlayMediaAction,
|
||||
RepeatAction,
|
||||
SequenceAction,
|
||||
SetConversationResponseAction,
|
||||
@@ -303,27 +302,6 @@ const tryDescribeAction = <T extends ActionType>(
|
||||
});
|
||||
}
|
||||
|
||||
if (actionType === "play_media") {
|
||||
const config = action as PlayMediaAction;
|
||||
const entityId = config.target?.entity_id || config.entity_id;
|
||||
const mediaStateObj = entityId ? hass.states[entityId] : undefined;
|
||||
return hass.localize(
|
||||
`${actionTranslationBaseKey}.play_media.description.full`,
|
||||
{
|
||||
hasMedia:
|
||||
config.metadata.title || config.data.media_content_id
|
||||
? "true"
|
||||
: "false",
|
||||
media:
|
||||
(config.metadata.title as string | undefined) ||
|
||||
config.data.media_content_id,
|
||||
hasMediaPlayer:
|
||||
mediaStateObj || entityId !== undefined ? "true" : "false",
|
||||
mediaPlayer: mediaStateObj ? computeStateName(mediaStateObj) : entityId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (actionType === "wait_for_trigger") {
|
||||
const config = action as WaitForTriggerAction;
|
||||
const triggers = ensureArray(config.wait_for_trigger);
|
||||
|
@@ -323,6 +323,7 @@ export interface MediaSelectorValue {
|
||||
media_class?: string;
|
||||
children_media_class?: string | null;
|
||||
navigateIds?: { media_content_type: string; media_content_id: string }[];
|
||||
browse_entity_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
106
src/dialogs/dialog-list-items/dialog-list-items.ts
Normal file
106
src/dialogs/dialog-list-items/dialog-list-items.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/ha-svg-icon";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HassDialog } from "../make-dialog-manager";
|
||||
import type { ListItemsDialogParams } from "./show-list-items-dialog";
|
||||
|
||||
@customElement("dialog-list-items")
|
||||
export class ListItemsDialog
|
||||
extends LitElement
|
||||
implements HassDialog<ListItemsDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@state() private _params?: ListItemsDialogParams;
|
||||
|
||||
public async showDialog(params: ListItemsDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _itemClicked(ev: CustomEvent): void {
|
||||
const item = (ev.currentTarget as any).item;
|
||||
if (!item) return;
|
||||
item.action();
|
||||
this._dialogClosed();
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
.heading=${createCloseHeading(this.hass, this._params.title ?? " ")}
|
||||
@closed=${this._dialogClosed}
|
||||
hideActions
|
||||
>
|
||||
<div class="container">
|
||||
<ha-md-list>
|
||||
${this._params.items.map(
|
||||
(item) => html`
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
@click=${this._itemClicked}
|
||||
.item=${item}
|
||||
>
|
||||
${item.iconPath
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: item.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
icon=${item.icon}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
: nothing}
|
||||
<span class="headline">${item.label}</span>
|
||||
${item.description
|
||||
? html`
|
||||
<span class="supporting-text">${item.description}</span>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-dialog {
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
--dialog-content-padding: 0;
|
||||
--md-list-item-leading-space: 24px;
|
||||
--md-list-item-trailing-space: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-list-items": ListItemsDialog;
|
||||
}
|
||||
}
|
24
src/dialogs/dialog-list-items/show-list-items-dialog.ts
Normal file
24
src/dialogs/dialog-list-items/show-list-items-dialog.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
interface ListItem {
|
||||
icon?: string;
|
||||
iconPath?: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
action: () => any;
|
||||
}
|
||||
|
||||
export interface ListItemsDialogParams {
|
||||
title?: string;
|
||||
items: ListItem[];
|
||||
}
|
||||
|
||||
export const showListItemsDialog = (
|
||||
element: HTMLElement,
|
||||
params: ListItemsDialogParams
|
||||
) =>
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-list-items",
|
||||
dialogImport: () => import("./dialog-list-items"),
|
||||
dialogParams: params,
|
||||
});
|
@@ -3,11 +3,11 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-dialog-header";
|
||||
import "../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../components/ha-md-dialog";
|
||||
import "../../components/ha-dialog-header";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../components/ha-textfield";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -52,7 +52,7 @@ class DialogBox extends LitElement {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const confirmPrompt = this._params.confirmation || this._params.prompt;
|
||||
const confirmPrompt = this._params.confirmation || !!this._params.prompt;
|
||||
|
||||
const dialogTitle =
|
||||
this._params.title ||
|
||||
@@ -62,7 +62,7 @@ class DialogBox extends LitElement {
|
||||
return html`
|
||||
<ha-md-dialog
|
||||
open
|
||||
.disableCancelAction=${confirmPrompt || false}
|
||||
.disableCancelAction=${confirmPrompt}
|
||||
@closed=${this._dialogClosed}
|
||||
type="alert"
|
||||
aria-labelledby="dialog-box-title"
|
||||
@@ -100,23 +100,22 @@ class DialogBox extends LitElement {
|
||||
: ""}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
${confirmPrompt &&
|
||||
html`
|
||||
<ha-button
|
||||
@click=${this._dismiss}
|
||||
?dialogInitialFocus=${!this._params.prompt &&
|
||||
this._params.destructive}
|
||||
appearance="plain"
|
||||
>
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
`}
|
||||
${confirmPrompt
|
||||
? html`
|
||||
<ha-button
|
||||
@click=${this._dismiss}
|
||||
?autofocus=${!this._params.prompt && this._params.destructive}
|
||||
appearance="plain"
|
||||
>
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button
|
||||
@click=${this._confirm}
|
||||
?dialogInitialFocus=${!this._params.prompt &&
|
||||
!this._params.destructive}
|
||||
?autofocus=${!this._params.prompt && !this._params.destructive}
|
||||
variant=${this._params.destructive ? "danger" : "brand"}
|
||||
>
|
||||
${this._params.confirmText
|
||||
|
@@ -8,6 +8,7 @@ import {
|
||||
mdiPencil,
|
||||
mdiPencilOff,
|
||||
mdiPencilOutline,
|
||||
mdiTransitConnectionVariant,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
@@ -311,6 +312,8 @@ export class MoreInfoDialog extends LitElement {
|
||||
const isAdmin = this.hass.user!.is_admin;
|
||||
|
||||
const deviceId = this._getDeviceId();
|
||||
const deviceType =
|
||||
(deviceId && this.hass.devices[deviceId].entry_type) || "device";
|
||||
|
||||
const isDefaultView = this._currView === DEFAULT_VIEW && !this._childView;
|
||||
const isSpecificInitialView =
|
||||
@@ -434,11 +437,18 @@ export class MoreInfoDialog extends LitElement {
|
||||
@request-selected=${this._goToDevice}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.more_info_control.device_info"
|
||||
"ui.dialogs.more_info_control.device_or_service_info",
|
||||
{
|
||||
type: this.hass.localize(
|
||||
`ui.dialogs.more_info_control.device_type.${deviceType}`
|
||||
),
|
||||
}
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiDevices}
|
||||
.path=${deviceType === "service"
|
||||
? mdiTransitConnectionVariant
|
||||
: mdiDevices}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
`
|
||||
|
@@ -1,121 +1,148 @@
|
||||
import { mdiAppleKeyboardCommand } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/chips/ha-assist-chip";
|
||||
import type { LocalizeKeys } from "../../common/translations/localize";
|
||||
import "../../components/ha-alert";
|
||||
import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-svg-icon";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { isMac } from "../../util/is_mac";
|
||||
|
||||
interface Text {
|
||||
type: "text";
|
||||
key: LocalizeKeys;
|
||||
textTranslationKey: LocalizeKeys;
|
||||
}
|
||||
|
||||
type ShortcutString = string | { key: LocalizeKeys };
|
||||
interface LocalizedShortcut {
|
||||
shortcutTranslationKey: LocalizeKeys;
|
||||
}
|
||||
|
||||
type ShortcutString = string | LocalizedShortcut;
|
||||
|
||||
interface Shortcut {
|
||||
type: "shortcut";
|
||||
shortcut: ShortcutString[];
|
||||
key: LocalizeKeys;
|
||||
descriptionTranslationKey: LocalizeKeys;
|
||||
}
|
||||
|
||||
interface Section {
|
||||
key: LocalizeKeys;
|
||||
titleTranslationKey: LocalizeKeys;
|
||||
items: (Text | Shortcut)[];
|
||||
}
|
||||
|
||||
const CTRL_CMD = "__CTRL_CMD__";
|
||||
|
||||
const _SHORTCUTS: Section[] = [
|
||||
{
|
||||
key: "ui.dialogs.shortcuts.searching.title",
|
||||
titleTranslationKey: "ui.dialogs.shortcuts.searching.title",
|
||||
items: [
|
||||
{ type: "text", key: "ui.dialogs.shortcuts.searching.on_any_page" },
|
||||
{
|
||||
type: "shortcut",
|
||||
textTranslationKey: "ui.dialogs.shortcuts.searching.on_any_page",
|
||||
},
|
||||
{
|
||||
shortcut: ["C"],
|
||||
key: "ui.dialogs.shortcuts.searching.search_command",
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.search_command",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: ["E"],
|
||||
key: "ui.dialogs.shortcuts.searching.search_entities",
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.search_entities",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: ["D"],
|
||||
key: "ui.dialogs.shortcuts.searching.search_devices",
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.search_devices",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
key: "ui.dialogs.shortcuts.searching.on_pages_with_tables",
|
||||
textTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.on_pages_with_tables",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "F"],
|
||||
key: "ui.dialogs.shortcuts.searching.search_in_table",
|
||||
shortcut: [CTRL_CMD, "F"],
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.searching.search_in_table",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "ui.dialogs.shortcuts.assist.title",
|
||||
titleTranslationKey: "ui.dialogs.shortcuts.assist.title",
|
||||
items: [
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: ["A"],
|
||||
key: "ui.dialogs.shortcuts.assist.open_assist",
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.assist.open_assist",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "ui.dialogs.shortcuts.automation_script.title",
|
||||
titleTranslationKey: "ui.dialogs.shortcuts.automation_script.title",
|
||||
items: [
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "V"],
|
||||
key: "ui.dialogs.shortcuts.automation_script.paste",
|
||||
shortcut: [CTRL_CMD, "C"],
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.automation_script.copy",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" }, "S"],
|
||||
key: "ui.dialogs.shortcuts.automation_script.save",
|
||||
shortcut: [CTRL_CMD, "X"],
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.automation_script.cut",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "ui.dialogs.shortcuts.charts.title",
|
||||
items: [
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [
|
||||
{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" },
|
||||
{ key: "ui.dialogs.shortcuts.shortcuts.drag" },
|
||||
CTRL_CMD,
|
||||
{ shortcutTranslationKey: "ui.dialogs.shortcuts.keys.del" },
|
||||
],
|
||||
key: "ui.dialogs.shortcuts.charts.drag_to_zoom",
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.automation_script.delete",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [
|
||||
{ key: "ui.dialogs.shortcuts.shortcuts.ctrl_cmd" },
|
||||
{ key: "ui.dialogs.shortcuts.shortcuts.scroll_wheel" },
|
||||
],
|
||||
key: "ui.dialogs.shortcuts.charts.scroll_to_zoom",
|
||||
shortcut: [CTRL_CMD, "V"],
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.automation_script.paste",
|
||||
},
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: [{ key: "ui.dialogs.shortcuts.shortcuts.double_click" }],
|
||||
key: "ui.dialogs.shortcuts.charts.double_click",
|
||||
shortcut: [CTRL_CMD, "S"],
|
||||
descriptionTranslationKey:
|
||||
"ui.dialogs.shortcuts.automation_script.save",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "ui.dialogs.shortcuts.other.title",
|
||||
titleTranslationKey: "ui.dialogs.shortcuts.charts.title",
|
||||
items: [
|
||||
{
|
||||
shortcut: [
|
||||
CTRL_CMD,
|
||||
{ shortcutTranslationKey: "ui.dialogs.shortcuts.shortcuts.drag" },
|
||||
],
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.charts.drag_to_zoom",
|
||||
},
|
||||
{
|
||||
shortcut: [
|
||||
CTRL_CMD,
|
||||
{
|
||||
shortcutTranslationKey:
|
||||
"ui.dialogs.shortcuts.shortcuts.scroll_wheel",
|
||||
},
|
||||
],
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.charts.scroll_to_zoom",
|
||||
},
|
||||
{
|
||||
shortcut: [
|
||||
{
|
||||
shortcutTranslationKey:
|
||||
"ui.dialogs.shortcuts.shortcuts.double_click",
|
||||
},
|
||||
],
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.charts.double_click",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
titleTranslationKey: "ui.dialogs.shortcuts.other.title",
|
||||
items: [
|
||||
{
|
||||
type: "shortcut",
|
||||
shortcut: ["M"],
|
||||
key: "ui.dialogs.shortcuts.other.my_link",
|
||||
descriptionTranslationKey: "ui.dialogs.shortcuts.other.my_link",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -137,17 +164,28 @@ class DialogShortcuts extends LitElement {
|
||||
}
|
||||
|
||||
private _renderShortcut(
|
||||
shortcuts: ShortcutString[],
|
||||
translationKey: LocalizeKeys
|
||||
shortcutKeys: ShortcutString[],
|
||||
descriptionKey: LocalizeKeys
|
||||
) {
|
||||
const keys = shortcuts.map((shortcut) =>
|
||||
typeof shortcut === "string" ? shortcut : this.hass.localize(shortcut.key)
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="shortcut">
|
||||
${keys.map((key) => html` <span>${key.toUpperCase()}</span>`)}
|
||||
${this.hass.localize(translationKey)}
|
||||
${shortcutKeys.map(
|
||||
(shortcutKey) =>
|
||||
html`<span
|
||||
>${shortcutKey === CTRL_CMD
|
||||
? isMac
|
||||
? html`<ha-svg-icon
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize("ui.panel.config.automation.editor.ctrl")
|
||||
: typeof shortcutKey === "string"
|
||||
? shortcutKey
|
||||
: this.hass.localize(
|
||||
shortcutKey.shortcutTranslationKey
|
||||
)}</span
|
||||
>`
|
||||
)}
|
||||
${this.hass.localize(descriptionKey)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -171,16 +209,18 @@ class DialogShortcuts extends LitElement {
|
||||
<div class="content">
|
||||
${_SHORTCUTS.map(
|
||||
(section) => html`
|
||||
<h3>${this.hass.localize(section.key)}</h3>
|
||||
<h3>${this.hass.localize(section.titleTranslationKey)}</h3>
|
||||
<div class="items">
|
||||
${section.items.map((item) => {
|
||||
if (item.type === "text") {
|
||||
return html`<p>${this.hass.localize(item.key)}</p>`;
|
||||
if ("shortcut" in item) {
|
||||
return this._renderShortcut(
|
||||
(item as Shortcut).shortcut,
|
||||
(item as Shortcut).descriptionTranslationKey
|
||||
);
|
||||
}
|
||||
if (item.type === "shortcut") {
|
||||
return this._renderShortcut(item.shortcut, item.key);
|
||||
}
|
||||
return nothing;
|
||||
return html`<p>
|
||||
${this.hass.localize((item as Text).textTranslationKey)}
|
||||
</p>`;
|
||||
})}
|
||||
</div>
|
||||
`
|
||||
@@ -232,6 +272,10 @@ class DialogShortcuts extends LitElement {
|
||||
.items p {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
width: 12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { LitElement } from "lit";
|
||||
import type { Constructor } from "../types";
|
||||
import { canOverrideAlphanumericInput } from "../common/dom/can-override-input";
|
||||
|
||||
declare global {
|
||||
type SupportedShortcuts = Record<string, () => void>;
|
||||
@@ -11,23 +12,62 @@ export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
|
||||
class extends superClass {
|
||||
private _keydownEvent = (event: KeyboardEvent) => {
|
||||
const supportedShortcuts = this.supportedShortcuts();
|
||||
if ((event.ctrlKey || event.metaKey) && event.key in supportedShortcuts) {
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
!event.shiftKey &&
|
||||
!event.altKey &&
|
||||
event.key in supportedShortcuts
|
||||
) {
|
||||
// Only capture the event if the user is not focused on an input
|
||||
if (!canOverrideAlphanumericInput(event.composedPath())) {
|
||||
return;
|
||||
}
|
||||
// Don't capture the event if the user is selecting text
|
||||
if (window.getSelection()?.toString()) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
supportedShortcuts[event.key]();
|
||||
return;
|
||||
}
|
||||
|
||||
const supportedSingleKeyShortcuts = this.supportedSingleKeyShortcuts();
|
||||
if (event.key in supportedSingleKeyShortcuts) {
|
||||
event.preventDefault();
|
||||
supportedSingleKeyShortcuts[event.key]();
|
||||
}
|
||||
};
|
||||
|
||||
private _listenersAdded = false;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("keydown", this._keydownEvent);
|
||||
this.addKeyboardShortcuts();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
window.removeEventListener("keydown", this._keydownEvent);
|
||||
this.removeKeyboardShortcuts();
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
public addKeyboardShortcuts() {
|
||||
if (this._listenersAdded) {
|
||||
return;
|
||||
}
|
||||
this._listenersAdded = true;
|
||||
window.addEventListener("keydown", this._keydownEvent);
|
||||
}
|
||||
|
||||
public removeKeyboardShortcuts() {
|
||||
this._listenersAdded = false;
|
||||
window.removeEventListener("keydown", this._keydownEvent);
|
||||
}
|
||||
|
||||
protected supportedShortcuts(): SupportedShortcuts {
|
||||
return {};
|
||||
}
|
||||
|
||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
@@ -5,11 +5,15 @@ import { dynamicElement } from "../../../../common/dom/dynamic-element-directive
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import { COLLAPSIBLE_ACTION_ELEMENTS } from "../../../../data/action";
|
||||
import { migrateAutomationAction, type Action } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { editorStyles } from "../styles";
|
||||
import { getAutomationActionType } from "./ha-automation-action-row";
|
||||
import { editorStyles, indentStyle } from "../styles";
|
||||
import {
|
||||
getAutomationActionType,
|
||||
type ActionElement,
|
||||
} from "./ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-editor")
|
||||
export default class HaAutomationActionEditor extends LitElement {
|
||||
@@ -34,6 +38,9 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
||||
|
||||
@query(COLLAPSIBLE_ACTION_ELEMENTS.join(", "))
|
||||
private _collapsibleElement?: ActionElement;
|
||||
|
||||
protected render() {
|
||||
const yamlMode = this.yamlMode || !this.uiSupported;
|
||||
const type = getAutomationActionType(this.action);
|
||||
@@ -43,9 +50,12 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
disabled:
|
||||
this.disabled || (this.action.enabled === false && !this.yamlMode),
|
||||
!this.indent &&
|
||||
(this.disabled ||
|
||||
(this.action.enabled === false && !this.yamlMode)),
|
||||
yaml: yamlMode,
|
||||
indent: this.indent,
|
||||
card: !this.inSidebar,
|
||||
})}
|
||||
>
|
||||
${yamlMode
|
||||
@@ -89,7 +99,7 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
fireEvent(this, this.inSidebar ? "yaml-changed" : "value-changed", {
|
||||
value: migrateAutomationAction(ev.detail.value),
|
||||
});
|
||||
}
|
||||
@@ -103,7 +113,15 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
static styles = editorStyles;
|
||||
public expandAll() {
|
||||
this._collapsibleElement?.expandAll?.();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._collapsibleElement?.collapseAll?.();
|
||||
}
|
||||
|
||||
static styles = [editorStyles, indentStyle];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -5,12 +5,12 @@ import {
|
||||
mdiArrowUp,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiPlay,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
@@ -26,6 +26,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
@@ -81,7 +82,6 @@ import "./types/ha-automation-action-device_id";
|
||||
import "./types/ha-automation-action-event";
|
||||
import "./types/ha-automation-action-if";
|
||||
import "./types/ha-automation-action-parallel";
|
||||
import "./types/ha-automation-action-play_media";
|
||||
import { getRepeatType } from "./types/ha-automation-action-repeat";
|
||||
import "./types/ha-automation-action-sequence";
|
||||
import "./types/ha-automation-action-service";
|
||||
@@ -96,7 +96,7 @@ export const getAutomationActionType = memoizeOne(
|
||||
return undefined;
|
||||
}
|
||||
if ("action" in action) {
|
||||
return getActionType(action) as "action" | "play_media";
|
||||
return getActionType(action) as "action";
|
||||
}
|
||||
if (CONDITION_BUILDING_BLOCKS.some((key) => key in action)) {
|
||||
return "condition" as const;
|
||||
@@ -109,6 +109,8 @@ export const getAutomationActionType = memoizeOne(
|
||||
|
||||
export interface ActionElement extends LitElement {
|
||||
action: Action;
|
||||
expandAll?: () => void;
|
||||
collapseAll?: () => void;
|
||||
}
|
||||
|
||||
export const handleChangeEvent = (element: ActionElement, ev: CustomEvent) => {
|
||||
@@ -143,13 +145,20 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public root = false;
|
||||
|
||||
@property({ type: Boolean }) public first?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public last?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public highlight?: boolean;
|
||||
|
||||
@property({ type: Boolean, attribute: "sidebar" })
|
||||
public optionsInSidebar = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
@@ -176,12 +185,27 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
@state() private _selected = false;
|
||||
|
||||
@state() private _collapsed = false;
|
||||
@state() private _collapsed = true;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query("ha-automation-action-editor")
|
||||
private actionEditor?: HaAutomationActionEditor;
|
||||
private _actionEditor?: HaAutomationActionEditor;
|
||||
|
||||
@query("ha-automation-row")
|
||||
private _automationRowElement?: HaAutomationRow;
|
||||
|
||||
get selected() {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
if (this.root) {
|
||||
this._collapsed = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties: PropertyValues) {
|
||||
if (changedProperties.has("yamlMode")) {
|
||||
@@ -243,26 +267,30 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<ha-svg-icon .path=${mdiAlertCircleCheck}></ha-svg-icon>
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar
|
||||
? html`<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-button-menu
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item .clickAction=${this._runAction}>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html` <ha-md-menu-item
|
||||
<ha-md-menu-item .clickAction=${this._runAction}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.run"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
@@ -270,59 +298,60 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || !!this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`<ha-md-menu-item
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this._uiModeAvailable || !!this._warnings}
|
||||
>
|
||||
@@ -330,45 +359,44 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.action.enabled === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.action.enabled === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.action.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.action.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -414,18 +442,24 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
${this.optionsInSidebar
|
||||
? html`<ha-automation-row
|
||||
.disabled=${this.action.enabled === false}
|
||||
@click=${this._toggleSidebar}
|
||||
.leftChevron=${[
|
||||
...ACTION_BUILDING_BLOCKS,
|
||||
...ACTION_COMBINED_BLOCKS,
|
||||
].includes(blockType!)}
|
||||
].includes(blockType!) ||
|
||||
(blockType === "condition" &&
|
||||
CONDITION_BUILDING_BLOCKS.includes(
|
||||
(this.action as Condition).condition
|
||||
))}
|
||||
.collapsed=${this._collapsed}
|
||||
.selected=${this._selected}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
.highlight=${this.highlight}
|
||||
.buildingBlock=${[
|
||||
...ACTION_BUILDING_BLOCKS,
|
||||
...ACTION_COMBINED_BLOCKS,
|
||||
].includes(blockType!)}
|
||||
.sortSelected=${this.sortSelected}
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
>${this._renderRow()}</ha-automation-row
|
||||
>`
|
||||
: html`
|
||||
@@ -479,11 +513,20 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this.openSidebar(value); // refresh sidebar
|
||||
|
||||
if (this._yamlMode && !this.optionsInSidebar) {
|
||||
this.actionEditor?.yamlEditor?.setValue(value);
|
||||
this._actionEditor?.yamlEditor?.setValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
private _runAction = async () => {
|
||||
requestAnimationFrame(() => {
|
||||
// @ts-ignore is supported in all browsers except firefox
|
||||
if (this.scrollIntoViewIfNeeded) {
|
||||
// @ts-ignore is supported in all browsers except firefox
|
||||
this.scrollIntoViewIfNeeded();
|
||||
return;
|
||||
}
|
||||
this.scrollIntoView();
|
||||
});
|
||||
const validated = await validateConfig(this.hass, {
|
||||
actions: this.action,
|
||||
});
|
||||
@@ -582,7 +625,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
if (this._selected && this.optionsInSidebar) {
|
||||
this.openSidebar(value); // refresh sidebar
|
||||
} else if (this._yamlMode) {
|
||||
this.actionEditor?.yamlEditor?.setValue(value);
|
||||
this._actionEditor?.yamlEditor?.setValue(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -593,6 +636,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
|
||||
private _copyAction = () => {
|
||||
this._setClipboard();
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.copied_to_clipboard"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
private _cutAction = () => {
|
||||
@@ -601,6 +650,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
if (this._selected) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.cut_to_clipboard"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
private _moveUp = () => {
|
||||
@@ -634,8 +689,7 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selected) {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
return;
|
||||
}
|
||||
this.openSidebar();
|
||||
@@ -649,39 +703,79 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
save: (value) => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
},
|
||||
close: () => {
|
||||
close: (focus?: boolean) => {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
rename: () => {
|
||||
this._renameAction();
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
config: sidebarAction,
|
||||
copy: this._copyAction,
|
||||
cut: this._cutAction,
|
||||
duplicate: this._duplicateAction,
|
||||
run: this._runAction,
|
||||
config: {
|
||||
action: sidebarAction,
|
||||
},
|
||||
uiSupported: actionType ? this._uiSupported(actionType) : false,
|
||||
yamlMode: this._yamlMode,
|
||||
} satisfies ActionSidebarConfig);
|
||||
this._selected = true;
|
||||
this._collapsed = false;
|
||||
|
||||
if (this.narrow) {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 180); // duration of transition of added padding for bottom sheet
|
||||
}
|
||||
}
|
||||
|
||||
public expand() {
|
||||
if (this.optionsInSidebar) {
|
||||
this._collapsed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
||||
});
|
||||
}
|
||||
|
||||
public collapse() {
|
||||
if (this.optionsInSidebar) {
|
||||
this._collapsed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = false;
|
||||
});
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this.expand();
|
||||
|
||||
this._actionEditor?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this.collapse();
|
||||
|
||||
this._actionEditor?.collapseAll();
|
||||
}
|
||||
|
||||
private _uiSupported = memoizeOne(
|
||||
(type: string) =>
|
||||
customElements.get(`ha-automation-action-${type}`) !== undefined
|
||||
@@ -691,6 +785,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this._collapsed = !this._collapsed;
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
static styles = rowStyles;
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-sortable";
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
VIRTUAL_ACTIONS,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import { automationRowsStyles } from "../styles";
|
||||
import type HaAutomationActionRow from "./ha-automation-action-row";
|
||||
import { getAutomationActionType } from "./ha-automation-action-row";
|
||||
|
||||
@@ -44,7 +44,7 @@ export default class HaAutomationAction extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
@@ -55,43 +55,34 @@ export default class HaAutomationAction extends LitElement {
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
|
||||
@queryAll("ha-automation-action-row")
|
||||
private _actionRowElements?: HaAutomationActionRow[];
|
||||
|
||||
private _focusLastActionOnChange = false;
|
||||
|
||||
private _focusActionIndexOnChange?: number;
|
||||
|
||||
private _actionKeys = new WeakMap<Action, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-action-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="actions"
|
||||
invert-swap
|
||||
@item-moved=${this._actionMoved}
|
||||
@item-added=${this._actionAdded}
|
||||
@item-removed=${this._actionRemoved}
|
||||
>
|
||||
<div class="actions">
|
||||
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
|
||||
${repeat(
|
||||
this.actions,
|
||||
(action) => this._getKey(action),
|
||||
(action, idx) => html`
|
||||
<ha-automation-action-row
|
||||
.root=${this.root}
|
||||
.sortableData=${action}
|
||||
.index=${idx}
|
||||
.first=${idx === 0}
|
||||
@@ -104,12 +95,22 @@ export default class HaAutomationAction extends LitElement {
|
||||
@move-up=${this._moveUp}
|
||||
@value-changed=${this._actionChanged}
|
||||
.hass=${this.hass}
|
||||
?highlight=${this.highlightedActions?.includes(action)}
|
||||
.highlight=${this.highlightedActions?.includes(action)}
|
||||
.optionsInSidebar=${this.optionsInSidebar}
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<div
|
||||
tabindex="0"
|
||||
class="handle ${this._rowSortSelected === idx
|
||||
? "active"
|
||||
: ""}"
|
||||
slot="icons"
|
||||
@keydown=${this._handleDragKeydown}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
@@ -149,19 +150,27 @@ export default class HaAutomationAction extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("actions") && this._focusLastActionOnChange) {
|
||||
this._focusLastActionOnChange = false;
|
||||
if (
|
||||
changedProps.has("actions") &&
|
||||
(this._focusLastActionOnChange ||
|
||||
this._focusActionIndexOnChange !== undefined)
|
||||
) {
|
||||
const mode = this._focusLastActionOnChange ? "new" : "moved";
|
||||
|
||||
const row = this.shadowRoot!.querySelector<HaAutomationActionRow>(
|
||||
"ha-automation-action-row:last-of-type"
|
||||
`ha-automation-action-row:${mode === "new" ? "last-of-type" : `nth-of-type(${this._focusActionIndexOnChange! + 1})`}`
|
||||
)!;
|
||||
|
||||
this._focusLastActionOnChange = false;
|
||||
this._focusActionIndexOnChange = undefined;
|
||||
|
||||
row.updateComplete.then(() => {
|
||||
// on new condition open the settings in the sidebar, except for building blocks
|
||||
const type = getAutomationActionType(row.action);
|
||||
if (
|
||||
type &&
|
||||
this.optionsInSidebar &&
|
||||
!ACTION_BUILDING_BLOCKS.includes(type)
|
||||
(!ACTION_BUILDING_BLOCKS.includes(type) || mode === "moved")
|
||||
) {
|
||||
row.openSidebar();
|
||||
if (this.narrow) {
|
||||
@@ -170,26 +179,34 @@ export default class HaAutomationAction extends LitElement {
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
} else if (!this.optionsInSidebar) {
|
||||
}
|
||||
|
||||
if (mode === "new") {
|
||||
row.expand();
|
||||
}
|
||||
row.focus();
|
||||
|
||||
if (!this.optionsInSidebar) {
|
||||
row.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
const rows = this.shadowRoot!.querySelectorAll<HaAutomationActionRow>(
|
||||
"ha-automation-action-row"
|
||||
)!;
|
||||
rows.forEach((row) => {
|
||||
row.expand();
|
||||
this._actionRowElements?.forEach((row) => {
|
||||
row.expandAll();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._actionRowElements?.forEach((row) => {
|
||||
row.collapseAll();
|
||||
});
|
||||
}
|
||||
|
||||
private _addActionDialog() {
|
||||
if (this.narrow) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
}
|
||||
|
||||
showAddAutomationElementDialog(this, {
|
||||
@@ -239,18 +256,30 @@ export default class HaAutomationAction extends LitElement {
|
||||
return this._actionKeys.get(action)!;
|
||||
}
|
||||
|
||||
private _moveUp(ev) {
|
||||
private async _moveUp(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationActionRow).first) {
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
private async _moveDown(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationActionRow).last) {
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _move(oldIndex: number, newIndex: number) {
|
||||
@@ -270,6 +299,9 @@ export default class HaAutomationAction extends LitElement {
|
||||
private async _actionAdded(ev: CustomEvent): Promise<void> {
|
||||
ev.stopPropagation();
|
||||
const { index, data } = ev.detail;
|
||||
const item = ev.detail.item as HaAutomationActionRow;
|
||||
const selected = item.selected;
|
||||
|
||||
let actions = [
|
||||
...this.actions.slice(0, index),
|
||||
data,
|
||||
@@ -277,6 +309,9 @@ export default class HaAutomationAction extends LitElement {
|
||||
];
|
||||
// Add action locally to avoid UI jump
|
||||
this.actions = actions;
|
||||
if (selected) {
|
||||
this._focusActionIndexOnChange = actions.length === 1 ? 0 : index;
|
||||
}
|
||||
await nextRender();
|
||||
if (this.actions !== actions) {
|
||||
// Ensure action is added even after update
|
||||
@@ -285,6 +320,9 @@ export default class HaAutomationAction extends LitElement {
|
||||
data,
|
||||
...this.actions.slice(index),
|
||||
];
|
||||
if (selected) {
|
||||
this._focusActionIndexOnChange = actions.length === 1 ? 0 : index;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
}
|
||||
@@ -299,7 +337,6 @@ export default class HaAutomationAction extends LitElement {
|
||||
// Ensure action is removed even after update
|
||||
const actions = this.actions.filter((a) => a !== action);
|
||||
fireEvent(this, "value-changed", { value: actions });
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
|
||||
private _actionChanged(ev: CustomEvent) {
|
||||
@@ -329,44 +366,21 @@ export default class HaAutomationAction extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.actions {
|
||||
padding: 16px 0 16px 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
private _handleDragKeydown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.stopPropagation();
|
||||
this._rowSortSelected =
|
||||
this._rowSortSelected === undefined
|
||||
? (ev.target as any).index
|
||||
: undefined;
|
||||
}
|
||||
:host([root]) .actions {
|
||||
padding-right: 8px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-action-row {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _stopSortSelection() {
|
||||
this._rowSortSelected = undefined;
|
||||
}
|
||||
|
||||
static styles = automationRowsStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,12 +1,15 @@
|
||||
import { type CSSResultGroup, LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { type CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-button";
|
||||
import type { Action, ChooseAction, Option } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../option/ha-automation-option";
|
||||
import type HaAutomationOption from "../../option/ha-automation-option";
|
||||
import "../../option/ha-automation-option-row";
|
||||
import type HaAutomationOptionRow from "../../option/ha-automation-option-row";
|
||||
import { indentStyle } from "../../styles";
|
||||
import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@@ -24,6 +27,11 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
|
||||
@state() private _showDefault = false;
|
||||
|
||||
@query("ha-automation-option") private _optionElement?: HaAutomationOption;
|
||||
|
||||
@query("ha-automation-option-row")
|
||||
private _defaultOptionRowElement?: HaAutomationOptionRow;
|
||||
|
||||
public static get defaultConfig(): ChooseAction {
|
||||
return { choose: [{ conditions: [], sequence: [] }] };
|
||||
}
|
||||
@@ -41,42 +49,29 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.optionsInSidebar=${this.indent}
|
||||
.showDefaultActions=${this._showDefault || !!action.default}
|
||||
@show-default-actions=${this._addDefault}
|
||||
></ha-automation-option>
|
||||
|
||||
${this._showDefault || action.default
|
||||
? html`
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.default"
|
||||
)}:
|
||||
</h2>
|
||||
<ha-automation-action
|
||||
.actions=${ensureArray(action.default) || []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._defaultChanged}
|
||||
.hass=${this.hass}
|
||||
<ha-automation-option-row
|
||||
.defaultActions=${(ensureArray(action.default) || []) as Action[]}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.optionsInSidebar=${this.indent}
|
||||
></ha-automation-action>
|
||||
@value-changed=${this._defaultChanged}
|
||||
></ha-automation-option-row>
|
||||
`
|
||||
: html`
|
||||
<div class="link-button-row">
|
||||
<button
|
||||
class="link"
|
||||
@click=${this._addDefault}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.add_default"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
private _addDefault() {
|
||||
private async _addDefault() {
|
||||
this._showDefault = true;
|
||||
await this._defaultOptionRowElement?.updateComplete;
|
||||
this._defaultOptionRowElement?.expand();
|
||||
}
|
||||
|
||||
private _optionsChanged(ev: CustomEvent) {
|
||||
@@ -104,12 +99,28 @@ export class HaChooseAction extends LitElement implements ActionElement {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._optionElement?.expandAll();
|
||||
this._defaultOptionRowElement?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._optionElement?.collapseAll();
|
||||
this._defaultOptionRowElement?.collapseAll();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
indentStyle,
|
||||
css`
|
||||
.link-button-row {
|
||||
padding: 14px 14px 0 14px;
|
||||
ha-automation-option-row {
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
}
|
||||
h3 {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../../../../common/string/compare";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import type { LocalizeFunc } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/ha-list-item";
|
||||
import "../../../../../components/ha-select";
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
} from "../../../../../data/condition";
|
||||
import type { Entries, HomeAssistant } from "../../../../../types";
|
||||
import "../../condition/ha-automation-condition-editor";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
import type HaAutomationConditionEditor from "../../condition/ha-automation-condition-editor";
|
||||
import "../../condition/types/ha-automation-condition-and";
|
||||
import "../../condition/types/ha-automation-condition-device";
|
||||
import "../../condition/types/ha-automation-condition-not";
|
||||
@@ -26,6 +27,7 @@ import "../../condition/types/ha-automation-condition-template";
|
||||
import "../../condition/types/ha-automation-condition-time";
|
||||
import "../../condition/types/ha-automation-condition-trigger";
|
||||
import "../../condition/types/ha-automation-condition-zone";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-condition")
|
||||
export class HaConditionAction extends LitElement implements ActionElement {
|
||||
@@ -41,6 +43,9 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
||||
|
||||
@query("ha-automation-condition-editor")
|
||||
private _conditionEditor?: HaAutomationConditionEditor;
|
||||
|
||||
public static get defaultConfig(): Omit<Condition, "state" | "entity_id"> {
|
||||
return { condition: "state" };
|
||||
}
|
||||
@@ -62,6 +67,7 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
||||
.value=${this.action.condition}
|
||||
naturalMenuWidth
|
||||
@selected=${this._typeChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${this._processedTypes(this.hass.localize).map(
|
||||
([opt, label, icon]) => html`
|
||||
@@ -146,6 +152,14 @@ export class HaConditionAction extends LitElement implements ActionElement {
|
||||
customElements.get(`ha-automation-condition-${type}`) !== undefined
|
||||
);
|
||||
|
||||
public expandAll() {
|
||||
this._conditionEditor?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._conditionEditor?.collapseAll();
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-select {
|
||||
margin-bottom: 24px;
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, queryAll } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { Action, IfAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { Condition } from "../../../../lovelace/common/validate-condition";
|
||||
import type HaAutomationCondition from "../../condition/ha-automation-condition";
|
||||
import "../ha-automation-action";
|
||||
import type HaAutomationAction from "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-if")
|
||||
@@ -22,7 +24,11 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public indent = false;
|
||||
|
||||
@state() private _showElse = false;
|
||||
@query("ha-automation-condition")
|
||||
private _conditionElement?: HaAutomationCondition;
|
||||
|
||||
@queryAll("ha-automation-action")
|
||||
private _actionElements?: HaAutomationAction[];
|
||||
|
||||
public static get defaultConfig(): IfAction {
|
||||
return {
|
||||
@@ -35,11 +41,11 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
const action = this.action;
|
||||
|
||||
return html`
|
||||
<h3>
|
||||
<h4>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.if.if"
|
||||
)}*:
|
||||
</h3>
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-condition
|
||||
.conditions=${action.if ?? []}
|
||||
.disabled=${this.disabled}
|
||||
@@ -49,11 +55,11 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
.optionsInSidebar=${this.indent}
|
||||
></ha-automation-condition>
|
||||
|
||||
<h3>
|
||||
<h4>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.if.then"
|
||||
)}*:
|
||||
</h3>
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
.actions=${action.then ?? []}
|
||||
.disabled=${this.disabled}
|
||||
@@ -62,40 +68,22 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
.narrow=${this.narrow}
|
||||
.optionsInSidebar=${this.indent}
|
||||
></ha-automation-action>
|
||||
${this._showElse || action.else
|
||||
? html`
|
||||
<h3>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.if.else"
|
||||
)}:
|
||||
</h3>
|
||||
<ha-automation-action
|
||||
.actions=${action.else || []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._elseChanged}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.optionsInSidebar=${this.indent}
|
||||
></ha-automation-action>
|
||||
`
|
||||
: html`<div class="link-button-row">
|
||||
<button
|
||||
class="link"
|
||||
@click=${this._addElse}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.if.add_else"
|
||||
)}
|
||||
</button>
|
||||
</div>`}
|
||||
<h4>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.if.else"
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
.actions=${action.else || []}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._elseChanged}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.optionsInSidebar=${this.indent}
|
||||
></ha-automation-action>
|
||||
`;
|
||||
}
|
||||
|
||||
private _addElse() {
|
||||
this._showElse = true;
|
||||
}
|
||||
|
||||
private _ifChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
const value = ev.detail.value as Condition[];
|
||||
@@ -120,7 +108,6 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
|
||||
private _elseChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
this._showElse = true;
|
||||
const elseAction = ev.detail.value as Action[];
|
||||
const newValue: IfAction = {
|
||||
...this.action,
|
||||
@@ -132,12 +119,26 @@ export class HaIfAction extends LitElement implements ActionElement {
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._conditionElement?.expandAll();
|
||||
this._actionElements?.forEach((element) => element.expandAll?.());
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._conditionElement?.collapseAll();
|
||||
this._actionElements?.forEach((element) => element.collapseAll?.());
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.link-button-row {
|
||||
padding: 14px;
|
||||
h4 {
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
h4:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { Action, ParallelAction } from "../../../../../data/script";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import type HaAutomationAction from "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
@customElement("ha-automation-action-parallel")
|
||||
@@ -21,6 +22,9 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public indent = false;
|
||||
|
||||
@query("ha-automation-action")
|
||||
private _actionElement?: HaAutomationAction;
|
||||
|
||||
public static get defaultConfig(): ParallelAction {
|
||||
return {
|
||||
parallel: [],
|
||||
@@ -53,6 +57,14 @@ export class HaParallelAction extends LitElement implements ActionElement {
|
||||
});
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._actionElement?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._actionElement?.collapseAll();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return haStyle;
|
||||
}
|
||||
|
@@ -1,79 +0,0 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-selector/ha-selector";
|
||||
import type { PlayMediaAction } from "../../../../../data/script";
|
||||
import type {
|
||||
MediaSelectorValue,
|
||||
Selector,
|
||||
} from "../../../../../data/selector";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
const MEDIA_SELECTOR_SCHEMA: Selector = {
|
||||
media: {},
|
||||
};
|
||||
|
||||
@customElement("ha-automation-action-play_media")
|
||||
export class HaPlayMediaAction extends LitElement implements ActionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ attribute: false }) public action!: PlayMediaAction;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
public static get defaultConfig(): PlayMediaAction {
|
||||
return {
|
||||
action: "media_player.play_media",
|
||||
target: { entity_id: "" },
|
||||
data: { media_content_id: "", media_content_type: "" },
|
||||
metadata: {},
|
||||
};
|
||||
}
|
||||
|
||||
private _getSelectorValue = memoizeOne(
|
||||
(action: PlayMediaAction): MediaSelectorValue => ({
|
||||
entity_id: action.target?.entity_id || action.entity_id,
|
||||
media_content_id: action.data?.media_content_id,
|
||||
media_content_type: action.data?.media_content_type,
|
||||
metadata: action.metadata,
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-selector
|
||||
.selector=${MEDIA_SELECTOR_SCHEMA}
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.value=${this._getSelectorValue(this.action)}
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-selector>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent<{ value: MediaSelectorValue }>) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.action,
|
||||
action: "media_player.play_media",
|
||||
target: { entity_id: ev.detail.value.entity_id },
|
||||
data: {
|
||||
media_content_id: ev.detail.value.media_content_id,
|
||||
media_content_type: ev.detail.value.media_content_type,
|
||||
},
|
||||
metadata: ev.detail.value.metadata || {},
|
||||
} as PlayMediaAction,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-action-play_media": HaPlayMediaAction;
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
@@ -12,10 +12,14 @@ import type { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
import { isTemplate } from "../../../../../common/string/has-template";
|
||||
import "../../../../../components/ha-form/ha-form";
|
||||
import type { HaForm } from "../../../../../components/ha-form/ha-form";
|
||||
import type {
|
||||
HaFormSchema,
|
||||
SchemaUnion,
|
||||
} from "../../../../../components/ha-form/types";
|
||||
import type { HaSelector } from "../../../../../components/ha-selector/ha-selector";
|
||||
import type { HaActionSelector } from "../../../../../components/ha-selector/ha-selector-action";
|
||||
import type { HaConditionSelector } from "../../../../../components/ha-selector/ha-selector-condition";
|
||||
|
||||
const OPTIONS = ["count", "while", "until", "for_each"] as const;
|
||||
type RepeatType = (typeof OPTIONS)[number];
|
||||
@@ -37,6 +41,9 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean, attribute: "indent" }) public indent = false;
|
||||
|
||||
@query("ha-form")
|
||||
private _formElement?: HaForm;
|
||||
|
||||
public static get defaultConfig(): RepeatAction {
|
||||
return { repeat: { count: 2, sequence: [] } };
|
||||
}
|
||||
@@ -175,6 +182,41 @@ export class HaRepeatAction extends LitElement implements ActionElement {
|
||||
];
|
||||
}
|
||||
|
||||
private _getSelectorElements() {
|
||||
if (this._formElement) {
|
||||
const selectors =
|
||||
this._formElement.shadowRoot?.querySelectorAll<HaSelector>(
|
||||
"ha-selector"
|
||||
);
|
||||
|
||||
const selectorElements: (HaConditionSelector | HaActionSelector)[] = [];
|
||||
|
||||
selectors?.forEach((selector) => {
|
||||
selectorElements.push(
|
||||
...Array.from(
|
||||
selector.shadowRoot?.querySelectorAll<
|
||||
HaConditionSelector | HaActionSelector
|
||||
>("ha-selector-condition, ha-selector-action") || []
|
||||
)
|
||||
);
|
||||
});
|
||||
return selectorElements;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._getSelectorElements().forEach((element) => {
|
||||
element.expandAll?.();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._getSelectorElements().forEach((element) => {
|
||||
element.collapseAll?.();
|
||||
});
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
schema: SchemaUnion<ReturnType<typeof this._schema>>
|
||||
): string => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { query, customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import "../../../../../components/ha-textfield";
|
||||
import type { Action, SequenceAction } from "../../../../../data/script";
|
||||
@@ -8,6 +8,7 @@ import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-action";
|
||||
import type { ActionElement } from "../ha-automation-action-row";
|
||||
import type HaAutomationAction from "../ha-automation-action";
|
||||
|
||||
@customElement("ha-automation-action-sequence")
|
||||
export class HaSequenceAction extends LitElement implements ActionElement {
|
||||
@@ -21,6 +22,9 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
||||
|
||||
@property({ type: Boolean }) public indent = false;
|
||||
|
||||
@query("ha-automation-action")
|
||||
private _actionElement?: HaAutomationAction;
|
||||
|
||||
public static get defaultConfig(): SequenceAction {
|
||||
return {
|
||||
sequence: [],
|
||||
@@ -53,6 +57,14 @@ export class HaSequenceAction extends LitElement implements ActionElement {
|
||||
});
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._actionElement?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._actionElement?.collapseAll();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return haStyle;
|
||||
}
|
||||
|
@@ -1,4 +1,9 @@
|
||||
import { mdiClose, mdiContentPaste, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiClose,
|
||||
mdiContentPaste,
|
||||
mdiPlus,
|
||||
} from "@mdi/js";
|
||||
import Fuse from "fuse.js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
@@ -41,11 +46,14 @@ import {
|
||||
} from "../../../data/integration";
|
||||
import { TRIGGER_GROUPS, TRIGGER_ICONS } from "../../../data/trigger";
|
||||
import type { HassDialog } from "../../../dialogs/make-dialog-manager";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { HaFuse } from "../../../resources/fuse";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
|
||||
import { PASTE_VALUE } from "./show-add-automation-element-dialog";
|
||||
import { HaFuse } from "../../../resources/fuse";
|
||||
|
||||
const TYPES = {
|
||||
trigger: { groups: TRIGGER_GROUPS, icons: TRIGGER_ICONS },
|
||||
@@ -85,7 +93,10 @@ const ENTITY_DOMAINS_OTHER = new Set([
|
||||
const ENTITY_DOMAINS_MAIN = new Set(["notify"]);
|
||||
|
||||
@customElement("add-automation-element-dialog")
|
||||
class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
class DialogAddAutomationElement
|
||||
extends KeyboardShortcutMixin(LitElement)
|
||||
implements HassDialog
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: AddAutomationElementDialogParams;
|
||||
@@ -108,9 +119,14 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
|
||||
@state() private _height?: number;
|
||||
|
||||
@state() private _narrow = false;
|
||||
|
||||
public showDialog(params): void {
|
||||
this._params = params;
|
||||
this._group = params.group;
|
||||
|
||||
this.addKeyboardShortcuts();
|
||||
|
||||
if (this._params?.type === "action") {
|
||||
this.hass.loadBackendTranslation("services");
|
||||
this._fetchManifests();
|
||||
@@ -120,9 +136,12 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
this._fullScreen = matchMedia(
|
||||
"all and (max-width: 450px), all and (max-height: 500px)"
|
||||
).matches;
|
||||
|
||||
this._narrow = matchMedia("(max-width: 870px)").matches;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this.removeKeyboardShortcuts();
|
||||
if (this._params) {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
@@ -244,14 +263,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
manifests?: DomainManifestLookup
|
||||
): ListItem[] => {
|
||||
if (type === "action" && isService(group)) {
|
||||
let result = this._services(localize, services, manifests, group);
|
||||
if (group === `${SERVICE_PREFIX}media_player`) {
|
||||
result = [
|
||||
this._convertToItem("play_media", {}, type, localize),
|
||||
...result,
|
||||
];
|
||||
}
|
||||
return result;
|
||||
return this._services(localize, services, manifests, group);
|
||||
}
|
||||
|
||||
const groups = this._getGroups(type, group);
|
||||
@@ -562,15 +574,37 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
.value=${PASTE_VALUE}
|
||||
@click=${this._selected}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.${this._params.type}s.paste`
|
||||
)}
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
// @ts-ignore
|
||||
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
|
||||
)}</span
|
||||
>
|
||||
<div class="shortcut-label">
|
||||
<div class="label">
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.${this._params.type}s.paste`
|
||||
)}
|
||||
</div>
|
||||
<div class="supporting-text">
|
||||
${this.hass.localize(
|
||||
// @ts-ignore
|
||||
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
${!this._narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>V</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiContentPaste}
|
||||
@@ -578,7 +612,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
><ha-svg-icon slot="end" .path=${mdiPlus}></ha-svg-icon>
|
||||
</ha-md-list-item>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>`
|
||||
: ""}
|
||||
: nothing}
|
||||
${repeat(
|
||||
items,
|
||||
(item) => item.key,
|
||||
@@ -644,6 +678,30 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
this._filter = ev.detail.value;
|
||||
}
|
||||
|
||||
private _addClipboard = () => {
|
||||
if (this._params?.clipboardItem) {
|
||||
this._params!.add(PASTE_VALUE);
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.item_pasted",
|
||||
{
|
||||
item: this.hass.localize(
|
||||
// @ts-ignore
|
||||
`ui.panel.config.automation.editor.${this._params.type}s.type.${this._params.clipboardItem}.label`
|
||||
),
|
||||
}
|
||||
),
|
||||
});
|
||||
this.closeDialog();
|
||||
}
|
||||
};
|
||||
|
||||
protected supportedShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
v: () => this._addClipboard(),
|
||||
};
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -667,6 +725,7 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
max-width: 100vw;
|
||||
--md-list-item-leading-space: 24px;
|
||||
--md-list-item-trailing-space: 24px;
|
||||
--md-list-item-supporting-text-font: var(--ha-font-size-s);
|
||||
}
|
||||
ha-md-list-item img {
|
||||
width: 24px;
|
||||
@@ -675,6 +734,27 @@ class DialogAddAutomationElement extends LitElement implements HassDialog {
|
||||
display: block;
|
||||
margin: 0 16px;
|
||||
}
|
||||
.shortcut-label {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.shortcut-label .supporting-text {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
.shortcut-label .shortcut {
|
||||
--mdc-icon-size: 12px;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.shortcut-label .shortcut span {
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-family: var(--ha-font-family-code);
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -8,9 +8,11 @@ import "../../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../../components/ha-yaml-editor";
|
||||
import type { Condition } from "../../../../data/automation";
|
||||
import { expandConditionWithShorthand } from "../../../../data/automation";
|
||||
import { COLLAPSIBLE_CONDITION_ELEMENTS } from "../../../../data/condition";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { editorStyles } from "../styles";
|
||||
import { editorStyles, indentStyle } from "../styles";
|
||||
import type { ConditionElement } from "./ha-automation-condition-row";
|
||||
|
||||
@customElement("ha-automation-condition-editor")
|
||||
export default class HaAutomationConditionEditor extends LitElement {
|
||||
@@ -26,6 +28,8 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public inSidebar = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public selected = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "supported" }) public uiSupported =
|
||||
@@ -33,6 +37,9 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
|
||||
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
||||
|
||||
@query(COLLAPSIBLE_CONDITION_ELEMENTS.join(", "))
|
||||
private _collapsibleElement?: ConditionElement;
|
||||
|
||||
private _processedCondition = memoizeOne((condition) =>
|
||||
expandConditionWithShorthand(condition)
|
||||
);
|
||||
@@ -46,10 +53,12 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
disabled:
|
||||
this.disabled ||
|
||||
(this.condition.enabled === false && !this.yamlMode),
|
||||
!this.indent &&
|
||||
(this.disabled ||
|
||||
(this.condition.enabled === false && !this.yamlMode)),
|
||||
yaml: yamlMode,
|
||||
indent: this.indent,
|
||||
card: !this.inSidebar,
|
||||
})}
|
||||
>
|
||||
${yamlMode
|
||||
@@ -95,8 +104,9 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
// @ts-ignore
|
||||
fireEvent(this, "value-changed", { value: ev.detail.value, yaml: true });
|
||||
fireEvent(this, this.inSidebar ? "yaml-changed" : "value-changed", {
|
||||
value: ev.detail.value,
|
||||
});
|
||||
}
|
||||
|
||||
private _onUiChanged(ev: CustomEvent) {
|
||||
@@ -108,8 +118,17 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._collapsibleElement?.expandAll?.();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._collapsibleElement?.collapseAll?.();
|
||||
}
|
||||
|
||||
static styles = [
|
||||
editorStyles,
|
||||
indentStyle,
|
||||
css`
|
||||
:host([action]) .card-content {
|
||||
padding: 0;
|
||||
@@ -119,6 +138,7 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
margin-right: 0;
|
||||
padding: 0;
|
||||
border-left: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -4,17 +4,17 @@ import {
|
||||
mdiArrowUp,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiFlask,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -26,6 +26,7 @@ import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
@@ -52,6 +53,7 @@ import {
|
||||
showPromptDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { rowStyles } from "../styles";
|
||||
import "./ha-automation-condition-editor";
|
||||
@@ -70,6 +72,8 @@ import "./types/ha-automation-condition-zone";
|
||||
|
||||
export interface ConditionElement extends LitElement {
|
||||
condition: Condition;
|
||||
expandAll?: () => void;
|
||||
collapseAll?: () => void;
|
||||
}
|
||||
|
||||
export const handleChangeEvent = (
|
||||
@@ -105,13 +109,20 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) public root = false;
|
||||
|
||||
@property({ type: Boolean }) public first?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public last?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@state() private _collapsed = false;
|
||||
@property({ type: Boolean }) public highlight?: boolean;
|
||||
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
@state() private _collapsed = true;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@@ -141,6 +152,13 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
@query("ha-automation-condition-editor")
|
||||
public conditionEditor?: HaAutomationConditionEditor;
|
||||
|
||||
@query("ha-automation-row")
|
||||
private _automationRowElement?: HaAutomationRow;
|
||||
|
||||
get selected() {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
private _renderRow() {
|
||||
return html`
|
||||
<ha-svg-icon
|
||||
@@ -156,90 +174,94 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
>
|
||||
</ha-icon-button>
|
||||
${!this.optionsInSidebar
|
||||
? html`<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
>
|
||||
</ha-icon-button>
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._testCondition}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
<ha-md-menu-item .clickAction=${this._testCondition}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`<ha-md-menu-item
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${this._uiSupported(this.condition.condition) ||
|
||||
!!this._warnings}
|
||||
@@ -248,45 +270,44 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.condition.enabled === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.condition.enabled === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.condition.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.condition.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -341,11 +362,13 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
)}
|
||||
.collapsed=${this._collapsed}
|
||||
.selected=${this._selected}
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
.highlight=${this.highlight}
|
||||
.buildingBlock=${CONDITION_BUILDING_BLOCKS.includes(
|
||||
this.condition.condition
|
||||
)}
|
||||
.sortSelected=${this.sortSelected}
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
>${this._renderRow()}</ha-automation-row
|
||||
>`
|
||||
: html`
|
||||
@@ -360,12 +383,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
error: this._testingResult === false,
|
||||
})}"
|
||||
>
|
||||
${this._testingResult
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.testing_pass"
|
||||
)
|
||||
${this._testingResult === undefined
|
||||
? nothing
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.testing_error"
|
||||
`ui.panel.config.automation.editor.conditions.testing_${
|
||||
this._testingResult ? "pass" : "error"
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -387,6 +410,14 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(changedProperties);
|
||||
|
||||
if (this.root) {
|
||||
this._collapsed = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected willUpdate(changedProperties) {
|
||||
// on yaml toggle --> clear warnings
|
||||
if (changedProperties.has("yamlMode")) {
|
||||
@@ -454,6 +485,15 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._testingResult = undefined;
|
||||
this._testing = true;
|
||||
const condition = this.condition;
|
||||
requestAnimationFrame(() => {
|
||||
// @ts-ignore is supported in all browsers expect firefox
|
||||
if (this.scrollIntoViewIfNeeded) {
|
||||
// @ts-ignore is supported in all browsers expect firefox
|
||||
this.scrollIntoViewIfNeeded();
|
||||
return;
|
||||
}
|
||||
this.scrollIntoView();
|
||||
});
|
||||
|
||||
try {
|
||||
const validateResult = await validateConfig(this.hass, {
|
||||
@@ -544,6 +584,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
private _copyCondition = () => {
|
||||
this._setClipboard();
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.copied_to_clipboard"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
private _cutCondition = () => {
|
||||
@@ -552,6 +598,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
if (this._selected) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.cut_to_clipboard"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
private _moveUp = () => {
|
||||
@@ -575,11 +627,32 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
};
|
||||
|
||||
public expand() {
|
||||
if (this.optionsInSidebar) {
|
||||
this._collapsed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
||||
});
|
||||
}
|
||||
|
||||
public collapse() {
|
||||
this._collapsed = true;
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this.expand();
|
||||
|
||||
this.conditionEditor?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this.collapse();
|
||||
|
||||
this.conditionEditor?.collapseAll();
|
||||
}
|
||||
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this._yamlMode) {
|
||||
@@ -591,8 +664,7 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selected) {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
return;
|
||||
}
|
||||
this.openSidebar();
|
||||
@@ -604,30 +676,40 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
save: (value) => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
},
|
||||
close: () => {
|
||||
close: (focus?: boolean) => {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
rename: () => {
|
||||
this._renameCondition();
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
duplicate: this._duplicateCondition,
|
||||
copy: this._copyCondition,
|
||||
cut: this._cutCondition,
|
||||
test: this._testCondition,
|
||||
config: sidebarCondition,
|
||||
uiSupported: this._uiSupported(sidebarCondition.condition),
|
||||
yamlMode: this._yamlMode,
|
||||
} satisfies ConditionSidebarConfig);
|
||||
this._selected = true;
|
||||
this._collapsed = false;
|
||||
|
||||
if (this.narrow) {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 180); // duration of transition of added padding for bottom sheet
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,6 +722,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._collapsed = !this._collapsed;
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
@@ -22,6 +21,7 @@ import {
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import { automationRowsStyles } from "../styles";
|
||||
import "./ha-automation-condition-row";
|
||||
import type HaAutomationConditionRow from "./ha-automation-condition-row";
|
||||
|
||||
@@ -42,7 +42,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
@@ -53,25 +53,15 @@ export default class HaAutomationCondition extends LitElement {
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
|
||||
@queryAll("ha-automation-condition-row")
|
||||
private _conditionRowElements?: HaAutomationConditionRow[];
|
||||
|
||||
private _focusLastConditionOnChange = false;
|
||||
|
||||
private _focusConditionIndexOnChange?: number;
|
||||
|
||||
private _conditionKeys = new WeakMap<Condition, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
if (!changedProperties.has("conditions")) {
|
||||
return;
|
||||
@@ -96,16 +86,25 @@ export default class HaAutomationCondition extends LitElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: updatedConditions,
|
||||
});
|
||||
} else if (this._focusLastConditionOnChange) {
|
||||
this._focusLastConditionOnChange = false;
|
||||
} else if (
|
||||
this._focusLastConditionOnChange ||
|
||||
this._focusConditionIndexOnChange !== undefined
|
||||
) {
|
||||
const mode = this._focusLastConditionOnChange ? "new" : "moved";
|
||||
|
||||
const row = this.shadowRoot!.querySelector<HaAutomationConditionRow>(
|
||||
"ha-automation-condition-row:last-of-type"
|
||||
`ha-automation-condition-row:${mode === "new" ? "last-of-type" : `nth-of-type(${this._focusConditionIndexOnChange! + 1})`}`
|
||||
)!;
|
||||
|
||||
this._focusLastConditionOnChange = false;
|
||||
this._focusConditionIndexOnChange = undefined;
|
||||
|
||||
row.updateComplete.then(() => {
|
||||
// on new condition open the settings in the sidebar, except for building blocks
|
||||
if (
|
||||
this.optionsInSidebar &&
|
||||
!CONDITION_BUILDING_BLOCKS.includes(row.condition.condition)
|
||||
(!CONDITION_BUILDING_BLOCKS.includes(row.condition.condition) ||
|
||||
mode === "moved")
|
||||
) {
|
||||
row.openSidebar();
|
||||
if (this.narrow) {
|
||||
@@ -114,20 +113,28 @@ export default class HaAutomationCondition extends LitElement {
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
} else if (!this.optionsInSidebar) {
|
||||
}
|
||||
|
||||
if (mode === "new") {
|
||||
row.expand();
|
||||
}
|
||||
row.focus();
|
||||
|
||||
if (!this.optionsInSidebar) {
|
||||
row.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
const rows = this.shadowRoot!.querySelectorAll<HaAutomationConditionRow>(
|
||||
"ha-automation-condition-row"
|
||||
)!;
|
||||
rows.forEach((row) => {
|
||||
row.expand();
|
||||
this._conditionRowElements?.forEach((row) => {
|
||||
row.expandAll();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._conditionRowElements?.forEach((row) => {
|
||||
row.collapseAll();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -139,19 +146,20 @@ export default class HaAutomationCondition extends LitElement {
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-condition-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="conditions"
|
||||
invert-swap
|
||||
@item-moved=${this._conditionMoved}
|
||||
@item-added=${this._conditionAdded}
|
||||
@item-removed=${this._conditionRemoved}
|
||||
>
|
||||
<div class="conditions">
|
||||
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
|
||||
${repeat(
|
||||
this.conditions.filter((c) => typeof c === "object"),
|
||||
(condition) => this._getKey(condition),
|
||||
(cond, idx) => html`
|
||||
<ha-automation-condition-row
|
||||
.root=${this.root}
|
||||
.sortableData=${cond}
|
||||
.index=${idx}
|
||||
.first=${idx === 0}
|
||||
@@ -165,12 +173,22 @@ export default class HaAutomationCondition extends LitElement {
|
||||
@move-up=${this._moveUp}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.hass=${this.hass}
|
||||
?highlight=${this.highlightedConditions?.includes(cond)}
|
||||
.highlight=${this.highlightedConditions?.includes(cond)}
|
||||
.optionsInSidebar=${this.optionsInSidebar}
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<div
|
||||
tabindex="0"
|
||||
class="handle ${this._rowSortSelected === idx
|
||||
? "active"
|
||||
: ""}"
|
||||
slot="icons"
|
||||
@keydown=${this._handleDragKeydown}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
@@ -209,7 +227,7 @@ export default class HaAutomationCondition extends LitElement {
|
||||
|
||||
private _addConditionDialog() {
|
||||
if (this.narrow) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
}
|
||||
showAddAutomationElementDialog(this, {
|
||||
type: "condition",
|
||||
@@ -259,15 +277,27 @@ export default class HaAutomationCondition extends LitElement {
|
||||
private _moveUp(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationConditionRow).first) {
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationConditionRow).last) {
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _move(oldIndex: number, newIndex: number) {
|
||||
@@ -287,6 +317,8 @@ export default class HaAutomationCondition extends LitElement {
|
||||
private async _conditionAdded(ev: CustomEvent): Promise<void> {
|
||||
ev.stopPropagation();
|
||||
const { index, data } = ev.detail;
|
||||
const item = ev.detail.item as HaAutomationConditionRow;
|
||||
const selected = item.selected;
|
||||
let conditions = [
|
||||
...this.conditions.slice(0, index),
|
||||
data,
|
||||
@@ -294,6 +326,9 @@ export default class HaAutomationCondition extends LitElement {
|
||||
];
|
||||
// Add condition locally to avoid UI jump
|
||||
this.conditions = conditions;
|
||||
if (selected) {
|
||||
this._focusConditionIndexOnChange = conditions.length === 1 ? 0 : index;
|
||||
}
|
||||
await nextRender();
|
||||
if (this.conditions !== conditions) {
|
||||
// Ensure condition is added even after update
|
||||
@@ -302,6 +337,9 @@ export default class HaAutomationCondition extends LitElement {
|
||||
data,
|
||||
...this.conditions.slice(index),
|
||||
];
|
||||
if (selected) {
|
||||
this._focusConditionIndexOnChange = conditions.length === 1 ? 0 : index;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
}
|
||||
@@ -316,7 +354,6 @@ export default class HaAutomationCondition extends LitElement {
|
||||
// Ensure condition is removed even after update
|
||||
const conditions = this.conditions.filter((c) => c !== condition);
|
||||
fireEvent(this, "value-changed", { value: conditions });
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
|
||||
private _conditionChanged(ev: CustomEvent) {
|
||||
@@ -348,44 +385,21 @@ export default class HaAutomationCondition extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.conditions {
|
||||
padding: 16px 0 16px 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
private _handleDragKeydown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.stopPropagation();
|
||||
this._rowSortSelected =
|
||||
this._rowSortSelected === undefined
|
||||
? (ev.target as any).index
|
||||
: undefined;
|
||||
}
|
||||
:host([root]) .conditions {
|
||||
padding-right: 8px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-condition-row {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _stopSortSelection() {
|
||||
this._rowSortSelected = undefined;
|
||||
}
|
||||
|
||||
static styles = automationRowsStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { LogicalCondition } from "../../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../ha-automation-condition";
|
||||
import type HaAutomationCondition from "../ha-automation-condition";
|
||||
import type { ConditionElement } from "../ha-automation-condition-row";
|
||||
|
||||
@customElement("ha-automation-condition-logical")
|
||||
@@ -22,6 +23,9 @@ export abstract class HaLogicalCondition
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
@query("ha-automation-condition")
|
||||
private _conditionElement?: HaAutomationCondition;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-automation-condition
|
||||
@@ -35,6 +39,14 @@ export abstract class HaLogicalCondition
|
||||
`;
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._conditionElement?.expandAll?.();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._conditionElement?.collapseAll?.();
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiCog,
|
||||
mdiContentDuplicate,
|
||||
mdiContentSave,
|
||||
mdiDebugStepOver,
|
||||
mdiDelete,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
mdiPlay,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiRobotConfused,
|
||||
mdiStopCircleOutline,
|
||||
@@ -25,7 +25,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { promiseTimeout } from "../../../common/util/promise-timeout";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
@@ -336,7 +335,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentDuplicate}
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
@@ -407,69 +406,9 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
class=${this._mode === "yaml" ? "yaml-mode" : ""}
|
||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||
>
|
||||
<div class="error-wrapper">
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${stateObj?.state === UNAVAILABLE
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.unavailable"
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
${this._errors || this._validationErrors}
|
||||
${stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiRobotConfused}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
</ha-alert>`
|
||||
: ""}
|
||||
${this._blueprintConfig
|
||||
? html`<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.confirm_take_control"
|
||||
)}
|
||||
<div slot="action" style="display: flex;">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._takeControlSave}
|
||||
>${this.hass.localize("ui.common.yes")}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._revertBlueprint}
|
||||
>${this.hass.localize("ui.common.no")}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-alert>`
|
||||
: this._readOnly
|
||||
? html`<ha-alert alert-type="warning" dismissable
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.read_only"
|
||||
)}
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
variant="warning"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.migrate"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._mode === "gui"
|
||||
? html`
|
||||
<div
|
||||
class=${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
${useBlueprint
|
||||
? html`
|
||||
<blueprint-automation-editor
|
||||
@@ -478,7 +417,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
.disabled=${Boolean(this._readOnly)}
|
||||
.disabled=${this._readOnly}
|
||||
.saving=${this._saving}
|
||||
.dirty=${this._dirty}
|
||||
@value-changed=${this._valueChanged}
|
||||
@@ -492,13 +431,94 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
.isWide=${this.isWide}
|
||||
.stateObj=${stateObj}
|
||||
.config=${this._config}
|
||||
.disabled=${Boolean(this._readOnly)}
|
||||
.disabled=${this._readOnly}
|
||||
.dirty=${this._dirty}
|
||||
.saving=${this._saving}
|
||||
@value-changed=${this._valueChanged}
|
||||
@save-automation=${this._handleSaveAutomation}
|
||||
@editor-save=${this._handleSaveAutomation}
|
||||
></manual-automation-editor>
|
||||
>
|
||||
<div class="alert-wrapper" slot="alerts">
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${stateObj?.state === UNAVAILABLE
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.unavailable"
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
${this._errors || this._validationErrors}
|
||||
${stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiRobotConfused}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${this._blueprintConfig
|
||||
? html`<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.confirm_take_control"
|
||||
)}
|
||||
<div slot="action" style="display: flex;">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._takeControlSave}
|
||||
>${this.hass.localize(
|
||||
"ui.common.yes"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._revertBlueprint}
|
||||
>${this.hass.localize(
|
||||
"ui.common.no"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-alert>`
|
||||
: this._readOnly
|
||||
? html`<ha-alert
|
||||
alert-type="warning"
|
||||
dismissable
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.read_only"
|
||||
)}
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
variant="warning"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.migrate"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${stateObj?.state === "off"
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.disabled"
|
||||
)}
|
||||
<ha-button
|
||||
size="small"
|
||||
slot="action"
|
||||
@click=${this._toggle}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
</manual-automation-editor>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
@@ -521,7 +541,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
copy-clipboard
|
||||
.hass=${this.hass}
|
||||
@@ -573,6 +593,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
this.hass
|
||||
) {
|
||||
const initData = getAutomationEditorInitData();
|
||||
this._dirty = !!initData;
|
||||
let baseConfig: Partial<AutomationConfig> = { description: "" };
|
||||
if (!initData || !("use_blueprint" in initData)) {
|
||||
baseConfig = {
|
||||
@@ -1092,6 +1113,10 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
protected supportedShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
s: () => this._handleSaveAutomation(),
|
||||
c: () => this._copySelectedRow(),
|
||||
x: () => this._cutSelectedRow(),
|
||||
Delete: () => this._deleteSelectedRow(),
|
||||
Backspace: () => this._deleteSelectedRow(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1103,6 +1128,28 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
return this._confirmUnsavedChanged();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
private _collapseAll() {
|
||||
this._manualEditor?.collapseAll();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
private _expandAll() {
|
||||
this._manualEditor?.expandAll();
|
||||
}
|
||||
|
||||
private _copySelectedRow() {
|
||||
this._manualEditor?.copySelectedRow();
|
||||
}
|
||||
|
||||
private _cutSelectedRow() {
|
||||
this._manualEditor?.cutSelectedRow();
|
||||
}
|
||||
|
||||
private _deleteSelectedRow() {
|
||||
this._manualEditor?.deleteSelectedRow();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1127,22 +1174,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
display: block;
|
||||
}
|
||||
|
||||
:not(.yaml-mode) > .error-wrapper {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
z-index: 3;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:not(.yaml-mode) > .error-wrapper ha-alert {
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
|
||||
manual-automation-editor {
|
||||
max-width: 1540px;
|
||||
padding: 0 12px;
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-bottom-sheet";
|
||||
import type { HaBottomSheet } from "../../../components/ha-bottom-sheet";
|
||||
import {
|
||||
@@ -34,6 +33,8 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@query("ha-bottom-sheet") private _bottomSheetElement?: HaBottomSheet;
|
||||
@@ -52,8 +53,9 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
@close-sidebar=${this.triggerCloseSidebar}
|
||||
></ha-automation-sidebar-trigger>
|
||||
`;
|
||||
}
|
||||
@@ -67,8 +69,9 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
@close-sidebar=${this.triggerCloseSidebar}
|
||||
></ha-automation-sidebar-condition>
|
||||
`;
|
||||
}
|
||||
@@ -82,8 +85,9 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
@close-sidebar=${this.triggerCloseSidebar}
|
||||
></ha-automation-sidebar-action>
|
||||
`;
|
||||
}
|
||||
@@ -96,7 +100,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.isWide=${this.isWide}
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
@close-sidebar=${this.triggerCloseSidebar}
|
||||
></ha-automation-sidebar-option>
|
||||
`;
|
||||
}
|
||||
@@ -110,8 +114,9 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
@close-sidebar=${this.triggerCloseSidebar}
|
||||
></ha-automation-sidebar-script-field-selector>
|
||||
`;
|
||||
}
|
||||
@@ -125,8 +130,9 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this._yamlMode}
|
||||
.sidebarKey=${this.sidebarKey}
|
||||
@toggle-yaml-mode=${this._toggleYamlMode}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
@close-sidebar=${this.triggerCloseSidebar}
|
||||
></ha-automation-sidebar-script-field>
|
||||
`;
|
||||
}
|
||||
@@ -175,15 +181,15 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
return "option";
|
||||
}
|
||||
|
||||
if ((this.config as ActionSidebarConfig)?.config) {
|
||||
if ((this.config as ActionSidebarConfig)?.config.action) {
|
||||
return "action";
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _handleCloseSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
public triggerCloseSidebar(ev?: CustomEvent) {
|
||||
ev?.stopPropagation();
|
||||
if (this.narrow) {
|
||||
this._bottomSheetElement?.closeSheet();
|
||||
return;
|
||||
@@ -193,21 +199,17 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
this.config?.close();
|
||||
this.config?.close(true);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
this._yamlMode = this.config!.toggleYamlMode();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
yamlMode: this._yamlMode,
|
||||
},
|
||||
});
|
||||
(this.config as ActionSidebarConfig)?.toggleYamlMode();
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
z-index: 6;
|
||||
outline: none;
|
||||
height: 100%;
|
||||
--ha-card-border-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
@@ -234,5 +236,8 @@ declare global {
|
||||
|
||||
interface HASSDomEvents {
|
||||
"toggle-yaml-mode": undefined;
|
||||
"yaml-changed": {
|
||||
value: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,15 @@
|
||||
import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { load } from "js-yaml";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
queryAll,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import {
|
||||
any,
|
||||
@@ -18,11 +24,17 @@ import {
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { canOverrideAlphanumericInput } from "../../../common/dom/can-override-input";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { constructUrlCurrentPath } from "../../../common/url/construct-url";
|
||||
import {
|
||||
extractSearchParam,
|
||||
removeSearchParam,
|
||||
} from "../../../common/url/search-params";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-markdown";
|
||||
import type {
|
||||
ActionSidebarConfig,
|
||||
AutomationConfig,
|
||||
Condition,
|
||||
ManualAutomationConfig,
|
||||
@@ -39,10 +51,13 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "./action/ha-automation-action";
|
||||
import type HaAutomationAction from "./action/ha-automation-action";
|
||||
import "./condition/ha-automation-condition";
|
||||
import type HaAutomationCondition from "./condition/ha-automation-condition";
|
||||
import "./ha-automation-sidebar";
|
||||
import type HaAutomationSidebar from "./ha-automation-sidebar";
|
||||
import { showPasteReplaceDialog } from "./paste-replace-dialog/show-dialog-paste-replace";
|
||||
import { saveFabStyles } from "./styles";
|
||||
import { manualEditorStyles, saveFabStyles } from "./styles";
|
||||
import "./trigger/ha-automation-trigger";
|
||||
|
||||
const baseConfigStruct = object({
|
||||
@@ -84,8 +99,14 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
@query(".content")
|
||||
private _contentElement?: HTMLDivElement;
|
||||
@state() private _sidebarKey?: string;
|
||||
|
||||
@query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar;
|
||||
|
||||
@queryAll("ha-automation-action, ha-automation-condition")
|
||||
private _collapsableElements?: NodeListOf<
|
||||
HaAutomationAction | HaAutomationCondition
|
||||
>;
|
||||
|
||||
private _previousConfig?: ManualAutomationConfig;
|
||||
|
||||
@@ -101,20 +122,6 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
private _renderContent() {
|
||||
return html`
|
||||
${this.stateObj?.state === "off"
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.disabled"
|
||||
)}
|
||||
<ha-button size="small" slot="action" @click=${this._enable}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.enable"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
${this.config.description
|
||||
? html`<ha-markdown
|
||||
class="description"
|
||||
@@ -159,6 +166,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.disabled=${this.disabled || this.saving}
|
||||
.narrow=${this.narrow}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@request-close-sidebar=${this._triggerCloseSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
root
|
||||
sidebar
|
||||
@@ -205,6 +213,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.disabled=${this.disabled || this.saving}
|
||||
.narrow=${this.narrow}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@request-close-sidebar=${this._triggerCloseSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
root
|
||||
sidebar
|
||||
@@ -246,6 +255,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.highlightedActions=${this._pastedConfig?.actions || []}
|
||||
@value-changed=${this._actionChanged}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@request-close-sidebar=${this._triggerCloseSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@@ -258,41 +268,75 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="split-view">
|
||||
<div
|
||||
class=${classMap({
|
||||
"has-sidebar": this._sidebarConfig && !this.narrow,
|
||||
})}
|
||||
>
|
||||
<div class="content-wrapper">
|
||||
<div class="content">${this._renderContent()}</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveAutomation}
|
||||
<div
|
||||
class="content ${this._sidebarConfig && this.narrow
|
||||
? "has-bottom-sheet"
|
||||
: ""}"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
<slot name="alerts"></slot>
|
||||
${this._renderContent()}
|
||||
</div>
|
||||
<div class="fab-positioner">
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveAutomation}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-positioner">
|
||||
<ha-automation-sidebar
|
||||
tabindex="-1"
|
||||
class=${classMap({ hidden: !this._sidebarConfig })}
|
||||
.isWide=${this.isWide}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.config=${this._sidebarConfig}
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
.disabled=${this.disabled}
|
||||
.sidebarKey=${this._sidebarKey}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
<ha-automation-sidebar
|
||||
class=${classMap({
|
||||
sidebar: true,
|
||||
hidden: !this._sidebarConfig,
|
||||
overlay: !this.isWide && !this.narrow,
|
||||
})}
|
||||
.isWide=${this.isWide}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.config=${this._sidebarConfig}
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _openSidebar(ev: CustomEvent<SidebarConfig>) {
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
const expanded = extractSearchParam("expanded");
|
||||
if (expanded === "1") {
|
||||
this._clearParam("expanded");
|
||||
this.expandAll();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearParam(param: string) {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
constructUrlCurrentPath(removeSearchParam(param))
|
||||
);
|
||||
}
|
||||
|
||||
private async _openSidebar(ev: CustomEvent<SidebarConfig>) {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
this._sidebarKey = JSON.stringify(this._sidebarConfig);
|
||||
|
||||
await this._sidebarElement?.updateComplete;
|
||||
this._sidebarElement?.focus();
|
||||
}
|
||||
|
||||
private _sidebarConfigChanged(ev: CustomEvent<{ value: SidebarConfig }>) {
|
||||
@@ -307,18 +351,18 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
private _triggerCloseSidebar() {
|
||||
if (this._sidebarConfig) {
|
||||
const closeRow = this._sidebarConfig?.close;
|
||||
this._sidebarConfig = undefined;
|
||||
closeRow?.();
|
||||
if (this._sidebarElement) {
|
||||
this._sidebarElement.triggerCloseSidebar();
|
||||
return;
|
||||
}
|
||||
this._sidebarConfig?.close();
|
||||
}
|
||||
}
|
||||
|
||||
private _handleCloseSidebar() {
|
||||
this._sidebarConfig = undefined;
|
||||
// fix content shift when bottom rows are scrolled into view
|
||||
this._contentElement?.scrollIntoView();
|
||||
}
|
||||
|
||||
private _triggerChanged(ev: CustomEvent): void {
|
||||
@@ -348,17 +392,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _enable(): Promise<void> {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
await this.hass.callService("automation", "turn_on", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
}
|
||||
|
||||
private _saveAutomation() {
|
||||
this._closeSidebar();
|
||||
this._triggerCloseSidebar();
|
||||
fireEvent(this, "save-automation");
|
||||
}
|
||||
|
||||
@@ -463,7 +498,12 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
if (normalized) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.dirty) {
|
||||
if (
|
||||
this.dirty ||
|
||||
ensureArray(this.config.triggers)?.length ||
|
||||
ensureArray(this.config.conditions)?.length ||
|
||||
ensureArray(this.config.actions)?.length
|
||||
) {
|
||||
const result = await new Promise<boolean>((resolve) => {
|
||||
showPasteReplaceDialog(this, {
|
||||
domain: "automation",
|
||||
@@ -572,79 +612,41 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._collapsableElements?.forEach((element) => {
|
||||
element.expandAll();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._collapsableElements?.forEach((element) => {
|
||||
element.collapseAll();
|
||||
});
|
||||
}
|
||||
|
||||
public copySelectedRow() {
|
||||
if ((this._sidebarConfig as ActionSidebarConfig)?.copy) {
|
||||
(this._sidebarConfig as ActionSidebarConfig).copy();
|
||||
}
|
||||
}
|
||||
|
||||
public cutSelectedRow() {
|
||||
if ((this._sidebarConfig as ActionSidebarConfig)?.cut) {
|
||||
(this._sidebarConfig as ActionSidebarConfig).cut();
|
||||
}
|
||||
}
|
||||
|
||||
public deleteSelectedRow() {
|
||||
if ((this._sidebarConfig as ActionSidebarConfig)?.delete) {
|
||||
(this._sidebarConfig as ActionSidebarConfig).delete();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
saveFabStyles,
|
||||
manualEditorStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.split-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
flex: 6;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px 16px 64px 0;
|
||||
height: calc(100vh - 153px);
|
||||
height: calc(100dvh - 153px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 12px 0;
|
||||
flex: 4;
|
||||
height: calc(100vh - 81px);
|
||||
height: calc(100dvh - 81px);
|
||||
width: 40%;
|
||||
}
|
||||
.sidebar.hidden {
|
||||
border-color: transparent;
|
||||
border-width: 0;
|
||||
overflow: hidden;
|
||||
flex: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebar.overlay {
|
||||
position: fixed;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
height: calc(100% - 70px);
|
||||
padding: 0;
|
||||
z-index: 5;
|
||||
box-shadow: -8px 0 16px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
.split-view {
|
||||
gap: 0;
|
||||
margin-right: -8px;
|
||||
}
|
||||
.sidebar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar.overlay.hidden {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -660,10 +662,7 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
.header .name {
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
flex: 1;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.header a {
|
||||
color: var(--secondary-text-color);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.header .small {
|
||||
font-size: small;
|
||||
@@ -671,9 +670,8 @@ export class HaManualAutomationEditor extends LitElement {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 16px;
|
||||
.description {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -687,6 +685,7 @@ declare global {
|
||||
|
||||
interface HASSDomEvents {
|
||||
"open-sidebar": SidebarConfig;
|
||||
"request-close-sidebar": undefined;
|
||||
"close-sidebar": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -2,14 +2,14 @@ import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiArrowDown,
|
||||
mdiArrowUp,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
@@ -17,6 +17,7 @@ import { preventDefaultStopPropagation } from "../../../../common/dom/prevent_de
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import { capitalizeFirstLetter } from "../../../../common/string/capitalize-first-letter";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
@@ -37,14 +38,18 @@ import {
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../action/ha-automation-action";
|
||||
import type HaAutomationAction from "../action/ha-automation-action";
|
||||
import "../condition/ha-automation-condition";
|
||||
import { editorStyles, rowStyles } from "../styles";
|
||||
import type HaAutomationCondition from "../condition/ha-automation-condition";
|
||||
import { editorStyles, indentStyle, rowStyles } from "../styles";
|
||||
|
||||
@customElement("ha-automation-option-row")
|
||||
export default class HaAutomationOptionRow extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public option!: Option;
|
||||
@property({ attribute: false }) public option?: Option;
|
||||
|
||||
@property({ attribute: false }) public defaultActions?: Action[];
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@@ -59,16 +64,32 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sidebar" })
|
||||
public optionsInSidebar = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
@state() private _expanded = false;
|
||||
|
||||
@state() private _selected = false;
|
||||
|
||||
@state() private _collapsed = false;
|
||||
@state() private _collapsed = true;
|
||||
|
||||
@state()
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
@query("ha-automation-condition")
|
||||
private _conditionElement?: HaAutomationCondition;
|
||||
|
||||
@query("ha-automation-action")
|
||||
private _actionElement?: HaAutomationAction;
|
||||
|
||||
@query("ha-automation-row")
|
||||
private _automationRowElement?: HaAutomationRow;
|
||||
|
||||
get selected() {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
private _expandedChanged(ev) {
|
||||
if (ev.currentTarget.id !== "option") {
|
||||
return;
|
||||
@@ -77,7 +98,7 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _getDescription() {
|
||||
const conditions = ensureArray<Condition | string>(this.option.conditions);
|
||||
const conditions = ensureArray<Condition | string>(this.option!.conditions);
|
||||
if (!conditions || conditions.length === 0) {
|
||||
return this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.no_conditions"
|
||||
@@ -101,30 +122,36 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
private _renderRow() {
|
||||
return html`
|
||||
<h3 slot="header">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option",
|
||||
{ number: this.index + 1 }
|
||||
)}:
|
||||
${this.option.alias || (this._expanded ? "" : this._getDescription())}
|
||||
${this.option
|
||||
? `${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option",
|
||||
{ number: this.index + 1 }
|
||||
)}: ${this.option.alias || (this._expanded ? "" : this._getDescription())}`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.default"
|
||||
)}
|
||||
</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
${this.option && !this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
@click=${this._renameOption}
|
||||
.disabled=${this.disabled}
|
||||
@@ -132,56 +159,59 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._duplicateOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentDuplicate}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
@click=${this._duplicateOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiArrowUp}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
@click=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_up"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiArrowDown}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._removeOption}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
<ha-md-menu-item
|
||||
@click=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._removeOption}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar ? this._renderContent() : nothing}
|
||||
`;
|
||||
}
|
||||
@@ -190,31 +220,42 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
"card-content": true,
|
||||
card: !this.optionsInSidebar,
|
||||
indent: this.optionsInSidebar,
|
||||
selected: this._selected,
|
||||
hidden: this._collapsed,
|
||||
hidden: this.optionsInSidebar && this._collapsed,
|
||||
})}
|
||||
>
|
||||
<h4>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.conditions"
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-condition
|
||||
.conditions=${ensureArray<string | Condition>(this.option.conditions)}
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.optionsInSidebar=${this.optionsInSidebar}
|
||||
></ha-automation-condition>
|
||||
<h4>
|
||||
${this.option
|
||||
? html`
|
||||
<h4 class="top">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.conditions"
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-condition
|
||||
.conditions=${ensureArray<string | Condition>(
|
||||
this.option.conditions
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@value-changed=${this._conditionChanged}
|
||||
.optionsInSidebar=${this.optionsInSidebar}
|
||||
></ha-automation-condition>
|
||||
`
|
||||
: nothing}
|
||||
<h4 class=${this.option ? "" : "top"}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.sequence"
|
||||
)}:
|
||||
</h4>
|
||||
<ha-automation-action
|
||||
.actions=${ensureArray(this.option.sequence) || []}
|
||||
.actions=${(this.option
|
||||
? ensureArray(this.option.sequence) || []
|
||||
: this.defaultActions
|
||||
? ensureArray(this.defaultActions) || []
|
||||
: []) as Action[]}
|
||||
.disabled=${this.disabled}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@@ -225,7 +266,7 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.option) return nothing;
|
||||
if (!this.option && !this.defaultActions) return nothing;
|
||||
|
||||
return html`
|
||||
<ha-card outlined class=${this._selected ? "selected" : ""}>
|
||||
@@ -234,8 +275,10 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
left-chevron
|
||||
.collapsed=${this._collapsed}
|
||||
.selected=${this._selected}
|
||||
.sortSelected=${this.sortSelected}
|
||||
@click=${this._toggleSidebar}
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
@delete-row=${this._removeOption}
|
||||
>${this._renderRow()}</ha-automation-row
|
||||
>`
|
||||
: html`
|
||||
@@ -253,9 +296,9 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _duplicateOption() {
|
||||
private _duplicateOption = () => {
|
||||
fireEvent(this, "duplicate");
|
||||
}
|
||||
};
|
||||
|
||||
private _moveUp() {
|
||||
fireEvent(this, "move-up");
|
||||
@@ -297,7 +340,7 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
),
|
||||
inputType: "string",
|
||||
placeholder: capitalizeFirstLetter(this._getDescription()),
|
||||
defaultValue: this.option.alias,
|
||||
defaultValue: this.option!.alias,
|
||||
confirmText: this.hass.localize("ui.common.submit"),
|
||||
});
|
||||
if (alias !== null) {
|
||||
@@ -323,6 +366,9 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
}
|
||||
|
||||
private _actionChanged(ev: CustomEvent) {
|
||||
if (this.defaultActions) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const actions = ev.detail.value as Action[];
|
||||
const value = { ...this.option, sequence: actions };
|
||||
@@ -335,8 +381,7 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selected) {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
return;
|
||||
}
|
||||
this.openSidebar();
|
||||
@@ -344,44 +389,88 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
|
||||
public openSidebar(): void {
|
||||
fireEvent(this, "open-sidebar", {
|
||||
close: () => {
|
||||
close: (focus?: boolean) => {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
rename: () => {
|
||||
this._renameOption();
|
||||
},
|
||||
toggleYamlMode: () => false, // no yaml mode for options
|
||||
delete: this._removeOption,
|
||||
duplicate: this._duplicateOption,
|
||||
defaultOption: !!this.defaultActions,
|
||||
} satisfies OptionSidebarConfig);
|
||||
this._selected = true;
|
||||
this._collapsed = false;
|
||||
|
||||
if (this.narrow) {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 180); // duration of transition of added padding for bottom sheet
|
||||
}
|
||||
}
|
||||
|
||||
public expand() {
|
||||
if (this.optionsInSidebar) {
|
||||
this._collapsed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateComplete.then(() => {
|
||||
this.shadowRoot!.querySelector("ha-expansion-panel")!.expanded = true;
|
||||
});
|
||||
}
|
||||
|
||||
public collapse() {
|
||||
this._collapsed = true;
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this.expand();
|
||||
|
||||
this._conditionElement?.expandAll();
|
||||
this._actionElement?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this.collapse();
|
||||
|
||||
this._conditionElement?.collapseAll();
|
||||
this._actionElement?.collapseAll();
|
||||
}
|
||||
|
||||
private _toggleCollapse() {
|
||||
this._collapsed = !this._collapsed;
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
editorStyles,
|
||||
indentStyle,
|
||||
css`
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
h4 {
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
h4 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
h4.top {
|
||||
margin-top: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -2,11 +2,10 @@ import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, queryAll, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-sortable";
|
||||
@@ -14,6 +13,7 @@ import "../../../../components/ha-svg-icon";
|
||||
import type { AutomationClipboard } from "../../../../data/automation";
|
||||
import type { Option } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { automationRowsStyles } from "../styles";
|
||||
import "./ha-automation-option-row";
|
||||
import type HaAutomationOptionRow from "./ha-automation-option-row";
|
||||
|
||||
@@ -30,7 +30,10 @@ export default class HaAutomationOption extends LitElement {
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public optionsInSidebar =
|
||||
false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
@property({ type: Boolean, attribute: "show-default" })
|
||||
public showDefaultActions = false;
|
||||
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
@@ -41,38 +44,28 @@ export default class HaAutomationOption extends LitElement {
|
||||
})
|
||||
public _clipboard?: AutomationClipboard;
|
||||
|
||||
@queryAll("ha-automation-option-row")
|
||||
private _optionRowElements?: HaAutomationOptionRow[];
|
||||
|
||||
private _focusLastOptionOnChange = false;
|
||||
|
||||
private _focusOptionIndexOnChange?: number;
|
||||
|
||||
private _optionsKeys = new WeakMap<Option, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-option-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="options"
|
||||
invert-swap
|
||||
@item-moved=${this._optionMoved}
|
||||
@item-added=${this._optionAdded}
|
||||
@item-removed=${this._optionRemoved}
|
||||
>
|
||||
<div class="options">
|
||||
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
|
||||
${repeat(
|
||||
this.options,
|
||||
(option) => this._getKey(option),
|
||||
@@ -91,10 +84,20 @@ export default class HaAutomationOption extends LitElement {
|
||||
@value-changed=${this._optionChanged}
|
||||
.hass=${this.hass}
|
||||
.optionsInSidebar=${this.optionsInSidebar}
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<div
|
||||
tabindex="0"
|
||||
class="handle ${this._rowSortSelected === idx
|
||||
? "active"
|
||||
: ""}"
|
||||
slot="icons"
|
||||
@keydown=${this._handleDragKeydown}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
@@ -114,6 +117,19 @@ export default class HaAutomationOption extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.type.choose.add_option"
|
||||
)}
|
||||
</ha-button>
|
||||
${!this.showDefaultActions
|
||||
? html`<ha-button
|
||||
appearance="plain"
|
||||
size="small"
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._showDefaultActions}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.add_default"
|
||||
)}
|
||||
</ha-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
</ha-sortable>
|
||||
@@ -123,33 +139,47 @@ export default class HaAutomationOption extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("options") && this._focusLastOptionOnChange) {
|
||||
this._focusLastOptionOnChange = false;
|
||||
if (
|
||||
changedProps.has("options") &&
|
||||
(this._focusLastOptionOnChange ||
|
||||
this._focusOptionIndexOnChange !== undefined)
|
||||
) {
|
||||
const mode = this._focusLastOptionOnChange ? "new" : "moved";
|
||||
|
||||
const row = this.shadowRoot!.querySelector<HaAutomationOptionRow>(
|
||||
"ha-automation-option-row:last-of-type"
|
||||
`ha-automation-option-row:${mode === "new" ? "last-of-type" : `nth-of-type(${this._focusOptionIndexOnChange! + 1})`}`
|
||||
)!;
|
||||
|
||||
this._focusLastOptionOnChange = false;
|
||||
this._focusOptionIndexOnChange = undefined;
|
||||
|
||||
row.updateComplete.then(() => {
|
||||
if (!this.optionsInSidebar) {
|
||||
row.expand();
|
||||
} else if (this.narrow) {
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
row.focus();
|
||||
|
||||
if (mode === "new") {
|
||||
row.expand();
|
||||
}
|
||||
|
||||
if (this.optionsInSidebar) {
|
||||
row.openSidebar();
|
||||
} else {
|
||||
row.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
const rows = this.shadowRoot!.querySelectorAll<HaAutomationOptionRow>(
|
||||
"ha-automation-option-row"
|
||||
)!;
|
||||
rows.forEach((row) => {
|
||||
row.expand();
|
||||
});
|
||||
this._optionRowElements?.forEach((row) => row.expandAll());
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._optionRowElements?.forEach((row) => row.collapseAll());
|
||||
}
|
||||
|
||||
private _addOption = () => {
|
||||
@@ -169,15 +199,27 @@ export default class HaAutomationOption extends LitElement {
|
||||
private _moveUp(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationOptionRow).first) {
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationOptionRow).last) {
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _move(oldIndex: number, newIndex: number) {
|
||||
@@ -197,6 +239,9 @@ export default class HaAutomationOption extends LitElement {
|
||||
private async _optionAdded(ev: CustomEvent): Promise<void> {
|
||||
ev.stopPropagation();
|
||||
const { index, data } = ev.detail;
|
||||
const item = ev.detail.item as HaAutomationOptionRow;
|
||||
const selected = item.selected;
|
||||
|
||||
const options = [
|
||||
...this.options.slice(0, index),
|
||||
data,
|
||||
@@ -204,6 +249,9 @@ export default class HaAutomationOption extends LitElement {
|
||||
];
|
||||
// Add option locally to avoid UI jump
|
||||
this.options = options;
|
||||
if (selected) {
|
||||
this._focusOptionIndexOnChange = options.length === 1 ? 0 : index;
|
||||
}
|
||||
await nextRender();
|
||||
fireEvent(this, "value-changed", { value: this.options });
|
||||
}
|
||||
@@ -247,45 +295,40 @@ export default class HaAutomationOption extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.options {
|
||||
padding: 16px 0 16px 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
private _showDefaultActions = () => {
|
||||
fireEvent(this, "show-default-actions");
|
||||
};
|
||||
|
||||
private _handleDragKeydown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.stopPropagation();
|
||||
this._rowSortSelected =
|
||||
this._rowSortSelected === undefined
|
||||
? (ev.target as any).index
|
||||
: undefined;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-option-row {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _stopSortSelection() {
|
||||
this._rowSortSelected = undefined;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
automationRowsStyles,
|
||||
css`
|
||||
:host([root]) .rows {
|
||||
padding-right: 8px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-automation-option": HaAutomationOption;
|
||||
}
|
||||
|
||||
interface HASSDomEvents {
|
||||
"show-default-actions": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { css, type CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import "../../../../components/ha-button";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../trigger/ha-automation-trigger-row";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { PasteReplaceDialogParams } from "./show-dialog-paste-replace";
|
||||
|
||||
@customElement("ha-dialog-paste-replace")
|
||||
|
@@ -1,12 +1,18 @@
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiDelete,
|
||||
mdiPlay,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
@@ -16,9 +22,11 @@ import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
|
||||
import type { ActionSidebarConfig } from "../../../../data/automation";
|
||||
import type { RepeatAction } from "../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import { getAutomationActionType } from "../action/ha-automation-action-row";
|
||||
import { getRepeatType } from "../action/types/ha-automation-action-repeat";
|
||||
import { sidebarEditorStyles } from "../styles";
|
||||
import "../trigger/ha-automation-trigger-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@@ -36,6 +44,8 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -47,23 +57,25 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
if (this.config) {
|
||||
this.yamlMode = this.config.yamlMode;
|
||||
if (this.yamlMode) {
|
||||
this.editor?.yamlEditor?.setValue(this.config.config);
|
||||
this.editor?.yamlEditor?.setValue(this.config.config.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const actionConfig = this.config.config.action;
|
||||
|
||||
const disabled =
|
||||
this.disabled ||
|
||||
("enabled" in this.config.config && this.config.config.enabled === false);
|
||||
("enabled" in actionConfig && actionConfig.enabled === false);
|
||||
|
||||
const actionType = getAutomationActionType(this.config.config);
|
||||
const actionType = getAutomationActionType(actionConfig);
|
||||
|
||||
const type =
|
||||
actionType !== "repeat"
|
||||
? actionType
|
||||
: `repeat_${getRepeatType((this.config.config as RepeatAction).repeat)}`;
|
||||
: `repeat_${getRepeatType((actionConfig as RepeatAction).repeat)}`;
|
||||
|
||||
const isBuildingBlock = ACTION_BUILDING_BLOCKS.includes(type || "");
|
||||
|
||||
@@ -91,25 +103,116 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.actions.run")}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.copy}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>C</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>X</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
@@ -117,13 +220,16 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
@@ -131,25 +237,51 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.del"
|
||||
)}</span
|
||||
>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: html`<ha-automation-action-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.action=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
sidebar
|
||||
narrow
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-action-editor>`}
|
||||
: keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-action-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.action=${actionConfig}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
sidebar
|
||||
narrow
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-action-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -169,24 +301,25 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
...this.config,
|
||||
config: ev.detail.value,
|
||||
config: {
|
||||
action: ev.detail.value,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
.description {
|
||||
padding-top: 16px;
|
||||
}
|
||||
`;
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { mdiClose, mdiDotsVertical } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
@@ -43,7 +45,22 @@ export default class HaAutomationSidebarCard extends LitElement {
|
||||
|
||||
@state() private _contentScrolled = false;
|
||||
|
||||
@query(".card-content") private _contentElement?: HTMLDivElement;
|
||||
@state() private _contentScrollable = false;
|
||||
|
||||
@query(".card-content") private _contentElement!: HTMLDivElement;
|
||||
|
||||
private _contentSize = new ResizeController(this, {
|
||||
target: null,
|
||||
callback: (entries) => {
|
||||
if (entries[0]?.target) {
|
||||
this._canScrollDown(entries[0].target);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValues): void {
|
||||
this._contentSize.observe(this._contentElement);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
@@ -67,10 +84,13 @@ export default class HaAutomationSidebarCard extends LitElement {
|
||||
<slot slot="subtitle" name="subtitle"></slot>
|
||||
<slot name="overflow-menu" slot="actionItems">
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
@click=${this._openOverflowMenu}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
.positioning=${this.narrow ? "absolute" : "fixed"}
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -91,14 +111,29 @@ export default class HaAutomationSidebarCard extends LitElement {
|
||||
<div class="card-content" @scroll=${this._onScroll}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
${this.narrow
|
||||
? html`
|
||||
<div
|
||||
class="fade ${this._contentScrollable ? "scrollable" : ""}"
|
||||
></div>
|
||||
`
|
||||
: nothing}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@eventOptions({ passive: true })
|
||||
private _onScroll() {
|
||||
const top = this._contentElement?.scrollTop ?? 0;
|
||||
private _onScroll(ev) {
|
||||
const top = ev.target.scrollTop ?? 0;
|
||||
this._contentScrolled = top > 0;
|
||||
|
||||
this._canScrollDown(ev.target);
|
||||
}
|
||||
|
||||
private _canScrollDown(element: HTMLElement) {
|
||||
this._contentScrollable =
|
||||
(element.scrollHeight ?? 0) - (element.clientHeight ?? 0) >
|
||||
(element.scrollTop ?? 0);
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
@@ -122,6 +157,7 @@ export default class HaAutomationSidebarCard extends LitElement {
|
||||
@media all and (max-width: 870px) {
|
||||
ha-card.mobile {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
ha-card.mobile {
|
||||
border-bottom-right-radius: var(--ha-border-radius-square);
|
||||
@@ -135,7 +171,6 @@ export default class HaAutomationSidebarCard extends LitElement {
|
||||
transition: box-shadow 180ms ease-in-out;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
z-index: 6;
|
||||
position: relative;
|
||||
background-color: var(
|
||||
--ha-dialog-surface-background,
|
||||
@@ -144,12 +179,28 @@ export default class HaAutomationSidebarCard extends LitElement {
|
||||
}
|
||||
|
||||
ha-dialog-header.scrolled {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||||
box-shadow: var(--bar-box-shadow);
|
||||
}
|
||||
|
||||
.fade {
|
||||
position: fixed;
|
||||
bottom: -12px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 12px;
|
||||
pointer-events: none;
|
||||
transition: box-shadow 180ms ease-in-out;
|
||||
}
|
||||
|
||||
.fade.scrollable {
|
||||
box-shadow: var(--bar-box-shadow);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.card-content {
|
||||
max-height: calc(100% - 80px);
|
||||
overflow: auto;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 450px) and (min-height: 500px) {
|
||||
|
@@ -1,19 +1,33 @@
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiDelete,
|
||||
mdiFlask,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { ConditionSidebarConfig } from "../../../../data/automation";
|
||||
import {
|
||||
testCondition,
|
||||
type ConditionSidebarConfig,
|
||||
} from "../../../../data/automation";
|
||||
import { CONDITION_BUILDING_BLOCKS } from "../../../../data/condition";
|
||||
import { validateConfig } from "../../../../data/config";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import { showAlertDialog } from "../../../lovelace/custom-card-helpers";
|
||||
import "../condition/ha-automation-condition-editor";
|
||||
import type HaAutomationConditionEditor from "../condition/ha-automation-condition-editor";
|
||||
import { sidebarEditorStyles } from "../styles";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-condition")
|
||||
@@ -30,8 +44,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@state() private _testing = false;
|
||||
|
||||
@state() private _testingResult?: boolean;
|
||||
|
||||
@query(".sidebar-editor")
|
||||
public editor?: HaAutomationConditionEditor;
|
||||
|
||||
@@ -45,6 +65,10 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset testing state when condition changes
|
||||
if (changedProperties.has("sidebarKey")) {
|
||||
this._testing = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -80,25 +104,121 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.copy}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>C</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>X</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
@@ -106,13 +226,16 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.disable}>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
@@ -120,26 +243,128 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.del"
|
||||
)}</span
|
||||
>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: html`<ha-automation-condition-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-condition-editor> `}
|
||||
: keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-condition-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.condition=${this.config.config}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
sidebar
|
||||
></ha-automation-condition-editor>`
|
||||
)}
|
||||
<div class="testing-wrapper">
|
||||
<div
|
||||
class="testing ${classMap({
|
||||
active: this._testing,
|
||||
pass: this._testingResult === true,
|
||||
error: this._testingResult === false,
|
||||
narrow: this.narrow,
|
||||
})}"
|
||||
>
|
||||
${this._testingResult === undefined
|
||||
? nothing
|
||||
: this.hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.testing_${
|
||||
this._testingResult ? "pass" : "error"
|
||||
}`
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _testCondition = async () => {
|
||||
if (this._testing) {
|
||||
return;
|
||||
}
|
||||
this._testingResult = undefined;
|
||||
this._testing = true;
|
||||
const condition = this.config.config;
|
||||
|
||||
try {
|
||||
const validateResult = await validateConfig(this.hass, {
|
||||
conditions: condition,
|
||||
});
|
||||
|
||||
// Abort if condition changed.
|
||||
if (this.config.config !== condition) {
|
||||
this._testing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validateResult.conditions.valid) {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.invalid_condition"
|
||||
),
|
||||
text: validateResult.conditions.error,
|
||||
});
|
||||
this._testing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let result: { result: boolean };
|
||||
try {
|
||||
result = await testCondition(this.hass, condition);
|
||||
} catch (err: any) {
|
||||
if (this.config.config !== condition) {
|
||||
this._testing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test_failed"
|
||||
),
|
||||
text: err.message,
|
||||
});
|
||||
this._testing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this._testingResult = result.result;
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this._testing = false;
|
||||
}, 2500);
|
||||
}
|
||||
};
|
||||
|
||||
private _handleUiModeNotAvailable(ev: CustomEvent) {
|
||||
this._warnings = handleStructError(this.hass, ev.detail).warnings;
|
||||
if (!this.yamlMode) {
|
||||
@@ -162,18 +387,67 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
.description {
|
||||
padding-top: 16px;
|
||||
}
|
||||
`;
|
||||
static styles = [
|
||||
sidebarEditorStyles,
|
||||
css`
|
||||
ha-automation-sidebar-card {
|
||||
position: relative;
|
||||
}
|
||||
.testing-wrapper {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
border-top-right-radius: var(
|
||||
--ha-card-border-radius,
|
||||
var(--ha-border-radius-lg)
|
||||
);
|
||||
border-top-left-radius: var(
|
||||
--ha-card-border-radius,
|
||||
var(--ha-border-radius-lg)
|
||||
);
|
||||
pointer-events: none;
|
||||
height: 100px;
|
||||
}
|
||||
.testing {
|
||||
--testing-color: var(--divider-color, #e0e0e0);
|
||||
text-transform: uppercase;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-bold);
|
||||
background-color: var(--testing-color);
|
||||
color: var(--text-primary-color);
|
||||
max-height: 0px;
|
||||
transition:
|
||||
max-height 0.3s ease-in-out,
|
||||
padding-top 0.3s ease-in-out;
|
||||
text-align: center;
|
||||
}
|
||||
.testing.active.narrow {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.testing.active {
|
||||
max-height: 100%;
|
||||
}
|
||||
.testing.error {
|
||||
--testing-color: var(--accent-color);
|
||||
}
|
||||
.testing.pass {
|
||||
--testing-color: var(--success-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,9 +1,16 @@
|
||||
import { mdiDelete, mdiRenameBox } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiDelete,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import type { OptionSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import { sidebarEditorStyles } from "../styles";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-option")
|
||||
@@ -29,11 +36,11 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
);
|
||||
|
||||
const title = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option_label"
|
||||
`ui.panel.config.automation.editor.actions.type.choose.${this.config.defaultOption ? "default_" : ""}option_label`
|
||||
);
|
||||
|
||||
const description = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.option_description"
|
||||
`ui.panel.config.automation.editor.actions.type.choose.${this.config.defaultOption ? "default_" : ""}option_description`
|
||||
);
|
||||
|
||||
return html`<ha-automation-sidebar-card
|
||||
@@ -43,44 +50,84 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
${this.config.defaultOption
|
||||
? html`<span slot="overflow-menu"></span>`
|
||||
: html`
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
@click=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.del"
|
||||
)}</span
|
||||
>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
`}
|
||||
|
||||
<div class="description">${description}</div>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
.description {
|
||||
padding-top: 16px;
|
||||
}
|
||||
`;
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,12 +1,15 @@
|
||||
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { mdiAppleKeyboardCommand, mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../../common/translations/localize";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import "../../script/ha-script-field-selector-editor";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import { sidebarEditorStyles } from "../styles";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-script-field-selector")
|
||||
@@ -23,6 +26,8 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -64,10 +69,13 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
@@ -75,19 +83,45 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.del"
|
||||
)}</span
|
||||
>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-script-field-selector-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.yamlMode=${this.yamlMode}
|
||||
></ha-script-field-selector-editor>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-selector-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
.yamlMode=${this.yamlMode}
|
||||
></ha-script-field-selector-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -115,15 +149,17 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { mdiAppleKeyboardCommand, mdiDelete, mdiPlaylistEdit } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import "../../script/ha-script-field-editor";
|
||||
import type HaAutomationConditionEditor from "../action/ha-automation-action-editor";
|
||||
import { sidebarEditorStyles } from "../styles";
|
||||
import "./ha-automation-sidebar-card";
|
||||
|
||||
@customElement("ha-automation-sidebar-script-field")
|
||||
@@ -22,6 +25,8 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
|
||||
@query(".sidebar-editor")
|
||||
@@ -57,10 +62,13 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
@@ -68,21 +76,47 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.del"
|
||||
)}</span
|
||||
>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-script-field-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.key=${this.config.config.key}
|
||||
.excludeKeys=${this.config.config.excludeKeys}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this.yamlMode}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
></ha-script-field-editor>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.field=${this.config.config.field}
|
||||
.key=${this.config.config.key}
|
||||
.excludeKeys=${this.config.config.excludeKeys}
|
||||
.disabled=${this.disabled}
|
||||
.yamlMode=${this.yamlMode}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
></ha-script-field-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
@@ -109,15 +143,17 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,18 +1,25 @@
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiDelete,
|
||||
mdiIdentifier,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { keyed } from "lit/directives/keyed";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import type { TriggerSidebarConfig } from "../../../../data/automation";
|
||||
import { isTriggerList } from "../../../../data/trigger";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import { sidebarEditorStyles } from "../styles";
|
||||
import "../trigger/ha-automation-trigger-editor";
|
||||
import type HaAutomationTriggerEditor from "../trigger/ha-automation-trigger-editor";
|
||||
import "./ha-automation-sidebar-card";
|
||||
@@ -31,6 +38,8 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: "sidebar-key" }) public sidebarKey?: string;
|
||||
|
||||
@state() private _requestShowId = false;
|
||||
|
||||
@state() private _warnings?: string[];
|
||||
@@ -82,10 +91,13 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.clickAction=${this.config.rename}
|
||||
.disabled=${disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
${!this.yamlMode &&
|
||||
!("id" in this.config.config) &&
|
||||
@@ -95,21 +107,105 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.clickAction=${this._showTriggerId}
|
||||
.disabled=${disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.copy}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>C</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span>X</span>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this.yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
@@ -121,13 +217,16 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.clickAction=${this.config.disable}
|
||||
.disabled=${type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${this.disabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.actions.${disabled ? "enable" : "disable"}`
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
@@ -135,22 +234,49 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
${!this.narrow
|
||||
? html`<span class="shortcut">
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.ctrl"
|
||||
)}</span
|
||||
>
|
||||
<span>+</span>
|
||||
<span
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.del"
|
||||
)}</span
|
||||
>
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-automation-trigger-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.trigger=${this.config.config}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
.showId=${this._requestShowId}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
></ha-automation-trigger-editor>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-trigger-editor
|
||||
class="sidebar-editor"
|
||||
.hass=${this.hass}
|
||||
.trigger=${this.config.config}
|
||||
@value-changed=${this._valueChangedSidebar}
|
||||
@yaml-changed=${this._yamlChangedSidebar}
|
||||
.uiSupported=${this.config.uiSupported}
|
||||
.showId=${this._requestShowId}
|
||||
.yamlMode=${this.yamlMode}
|
||||
.disabled=${this.disabled}
|
||||
@ui-mode-not-available=${this._handleUiModeNotAvailable}
|
||||
sidebar
|
||||
></ha-automation-trigger-editor>`
|
||||
)}
|
||||
</ha-automation-sidebar-card>
|
||||
`;
|
||||
}
|
||||
@@ -177,6 +303,12 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChangedSidebar(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
|
||||
this.config?.save?.(ev.detail.value);
|
||||
}
|
||||
|
||||
private _toggleYamlMode = () => {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
@@ -185,11 +317,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
this._requestShowId = true;
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
.sidebar-editor {
|
||||
padding-top: 64px;
|
||||
}
|
||||
`;
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -37,12 +37,6 @@ export const rowStyles = css`
|
||||
ha-tooltip {
|
||||
cursor: default;
|
||||
}
|
||||
:host([highlight]) ha-card {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--state-inactive-color);
|
||||
border-color: var(--state-inactive-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
@@ -50,11 +44,10 @@ export const rowStyles = css`
|
||||
|
||||
export const editorStyles = css`
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
.card-content.card {
|
||||
padding: 16px;
|
||||
}
|
||||
.card-content.yaml {
|
||||
@@ -62,18 +55,31 @@ export const editorStyles = css`
|
||||
border-top: 1px solid var(--divider-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
.card-content.indent {
|
||||
`;
|
||||
|
||||
export const indentStyle = css`
|
||||
.card-content.indent,
|
||||
.selector-row,
|
||||
:host([indent]) ha-form {
|
||||
margin-left: 12px;
|
||||
margin-right: -4px;
|
||||
padding: 12px 24px 16px 16px;
|
||||
padding: 12px 0 16px 16px;
|
||||
border-left: 2px solid var(--ha-color-border-neutral-quiet);
|
||||
border-bottom: 2px solid var(--ha-color-border-neutral-quiet);
|
||||
border-radius: 0;
|
||||
border-bottom-left-radius: var(--ha-border-radius-lg);
|
||||
}
|
||||
.card-content.indent.selected,
|
||||
:host([selected]) .card-content.indent {
|
||||
:host([selected]) .card-content.indent,
|
||||
.selector-row.parent-selected,
|
||||
:host([selected]) ha-form {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
border-top-right-radius: var(--ha-border-radius-xl);
|
||||
border-bottom-right-radius: var(--ha-border-radius-xl);
|
||||
background: var(--ha-color-fill-primary-quiet-resting);
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--ha-color-fill-primary-quiet-resting) 0%,
|
||||
var(--ha-color-fill-primary-quiet-resting) 80%,
|
||||
rgba(var(--rgb-primary-color), 0) 100%
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -91,3 +97,161 @@ export const saveFabStyles = css`
|
||||
bottom: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const manualEditorStyles = css`
|
||||
:host {
|
||||
display: block;
|
||||
--sidebar-width: 0;
|
||||
--sidebar-gap: 0;
|
||||
}
|
||||
|
||||
.has-sidebar {
|
||||
--sidebar-width: min(35vw, 500px);
|
||||
--sidebar-gap: 16px;
|
||||
}
|
||||
|
||||
.fab-positioner {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.fab-positioner ha-fab {
|
||||
position: fixed;
|
||||
right: unset;
|
||||
left: unset;
|
||||
bottom: calc(-80px - var(--safe-area-inset-bottom));
|
||||
transition: bottom 0.3s;
|
||||
}
|
||||
.fab-positioner ha-fab.dirty {
|
||||
bottom: 16px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding-right: calc(var(--sidebar-width) + var(--sidebar-gap));
|
||||
padding-inline-end: calc(var(--sidebar-width) + var(--sidebar-gap));
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 72px;
|
||||
transition: padding-bottom 180ms ease-in-out;
|
||||
}
|
||||
|
||||
.content.has-bottom-sheet {
|
||||
padding-bottom: calc(90vh - 72px);
|
||||
}
|
||||
|
||||
ha-automation-sidebar {
|
||||
position: fixed;
|
||||
top: calc(var(--header-height) + 16px);
|
||||
height: calc(-81px + 100dvh);
|
||||
width: var(--sidebar-width);
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-automation-sidebar.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-positioner {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0;
|
||||
}
|
||||
.header a {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
|
||||
export const automationRowsStyles = css`
|
||||
.rows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.rows.no-sidebar {
|
||||
margin-right: 0;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-action-row {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
.handle {
|
||||
padding: 4px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
border-radius: var(--ha-border-radius-pill);
|
||||
}
|
||||
.handle:focus {
|
||||
outline: var(--wa-focus-ring);
|
||||
background: var(--ha-color-fill-neutral-quiet-resting);
|
||||
}
|
||||
.handle.active {
|
||||
outline: var(--wa-focus-ring);
|
||||
background: var(--ha-color-fill-neutral-normal-active);
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const sidebarEditorStyles = css`
|
||||
.sidebar-editor {
|
||||
display: block;
|
||||
padding-top: 8px;
|
||||
}
|
||||
.description {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.overflow-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.overflow-label .shortcut {
|
||||
--mdc-icon-size: 12px;
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.overflow-label .shortcut span {
|
||||
font-size: var(--ha-font-size-s);
|
||||
font-family: var(--ha-font-family-code);
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
.shortcut-placeholder {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
}
|
||||
.shortcut-placeholder.mac {
|
||||
width: 46px;
|
||||
}
|
||||
@media all and (max-width: 870px) {
|
||||
.shortcut-placeholder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
ha-md-menu-item {
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
`;
|
||||
|
@@ -29,6 +29,8 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
|
||||
@property({ type: Boolean, attribute: "show-id" }) public showId = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "sidebar" }) public inSidebar = false;
|
||||
|
||||
@query("ha-yaml-editor") public yamlEditor?: HaYamlEditor;
|
||||
|
||||
protected render() {
|
||||
@@ -47,6 +49,7 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
this.trigger.enabled === false &&
|
||||
!this.yamlMode),
|
||||
yaml: yamlMode,
|
||||
card: !this.inSidebar,
|
||||
})}
|
||||
>
|
||||
${yamlMode
|
||||
@@ -118,7 +121,7 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
if (!ev.detail.isValid) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
fireEvent(this, this.inSidebar ? "yaml-changed" : "value-changed", {
|
||||
value: migrateAutomationTrigger(ev.detail.value),
|
||||
});
|
||||
}
|
||||
@@ -138,7 +141,6 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
haStyle,
|
||||
css`
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
@@ -4,12 +4,12 @@ import {
|
||||
mdiArrowUp,
|
||||
mdiContentCopy,
|
||||
mdiContentCut,
|
||||
mdiContentDuplicate,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiIdentifier,
|
||||
mdiPlayCircleOutline,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiStopCircleOutline,
|
||||
} from "@mdi/js";
|
||||
@@ -28,6 +28,7 @@ import { handleStructError } from "../../../../common/structs/handle-errors";
|
||||
import { debounce } from "../../../../common/util/debounce";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
@@ -51,6 +52,7 @@ import {
|
||||
showPromptDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { rowStyles } from "../styles";
|
||||
import "./ha-automation-trigger-editor";
|
||||
@@ -112,9 +114,14 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public last?: boolean;
|
||||
|
||||
@property({ type: Boolean }) public highlight?: boolean;
|
||||
|
||||
@property({ type: Boolean, attribute: "sidebar" })
|
||||
public optionsInSidebar = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "sort-selected" })
|
||||
public sortSelected = false;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _triggered?: Record<string, unknown>;
|
||||
@@ -132,6 +139,9 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
@query("ha-automation-trigger-editor")
|
||||
public triggerEditor?: HaAutomationTriggerEditor;
|
||||
|
||||
@query("ha-automation-row")
|
||||
private _automationRowElement?: HaAutomationRow;
|
||||
|
||||
@storage({
|
||||
key: "automationClipboard",
|
||||
state: false,
|
||||
@@ -144,6 +154,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
@consume({ context: fullEntitiesContext, subscribe: true })
|
||||
_entityReg!: EntityRegistryEntry[];
|
||||
|
||||
get selected() {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
private _triggerUnsub?: Promise<UnsubscribeFunc>;
|
||||
|
||||
private _renderRow() {
|
||||
@@ -165,141 +179,142 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html` <ha-md-menu-item
|
||||
.clickAction=${this._renameTrigger}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._showTriggerId}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.duplicate"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentDuplicate}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_down")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!supported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlaylistEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${"enabled" in this.trigger && this.trigger.enabled === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
${!this.optionsInSidebar
|
||||
? html`<ha-md-button-menu
|
||||
quick
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameTrigger}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${"enabled" in this.trigger && this.trigger.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._showTriggerId}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.edit_id"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.move_up")}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveDown}
|
||||
.disabled=${this.disabled || this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
.disabled=${!supported || !!this._warnings}
|
||||
>
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
${"enabled" in this.trigger && this.trigger.enabled === false
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.enable"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.disable"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${"enabled" in this.trigger &&
|
||||
this.trigger.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
class="warning"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -339,8 +354,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
? html`<ha-automation-row
|
||||
.disabled=${"enabled" in this.trigger &&
|
||||
this.trigger.enabled === false}
|
||||
@click=${this._toggleSidebar}
|
||||
.selected=${this._selected}
|
||||
.highlight=${this.highlight}
|
||||
.sortSelected=${this.sortSelected}
|
||||
@click=${this._toggleSidebar}
|
||||
>${this._selected
|
||||
? "selected"
|
||||
: nothing}${this._renderRow()}</ha-automation-row
|
||||
@@ -461,8 +478,7 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selected) {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
return;
|
||||
}
|
||||
this.openSidebar();
|
||||
@@ -473,19 +489,25 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
save: (value) => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
},
|
||||
close: () => {
|
||||
close: (focus?: boolean) => {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
},
|
||||
rename: () => {
|
||||
this._renameTrigger();
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
disable: this._onDisable,
|
||||
delete: this._onDelete,
|
||||
copy: this._copyTrigger,
|
||||
duplicate: this._duplicateTrigger,
|
||||
cut: this._cutTrigger,
|
||||
config: trigger || this.trigger,
|
||||
uiSupported: this._uiSupported(this._getType(trigger || this.trigger)),
|
||||
yamlMode: this._yamlMode,
|
||||
@@ -493,10 +515,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
this._selected = true;
|
||||
|
||||
if (this.narrow) {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 180);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,6 +638,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
private _copyTrigger = () => {
|
||||
this._setClipboard();
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copied_to_clipboard"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
private _cutTrigger = () => {
|
||||
@@ -622,6 +652,12 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
if (this._selected) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut_to_clipboard"
|
||||
),
|
||||
duration: 2000,
|
||||
});
|
||||
};
|
||||
|
||||
private _moveUp = () => {
|
||||
@@ -659,6 +695,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
customElements.get(`ha-automation-trigger-${type}`) !== undefined
|
||||
);
|
||||
|
||||
public focus() {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { mdiDrag, mdiPlus } from "@mdi/js";
|
||||
import deepClone from "deep-clone-simple";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { storage } from "../../../../common/decorators/storage";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { listenMediaQuery } from "../../../../common/dom/media_query";
|
||||
import { nextRender } from "../../../../common/util/render-status";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-button-menu";
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
PASTE_VALUE,
|
||||
showAddAutomationElementDialog,
|
||||
} from "../show-add-automation-element-dialog";
|
||||
import { automationRowsStyles } from "../styles";
|
||||
import "./ha-automation-trigger-row";
|
||||
import type HaAutomationTriggerRow from "./ha-automation-trigger-row";
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public root = false;
|
||||
|
||||
@state() private _showReorder = false;
|
||||
@state() private _rowSortSelected?: number;
|
||||
|
||||
@state()
|
||||
@storage({
|
||||
@@ -56,36 +56,23 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
private _focusLastTriggerOnChange = false;
|
||||
|
||||
private _focusTriggerIndexOnChange?: number;
|
||||
|
||||
private _triggerKeys = new WeakMap<Trigger, string>();
|
||||
|
||||
private _unsubMql?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._unsubMql = listenMediaQuery("(min-width: 600px)", (matches) => {
|
||||
this._showReorder = matches;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._unsubMql?.();
|
||||
this._unsubMql = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector="ha-automation-trigger-row"
|
||||
.disabled=${!this._showReorder || this.disabled}
|
||||
.disabled=${this.disabled}
|
||||
group="triggers"
|
||||
invert-swap
|
||||
@item-moved=${this._triggerMoved}
|
||||
@item-added=${this._triggerAdded}
|
||||
@item-removed=${this._triggerRemoved}
|
||||
>
|
||||
<div class="triggers">
|
||||
<div class="rows ${!this.optionsInSidebar ? "no-sidebar" : ""}">
|
||||
${repeat(
|
||||
this.triggers,
|
||||
(trigger) => this._getKey(trigger),
|
||||
@@ -103,12 +90,22 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.disabled=${this.disabled}
|
||||
.narrow=${this.narrow}
|
||||
?highlight=${this.highlightedTriggers?.includes(trg)}
|
||||
.highlight=${this.highlightedTriggers?.includes(trg)}
|
||||
.optionsInSidebar=${this.optionsInSidebar}
|
||||
.sortSelected=${this._rowSortSelected === idx}
|
||||
@stop-sort-selection=${this._stopSortSelection}
|
||||
>
|
||||
${this._showReorder && !this.disabled
|
||||
${!this.disabled
|
||||
? html`
|
||||
<div class="handle" slot="icons">
|
||||
<div
|
||||
tabindex="0"
|
||||
class="handle ${this._rowSortSelected === idx
|
||||
? "active"
|
||||
: ""}"
|
||||
slot="icons"
|
||||
@keydown=${this._handleDragKeydown}
|
||||
.index=${idx}
|
||||
>
|
||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||
</div>
|
||||
`
|
||||
@@ -136,7 +133,7 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
|
||||
private _addTriggerDialog() {
|
||||
if (this.narrow) {
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
}
|
||||
showAddAutomationElementDialog(this, {
|
||||
type: "trigger",
|
||||
@@ -171,12 +168,18 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("triggers") && this._focusLastTriggerOnChange) {
|
||||
this._focusLastTriggerOnChange = false;
|
||||
|
||||
if (
|
||||
changedProps.has("triggers") &&
|
||||
(this._focusLastTriggerOnChange ||
|
||||
this._focusTriggerIndexOnChange !== undefined)
|
||||
) {
|
||||
const row = this.shadowRoot!.querySelector<HaAutomationTriggerRow>(
|
||||
"ha-automation-trigger-row:last-of-type"
|
||||
`ha-automation-trigger-row:${this._focusLastTriggerOnChange ? "last-of-type" : `nth-of-type(${this._focusTriggerIndexOnChange! + 1})`}`
|
||||
)!;
|
||||
|
||||
this._focusLastTriggerOnChange = false;
|
||||
this._focusTriggerIndexOnChange = undefined;
|
||||
|
||||
row.updateComplete.then(() => {
|
||||
if (this.optionsInSidebar) {
|
||||
row.openSidebar();
|
||||
@@ -188,17 +191,18 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
}
|
||||
} else {
|
||||
row.expand();
|
||||
row.focus();
|
||||
}
|
||||
row.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
const rows = this.shadowRoot!.querySelectorAll<HaAutomationTriggerRow>(
|
||||
"ha-automation-trigger-row"
|
||||
)!;
|
||||
rows.forEach((row) => {
|
||||
const triggerRows =
|
||||
this.shadowRoot!.querySelectorAll<HaAutomationTriggerRow>(
|
||||
"ha-automation-trigger-row"
|
||||
)!;
|
||||
triggerRows.forEach((row) => {
|
||||
row.expand();
|
||||
});
|
||||
}
|
||||
@@ -214,15 +218,27 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
private _moveUp(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationTriggerRow).first) {
|
||||
const newIndex = index - 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _moveDown(ev) {
|
||||
ev.stopPropagation();
|
||||
const index = (ev.target as any).index;
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (!(ev.target as HaAutomationTriggerRow).last) {
|
||||
const newIndex = index + 1;
|
||||
this._move(index, newIndex);
|
||||
if (this._rowSortSelected === index) {
|
||||
this._rowSortSelected = newIndex;
|
||||
}
|
||||
ev.target.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private _move(oldIndex: number, newIndex: number) {
|
||||
@@ -242,6 +258,9 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
private async _triggerAdded(ev: CustomEvent): Promise<void> {
|
||||
ev.stopPropagation();
|
||||
const { index, data } = ev.detail;
|
||||
const item = ev.detail.item as HaAutomationTriggerRow;
|
||||
const selected = item.selected;
|
||||
|
||||
let triggers = [
|
||||
...this.triggers.slice(0, index),
|
||||
data,
|
||||
@@ -249,6 +268,9 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
];
|
||||
// Add trigger locally to avoid UI jump
|
||||
this.triggers = triggers;
|
||||
if (selected) {
|
||||
this._focusTriggerIndexOnChange = triggers.length === 1 ? 0 : index;
|
||||
}
|
||||
await nextRender();
|
||||
if (this.triggers !== triggers) {
|
||||
// Ensure trigger is added even after update
|
||||
@@ -257,6 +279,9 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
data,
|
||||
...this.triggers.slice(index),
|
||||
];
|
||||
if (selected) {
|
||||
this._focusTriggerIndexOnChange = triggers.length === 1 ? 0 : index;
|
||||
}
|
||||
}
|
||||
fireEvent(this, "value-changed", { value: triggers });
|
||||
}
|
||||
@@ -300,44 +325,21 @@ export default class HaAutomationTrigger extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.triggers {
|
||||
padding: 16px 0 16px 16px;
|
||||
margin: -16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
private _handleDragKeydown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter" || ev.key === " ") {
|
||||
ev.stopPropagation();
|
||||
this._rowSortSelected =
|
||||
this._rowSortSelected === undefined
|
||||
? (ev.target as any).index
|
||||
: undefined;
|
||||
}
|
||||
:host([root]) .triggers {
|
||||
padding-right: 8px;
|
||||
}
|
||||
.sortable-ghost {
|
||||
background: none;
|
||||
border-radius: var(--ha-card-border-radius, var(--ha-border-radius-lg));
|
||||
}
|
||||
.sortable-drag {
|
||||
background: none;
|
||||
}
|
||||
ha-automation-trigger-row {
|
||||
display: block;
|
||||
scroll-margin-top: 48px;
|
||||
}
|
||||
.handle {
|
||||
padding: 12px;
|
||||
cursor: move; /* fallback if grab cursor is unsupported */
|
||||
cursor: grab;
|
||||
}
|
||||
.handle ha-svg-icon {
|
||||
pointer-events: none;
|
||||
height: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
order: 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
private _stopSortSelection() {
|
||||
this._rowSortSelected = undefined;
|
||||
}
|
||||
|
||||
static styles = automationRowsStyles;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -42,6 +42,8 @@ export class HaTagTrigger extends LitElement implements TriggerElement {
|
||||
.disabled=${this.disabled || this._tags.length === 0}
|
||||
.value=${this.trigger.tag_id}
|
||||
@selected=${this._tagChanged}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
>
|
||||
${this._tags.map(
|
||||
(tag) => html`
|
||||
|
@@ -125,7 +125,7 @@ export class HaWebhookTrigger extends LitElement {
|
||||
.path=${mdiContentCopy}
|
||||
></ha-icon-button>
|
||||
</ha-textfield>
|
||||
<ha-button-menu multi @closed=${stopPropagation}>
|
||||
<ha-button-menu multi @closed=${stopPropagation} fixed>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass!.localize(
|
||||
|
@@ -26,15 +26,15 @@ interface Strategy {
|
||||
|
||||
const STRATEGIES = [
|
||||
{
|
||||
type: "default",
|
||||
type: "overview",
|
||||
images: {
|
||||
light:
|
||||
"/static/images/dashboard-options/light/icon-dashboard-default.svg",
|
||||
dark: "/static/images/dashboard-options/dark/icon-dashboard-default.svg",
|
||||
"/static/images/dashboard-options/light/icon-dashboard-overview.svg",
|
||||
dark: "/static/images/dashboard-options/dark/icon-dashboard-overview.svg",
|
||||
},
|
||||
name: "ui.panel.config.lovelace.dashboards.dialog_new.strategy.default.title",
|
||||
name: "ui.panel.config.lovelace.dashboards.dialog_new.strategy.overview.title",
|
||||
description:
|
||||
"ui.panel.config.lovelace.dashboards.dialog_new.strategy.default.description",
|
||||
"ui.panel.config.lovelace.dashboards.dialog_new.strategy.overview.description",
|
||||
},
|
||||
{
|
||||
type: "areas",
|
||||
@@ -47,14 +47,14 @@ const STRATEGIES = [
|
||||
"ui.panel.config.lovelace.dashboards.dialog_new.strategy.areas.description",
|
||||
},
|
||||
{
|
||||
type: "overview",
|
||||
type: "home",
|
||||
images: {
|
||||
light: "/static/images/dashboard-options/light/icon-dashboard-areas.svg",
|
||||
dark: "/static/images/dashboard-options/dark/icon-dashboard-areas.svg",
|
||||
light: "/static/images/dashboard-options/light/icon-dashboard-home.svg",
|
||||
dark: "/static/images/dashboard-options/dark/icon-dashboard-home.svg",
|
||||
},
|
||||
name: "ui.panel.config.lovelace.dashboards.dialog_new.strategy.overview.title",
|
||||
name: "ui.panel.config.lovelace.dashboards.dialog_new.strategy.home.title",
|
||||
description:
|
||||
"ui.panel.config.lovelace.dashboards.dialog_new.strategy.overview.description",
|
||||
"ui.panel.config.lovelace.dashboards.dialog_new.strategy.home.description",
|
||||
},
|
||||
{
|
||||
type: "map",
|
||||
@@ -254,7 +254,7 @@ class DialogNewDashboard extends LitElement implements HassDialog {
|
||||
if (target.config) {
|
||||
config = target.config;
|
||||
} else if (target.strategy) {
|
||||
if (target.strategy === "default") {
|
||||
if (target.strategy === "overview") {
|
||||
config = null;
|
||||
} else {
|
||||
config = this._generateStrategyConfig(target.strategy);
|
||||
|
@@ -132,7 +132,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._submitting}
|
||||
appearance="plain"
|
||||
|
@@ -773,6 +773,11 @@ export class HaConfigDevicePage extends LitElement {
|
||||
appearance="plain"
|
||||
target=${ifDefined(firstDeviceAction!.target)}
|
||||
class=${ifDefined(firstDeviceAction!.classes)}
|
||||
.variant=${firstDeviceAction!.classes?.includes(
|
||||
"warning"
|
||||
)
|
||||
? "danger"
|
||||
: "brand"}
|
||||
.action=${firstDeviceAction!.action}
|
||||
@click=${this._deviceActionClicked}
|
||||
>
|
||||
|
@@ -226,6 +226,7 @@ export class EnergyBatterySettings extends LitElement {
|
||||
.label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -5,20 +5,24 @@ import {
|
||||
mdiDevices,
|
||||
mdiDotsVertical,
|
||||
mdiHandExtendedOutline,
|
||||
mdiRenameBox,
|
||||
mdiShapeOutline,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { ConfigEntry, SubEntry } from "../../../data/config_entries";
|
||||
import { deleteSubEntry } from "../../../data/config_entries";
|
||||
import { deleteSubEntry, updateSubEntry } from "../../../data/config_entries";
|
||||
import type { DeviceRegistryEntry } from "../../../data/device_registry";
|
||||
import type { DiagnosticInfo } from "../../../data/diagnostics";
|
||||
import type { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { showSubConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-sub-config-flow";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showConfirmationDialog } from "../../lovelace/custom-card-helpers";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showPromptDialog,
|
||||
} from "../../lovelace/custom-card-helpers";
|
||||
import "./ha-config-entry-device-row";
|
||||
|
||||
@customElement("ha-config-sub-entry-row")
|
||||
@@ -141,6 +145,12 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
</ha-md-menu-item>
|
||||
`
|
||||
: nothing}
|
||||
<ha-md-menu-item @click=${this._handleRenameSub}>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_entry.rename"
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item class="warning" @click=${this._handleDeleteSub}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
@@ -212,6 +222,25 @@ class HaConfigSubEntryRow extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _handleRenameSub(): Promise<void> {
|
||||
const newName = await showPromptDialog(this, {
|
||||
title: this.hass.localize("ui.common.rename"),
|
||||
defaultValue: this.subEntry.title,
|
||||
inputLabel: this.hass.localize(
|
||||
"ui.panel.config.integrations.rename_input_label"
|
||||
),
|
||||
});
|
||||
if (newName === null) {
|
||||
return;
|
||||
}
|
||||
await updateSubEntry(
|
||||
this.hass,
|
||||
this.entry.entry_id,
|
||||
this.subEntry.subentry_id,
|
||||
{ title: newName }
|
||||
);
|
||||
}
|
||||
|
||||
private async _handleDeleteSub(): Promise<void> {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
|
@@ -23,10 +23,7 @@ class ConfigNetwork extends LitElement {
|
||||
@state() private _error?: { code: string; message: string };
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this.hass.userData?.showAdvanced ||
|
||||
!isComponentLoaded(this.hass, "network")
|
||||
) {
|
||||
if (!isComponentLoaded(this.hass, "network")) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiCog,
|
||||
mdiContentDuplicate,
|
||||
mdiContentSave,
|
||||
mdiDebugStepOver,
|
||||
mdiDelete,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
mdiInformationOutline,
|
||||
mdiPlay,
|
||||
mdiPlaylistEdit,
|
||||
mdiPlusCircleMultipleOutline,
|
||||
mdiRenameBox,
|
||||
mdiRobotConfused,
|
||||
mdiTag,
|
||||
@@ -23,7 +23,6 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { promiseTimeout } from "../../../common/util/promise-timeout";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
@@ -307,7 +306,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiContentDuplicate}
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
|
||||
@@ -359,67 +358,9 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<div class=${this._mode === "yaml" ? "yaml-mode" : ""}>
|
||||
<div class="error-wrapper">
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${stateObj?.state === UNAVAILABLE
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.script.editor.unavailable"
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
${this._errors || this._validationErrors}
|
||||
${stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiRobotConfused}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${this._blueprintConfig
|
||||
? html`<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.confirm_take_control"
|
||||
)}
|
||||
<div slot="action" style="display: flex;">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._takeControlSave}
|
||||
>${this.hass.localize("ui.common.yes")}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._revertBlueprint}
|
||||
>${this.hass.localize("ui.common.no")}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-alert>`
|
||||
: this._readOnly
|
||||
? html`<ha-alert alert-type="warning" dismissable
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.script.editor.read_only"
|
||||
)}
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.migrate"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
</div>
|
||||
${this._mode === "gui"
|
||||
? html`
|
||||
<div
|
||||
class=${classMap({
|
||||
rtl: computeRTL(this.hass),
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
${useBlueprint
|
||||
? html`
|
||||
<blueprint-script-editor
|
||||
@@ -446,7 +387,68 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
@value-changed=${this._valueChanged}
|
||||
@editor-save=${this._handleSaveScript}
|
||||
@save-script=${this._handleSaveScript}
|
||||
></manual-script-editor>
|
||||
>
|
||||
<div class="alert-wrapper" slot="alerts">
|
||||
${this._errors || stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-alert
|
||||
alert-type="error"
|
||||
.title=${stateObj?.state === UNAVAILABLE
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.script.editor.unavailable"
|
||||
)
|
||||
: undefined}
|
||||
>
|
||||
${this._errors || this._validationErrors}
|
||||
${stateObj?.state === UNAVAILABLE
|
||||
? html`<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiRobotConfused}
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
${this._blueprintConfig
|
||||
? html`<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.confirm_take_control"
|
||||
)}
|
||||
<div slot="action" style="display: flex;">
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._takeControlSave}
|
||||
>${this.hass.localize(
|
||||
"ui.common.yes"
|
||||
)}</ha-button
|
||||
>
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
@click=${this._revertBlueprint}
|
||||
>${this.hass.localize(
|
||||
"ui.common.no"
|
||||
)}</ha-button
|
||||
>
|
||||
</div>
|
||||
</ha-alert>`
|
||||
: this._readOnly
|
||||
? html`<ha-alert
|
||||
alert-type="warning"
|
||||
dismissable
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.script.editor.read_only"
|
||||
)}
|
||||
<ha-button
|
||||
appearance="plain"
|
||||
slot="action"
|
||||
@click=${this._duplicate}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.script.editor.migrate"
|
||||
)}
|
||||
</ha-button>
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
</div>
|
||||
</manual-script-editor>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
@@ -1019,6 +1021,10 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
protected supportedShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
s: () => this._handleSaveScript(),
|
||||
c: () => this._copySelectedRow(),
|
||||
x: () => this._cutSelectedRow(),
|
||||
Delete: () => this._deleteSelectedRow(),
|
||||
Backspace: () => this._deleteSelectedRow(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1030,6 +1036,28 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
return this._confirmUnsavedChanged();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
private _collapseAll() {
|
||||
this._manualEditor?.collapseAll();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
private _expandAll() {
|
||||
this._manualEditor?.expandAll();
|
||||
}
|
||||
|
||||
private _copySelectedRow() {
|
||||
this._manualEditor?.copySelectedRow();
|
||||
}
|
||||
|
||||
private _cutSelectedRow() {
|
||||
this._manualEditor?.cutSelectedRow();
|
||||
}
|
||||
|
||||
private _deleteSelectedRow() {
|
||||
this._manualEditor?.deleteSelectedRow();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1063,6 +1091,28 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
|
||||
.alert-wrapper {
|
||||
position: sticky;
|
||||
top: -24px;
|
||||
margin-top: -24px;
|
||||
margin-bottom: 8px;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.alert-wrapper ha-alert {
|
||||
background-color: var(--card-background-color);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
margin-bottom: 0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
manual-script-editor {
|
||||
max-width: 1540px;
|
||||
padding: 0 12px;
|
||||
|
@@ -152,7 +152,12 @@ export default class HaScriptFieldEditor extends LitElement {
|
||||
ev.stopPropagation();
|
||||
const value = { ...ev.detail.value };
|
||||
|
||||
if (typeof value !== "object" || Object.keys(value).length !== 1) {
|
||||
if (
|
||||
typeof value !== "object" ||
|
||||
Object.keys(value).length !== 1 ||
|
||||
!value[Object.keys(value)[0]] ||
|
||||
!value[Object.keys(value)[0]].selector
|
||||
) {
|
||||
this._yamlError = "yaml_error";
|
||||
return;
|
||||
}
|
||||
@@ -165,7 +170,7 @@ export default class HaScriptFieldEditor extends LitElement {
|
||||
|
||||
const newValue = { ...value[key], key };
|
||||
|
||||
fireEvent(this, "value-changed", { value: newValue });
|
||||
fireEvent(this, "yaml-changed", { value: newValue });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
|
@@ -1,24 +1,21 @@
|
||||
import { mdiDelete, mdiDotsVertical } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { preventDefaultStopPropagation } from "../../../common/dom/prevent_default_stop_propagation";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../components/ha-automation-row";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-button-menu";
|
||||
import "../../../components/ha-md-menu-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../data/automation";
|
||||
import type { Field } from "../../../data/script";
|
||||
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { indentStyle } from "../automation/styles";
|
||||
import "./ha-script-field-selector-editor";
|
||||
import type HaScriptFieldSelectorEditor from "./ha-script-field-selector-editor";
|
||||
|
||||
@customElement("ha-script-field-row")
|
||||
export default class HaScriptFieldRow extends LitElement {
|
||||
@@ -35,6 +32,8 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ type: Boolean }) public highlight?: boolean;
|
||||
|
||||
@state() private _yamlMode = false;
|
||||
|
||||
@state() private _selected = false;
|
||||
@@ -45,6 +44,15 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
|
||||
@state() private _selectorRowCollapsed = false;
|
||||
|
||||
@query("ha-script-field-selector-editor")
|
||||
private _selectorEditor?: HaScriptFieldSelectorEditor;
|
||||
|
||||
@query("ha-automation-row:first-of-type")
|
||||
private _fieldRowElement?: HaAutomationRow;
|
||||
|
||||
@query(".selector-row ha-automation-row")
|
||||
private _selectorRowElement?: HaAutomationRow;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<ha-card outlined>
|
||||
@@ -55,38 +63,12 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
left-chevron
|
||||
@toggle-collapsed=${this._toggleCollapse}
|
||||
.collapsed=${this._collapsed}
|
||||
.highlight=${this.highlight}
|
||||
@delete-row=${this._onDelete}
|
||||
>
|
||||
<h3 slot="header">${this.key}</h3>
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
<ha-md-button-menu
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="graphic"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-automation-row>
|
||||
</ha-card>
|
||||
<div
|
||||
@@ -105,6 +87,7 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
.leftChevron=${SELECTOR_SELECTOR_BUILDING_BLOCKS.includes(
|
||||
Object.keys(this.field.selector)[0]
|
||||
)}
|
||||
.highlight=${this.highlight}
|
||||
>
|
||||
<h3 slot="header">
|
||||
${this.hass.localize(
|
||||
@@ -141,20 +124,50 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
this._collapsed = !this._collapsed;
|
||||
}
|
||||
|
||||
public expand() {
|
||||
this._collapsed = false;
|
||||
}
|
||||
|
||||
public collapse() {
|
||||
this._collapsed = true;
|
||||
}
|
||||
|
||||
public expandSelectorRow() {
|
||||
this._selectorRowCollapsed = false;
|
||||
}
|
||||
|
||||
public collapseSelectorRow() {
|
||||
this._selectorRowCollapsed = true;
|
||||
}
|
||||
|
||||
private _toggleSelectorRowCollapse() {
|
||||
this._selectorRowCollapsed = !this._selectorRowCollapsed;
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this.expand();
|
||||
this.expandSelectorRow();
|
||||
|
||||
this._selectorEditor?.expandAll();
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this.collapse();
|
||||
this.collapseSelectorRow();
|
||||
|
||||
this._selectorEditor?.collapseAll();
|
||||
}
|
||||
|
||||
private _toggleSidebar(ev: Event) {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selected) {
|
||||
this._selected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
return;
|
||||
}
|
||||
|
||||
this._selected = true;
|
||||
this._collapsed = false;
|
||||
this.openSidebar();
|
||||
}
|
||||
|
||||
@@ -162,12 +175,12 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
ev?.stopPropagation();
|
||||
|
||||
if (this._selectorRowSelected) {
|
||||
this._selectorRowSelected = false;
|
||||
fireEvent(this, "close-sidebar");
|
||||
fireEvent(this, "request-close-sidebar");
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectorRowSelected = true;
|
||||
this._selectorRowCollapsed = false;
|
||||
this.openSidebar(true);
|
||||
}
|
||||
|
||||
@@ -192,17 +205,23 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
save: (value) => {
|
||||
fireEvent(this, "value-changed", { value });
|
||||
},
|
||||
close: () => {
|
||||
close: (focus?: boolean) => {
|
||||
if (selectorEditor) {
|
||||
this._selectorRowSelected = false;
|
||||
if (focus) {
|
||||
this.focusSelector();
|
||||
}
|
||||
} else {
|
||||
this._selected = false;
|
||||
if (focus) {
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
fireEvent(this, "close-sidebar");
|
||||
},
|
||||
toggleYamlMode: () => {
|
||||
this._toggleYamlMode();
|
||||
return this._yamlMode;
|
||||
this.openSidebar();
|
||||
},
|
||||
delete: this._onDelete,
|
||||
config: {
|
||||
@@ -215,10 +234,12 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
} satisfies ScriptFieldSidebarConfig);
|
||||
|
||||
if (this.narrow) {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
this.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 180); // duration of transition of added padding for bottom sheet
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,14 +267,19 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
});
|
||||
};
|
||||
|
||||
public focus() {
|
||||
this._fieldRowElement?.focus();
|
||||
}
|
||||
|
||||
public focusSelector() {
|
||||
this._selectorRowElement?.focus();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
indentStyle,
|
||||
css`
|
||||
ha-button-menu,
|
||||
ha-icon-button {
|
||||
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
@@ -299,9 +325,6 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
ha-md-menu-item[disabled] {
|
||||
--mdc-theme-text-primary-on-background: var(--disabled-text-color);
|
||||
}
|
||||
.warning ul {
|
||||
margin: 4px 0;
|
||||
}
|
||||
@@ -311,23 +334,8 @@ export default class HaScriptFieldRow extends LitElement {
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
:host([highlight]) ha-card {
|
||||
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
|
||||
--shadow-focus: 0 0 0 1px var(--state-inactive-color);
|
||||
border-color: var(--state-inactive-color);
|
||||
box-shadow: var(--shadow-default), var(--shadow-focus);
|
||||
}
|
||||
.selector-row {
|
||||
margin-left: 12px;
|
||||
padding: 12px 4px 16px 16px;
|
||||
margin-right: -4px;
|
||||
border-left: 2px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
.selector-row.parent-selected {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
border-top-right-radius: var(--ha-border-radius-xl);
|
||||
border-bottom-right-radius: var(--ha-border-radius-xl);
|
||||
padding: 12px 0 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,17 +1,22 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import type { LocalizeKeys } from "../../../common/translations/localize";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import type { HaForm } from "../../../components/ha-form/ha-form";
|
||||
import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import type { HaSelector } from "../../../components/ha-selector/ha-selector";
|
||||
import type { HaActionSelector } from "../../../components/ha-selector/ha-selector-action";
|
||||
import type { HaConditionSelector } from "../../../components/ha-selector/ha-selector-condition";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { Field } from "../../../data/script";
|
||||
import { SELECTOR_SELECTOR_BUILDING_BLOCKS } from "../../../data/selector/selector_selector";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { indentStyle } from "../automation/styles";
|
||||
|
||||
@customElement("ha-script-field-selector-editor")
|
||||
export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
@@ -33,6 +38,9 @@ export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
|
||||
@state() private _yamlError?: undefined | "yaml_error" | "key_not_unique";
|
||||
|
||||
@query("ha-form")
|
||||
private _formElement?: HaForm;
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(selector: any) =>
|
||||
[
|
||||
@@ -124,7 +132,7 @@ export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", { value });
|
||||
fireEvent(this, "yaml-changed", { value });
|
||||
}
|
||||
|
||||
private _computeLabelCallback = (
|
||||
@@ -138,22 +146,51 @@ export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
this.hass.localize(`ui.panel.config.script.editor.field.${error}` as any) ||
|
||||
error;
|
||||
|
||||
private _getSelectorElements() {
|
||||
if (this._formElement) {
|
||||
const selectors =
|
||||
this._formElement.shadowRoot?.querySelectorAll<HaSelector>(
|
||||
"ha-selector"
|
||||
);
|
||||
|
||||
const selectorElements: (HaConditionSelector | HaActionSelector)[] = [];
|
||||
|
||||
selectors?.forEach((selector) => {
|
||||
selectorElements.push(
|
||||
...Array.from(
|
||||
selector.shadowRoot?.querySelectorAll<
|
||||
HaConditionSelector | HaActionSelector
|
||||
>("ha-selector-condition, ha-selector-action") || []
|
||||
)
|
||||
);
|
||||
});
|
||||
return selectorElements;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._getSelectorElements().forEach((element) => {
|
||||
element.expandAll?.();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._getSelectorElements().forEach((element) => {
|
||||
element.collapseAll?.();
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
indentStyle,
|
||||
css`
|
||||
:host([indent]) ha-form {
|
||||
display: block;
|
||||
margin-left: 12px;
|
||||
padding: 12px 20px 16px 16px;
|
||||
margin-right: -4px;
|
||||
border-left: 2px solid var(--ha-color-border-neutral-quiet);
|
||||
}
|
||||
:host([selected]) ha-form {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--ha-color-fill-primary-quiet-resting);
|
||||
border-top-right-radius: var(--ha-border-radius-xl);
|
||||
border-bottom-right-radius: var(--ha-border-radius-xl);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { mdiPlus } from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, queryAll } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
@@ -23,6 +23,9 @@ export default class HaScriptFields extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@queryAll("ha-script-field-row")
|
||||
private _fieldRowElements?: HaScriptFieldRow[];
|
||||
|
||||
private _focusLastActionOnChange = false;
|
||||
|
||||
protected render() {
|
||||
@@ -40,7 +43,7 @@ export default class HaScriptFields extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._fieldChanged}
|
||||
.hass=${this.hass}
|
||||
?highlight=${this.highlightedFields?.[key] !== undefined}
|
||||
.highlight=${this.highlightedFields?.[key] !== undefined}
|
||||
.narrow=${this.narrow}
|
||||
>
|
||||
</ha-script-field-row>
|
||||
@@ -48,12 +51,7 @@ export default class HaScriptFields extends LitElement {
|
||||
)}
|
||||
</div> `
|
||||
: nothing}
|
||||
<ha-button
|
||||
appearance="filled"
|
||||
size="small"
|
||||
@click=${this._addField}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-button @click=${this._addField} .disabled=${this.disabled}>
|
||||
<ha-svg-icon .path=${mdiPlus} slot="start"></ha-svg-icon>
|
||||
${this.hass.localize("ui.panel.config.script.editor.field.add_field")}
|
||||
</ha-button>
|
||||
@@ -78,10 +76,12 @@ export default class HaScriptFields extends LitElement {
|
||||
row.focus();
|
||||
|
||||
if (this.narrow) {
|
||||
row.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
window.setTimeout(() => {
|
||||
row.scrollIntoView({
|
||||
block: "start",
|
||||
behavior: "smooth",
|
||||
});
|
||||
}, 180); // duration of transition of added padding for bottom sheet
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -144,6 +144,18 @@ export default class HaScriptFields extends LitElement {
|
||||
return key;
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._fieldRowElements?.forEach((row) => {
|
||||
row.expandAll();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._fieldRowElements?.forEach((row) => {
|
||||
row.collapseAll();
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-script-field-row {
|
||||
display: block;
|
||||
|
@@ -1,8 +1,14 @@
|
||||
import { mdiContentSave, mdiHelpCircle } from "@mdi/js";
|
||||
import { load } from "js-yaml";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
query,
|
||||
queryAll,
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import {
|
||||
any,
|
||||
@@ -17,9 +23,17 @@ import {
|
||||
import { ensureArray } from "../../../common/array/ensure-array";
|
||||
import { canOverrideAlphanumericInput } from "../../../common/dom/can-override-input";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { constructUrlCurrentPath } from "../../../common/url/construct-url";
|
||||
import {
|
||||
extractSearchParam,
|
||||
removeSearchParam,
|
||||
} from "../../../common/url/search-params";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-markdown";
|
||||
import type { SidebarConfig } from "../../../data/automation";
|
||||
import type {
|
||||
ActionSidebarConfig,
|
||||
SidebarConfig,
|
||||
} from "../../../data/automation";
|
||||
import type { Action, Fields, ScriptConfig } from "../../../data/script";
|
||||
import {
|
||||
getActionType,
|
||||
@@ -30,9 +44,11 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "../automation/action/ha-automation-action";
|
||||
import type HaAutomationAction from "../automation/action/ha-automation-action";
|
||||
import "../automation/ha-automation-sidebar";
|
||||
import type HaAutomationSidebar from "../automation/ha-automation-sidebar";
|
||||
import { showPasteReplaceDialog } from "../automation/paste-replace-dialog/show-dialog-paste-replace";
|
||||
import { saveFabStyles } from "../automation/styles";
|
||||
import { manualEditorStyles, saveFabStyles } from "../automation/styles";
|
||||
import "./ha-script-fields";
|
||||
import type HaScriptFields from "./ha-script-fields";
|
||||
|
||||
@@ -62,17 +78,26 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public dirty = false;
|
||||
|
||||
@query("ha-script-fields")
|
||||
private _scriptFields?: HaScriptFields;
|
||||
|
||||
private _openFields = false;
|
||||
|
||||
@state() private _pastedConfig?: ScriptConfig;
|
||||
|
||||
@state() private _sidebarConfig?: SidebarConfig;
|
||||
|
||||
@state() private _sidebarKey?: string;
|
||||
|
||||
@query("ha-script-fields")
|
||||
private _scriptFields?: HaScriptFields;
|
||||
|
||||
@query("ha-automation-sidebar") private _sidebarElement?: HaAutomationSidebar;
|
||||
|
||||
@queryAll("ha-automation-action, ha-script-fields")
|
||||
private _collapsableElements?: NodeListOf<
|
||||
HaAutomationAction | HaScriptFields
|
||||
>;
|
||||
|
||||
private _previousConfig?: ScriptConfig;
|
||||
|
||||
private _openFields = false;
|
||||
|
||||
public addFields() {
|
||||
this._openFields = true;
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -145,6 +170,7 @@ export class HaManualScriptEditor extends LitElement {
|
||||
.disabled=${this.disabled}
|
||||
.narrow=${this.narrow}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@request-close-sidebar=${this._triggerCloseSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
></ha-script-fields>`
|
||||
: nothing
|
||||
@@ -175,6 +201,7 @@ export class HaManualScriptEditor extends LitElement {
|
||||
.highlightedActions=${this._pastedConfig?.sequence || []}
|
||||
@value-changed=${this._sequenceChanged}
|
||||
@open-sidebar=${this._openSidebar}
|
||||
@request-close-sidebar=${this._triggerCloseSidebar}
|
||||
@close-sidebar=${this._handleCloseSidebar}
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
@@ -187,37 +214,69 @@ export class HaManualScriptEditor extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="split-view">
|
||||
<div
|
||||
class=${classMap({
|
||||
"has-sidebar": this._sidebarConfig && !this.narrow,
|
||||
})}
|
||||
>
|
||||
<div class="content-wrapper">
|
||||
<div class="content">${this._renderContent()}</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveScript}
|
||||
<div
|
||||
class="content ${this._sidebarConfig && this.narrow
|
||||
? "has-bottom-sheet"
|
||||
: ""}"
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
<slot name="alerts"></slot>
|
||||
${this._renderContent()}
|
||||
</div>
|
||||
<div class="fab-positioner">
|
||||
<div class="fab-positioner">
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
class=${this.dirty ? "dirty" : ""}
|
||||
.label=${this.hass.localize("ui.common.save")}
|
||||
.disabled=${this.saving}
|
||||
extended
|
||||
@click=${this._saveScript}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||
</ha-fab>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-positioner">
|
||||
<ha-automation-sidebar
|
||||
.sidebarKey=${this._sidebarKey}
|
||||
tabindex="-1"
|
||||
class=${classMap({ hidden: !this._sidebarConfig })}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.hass=${this.hass}
|
||||
.config=${this._sidebarConfig}
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
<ha-automation-sidebar
|
||||
class=${classMap({
|
||||
sidebar: true,
|
||||
hidden: !this._sidebarConfig,
|
||||
overlay: !this.isWide,
|
||||
})}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
.hass=${this.hass}
|
||||
.config=${this._sidebarConfig}
|
||||
@value-changed=${this._sidebarConfigChanged}
|
||||
.disabled=${this.disabled}
|
||||
></ha-automation-sidebar>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
const expanded = extractSearchParam("expanded");
|
||||
if (expanded === "1") {
|
||||
this._clearParam("expanded");
|
||||
this.expandAll();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearParam(param: string) {
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
constructUrlCurrentPath(removeSearchParam(param))
|
||||
);
|
||||
}
|
||||
|
||||
private _fieldsChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
this.resetPastedConfig();
|
||||
@@ -318,7 +377,11 @@ export class HaManualScriptEditor extends LitElement {
|
||||
if (normalized) {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.dirty) {
|
||||
if (
|
||||
this.dirty ||
|
||||
ensureArray(this.config.sequence)?.length ||
|
||||
Object.keys(this.config.fields || {}).length
|
||||
) {
|
||||
const result = await new Promise<boolean>((resolve) => {
|
||||
showPasteReplaceDialog(this, {
|
||||
domain: "script",
|
||||
@@ -423,10 +486,14 @@ export class HaManualScriptEditor extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _openSidebar(ev: CustomEvent<SidebarConfig>) {
|
||||
private async _openSidebar(ev: CustomEvent<SidebarConfig>) {
|
||||
// deselect previous selected row
|
||||
this._sidebarConfig?.close?.();
|
||||
this._sidebarConfig = ev.detail;
|
||||
this._sidebarKey = JSON.stringify(this._sidebarConfig);
|
||||
|
||||
await this._sidebarElement?.updateComplete;
|
||||
this._sidebarElement?.focus();
|
||||
}
|
||||
|
||||
private _sidebarConfigChanged(ev: CustomEvent<{ value: SidebarConfig }>) {
|
||||
@@ -441,11 +508,13 @@ export class HaManualScriptEditor extends LitElement {
|
||||
};
|
||||
}
|
||||
|
||||
private _closeSidebar() {
|
||||
private _triggerCloseSidebar() {
|
||||
if (this._sidebarConfig) {
|
||||
const closeRow = this._sidebarConfig?.close;
|
||||
this._sidebarConfig = undefined;
|
||||
closeRow?.();
|
||||
if (this._sidebarElement) {
|
||||
this._sidebarElement.triggerCloseSidebar();
|
||||
return;
|
||||
}
|
||||
this._sidebarConfig?.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,81 +523,45 @@ export class HaManualScriptEditor extends LitElement {
|
||||
}
|
||||
|
||||
private _saveScript() {
|
||||
this._closeSidebar();
|
||||
this._triggerCloseSidebar();
|
||||
fireEvent(this, "save-script");
|
||||
}
|
||||
|
||||
public expandAll() {
|
||||
this._collapsableElements?.forEach((element) => {
|
||||
element.expandAll();
|
||||
});
|
||||
}
|
||||
|
||||
public collapseAll() {
|
||||
this._collapsableElements?.forEach((element) => {
|
||||
element.collapseAll();
|
||||
});
|
||||
}
|
||||
|
||||
public copySelectedRow() {
|
||||
if ((this._sidebarConfig as ActionSidebarConfig)?.copy) {
|
||||
(this._sidebarConfig as ActionSidebarConfig).copy();
|
||||
}
|
||||
}
|
||||
|
||||
public cutSelectedRow() {
|
||||
if ((this._sidebarConfig as ActionSidebarConfig)?.cut) {
|
||||
(this._sidebarConfig as ActionSidebarConfig).cut();
|
||||
}
|
||||
}
|
||||
|
||||
public deleteSelectedRow() {
|
||||
if ((this._sidebarConfig as ActionSidebarConfig)?.delete) {
|
||||
(this._sidebarConfig as ActionSidebarConfig).delete();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
saveFabStyles,
|
||||
manualEditorStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.split-view {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
position: relative;
|
||||
flex: 6;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 32px 16px 64px 0;
|
||||
height: calc(100vh - 153px);
|
||||
height: calc(100dvh - 153px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 12px 0;
|
||||
flex: 4;
|
||||
height: calc(100vh - 81px);
|
||||
height: calc(100dvh - 81px);
|
||||
width: 40%;
|
||||
}
|
||||
.sidebar.hidden {
|
||||
border-color: transparent;
|
||||
border-width: 0;
|
||||
overflow: hidden;
|
||||
flex: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.sidebar.overlay {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: calc(100% - 64px);
|
||||
padding: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
.split-view {
|
||||
gap: 0;
|
||||
margin-right: -8px;
|
||||
}
|
||||
.sidebar {
|
||||
height: 0;
|
||||
width: 0;
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar.overlay.hidden {
|
||||
width: 0;
|
||||
}
|
||||
.description {
|
||||
margin: 0;
|
||||
}
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -541,8 +574,9 @@ export class HaManualScriptEditor extends LitElement {
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
flex: 1;
|
||||
}
|
||||
.header a {
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
.description {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
mdiBackupRestore,
|
||||
mdiFolder,
|
||||
mdiInformation,
|
||||
mdiNas,
|
||||
mdiPlayBox,
|
||||
mdiReload,
|
||||
@@ -18,7 +19,6 @@ import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-list";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-metric";
|
||||
import "../../../components/ha-segmented-bar";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
@@ -47,6 +47,7 @@ import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk";
|
||||
import { showMountViewDialog } from "./show-dialog-view-mount";
|
||||
import type { Segment } from "../../../components/ha-segmented-bar";
|
||||
import { getGraphColorByIndex } from "../../../common/color/colors";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
|
||||
@customElement("ha-config-section-storage")
|
||||
class HaConfigSectionStorage extends LitElement {
|
||||
@@ -107,21 +108,7 @@ class HaConfigSectionStorage extends LitElement {
|
||||
this._hostInfo,
|
||||
this._storageInfo
|
||||
)}
|
||||
${this._hostInfo.disk_life_time !== null
|
||||
? // prettier-ignore
|
||||
html`
|
||||
<ha-metric
|
||||
.heading=${this.hass.localize(
|
||||
"ui.panel.config.storage.lifetime_used"
|
||||
)}
|
||||
.value=${this._hostInfo.disk_life_time}
|
||||
.tooltip=${this.hass.localize(
|
||||
"ui.panel.config.storage.lifetime_used_description"
|
||||
)}
|
||||
class="emmc"
|
||||
></ha-metric>
|
||||
`
|
||||
: ""}
|
||||
${this._renderDiskLifeTime(this._hostInfo.disk_life_time)}
|
||||
</div>
|
||||
${this._hostInfo
|
||||
? html`<div class="card-actions">
|
||||
@@ -237,6 +224,51 @@ class HaConfigSectionStorage extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderDiskLifeTime(diskLifeTime: number | null) {
|
||||
if (diskLifeTime === null) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const segments: Segment[] = [
|
||||
{
|
||||
color: "var(--primary-color)",
|
||||
value: diskLifeTime,
|
||||
},
|
||||
{
|
||||
color:
|
||||
"var(--ha-bar-background-color, var(--secondary-background-color))",
|
||||
value: 100 - diskLifeTime,
|
||||
},
|
||||
];
|
||||
|
||||
return html`
|
||||
<ha-segmented-bar
|
||||
.heading=${this.hass.localize("ui.panel.config.storage.lifetime")}
|
||||
.description=${this.hass.localize(
|
||||
"ui.panel.config.storage.lifetime_description",
|
||||
{
|
||||
lifetime: `${diskLifeTime}${blankBeforePercent(this.hass.locale)}%`,
|
||||
}
|
||||
)}
|
||||
.segments=${segments}
|
||||
hide-legend
|
||||
hide-tooltip
|
||||
>
|
||||
<ha-tooltip slot="extra">
|
||||
<ha-icon-button
|
||||
.path=${mdiInformation}
|
||||
class="help-button"
|
||||
></ha-icon-button>
|
||||
<p class="metric-description" slot="content">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.storage.lifetime_used_description"
|
||||
)}
|
||||
</p>
|
||||
</ha-tooltip>
|
||||
</ha-segmented-bar>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderStorageMetrics = memoizeOne(
|
||||
(hostInfo?: HassioHostInfo, storageInfo?: HostDisksUsage | null) => {
|
||||
if (!hostInfo) {
|
||||
@@ -295,8 +327,7 @@ class HaConfigSectionStorage extends LitElement {
|
||||
>${roundWithOneDecimal(freeSpaceGB)} GB</span
|
||||
>`,
|
||||
});
|
||||
const chart = html`
|
||||
<ha-segmented-bar
|
||||
return html`<ha-segmented-bar
|
||||
.heading=${this.hass.localize("ui.panel.config.storage.used_space")}
|
||||
.description=${this.hass.localize(
|
||||
"ui.panel.config.storage.detailed_description",
|
||||
@@ -307,17 +338,15 @@ class HaConfigSectionStorage extends LitElement {
|
||||
)}
|
||||
.segments=${segments}
|
||||
></ha-segmented-bar>
|
||||
`;
|
||||
return storageInfo || storageInfo === null
|
||||
? chart
|
||||
: html`
|
||||
<div class="loading-container">
|
||||
${chart}
|
||||
<div class="loading-overlay">
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
${!storageInfo || storageInfo === null
|
||||
? html`<ha-alert alert-type="info">
|
||||
<ha-spinner slot="icon"></ha-spinner>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.storage.loading_detailed"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: nothing}`;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -462,6 +491,12 @@ class HaConfigSectionStorage extends LitElement {
|
||||
inset-inline-start: initial;
|
||||
}
|
||||
|
||||
.help-button {
|
||||
--mdc-icon-button-size: 20px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.no-mounts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -484,6 +519,10 @@ class HaConfigSectionStorage extends LitElement {
|
||||
ha-icon-next {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
ha-alert ha-spinner {
|
||||
--ha-spinner-size: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,8 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import { hasScriptFields } from "../../../data/script";
|
||||
import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info-dialog";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
@@ -46,6 +48,14 @@ class HuiButtonCardFeature extends LitElement implements LovelaceCardFeature {
|
||||
const service =
|
||||
domain === "button" || domain === "input_button" ? "press" : "turn_on";
|
||||
|
||||
if (domain === "script") {
|
||||
const entityId = this._stateObj.entity_id;
|
||||
if (hasScriptFields(this.hass!, entityId)) {
|
||||
showMoreInfoDialog(this, { entityId: entityId });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.hass.callService(domain, service, {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
|
@@ -1,301 +0,0 @@
|
||||
import { css, html, LitElement, nothing, svg } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import {
|
||||
computeHistory,
|
||||
subscribeHistoryStatesTimeWindow,
|
||||
} from "../../../data/history";
|
||||
import type {
|
||||
HistoryResult,
|
||||
LineChartUnit,
|
||||
TimelineEntity,
|
||||
} from "../../../data/history";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature } from "../types";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
HistoryChartCardFeatureConfig,
|
||||
} from "./types";
|
||||
import { getSensorNumericDeviceClasses } from "../../../data/sensor";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { getGraphColorByIndex } from "../../../common/color/colors";
|
||||
import { computeTimelineColor } from "../../../components/chart/timeline-color";
|
||||
import { downSampleLineData } from "../../../components/chart/down-sample";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export const supportsHistoryChartCardFeature = (
|
||||
_hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
) =>
|
||||
!!context.entity_id &&
|
||||
["sensor", "binary_sensor"].includes(computeDomain(context.entity_id));
|
||||
|
||||
@customElement("hui-history-chart-card-feature")
|
||||
class HuiHistoryChartCardFeature
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false, hasChanged: () => false })
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: HistoryChartCardFeatureConfig;
|
||||
|
||||
@state() private _stateHistory?: HistoryResult;
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
static getStubConfig(): HistoryChartCardFeatureConfig {
|
||||
return {
|
||||
type: "history-chart",
|
||||
hours_to_show: 24,
|
||||
};
|
||||
}
|
||||
|
||||
public setConfig(config: HistoryChartCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// redraw the graph every minute to update the time axis
|
||||
clearInterval(this._interval);
|
||||
this._interval = window.setInterval(() => this.requestUpdate(), 1000 * 60);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
clearInterval(this._interval);
|
||||
}
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [this._subscribeHistory()];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.context ||
|
||||
!this._stateHistory ||
|
||||
!supportsHistoryChartCardFeature(this.hass, this.context)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const line = this._stateHistory.line[0];
|
||||
const timeline = this._stateHistory.timeline[0];
|
||||
const width = this.clientWidth;
|
||||
const height = this.clientHeight;
|
||||
if (line) {
|
||||
const points = this._generateLinePoints(line);
|
||||
const { paths, filledPaths } = this._getLinePaths(points);
|
||||
const color = getGraphColorByIndex(0, this.style);
|
||||
|
||||
return html`
|
||||
<div class="line" @click=${this._handleClick}>
|
||||
${svg`<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
||||
${paths.map(
|
||||
(path) =>
|
||||
svg`<path d="${path}" stroke="${color}" stroke-width="1" stroke-linecap="round" fill="none" />`
|
||||
)}
|
||||
${filledPaths.map(
|
||||
(path) =>
|
||||
svg`<path d="${path}" stroke="none" stroke-linecap="round" fill="${color}" fill-opacity="0.2" />`
|
||||
)}
|
||||
</svg>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (timeline) {
|
||||
const ranges = this._generateTimelineRanges(timeline);
|
||||
return html`
|
||||
<div class="timeline" @click=${this._handleClick}>
|
||||
${svg`<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
|
||||
<g>
|
||||
${ranges.map((r) => svg`<rect x="${r.startX}" y="0" width="${r.endX - r.startX}" height="${height}" fill="${r.color}" />`)}
|
||||
</g>
|
||||
</svg>`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private _handleClick() {
|
||||
// open more info dialog to show more detailed history
|
||||
fireEvent(this, "hass-more-info", { entityId: this.context!.entity_id! });
|
||||
}
|
||||
|
||||
private async _subscribeHistory(): Promise<() => Promise<void>> {
|
||||
if (
|
||||
!isComponentLoaded(this.hass!, "history") ||
|
||||
!this.context?.entity_id ||
|
||||
!this._config
|
||||
) {
|
||||
return () => Promise.resolve();
|
||||
}
|
||||
|
||||
const { numeric_device_classes: sensorNumericDeviceClasses } =
|
||||
await getSensorNumericDeviceClasses(this.hass!);
|
||||
|
||||
return subscribeHistoryStatesTimeWindow(
|
||||
this.hass!,
|
||||
(historyStates) => {
|
||||
this._stateHistory = computeHistory(
|
||||
this.hass!,
|
||||
historyStates,
|
||||
[this.context!.entity_id!],
|
||||
this.hass!.localize,
|
||||
sensorNumericDeviceClasses,
|
||||
false
|
||||
);
|
||||
},
|
||||
this._config!.hours_to_show ?? 24,
|
||||
[this.context!.entity_id!]
|
||||
);
|
||||
}
|
||||
|
||||
private _generateLinePoints(line: LineChartUnit): { x: number; y: number }[] {
|
||||
const width = this.clientWidth;
|
||||
const height = this.clientHeight;
|
||||
let minY = Number(line.data[0].states[0].state);
|
||||
let maxY = Number(line.data[0].states[0].state);
|
||||
const minX = line.data[0].states[0].last_changed;
|
||||
const maxX = Date.now();
|
||||
line.data[0].states.forEach((stateData) => {
|
||||
const stateValue = Number(stateData.state);
|
||||
if (stateValue < minY) {
|
||||
minY = stateValue;
|
||||
}
|
||||
if (stateValue > maxY) {
|
||||
maxY = stateValue;
|
||||
}
|
||||
});
|
||||
const rangeY = maxY - minY || minY * 0.1;
|
||||
const sampledData = downSampleLineData(
|
||||
line.data[0].states.map((stateData) => [
|
||||
stateData.last_changed,
|
||||
Number(stateData.state),
|
||||
]),
|
||||
width,
|
||||
minX,
|
||||
maxX
|
||||
);
|
||||
// add margin to the min and max
|
||||
minY -= rangeY * 0.1;
|
||||
maxY += rangeY * 0.1;
|
||||
const yDenom = maxY - minY || 1;
|
||||
const xDenom = maxX - minX || 1;
|
||||
const points = sampledData!.map((point) => {
|
||||
const x = ((point![0] - minX) / xDenom) * width;
|
||||
const y = height - ((Number(point![1]) - minY) / yDenom) * height;
|
||||
return { x, y };
|
||||
});
|
||||
points.push({ x: width, y: points[points.length - 1].y });
|
||||
return points;
|
||||
}
|
||||
|
||||
private _generateTimelineRanges(timeline: TimelineEntity) {
|
||||
if (timeline.data.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const width = this.clientWidth;
|
||||
const minX = timeline.data[0].last_changed;
|
||||
const maxX = Date.now();
|
||||
let prevEndX = 0;
|
||||
let prevStateColor = "";
|
||||
const ranges = timeline.data.map((t) => {
|
||||
const x = ((t.last_changed - minX) / (maxX - minX)) * width;
|
||||
const range = {
|
||||
startX: prevEndX,
|
||||
endX: x,
|
||||
color: prevStateColor,
|
||||
};
|
||||
prevStateColor = computeTimelineColor(
|
||||
t.state,
|
||||
computedStyles,
|
||||
this.hass!.states[timeline.entity_id]
|
||||
);
|
||||
prevEndX = x;
|
||||
return range;
|
||||
});
|
||||
ranges.push({
|
||||
startX: prevEndX,
|
||||
endX: width,
|
||||
color: prevStateColor,
|
||||
});
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private _getLinePaths(points: { x: number; y: number }[]) {
|
||||
const paths: string[] = [];
|
||||
const filledPaths: string[] = [];
|
||||
if (!points.length) {
|
||||
return { paths, filledPaths };
|
||||
}
|
||||
// path can interupted by missing data, so we need to split the path into segments
|
||||
const pathSegments: { x: number; y: number }[][] = [[]];
|
||||
points.forEach((point) => {
|
||||
if (!isNaN(point.y)) {
|
||||
pathSegments[pathSegments.length - 1].push(point);
|
||||
} else if (pathSegments[pathSegments.length - 1].length > 0) {
|
||||
pathSegments.push([]);
|
||||
}
|
||||
});
|
||||
|
||||
pathSegments.forEach((pathPoints) => {
|
||||
// create a smoothed path
|
||||
let next: { x: number; y: number };
|
||||
let path = "";
|
||||
let last = pathPoints[0];
|
||||
|
||||
path += `M ${last.x},${last.y}`;
|
||||
|
||||
pathPoints.forEach((coord) => {
|
||||
next = coord;
|
||||
path += ` ${(next.x + last.x) / 2},${(next.y + last.y) / 2}`;
|
||||
path += ` Q${next.x},${next.y}`;
|
||||
last = next;
|
||||
});
|
||||
|
||||
path += ` ${next!.x},${next!.y}`;
|
||||
paths.push(path);
|
||||
filledPaths.push(
|
||||
path +
|
||||
` L ${next!.x},${this.clientHeight} L ${pathPoints[0].x},${this.clientHeight} Z`
|
||||
);
|
||||
});
|
||||
|
||||
return { paths, filledPaths };
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: var(--feature-height);
|
||||
}
|
||||
:host > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.timeline {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-history-chart-card-feature": HuiHistoryChartCardFeature;
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { isNumericFromAttributes } from "../../../common/number/format_number";
|
||||
import "../../../components/ha-spinner";
|
||||
import { subscribeHistoryStatesTimeWindow } from "../../../data/history";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { coordinatesMinimalResponseCompressedState } from "../common/graph/coordinates";
|
||||
import "../components/hui-graph-base";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import type {
|
||||
TrendGraphCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
} from "./types";
|
||||
|
||||
export const supportsTrendGraphCardFeature = (
|
||||
hass: HomeAssistant,
|
||||
context: LovelaceCardFeatureContext
|
||||
) => {
|
||||
const stateObj = context.entity_id
|
||||
? hass.states[context.entity_id]
|
||||
: undefined;
|
||||
if (!stateObj) return false;
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
return domain === "sensor" && isNumericFromAttributes(stateObj.attributes);
|
||||
};
|
||||
|
||||
export const DEFAULT_HOURS_TO_SHOW = 24;
|
||||
|
||||
@customElement("hui-trend-graph-card-feature")
|
||||
class HuiHistoryChartCardFeature
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCardFeature
|
||||
{
|
||||
@property({ attribute: false, hasChanged: () => false })
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public context?: LovelaceCardFeatureContext;
|
||||
|
||||
@state() private _config?: TrendGraphCardFeatureConfig;
|
||||
|
||||
@state() private _coordinates?: [number, number][];
|
||||
|
||||
private _interval?: number;
|
||||
|
||||
static getStubConfig(): TrendGraphCardFeatureConfig {
|
||||
return {
|
||||
type: "trend-graph",
|
||||
};
|
||||
}
|
||||
|
||||
public static async getConfigElement(): Promise<LovelaceCardFeatureEditor> {
|
||||
await import(
|
||||
"../editor/config-elements/hui-trend-graph-card-feature-editor"
|
||||
);
|
||||
return document.createElement("hui-trend-graph-card-feature-editor");
|
||||
}
|
||||
|
||||
public setConfig(config: TrendGraphCardFeatureConfig): void {
|
||||
if (!config) {
|
||||
throw new Error("Invalid configuration");
|
||||
}
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
// redraw the graph every minute to update the time axis
|
||||
clearInterval(this._interval);
|
||||
this._interval = window.setInterval(() => this.requestUpdate(), 1000 * 60);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
clearInterval(this._interval);
|
||||
}
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [this._subscribeHistory()];
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
!this._config ||
|
||||
!this.hass ||
|
||||
!this.context ||
|
||||
!supportsTrendGraphCardFeature(this.hass, this.context)
|
||||
) {
|
||||
return nothing;
|
||||
}
|
||||
if (!this._coordinates) {
|
||||
return html`
|
||||
<div class="container">
|
||||
<ha-spinner size="small"></ha-spinner>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
if (!this._coordinates.length) {
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="info">No state history found.</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<hui-graph-base .coordinates=${this._coordinates}></hui-graph-base>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _subscribeHistory(): Promise<() => Promise<void>> {
|
||||
if (
|
||||
!isComponentLoaded(this.hass!, "history") ||
|
||||
!this.context?.entity_id ||
|
||||
!this._config
|
||||
) {
|
||||
return () => Promise.resolve();
|
||||
}
|
||||
|
||||
const hourToShow = this._config.hours_to_show ?? DEFAULT_HOURS_TO_SHOW;
|
||||
|
||||
return subscribeHistoryStatesTimeWindow(
|
||||
this.hass!,
|
||||
(historyStates) => {
|
||||
this._coordinates =
|
||||
coordinatesMinimalResponseCompressedState(
|
||||
historyStates[this.context!.entity_id!],
|
||||
hourToShow,
|
||||
500,
|
||||
2,
|
||||
undefined
|
||||
) || [];
|
||||
},
|
||||
hourToShow,
|
||||
[this.context!.entity_id!]
|
||||
);
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: var(--feature-height);
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
hui-graph-base {
|
||||
width: 100%;
|
||||
--accent-color: var(--feature-color);
|
||||
border-bottom-right-radius: 8px;
|
||||
border-bottom-left-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-trend-graph-card-feature": HuiHistoryChartCardFeature;
|
||||
}
|
||||
}
|
@@ -187,9 +187,9 @@ export interface UpdateActionsCardFeatureConfig {
|
||||
backup?: "yes" | "no" | "ask";
|
||||
}
|
||||
|
||||
export interface HistoryChartCardFeatureConfig {
|
||||
type: "history-chart";
|
||||
hours_to_show: number;
|
||||
export interface TrendGraphCardFeatureConfig {
|
||||
type: "trend-graph";
|
||||
hours_to_show?: number;
|
||||
}
|
||||
|
||||
export const AREA_CONTROLS = [
|
||||
@@ -239,6 +239,7 @@ export type LovelaceCardFeatureConfig =
|
||||
| FanOscillateCardFeatureConfig
|
||||
| FanPresetModesCardFeatureConfig
|
||||
| FanSpeedCardFeatureConfig
|
||||
| TrendGraphCardFeatureConfig
|
||||
| HumidifierToggleCardFeatureConfig
|
||||
| HumidifierModesCardFeatureConfig
|
||||
| LawnMowerCommandsCardFeatureConfig
|
||||
@@ -250,7 +251,7 @@ export type LovelaceCardFeatureConfig =
|
||||
| MediaPlayerVolumeSliderCardFeatureConfig
|
||||
| NumericInputCardFeatureConfig
|
||||
| SelectOptionsCardFeatureConfig
|
||||
| HistoryChartCardFeatureConfig
|
||||
| TrendGraphCardFeatureConfig
|
||||
| TargetHumidityCardFeatureConfig
|
||||
| TargetTemperatureCardFeatureConfig
|
||||
| ToggleCardFeatureConfig
|
||||
|
303
src/panels/lovelace/cards/clock/hui-clock-card-analog.ts
Normal file
303
src/panels/lovelace/cards/clock/hui-clock-card-analog.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import type { ClockCardConfig } from "../types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { INTERVAL } from "../hui-clock-card";
|
||||
import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone";
|
||||
|
||||
@customElement("hui-clock-card-analog")
|
||||
export class HuiClockCardAnalog extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config?: ClockCardConfig;
|
||||
|
||||
@state() private _dateTimeFormat?: Intl.DateTimeFormat;
|
||||
|
||||
@state() private _hourDeg?: number;
|
||||
|
||||
@state() private _minuteDeg?: number;
|
||||
|
||||
@state() private _secondDeg?: number;
|
||||
|
||||
private _tickInterval?: undefined | number;
|
||||
|
||||
private _initDate() {
|
||||
if (!this.config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
let locale = this.hass.locale;
|
||||
if (this.config.time_format) {
|
||||
locale = { ...locale, time_format: this.config.time_format };
|
||||
}
|
||||
|
||||
this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: "h12",
|
||||
timeZone:
|
||||
this.config.time_zone ||
|
||||
resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
|
||||
});
|
||||
|
||||
this._tick();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.locale !== this.hass?.locale) {
|
||||
this._initDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._startTick();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._stopTick();
|
||||
}
|
||||
|
||||
private _startTick() {
|
||||
this._tickInterval = window.setInterval(() => this._tick(), INTERVAL);
|
||||
this._tick();
|
||||
}
|
||||
|
||||
private _stopTick() {
|
||||
if (this._tickInterval) {
|
||||
clearInterval(this._tickInterval);
|
||||
this._tickInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _tick() {
|
||||
if (!this._dateTimeFormat) return;
|
||||
|
||||
const parts = this._dateTimeFormat.formatToParts();
|
||||
const hourStr = parts.find((p) => p.type === "hour")?.value;
|
||||
const minuteStr = parts.find((p) => p.type === "minute")?.value;
|
||||
const secondStr = parts.find((p) => p.type === "second")?.value;
|
||||
|
||||
const hour = hourStr ? parseInt(hourStr, 10) : 0;
|
||||
const minute = minuteStr ? parseInt(minuteStr, 10) : 0;
|
||||
const second = secondStr ? parseInt(secondStr, 10) : 0;
|
||||
|
||||
this._hourDeg = hour * 30 + minute * 0.5; // 30deg per hour + 0.5deg per minute
|
||||
this._minuteDeg = minute * 6 + second * 0.1; // 6deg per minute + 0.1deg per second
|
||||
this._secondDeg = this.config?.show_seconds ? second * 6 : undefined; // 6deg per second
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.config) return nothing;
|
||||
|
||||
const sizeClass = this.config.clock_size
|
||||
? `size-${this.config.clock_size}`
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="analog-clock ${sizeClass}"
|
||||
role="img"
|
||||
aria-label="Analog clock"
|
||||
>
|
||||
<div
|
||||
class=${classMap({
|
||||
dial: true,
|
||||
"dial-border": this.config.border ?? false,
|
||||
})}
|
||||
>
|
||||
${this.config.ticks === "quarter"
|
||||
? Array.from({ length: 4 }, (_, i) => i).map(
|
||||
(i) =>
|
||||
// 4 ticks
|
||||
html`
|
||||
<div
|
||||
aria-hidden
|
||||
class="tick hour"
|
||||
style=${`--tick-rotation: ${i * 90}deg;`}
|
||||
>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
: !this.config.ticks || // Default to hour ticks
|
||||
this.config.ticks === "hour"
|
||||
? Array.from({ length: 12 }, (_, i) => i).map(
|
||||
(i) =>
|
||||
// 12 ticks
|
||||
html`
|
||||
<div
|
||||
aria-hidden
|
||||
class="tick hour"
|
||||
style=${`--tick-rotation: ${i * 30}deg;`}
|
||||
>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
: this.config.ticks === "minute"
|
||||
? Array.from({ length: 60 }, (_, i) => i).map(
|
||||
(i) =>
|
||||
// 60 ticks
|
||||
html`
|
||||
<div
|
||||
aria-hidden
|
||||
class="tick ${i % 5 === 0 ? "hour" : "minute"}"
|
||||
style=${`--tick-rotation: ${i * 6}deg;`}
|
||||
>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
`
|
||||
)
|
||||
: nothing}
|
||||
<div class="center-dot"></div>
|
||||
<div
|
||||
class="hand hour"
|
||||
style=${`--hand-rotation: ${this._hourDeg ?? 0}deg;`}
|
||||
></div>
|
||||
<div
|
||||
class="hand minute"
|
||||
style=${`--hand-rotation: ${this._minuteDeg ?? 0}deg;`}
|
||||
></div>
|
||||
${this.config.show_seconds
|
||||
? html`<div
|
||||
class="hand second"
|
||||
style=${`--hand-rotation: ${this._secondDeg ?? 0}deg;`}
|
||||
></div>`
|
||||
: nothing}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.analog-clock {
|
||||
--clock-size: 100px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--clock-size);
|
||||
height: var(--clock-size);
|
||||
background: var(--ha-clock-card-analog-face-background, none);
|
||||
border-radius: var(--ha-clock-card-analog-face-border-radius, none);
|
||||
padding: var(--ha-clock-card-analog-face-padding, none);
|
||||
}
|
||||
|
||||
.analog-clock.size-medium {
|
||||
--clock-size: 160px;
|
||||
}
|
||||
|
||||
.analog-clock.size-large {
|
||||
--clock-size: 220px;
|
||||
}
|
||||
|
||||
.dial {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dial-border {
|
||||
border: 2px solid var(--divider-color);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.tick {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(var(--tick-rotation));
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.tick .line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 1px;
|
||||
height: calc(var(--clock-size) * 0.04);
|
||||
background: var(--primary-text-color);
|
||||
opacity: 0.5;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.tick.hour .line {
|
||||
width: 2px;
|
||||
height: calc(var(--clock-size) * 0.07);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.center-dot {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--primary-text-color);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.hand {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 50%;
|
||||
transform-origin: 50% 100%;
|
||||
transform: translate(-50%, 0) rotate(var(--hand-rotation, 0deg));
|
||||
background: var(--primary-text-color);
|
||||
border-radius: 2px;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.hand.hour {
|
||||
width: 4px;
|
||||
height: calc(var(--clock-size) * 0.25); /* 25% of the clock size */
|
||||
background: var(--primary-text-color);
|
||||
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.2);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hand.minute {
|
||||
width: 3px;
|
||||
height: calc(var(--clock-size) * 0.35); /* 35% of the clock size */
|
||||
background: var(--primary-text-color);
|
||||
box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.2);
|
||||
opacity: 0.9;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.hand.second {
|
||||
width: 2px;
|
||||
height: calc(var(--clock-size) * 0.42); /* 42% of the clock size */
|
||||
background: var(--ha-color-border-danger-normal);
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.2);
|
||||
opacity: 0.8;
|
||||
z-index: 2;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-clock-card-analog": HuiClockCardAnalog;
|
||||
}
|
||||
}
|
196
src/panels/lovelace/cards/clock/hui-clock-card-digital.ts
Normal file
196
src/panels/lovelace/cards/clock/hui-clock-card-digital.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { ClockCardConfig } from "../types";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { INTERVAL } from "../hui-clock-card";
|
||||
import { useAmPm } from "../../../../common/datetime/use_am_pm";
|
||||
import { resolveTimeZone } from "../../../../common/datetime/resolve-time-zone";
|
||||
|
||||
@customElement("hui-clock-card-digital")
|
||||
export class HuiClockCardDigital extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public config?: ClockCardConfig;
|
||||
|
||||
@state() private _dateTimeFormat?: Intl.DateTimeFormat;
|
||||
|
||||
@state() private _timeHour?: string;
|
||||
|
||||
@state() private _timeMinute?: string;
|
||||
|
||||
@state() private _timeSecond?: string;
|
||||
|
||||
@state() private _timeAmPm?: string;
|
||||
|
||||
private _tickInterval?: undefined | number;
|
||||
|
||||
private _initDate() {
|
||||
if (!this.config || !this.hass) {
|
||||
return;
|
||||
}
|
||||
|
||||
let locale = this.hass?.locale;
|
||||
|
||||
if (this.config?.time_format) {
|
||||
locale = { ...locale, time_format: this.config.time_format };
|
||||
}
|
||||
|
||||
this._dateTimeFormat = new Intl.DateTimeFormat(this.hass.locale.language, {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hourCycle: useAmPm(locale) ? "h12" : "h23",
|
||||
timeZone:
|
||||
this.config?.time_zone ||
|
||||
resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
|
||||
});
|
||||
|
||||
this._tick();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass");
|
||||
if (!oldHass || oldHass.locale !== this.hass?.locale) {
|
||||
this._initDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._startTick();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._stopTick();
|
||||
}
|
||||
|
||||
private _startTick() {
|
||||
this._tickInterval = window.setInterval(() => this._tick(), INTERVAL);
|
||||
this._tick();
|
||||
}
|
||||
|
||||
private _stopTick() {
|
||||
if (this._tickInterval) {
|
||||
clearInterval(this._tickInterval);
|
||||
this._tickInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _tick() {
|
||||
if (!this._dateTimeFormat) return;
|
||||
|
||||
const parts = this._dateTimeFormat.formatToParts();
|
||||
|
||||
this._timeHour = parts.find((part) => part.type === "hour")?.value;
|
||||
this._timeMinute = parts.find((part) => part.type === "minute")?.value;
|
||||
this._timeSecond = this.config?.show_seconds
|
||||
? parts.find((part) => part.type === "second")?.value
|
||||
: undefined;
|
||||
this._timeAmPm = parts.find((part) => part.type === "dayPeriod")?.value;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.config) return nothing;
|
||||
|
||||
const sizeClass = this.config.clock_size
|
||||
? `size-${this.config.clock_size}`
|
||||
: "";
|
||||
|
||||
return html`
|
||||
<div class="time-parts ${sizeClass}">
|
||||
<div class="time-part hour">${this._timeHour}</div>
|
||||
<div class="time-part minute">${this._timeMinute}</div>
|
||||
${this._timeSecond !== undefined
|
||||
? html`<div class="time-part second">${this._timeSecond}</div>`
|
||||
: nothing}
|
||||
${this._timeAmPm !== undefined
|
||||
? html`<div class="time-part am-pm">${this._timeAmPm}</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.time-parts {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"hour minute second"
|
||||
"hour minute am-pm";
|
||||
|
||||
font-size: 1.5rem;
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
line-height: 0.8;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.time-title + .time-parts {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.time-parts.size-medium {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.time-parts.size-large {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.time-parts.size-medium .time-part.second,
|
||||
.time-parts.size-medium .time-part.am-pm {
|
||||
font-size: var(--ha-font-size-l);
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.time-parts.size-large .time-part.second,
|
||||
.time-parts.size-large .time-part.am-pm {
|
||||
font-size: var(--ha-font-size-2xl);
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.time-parts .time-part.hour {
|
||||
grid-area: hour;
|
||||
}
|
||||
|
||||
.time-parts .time-part.minute {
|
||||
grid-area: minute;
|
||||
}
|
||||
|
||||
.time-parts .time-part.second {
|
||||
grid-area: second;
|
||||
line-height: 0.9;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.time-parts .time-part.am-pm {
|
||||
grid-area: am-pm;
|
||||
line-height: 0.9;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.time-parts .time-part.second,
|
||||
.time-parts .time-part.am-pm {
|
||||
font-size: var(--ha-font-size-xs);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.time-parts .time-part.hour:after {
|
||||
content: ":";
|
||||
margin: 0 2px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-clock-card-digital": HuiClockCardDigital;
|
||||
}
|
||||
}
|
@@ -109,7 +109,7 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.home"
|
||||
),
|
||||
value: Math.max(0, consumption.total.used_total),
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
color: computedStyle.getPropertyValue("--primary-color").trim(),
|
||||
index: 1,
|
||||
};
|
||||
nodes.push(homeNode);
|
||||
@@ -125,7 +125,9 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.battery"
|
||||
),
|
||||
value: totalBatteryOut,
|
||||
color: computedStyle.getPropertyValue("--energy-battery-out-color"),
|
||||
color: computedStyle
|
||||
.getPropertyValue("--energy-battery-out-color")
|
||||
.trim(),
|
||||
index: 0,
|
||||
});
|
||||
links.push({
|
||||
@@ -141,7 +143,9 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.battery"
|
||||
),
|
||||
value: totalBatteryIn,
|
||||
color: computedStyle.getPropertyValue("--energy-battery-in-color"),
|
||||
color: computedStyle
|
||||
.getPropertyValue("--energy-battery-in-color")
|
||||
.trim(),
|
||||
index: 1,
|
||||
});
|
||||
if (consumption.total.grid_to_battery > 0) {
|
||||
@@ -169,9 +173,9 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.grid"
|
||||
),
|
||||
value: totalFromGrid,
|
||||
color: computedStyle.getPropertyValue(
|
||||
"--energy-grid-consumption-color"
|
||||
),
|
||||
color: computedStyle
|
||||
.getPropertyValue("--energy-grid-consumption-color")
|
||||
.trim(),
|
||||
index: 0,
|
||||
});
|
||||
|
||||
@@ -192,7 +196,7 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.solar"
|
||||
),
|
||||
value: totalSolarProduction,
|
||||
color: computedStyle.getPropertyValue("--energy-solar-color"),
|
||||
color: computedStyle.getPropertyValue("--energy-solar-color").trim(),
|
||||
index: 0,
|
||||
});
|
||||
|
||||
@@ -213,13 +217,15 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_distribution.grid"
|
||||
),
|
||||
value: totalToGrid,
|
||||
color: computedStyle.getPropertyValue("--energy-grid-return-color"),
|
||||
color: computedStyle
|
||||
.getPropertyValue("--energy-grid-return-color")
|
||||
.trim(),
|
||||
index: 1,
|
||||
});
|
||||
if (consumption.total.battery_to_grid > 0) {
|
||||
links.push({
|
||||
source: "battery",
|
||||
target: "grid",
|
||||
target: "grid_return",
|
||||
value: consumption.total.battery_to_grid,
|
||||
});
|
||||
}
|
||||
@@ -295,7 +301,7 @@ class HuiEnergySankeyCard
|
||||
label: this.hass.floors[floorId].name,
|
||||
value: floors[floorId].value,
|
||||
index: 2,
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
color: computedStyle.getPropertyValue("--primary-color").trim(),
|
||||
});
|
||||
links.push({
|
||||
source: "home",
|
||||
@@ -316,7 +322,7 @@ class HuiEnergySankeyCard
|
||||
label: this.hass.areas[areaId]!.name,
|
||||
value: areas[areaId].value,
|
||||
index: 3,
|
||||
color: computedStyle.getPropertyValue("--primary-color"),
|
||||
color: computedStyle.getPropertyValue("--primary-color").trim(),
|
||||
});
|
||||
links.push({
|
||||
source: floorNodeId,
|
||||
@@ -360,7 +366,9 @@ class HuiEnergySankeyCard
|
||||
"ui.panel.lovelace.cards.energy.energy_devices_detail_graph.untracked_consumption"
|
||||
),
|
||||
value: untrackedConsumption,
|
||||
color: computedStyle.getPropertyValue("--state-unavailable-color"),
|
||||
color: computedStyle
|
||||
.getPropertyValue("--state-unavailable-color")
|
||||
.trim(),
|
||||
index: 3 + deviceSections.length,
|
||||
});
|
||||
links.push({
|
||||
|
@@ -54,7 +54,7 @@ export const DEVICE_CLASSES = {
|
||||
binary_sensor: ["motion", "moisture"],
|
||||
};
|
||||
|
||||
const SUM_DEVICE_CLASSES = [
|
||||
export const SUM_DEVICE_CLASSES = [
|
||||
"power",
|
||||
"apparent_power",
|
||||
"reactive_power",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user