mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-05 07:37:20 +00:00
Compare commits
102 Commits
20251203.0
...
fix-profil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
438a1b18c1 | ||
|
|
1400398422 | ||
|
|
506d466c03 | ||
|
|
46735c72ed | ||
|
|
c43d41053b | ||
|
|
844d53a0ba | ||
|
|
1c8b78eae9 | ||
|
|
a918e878fa | ||
|
|
ebc354bf55 | ||
|
|
98a1f5ca3a | ||
|
|
48015ab312 | ||
|
|
09515b1937 | ||
|
|
5a40627676 | ||
|
|
cd34447603 | ||
|
|
803fabbf64 | ||
|
|
78c4dc48d0 | ||
|
|
147600ea43 | ||
|
|
2f91f0dd15 | ||
|
|
3fa330acfb | ||
|
|
e0a6b671ce | ||
|
|
d6edd150a8 | ||
|
|
2c00889921 | ||
|
|
0447d87f18 | ||
|
|
d7e18b0520 | ||
|
|
e7254b1587 | ||
|
|
8681a7d450 | ||
|
|
fff12acb6b | ||
|
|
3d327ed628 | ||
|
|
0d51648de1 | ||
|
|
c5642c15b8 | ||
|
|
7f885010de | ||
|
|
356d51f974 | ||
|
|
38a907e51e | ||
|
|
87c0b1d887 | ||
|
|
0f195015b7 | ||
|
|
17a976af67 | ||
|
|
a41a7e822a | ||
|
|
5473bf56c6 | ||
|
|
029eba7ab8 | ||
|
|
824a3f288d | ||
|
|
fdd89c05d3 | ||
|
|
c33cb7fff9 | ||
|
|
de53ad8dce | ||
|
|
2dec7490b6 | ||
|
|
6ed4bd5ce8 | ||
|
|
4e899c56ed | ||
|
|
53c20a0493 | ||
|
|
be2d6e9212 | ||
|
|
915442c571 | ||
|
|
de6ebc2d0a | ||
|
|
bcb12fa062 | ||
|
|
078915743d | ||
|
|
54c524127f | ||
|
|
334e1c35e1 | ||
|
|
528c7727e2 | ||
|
|
d1043e33df | ||
|
|
b088f2c0e5 | ||
|
|
b33f407493 | ||
|
|
502d76b316 | ||
|
|
1ddc07c215 | ||
|
|
a611a5fc4e | ||
|
|
c13bece6d0 | ||
|
|
28a89ff9e6 | ||
|
|
81b5ddec9d | ||
|
|
ce86aabe32 | ||
|
|
a8910bcbe4 | ||
|
|
0e4cf9f62d | ||
|
|
506635d649 | ||
|
|
27e24ee49b | ||
|
|
bcd712b48c | ||
|
|
461ef9b916 | ||
|
|
b794989daa | ||
|
|
6727ffaae0 | ||
|
|
4df8501b20 | ||
|
|
539d0e443f | ||
|
|
9c9d274b5c | ||
|
|
d6e6bc0e80 | ||
|
|
530a70b168 | ||
|
|
23137500f8 | ||
|
|
63e7ed21a4 | ||
|
|
92611f46f4 | ||
|
|
9bc896241d | ||
|
|
2baafe620c | ||
|
|
ce52bbaf8c | ||
|
|
0b4b8d9082 | ||
|
|
bddbb773b7 | ||
|
|
d52e1e8835 | ||
|
|
0a9dccfd19 | ||
|
|
bfd78670cc | ||
|
|
11276af1a0 | ||
|
|
d7be46c00b | ||
|
|
94f32ce242 | ||
|
|
ef3e8186bc | ||
|
|
50fcf622aa | ||
|
|
77c2444be8 | ||
|
|
e5cb26cd3d | ||
|
|
2896519bfd | ||
|
|
0b6e35eb53 | ||
|
|
e80a855f87 | ||
|
|
7c88cf4e30 | ||
|
|
9001cd3e65 | ||
|
|
ca8923d8f4 |
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -57,4 +57,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
|
||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
||||
|
||||
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
|
||||
2
.github/workflows/relative-ci.yaml
vendored
2
.github/workflows/relative-ci.yaml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send bundle stats and build information to RelativeCI
|
||||
uses: relative-ci/agent-action@feb19ddc698445db27401f1490f6ac182da0816f # v3.2.0
|
||||
uses: relative-ci/agent-action@c45aaa919ef85620af54242a241ac17a8fa35983 # v3.2.1
|
||||
with:
|
||||
key: ${{ secrets[format('RELATIVE_CI_KEY_{0}_{1}', matrix.bundle, matrix.build)] }}
|
||||
token: ${{ github.token }}
|
||||
|
||||
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
script/release
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
files: |
|
||||
dist/*.whl
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
# home-assistant/wheels doesn't support SHA pinning
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2025.10.0
|
||||
uses: home-assistant/wheels@2025.11.0
|
||||
with:
|
||||
abi: cp313
|
||||
tag: musllinux_1_2
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
- name: Tar folder
|
||||
run: tar -czf landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz -C landing-page/dist .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
files: landing-page/home_assistant_frontend_landingpage-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
@@ -137,6 +137,6 @@ jobs:
|
||||
- name: Tar folder
|
||||
run: tar -czf hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz -C hassio/build .
|
||||
- name: Upload release asset
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||
with:
|
||||
files: hassio/home_assistant_frontend_supervisor-${{ github.event.release.tag_name }}.tar.gz
|
||||
|
||||
@@ -156,7 +156,9 @@ const createTestTranslation = () =>
|
||||
*/
|
||||
const createMasterTranslation = () =>
|
||||
gulp
|
||||
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])])
|
||||
.src([EN_SRC, ...(mergeBackend ? [`${inBackendDir}/en.json`] : [])], {
|
||||
allowEmpty: true,
|
||||
})
|
||||
.pipe(new CustomJSON(lokaliseTransform))
|
||||
.pipe(new MergeJSON("en"))
|
||||
.pipe(gulp.dest(workDir));
|
||||
|
||||
@@ -305,9 +305,8 @@ export class HcMain extends HassElement {
|
||||
await llColl.refresh();
|
||||
this._unsubLovelace = llColl.subscribe(async (rawConfig) => {
|
||||
if (isStrategyDashboard(rawConfig)) {
|
||||
const { generateLovelaceDashboardStrategy } = await import(
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
const { generateLovelaceDashboardStrategy } =
|
||||
await import("../../../../src/panels/lovelace/strategies/get-strategy");
|
||||
const config = await generateLovelaceDashboardStrategy(
|
||||
rawConfig,
|
||||
this.hass!
|
||||
@@ -347,9 +346,8 @@ export class HcMain extends HassElement {
|
||||
}
|
||||
|
||||
private async _generateDefaultLovelaceConfig() {
|
||||
const { generateLovelaceDashboardStrategy } = await import(
|
||||
"../../../../src/panels/lovelace/strategies/get-strategy"
|
||||
);
|
||||
const { generateLovelaceDashboardStrategy } =
|
||||
await import("../../../../src/panels/lovelace/strategies/get-strategy");
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(DEFAULT_CONFIG, this.hass!)
|
||||
);
|
||||
|
||||
@@ -44,18 +44,24 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
power: [
|
||||
{ stat_rate: "sensor.power_grid" },
|
||||
{ stat_rate: "sensor.power_grid_return" },
|
||||
],
|
||||
cost_adjustment_day: 0,
|
||||
},
|
||||
{
|
||||
type: "solar",
|
||||
stat_energy_from: "sensor.solar_production",
|
||||
stat_rate: "sensor.power_solar",
|
||||
config_entry_solar_forecast: ["solar_forecast"],
|
||||
},
|
||||
/* {
|
||||
{
|
||||
type: "battery",
|
||||
stat_energy_from: "sensor.battery_output",
|
||||
stat_energy_to: "sensor.battery_input",
|
||||
}, */
|
||||
stat_rate: "sensor.power_battery",
|
||||
},
|
||||
{
|
||||
type: "gas",
|
||||
stat_energy_from: "sensor.energy_gas",
|
||||
@@ -63,28 +69,48 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
{
|
||||
type: "water",
|
||||
stat_energy_from: "sensor.energy_water",
|
||||
stat_cost: "sensor.energy_water_cost",
|
||||
entity_energy_price: null,
|
||||
number_energy_price: null,
|
||||
},
|
||||
],
|
||||
device_consumption: [
|
||||
{
|
||||
stat_consumption: "sensor.energy_car",
|
||||
stat_rate: "sensor.power_car",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_ac",
|
||||
stat_rate: "sensor.power_ac",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_washing_machine",
|
||||
stat_rate: "sensor.power_washing_machine",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_dryer",
|
||||
stat_rate: "sensor.power_dryer",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_heat_pump",
|
||||
stat_rate: "sensor.power_heat_pump",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.energy_boiler",
|
||||
stat_rate: "sensor.power_boiler",
|
||||
},
|
||||
],
|
||||
device_consumption_water: [
|
||||
{
|
||||
stat_consumption: "sensor.water_kitchen",
|
||||
},
|
||||
{
|
||||
stat_consumption: "sensor.water_garden",
|
||||
},
|
||||
],
|
||||
device_consumption_water: [],
|
||||
})
|
||||
);
|
||||
hass.mockWS(
|
||||
|
||||
@@ -154,6 +154,38 @@ export const energyEntities = () =>
|
||||
unit_of_measurement: "EUR",
|
||||
},
|
||||
},
|
||||
"sensor.power_grid": {
|
||||
entity_id: "sensor.power_grid",
|
||||
state: "500",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_grid_return": {
|
||||
entity_id: "sensor.power_grid_return",
|
||||
state: "-100",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_solar": {
|
||||
entity_id: "sensor.power_solar",
|
||||
state: "200",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_battery": {
|
||||
entity_id: "sensor.power_battery",
|
||||
state: "100",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.energy_gas_cost": {
|
||||
entity_id: "sensor.energy_gas_cost",
|
||||
state: "2",
|
||||
@@ -171,6 +203,15 @@ export const energyEntities = () =>
|
||||
unit_of_measurement: "m³",
|
||||
},
|
||||
},
|
||||
"sensor.energy_water": {
|
||||
entity_id: "sensor.energy_water",
|
||||
state: "4000",
|
||||
attributes: {
|
||||
last_reset: "1970-01-01T00:00:00:00+00",
|
||||
friendly_name: "Water",
|
||||
unit_of_measurement: "L",
|
||||
},
|
||||
},
|
||||
"sensor.energy_car": {
|
||||
entity_id: "sensor.energy_car",
|
||||
state: "4",
|
||||
@@ -225,4 +266,58 @@ export const energyEntities = () =>
|
||||
unit_of_measurement: "kWh",
|
||||
},
|
||||
},
|
||||
"sensor.power_car": {
|
||||
entity_id: "sensor.power_car",
|
||||
state: "40",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Electric car",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_ac": {
|
||||
entity_id: "sensor.power_ac",
|
||||
state: "30",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Air conditioning",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_washing_machine": {
|
||||
entity_id: "sensor.power_washing_machine",
|
||||
state: "60",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Washing machine",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_dryer": {
|
||||
entity_id: "sensor.power_dryer",
|
||||
state: "55",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Dryer",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_heat_pump": {
|
||||
entity_id: "sensor.power_heat_pump",
|
||||
state: "60",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Heat pump",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
"sensor.power_boiler": {
|
||||
entity_id: "sensor.power_boiler",
|
||||
state: "70",
|
||||
attributes: {
|
||||
state_class: "measurement",
|
||||
friendly_name: "Boiler",
|
||||
unit_of_measurement: "W",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,17 +17,15 @@ const generateMeanStatistics = (
|
||||
end: Date,
|
||||
// eslint-disable-next-line default-param-last
|
||||
period: "5minute" | "hour" | "day" | "month" = "hour",
|
||||
initValue: number,
|
||||
maxDiff: number
|
||||
): StatisticValue[] => {
|
||||
const statistics: StatisticValue[] = [];
|
||||
let currentDate = new Date(start);
|
||||
currentDate.setMinutes(0, 0, 0);
|
||||
let lastVal = initValue;
|
||||
const now = new Date();
|
||||
while (end > currentDate && currentDate < now) {
|
||||
const delta = Math.random() * maxDiff;
|
||||
const mean = lastVal + delta;
|
||||
const mean = delta;
|
||||
statistics.push({
|
||||
start: currentDate.getTime(),
|
||||
end: currentDate.getTime(),
|
||||
@@ -38,7 +36,6 @@ const generateMeanStatistics = (
|
||||
state: mean,
|
||||
sum: null,
|
||||
});
|
||||
lastVal = mean;
|
||||
currentDate =
|
||||
period === "day"
|
||||
? addDays(currentDate, 1)
|
||||
@@ -336,7 +333,6 @@ export const mockRecorder = (mockHass: MockHomeAssistant) => {
|
||||
start,
|
||||
end,
|
||||
period,
|
||||
state,
|
||||
state * (state > 80 ? 0.05 : 0.1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -381,10 +381,6 @@ export class DemoHaWaDialog extends LitElement {
|
||||
<td><code>--dialog-z-index</code></td>
|
||||
<td>Z-index for the dialog.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-surface-position</code></td>
|
||||
<td>CSS position of the dialog surface.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>--dialog-surface-margin-top</code></td>
|
||||
<td>Top margin for the dialog surface.</td>
|
||||
|
||||
@@ -88,8 +88,8 @@ class HassioRegistriesDialog extends LitElement {
|
||||
<ha-button
|
||||
?disabled=${Boolean(
|
||||
!this._input.registry ||
|
||||
!this._input.username ||
|
||||
!this._input.password
|
||||
!this._input.username ||
|
||||
!this._input.password
|
||||
)}
|
||||
@click=${this._addNewRegistry}
|
||||
appearance="filled"
|
||||
|
||||
20
package.json
20
package.json
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.0",
|
||||
"@home-assistant/webawesome": "3.0.0-ha.1",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
@@ -89,8 +89,8 @@
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@tsparticles/engine": "3.9.1",
|
||||
"@tsparticles/preset-links": "3.2.0",
|
||||
"@vaadin/combo-box": "24.9.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.9.5",
|
||||
"@vaadin/combo-box": "24.9.6",
|
||||
"@vaadin/vaadin-themable-mixin": "24.9.6",
|
||||
"@vibrant/color": "4.0.0",
|
||||
"@vue/web-component-wrapper": "1.3.0",
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.10",
|
||||
@@ -152,13 +152,13 @@
|
||||
"@babel/helper-define-polyfill-provider": "0.6.5",
|
||||
"@babel/plugin-transform-runtime": "7.28.5",
|
||||
"@babel/preset-env": "7.28.5",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.6",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.21.7",
|
||||
"@lokalise/node-api": "15.4.0",
|
||||
"@octokit/auth-oauth-device": "8.0.3",
|
||||
"@octokit/plugin-retry": "8.0.3",
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@rsdoctor/rspack-plugin": "1.3.11",
|
||||
"@rspack/core": "1.6.4",
|
||||
"@rspack/core": "1.6.5",
|
||||
"@rspack/dev-server": "1.1.4",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.22",
|
||||
@@ -178,7 +178,7 @@
|
||||
"@types/tar": "6.1.13",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@vitest/coverage-v8": "4.0.13",
|
||||
"@vitest/coverage-v8": "4.0.14",
|
||||
"babel-loader": "10.0.0",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"browserslist-useragent-regexp": "4.1.3",
|
||||
@@ -194,7 +194,7 @@
|
||||
"eslint-plugin-wc": "3.0.2",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.3.2",
|
||||
"glob": "12.0.0",
|
||||
"glob": "13.0.0",
|
||||
"gulp": "5.0.1",
|
||||
"gulp-brotli": "3.0.0",
|
||||
"gulp-json-transform": "0.5.0",
|
||||
@@ -209,7 +209,7 @@
|
||||
"lodash.template": "4.5.0",
|
||||
"map-stream": "0.0.7",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.6.2",
|
||||
"prettier": "3.7.3",
|
||||
"rspack-manifest-plugin": "5.2.0",
|
||||
"serve": "14.2.5",
|
||||
"sinon": "21.0.0",
|
||||
@@ -217,9 +217,9 @@
|
||||
"terser-webpack-plugin": "5.3.14",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.47.0",
|
||||
"typescript-eslint": "8.48.0",
|
||||
"vite-tsconfig-paths": "5.1.4",
|
||||
"vitest": "4.0.13",
|
||||
"vitest": "4.0.14",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
"webpackbar": "7.0.0",
|
||||
"workbox-build": "patch:workbox-build@npm%3A7.1.1#~/.yarn/patches/workbox-build-npm-7.1.1-a854f3faae.patch"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20251029.0"
|
||||
version = "20251203.0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { AuthData } from "home-assistant-js-websocket";
|
||||
import { extractSearchParam } from "../url/search-params";
|
||||
import { hassUrl } from "../../data/auth";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -30,7 +31,11 @@ export function askWrite() {
|
||||
export function saveTokens(tokens: AuthData | null) {
|
||||
tokenCache.tokens = tokens;
|
||||
|
||||
if (!tokenCache.writeEnabled && extractSearchParam("storeToken") === "true") {
|
||||
if (
|
||||
!tokenCache.writeEnabled &&
|
||||
(extractSearchParam("storeToken") === "true" ||
|
||||
hassUrl !== `${location.protocol}//${location.host}`)
|
||||
) {
|
||||
tokenCache.writeEnabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,8 @@ export const computeFormatFunctions = async (
|
||||
formatEntityAttributeName: FormatEntityAttributeNameFunc;
|
||||
formatEntityName: FormatEntityNameFunc;
|
||||
}> => {
|
||||
const { computeStateDisplay } = await import(
|
||||
"../entity/compute_state_display"
|
||||
);
|
||||
const { computeStateDisplay } =
|
||||
await import("../entity/compute_state_display");
|
||||
const { computeAttributeValueDisplay, computeAttributeNameDisplay } =
|
||||
await import("../entity/compute_attribute_display");
|
||||
|
||||
|
||||
@@ -593,6 +593,7 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
const options = {
|
||||
animation: !this._reducedMotion,
|
||||
animationDuration: 500,
|
||||
darkMode: this._themes.darkMode ?? false,
|
||||
aria: { show: true },
|
||||
dataZoom: this._getDataZoomConfig(),
|
||||
|
||||
@@ -167,6 +167,7 @@ export class HaSankeyChart extends LitElement {
|
||||
curveness: 0.5,
|
||||
},
|
||||
layoutIterations: 0,
|
||||
animationDuration: 500,
|
||||
label: {
|
||||
formatter: (params) =>
|
||||
data.nodes.find((node) => node.id === (params.data as Node).id)
|
||||
@@ -279,6 +280,7 @@ export class HaSankeyChart extends LitElement {
|
||||
:host {
|
||||
display: block;
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
background: var(--ha-card-background, var(--card-background-color));
|
||||
}
|
||||
ha-chart-base {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import type { VisualMapComponentOption } from "echarts/components";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import type { YAXisOption } from "echarts/types/dist/shared";
|
||||
@@ -27,6 +27,7 @@ const safeParseFloat = (value) => {
|
||||
return isFinite(parsed) ? parsed : null;
|
||||
};
|
||||
|
||||
@customElement("state-history-chart-line")
|
||||
export class StateHistoryChartLine extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -795,7 +796,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
return Math.abs(value) < 1 ? value : roundingFn(value);
|
||||
}
|
||||
}
|
||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
||||
@@ -373,6 +373,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
itemName: 3,
|
||||
},
|
||||
renderItem: this._renderItem,
|
||||
progressive: 0,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -838,10 +838,10 @@ export class HaDataTable extends LitElement {
|
||||
} else if (this.sortDirection === "asc") {
|
||||
this.sortDirection = "desc";
|
||||
} else {
|
||||
this.sortDirection = null;
|
||||
this.sortDirection = "asc";
|
||||
}
|
||||
|
||||
this.sortColumn = this.sortDirection === null ? undefined : columnId;
|
||||
this.sortColumn = columnId;
|
||||
|
||||
fireEvent(this, "sorting-changed", {
|
||||
column: columnId,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mdiAlert } from "@mdi/js";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { computeDomain } from "../../common/entity/compute_domain";
|
||||
@@ -17,6 +17,7 @@ import { CLIMATE_HVAC_ACTION_TO_MODE } from "../../data/climate";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "../ha-state-icon";
|
||||
|
||||
@customElement("state-badge")
|
||||
export class StateBadge extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@@ -265,5 +266,3 @@ declare global {
|
||||
"state-badge": StateBadge;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("state-badge", StateBadge);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-expansion-panel";
|
||||
import "./ha-items-display-editor";
|
||||
@@ -37,11 +36,7 @@ export class HaAreasDisplayEditor extends LitElement {
|
||||
public showNavigationButton = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const compare = areaCompare(this.hass.areas);
|
||||
|
||||
const areas = Object.values(this.hass.areas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
const areas = Object.values(this.hass.areas);
|
||||
|
||||
const items: DisplayItem[] = areas.map((area) => {
|
||||
const { floor } = getAreaContext(area, this.hass.floors);
|
||||
|
||||
@@ -7,7 +7,6 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeFloorName } from "../common/entity/compute_floor_name";
|
||||
import { getAreaContext } from "../common/entity/context/get_area_context";
|
||||
import { areaCompare } from "../data/area_registry";
|
||||
import type { FloorRegistryEntry } from "../data/floor_registry";
|
||||
import { getFloors } from "../panels/lovelace/strategies/areas/helpers/areas-strategy-helper";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -131,11 +130,8 @@ export class HaAreasFloorsDisplayEditor extends LitElement {
|
||||
// update items if floors change
|
||||
_hassFloors: HomeAssistant["floors"]
|
||||
): Record<string, DisplayItem[]> => {
|
||||
const compare = areaCompare(hassAreas);
|
||||
const areas = Object.values(hassAreas);
|
||||
|
||||
const areas = Object.values(hassAreas).sort((areaA, areaB) =>
|
||||
compare(areaA.area_id, areaB.area_id)
|
||||
);
|
||||
const groupedItems: Record<string, DisplayItem[]> = areas.reduce(
|
||||
(acc, area) => {
|
||||
const { floor } = getAreaContext(area, this.hass.floors);
|
||||
|
||||
@@ -659,6 +659,7 @@ export class HaAssistChat extends LitElement {
|
||||
--markdown-table-border-color: var(--divider-color);
|
||||
--markdown-code-background-color: var(--primary-background-color);
|
||||
--markdown-code-text-color: var(--primary-text-color);
|
||||
--markdown-list-indent: 1rem;
|
||||
&:not(:has(ha-markdown-element)) {
|
||||
min-height: 1lh;
|
||||
min-width: 1lh;
|
||||
|
||||
@@ -21,7 +21,8 @@ export class HaBottomSheet extends LitElement {
|
||||
|
||||
private _isDragging = false;
|
||||
|
||||
private _handleAfterHide() {
|
||||
private _handleAfterHide(afterHideEvent: Event) {
|
||||
afterHideEvent.stopPropagation();
|
||||
this.open = false;
|
||||
const ev = new Event("closed", {
|
||||
bubbles: true,
|
||||
|
||||
@@ -202,6 +202,7 @@ export class HaControlSelect extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
border-radius: var(--control-select-border-radius);
|
||||
}
|
||||
:host([vertical]) {
|
||||
width: var(--control-select-thickness);
|
||||
@@ -211,7 +212,6 @@ export class HaControlSelect extends LitElement {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--control-select-border-radius);
|
||||
transform: translateZ(0);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -167,30 +167,33 @@ export class HaFilterLabels extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
private async _labelSelected(ev: CustomEvent<SelectedDetail<Set<number>>>) {
|
||||
if (!ev.detail.index.size) {
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value: [],
|
||||
items: undefined,
|
||||
});
|
||||
this.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const value: string[] = [];
|
||||
const filteredLabels = this._filteredLabels(
|
||||
this._labels,
|
||||
this._filter,
|
||||
this.value
|
||||
);
|
||||
|
||||
const filteredLabelIds = new Set(filteredLabels.map((l) => l.label_id));
|
||||
|
||||
// Keep previously selected labels that are not in the current filtered view
|
||||
const preservedLabels = (this.value || []).filter(
|
||||
(id) => !filteredLabelIds.has(id)
|
||||
);
|
||||
|
||||
// Build the new selection from the filtered labels based on selected indices
|
||||
const newlySelectedLabels: string[] = [];
|
||||
for (const index of ev.detail.index) {
|
||||
const labelId = filteredLabels[index].label_id;
|
||||
value.push(labelId);
|
||||
const labelId = filteredLabels[index]?.label_id;
|
||||
if (labelId) {
|
||||
newlySelectedLabels.push(labelId);
|
||||
}
|
||||
}
|
||||
this.value = value;
|
||||
|
||||
const value = [...preservedLabels, ...newlySelectedLabels];
|
||||
this.value = value.length ? value : [];
|
||||
|
||||
fireEvent(this, "data-table-filter-changed", {
|
||||
value,
|
||||
value: value.length ? value : undefined,
|
||||
items: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ export class HaGenericPicker extends LitElement {
|
||||
});
|
||||
};
|
||||
|
||||
private _hidePicker(ev) {
|
||||
private _hidePicker(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
if (this._newValue) {
|
||||
fireEvent(this, "value-changed", { value: this._newValue });
|
||||
|
||||
@@ -73,6 +73,8 @@ export class HaLanguagePicker extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property() public helper?: string;
|
||||
|
||||
@property({ attribute: "native-name", type: Boolean })
|
||||
public nativeName = false;
|
||||
|
||||
@@ -135,6 +137,7 @@ export class HaLanguagePicker extends LitElement {
|
||||
.value=${value}
|
||||
.valueRenderer=${this._valueRenderer}
|
||||
.disabled=${this.disabled}
|
||||
.helper=${this.helper}
|
||||
.getItems=${this._getItems}
|
||||
@value-changed=${this._changed}
|
||||
hide-clear-icon
|
||||
|
||||
@@ -71,7 +71,7 @@ class HaMarkdownElement extends ReactiveElement {
|
||||
if (!this.innerHTML && this.cache) {
|
||||
const key = this._computeCacheKey();
|
||||
if (markdownCache.has(key)) {
|
||||
render(markdownCache.get(key)!, this.renderRoot);
|
||||
render(h(unsafeHTML(markdownCache.get(key))), this.renderRoot);
|
||||
this._resize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,13 +71,11 @@ export class HaMarkdown extends LitElement {
|
||||
color: var(--markdown-link-color, var(--primary-color));
|
||||
}
|
||||
img {
|
||||
background-color: rgba(10, 10, 10, 0.15);
|
||||
background-color: var(--markdown-image-background-color);
|
||||
border-radius: var(--markdown-image-border-radius);
|
||||
max-width: 100%;
|
||||
min-height: 2lh;
|
||||
height: auto;
|
||||
width: auto;
|
||||
text-indent: 4px;
|
||||
transition: height 0.2s ease-in-out;
|
||||
}
|
||||
p:first-child > img:first-child {
|
||||
@@ -86,10 +84,9 @@ export class HaMarkdown extends LitElement {
|
||||
p:first-child > img:last-child {
|
||||
vertical-align: top;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style-position: inside;
|
||||
padding-inline-start: 0;
|
||||
:host > ul,
|
||||
:host > ol {
|
||||
padding-inline-start: var(--markdown-list-indent, revert);
|
||||
}
|
||||
li {
|
||||
&:has(input[type="checkbox"]) {
|
||||
@@ -140,16 +137,19 @@ export class HaMarkdown extends LitElement {
|
||||
margin: var(--ha-space-4) 0;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
border-collapse: var(--markdown-table-border-collapse, collapse);
|
||||
}
|
||||
div:has(> table) {
|
||||
overflow: auto;
|
||||
}
|
||||
th {
|
||||
text-align: start;
|
||||
}
|
||||
td,
|
||||
th {
|
||||
border: 1px solid var(--markdown-table-border-color, transparent);
|
||||
border-width: var(--markdown-table-border-width, 1px);
|
||||
border-style: var(--markdown-table-border-style, solid);
|
||||
border-color: var(--markdown-table-border-color, var(--divider-color));
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
blockquote {
|
||||
|
||||
@@ -103,8 +103,8 @@ export class HaPickerField extends LitElement {
|
||||
--md-list-item-two-line-container-height: 56px;
|
||||
--md-list-item-top-space: 0px;
|
||||
--md-list-item-bottom-space: 0px;
|
||||
--md-list-item-leading-space: 8px;
|
||||
--md-list-item-trailing-space: 8px;
|
||||
--md-list-item-leading-space: var(--ha-space-4);
|
||||
--md-list-item-trailing-space: var(--ha-space-2);
|
||||
--ha-md-list-item-gap: var(--ha-space-2);
|
||||
/* Remove the default focus ring */
|
||||
--md-focus-ring-width: 0px;
|
||||
|
||||
@@ -450,7 +450,7 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
!shouldRenderServiceDataYaml &&
|
||||
serviceData?.flatFields.some((field) => showOptionalToggle(field))
|
||||
serviceData?.flatFields.some((field) => showOptionalToggle(field))
|
||||
);
|
||||
|
||||
const targetEntities = this._getTargetedEntities(
|
||||
@@ -467,7 +467,7 @@ export class HaServiceControl extends LitElement {
|
||||
|
||||
const descriptionPlaceholders =
|
||||
domain && serviceName
|
||||
? this.hass.services[domain][serviceName].description_placeholders
|
||||
? this.hass.services[domain]?.[serviceName]?.description_placeholders
|
||||
: undefined;
|
||||
|
||||
const description =
|
||||
|
||||
@@ -197,6 +197,8 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
|
||||
private _mouseLeaveTimeout?: number;
|
||||
|
||||
private _touchendTimeout?: number;
|
||||
|
||||
private _tooltipHideTimeout?: number;
|
||||
|
||||
private _recentKeydownActiveUntil = 0;
|
||||
@@ -237,6 +239,18 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
];
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
// clear timeouts
|
||||
clearTimeout(this._mouseLeaveTimeout);
|
||||
clearTimeout(this._tooltipHideTimeout);
|
||||
clearTimeout(this._touchendTimeout);
|
||||
// set undefined values
|
||||
this._mouseLeaveTimeout = undefined;
|
||||
this._tooltipHideTimeout = undefined;
|
||||
this._touchendTimeout = undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
@@ -406,6 +420,7 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
class="ha-scrollbar"
|
||||
@focusin=${this._listboxFocusIn}
|
||||
@focusout=${this._listboxFocusOut}
|
||||
@touchend=${this._listboxTouchend}
|
||||
@scroll=${this._listboxScroll}
|
||||
@keydown=${this._listboxKeydown}
|
||||
>
|
||||
@@ -620,6 +635,14 @@ class HaSidebar extends SubscribeMixin(LitElement) {
|
||||
this._hideTooltip();
|
||||
}
|
||||
|
||||
private _listboxTouchend() {
|
||||
clearTimeout(this._touchendTimeout);
|
||||
this._touchendTimeout = window.setTimeout(() => {
|
||||
// Allow 1 second for users to read the tooltip on touch devices
|
||||
this._hideTooltip();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
@eventOptions({
|
||||
passive: true,
|
||||
})
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,6 @@ import {
|
||||
mdiDevices,
|
||||
mdiFormatListBulleted,
|
||||
mdiGestureDoubleTap,
|
||||
mdiHomeAssistant,
|
||||
mdiMapMarker,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMessageAlert,
|
||||
@@ -23,6 +22,7 @@ import { customElement, property } from "lit/decorators";
|
||||
import { until } from "lit/directives/until";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { FALLBACK_DOMAIN_ICONS, triggerIcon } from "../data/icons";
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import "./ha-icon";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import {
|
||||
customElement,
|
||||
@@ -7,8 +9,6 @@ import {
|
||||
state,
|
||||
} from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import "@home-assistant/webawesome/dist/components/dialog/dialog";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
@@ -49,10 +49,10 @@ export type DialogWidth = "small" | "medium" | "large" | "full";
|
||||
* @cssprop --ha-dialog-surface-background - Dialog background color.
|
||||
* @cssprop --ha-dialog-border-radius - Border radius of the dialog surface.
|
||||
* @cssprop --dialog-z-index - Z-index for the dialog.
|
||||
* @cssprop --dialog-surface-position - CSS position of the dialog surface.
|
||||
* @cssprop --dialog-surface-margin-top - Top margin for the dialog surface.
|
||||
*
|
||||
* @attr {boolean} open - Controls the dialog open state.
|
||||
* @attr {("alert"|"standard")} type - Dialog type. Defaults to "standard".
|
||||
* @attr {("small"|"medium"|"large"|"full")} width - Preferred dialog width preset. Defaults to "medium".
|
||||
* @attr {boolean} prevent-scrim-close - Prevents closing the dialog by clicking the scrim/overlay. Defaults to false.
|
||||
* @attr {string} header-title - Header title text. If not set, the headerTitle slot is used.
|
||||
@@ -84,6 +84,9 @@ export class HaWaDialog extends LitElement {
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public open = false;
|
||||
|
||||
@property({ reflect: true })
|
||||
public type: "alert" | "standard" = "standard";
|
||||
|
||||
@property({ type: String, reflect: true, attribute: "width" })
|
||||
public width: DialogWidth = "medium";
|
||||
|
||||
@@ -172,7 +175,9 @@ export class HaWaDialog extends LitElement {
|
||||
|
||||
await this.updateComplete;
|
||||
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
requestAnimationFrame(() => {
|
||||
(this.querySelector("[autofocus]") as HTMLElement | null)?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
private _handleAfterShow = () => {
|
||||
@@ -198,18 +203,7 @@ export class HaWaDialog extends LitElement {
|
||||
haStyleScrollbar,
|
||||
css`
|
||||
wa-dialog {
|
||||
--full-width: var(
|
||||
--ha-dialog-width-full,
|
||||
min(
|
||||
95vw,
|
||||
calc(
|
||||
100vw - var(--safe-area-inset-left, var(--ha-space-0)) - var(
|
||||
--safe-area-inset-right,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
--full-width: var(--ha-dialog-width-full, min(95vw, var(--safe-width)));
|
||||
--width: min(var(--ha-dialog-width-md, 580px), var(--full-width));
|
||||
--spacing: var(--dialog-content-padding, var(--ha-space-6));
|
||||
--show-duration: var(--ha-dialog-show-duration, 200ms);
|
||||
@@ -226,8 +220,7 @@ export class HaWaDialog extends LitElement {
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-3xl)
|
||||
);
|
||||
max-width: var(--ha-dialog-max-width, 100vw);
|
||||
max-width: var(--ha-dialog-max-width, 100svw);
|
||||
max-width: var(--ha-dialog-max-width, var(--safe-width));
|
||||
}
|
||||
|
||||
:host([width="small"]) wa-dialog {
|
||||
@@ -247,34 +240,56 @@ export class HaWaDialog extends LitElement {
|
||||
max-width: var(--width, var(--full-width));
|
||||
max-height: var(
|
||||
--ha-dialog-max-height,
|
||||
calc(100% - var(--ha-space-20))
|
||||
calc(var(--safe-height) - var(--ha-space-20))
|
||||
);
|
||||
min-height: var(--ha-dialog-min-height);
|
||||
position: var(--dialog-surface-position, relative);
|
||||
margin-top: var(--dialog-surface-margin-top, auto);
|
||||
/* Used to offset the dialog from the safe areas when space is limited */
|
||||
transform: translate(
|
||||
calc(
|
||||
var(--safe-area-offset-left, var(--ha-space-0)) - var(
|
||||
--safe-area-offset-right,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
),
|
||||
calc(
|
||||
var(--safe-area-offset-top, var(--ha-space-0)) - var(
|
||||
--safe-area-offset-bottom,
|
||||
var(--ha-space-0)
|
||||
)
|
||||
)
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
:host {
|
||||
:host([type="standard"]) {
|
||||
--ha-dialog-border-radius: var(--ha-space-0);
|
||||
}
|
||||
|
||||
wa-dialog {
|
||||
--full-width: var(--ha-dialog-width-full, 100vw);
|
||||
}
|
||||
wa-dialog {
|
||||
/* Make the container fill the whole screen width and not the safe width */
|
||||
--full-width: var(--ha-dialog-width-full, 100vw);
|
||||
--width: var(--full-width);
|
||||
}
|
||||
|
||||
wa-dialog::part(dialog) {
|
||||
min-height: var(--ha-dialog-min-height, 100vh);
|
||||
min-height: var(--ha-dialog-min-height, 100svh);
|
||||
max-height: var(--ha-dialog-max-height, 100vh);
|
||||
max-height: var(--ha-dialog-max-height, 100svh);
|
||||
padding-top: var(--safe-area-inset-top, var(--ha-space-0));
|
||||
padding-bottom: var(--safe-area-inset-bottom, var(--ha-space-0));
|
||||
padding-left: var(--safe-area-inset-left, var(--ha-space-0));
|
||||
padding-right: var(--safe-area-inset-right, var(--ha-space-0));
|
||||
wa-dialog::part(dialog) {
|
||||
/* Make the dialog fill the whole screen height and not the safe height */
|
||||
min-height: var(--ha-dialog-min-height, 100vh);
|
||||
min-height: var(--ha-dialog-min-height, 100dvh);
|
||||
max-height: var(--ha-dialog-max-height, 100vh);
|
||||
max-height: var(--ha-dialog-max-height, 100dvh);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
/* Use safe area as padding instead of the container size */
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
/* Reset the transform to center the dialog */
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { property } from "lit/decorators";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../ha-state-icon";
|
||||
|
||||
@customElement("ha-entity-marker")
|
||||
class HaEntityMarker extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -89,8 +90,6 @@ class HaEntityMarker extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("ha-entity-marker", HaEntityMarker);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-entity-marker": HaEntityMarker;
|
||||
|
||||
@@ -223,6 +223,7 @@ const getAreasAndFloorsItems = (
|
||||
}
|
||||
|
||||
let outputAreas = areas;
|
||||
let outputFloors = floors;
|
||||
|
||||
let areaIds: string[] | undefined;
|
||||
|
||||
@@ -254,9 +255,29 @@ const getAreasAndFloorsItems = (
|
||||
outputAreas = outputAreas.filter(
|
||||
(area) => !area.floor_id || !excludeFloors!.includes(area.floor_id)
|
||||
);
|
||||
|
||||
outputFloors = outputFloors.filter(
|
||||
(floor) => !excludeFloors!.includes(floor.floor_id)
|
||||
);
|
||||
}
|
||||
|
||||
const hierarchy = getAreasFloorHierarchy(floors, outputAreas);
|
||||
if (
|
||||
entityFilter ||
|
||||
deviceFilter ||
|
||||
includeDomains ||
|
||||
excludeDomains ||
|
||||
includeDeviceClasses
|
||||
) {
|
||||
// Ensure we only include floors that have areas with the filtered entities/devices
|
||||
const validFloorIds = new Set(
|
||||
outputAreas.map((area) => area.floor_id).filter((id) => id)
|
||||
);
|
||||
outputFloors = outputFloors.filter((floor) =>
|
||||
validFloorIds.has(floor.floor_id)
|
||||
);
|
||||
}
|
||||
|
||||
const hierarchy = getAreasFloorHierarchy(outputFloors, outputAreas);
|
||||
|
||||
const items: (
|
||||
| FloorComboBoxItem
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { DeviceRegistryEntry } from "./device_registry";
|
||||
import type {
|
||||
@@ -75,17 +74,11 @@ export const reorderAreaRegistryEntries = (
|
||||
});
|
||||
|
||||
export const getAreaEntityLookup = (
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
||||
filterHidden = false
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
): AreaEntityLookup => {
|
||||
const areaEntityLookup: AreaEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
!entity.area_id ||
|
||||
(filterHidden &&
|
||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
||||
(entity as EntityRegistryEntry).hidden_by))
|
||||
) {
|
||||
if (!entity.area_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.area_id in areaEntityLookup)) {
|
||||
@@ -111,22 +104,3 @@ export const getAreaDeviceLookup = (
|
||||
}
|
||||
return areaDeviceLookup;
|
||||
};
|
||||
|
||||
export const areaCompare =
|
||||
(entries?: HomeAssistant["areas"], order?: string[]) =>
|
||||
(a: string, b: string) => {
|
||||
const indexA = order ? order.indexOf(a) : -1;
|
||||
const indexB = order ? order.indexOf(b) : -1;
|
||||
if (indexA === -1 && indexB === -1) {
|
||||
const nameA = entries?.[a]?.name ?? a;
|
||||
const nameB = entries?.[b]?.name ?? b;
|
||||
return stringCompare(nameA, nameB);
|
||||
}
|
||||
if (indexA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (indexB === -1) {
|
||||
return -1;
|
||||
}
|
||||
return indexA - indexB;
|
||||
};
|
||||
|
||||
@@ -144,9 +144,7 @@ const tryDescribeTrigger = (
|
||||
const type = getTriggerObjectId(trigger.trigger);
|
||||
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${domain}.triggers.${type}.description_configured`
|
||||
) ||
|
||||
hass.localize(`component.${domain}.triggers.${type}.name`) ||
|
||||
hass.localize(
|
||||
`ui.panel.config.automation.editor.triggers.type.${triggerType as LegacyTrigger["trigger"]}.label`
|
||||
) ||
|
||||
@@ -919,9 +917,7 @@ const tryDescribeCondition = (
|
||||
const type = getConditionObjectId(condition.condition);
|
||||
|
||||
return (
|
||||
hass.localize(
|
||||
`component.${domain}.conditions.${type}.description_configured`
|
||||
) ||
|
||||
hass.localize(`component.${domain}.conditions.${type}.name`) ||
|
||||
hass.localize(
|
||||
`ui.panel.config.automation.editor.conditions.type.${conditionType as LegacyCondition["condition"]}.label`
|
||||
) ||
|
||||
|
||||
@@ -111,17 +111,11 @@ export const sortDeviceRegistryByName = (
|
||||
);
|
||||
|
||||
export const getDeviceEntityLookup = (
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[],
|
||||
filterHidden = false
|
||||
entities: (EntityRegistryEntry | EntityRegistryDisplayEntry)[]
|
||||
): DeviceEntityLookup => {
|
||||
const deviceEntityLookup: DeviceEntityLookup = {};
|
||||
for (const entity of entities) {
|
||||
if (
|
||||
!entity.device_id ||
|
||||
(filterHidden &&
|
||||
((entity as EntityRegistryDisplayEntry).hidden ||
|
||||
(entity as EntityRegistryEntry).hidden_by))
|
||||
) {
|
||||
if (!entity.device_id) {
|
||||
continue;
|
||||
}
|
||||
if (!(entity.device_id in deviceEntityLookup)) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { stringCompare } from "../common/string/compare";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import type { AreaRegistryEntry } from "./area_registry";
|
||||
import type { RegistryEntry } from "./registry";
|
||||
@@ -75,27 +74,3 @@ export const getFloorAreaLookup = (
|
||||
}
|
||||
return floorAreaLookup;
|
||||
};
|
||||
|
||||
export const floorCompare =
|
||||
(entries?: HomeAssistant["floors"], order?: string[]) =>
|
||||
(a: string, b: string) => {
|
||||
const indexA = order ? order.indexOf(a) : -1;
|
||||
const indexB = order ? order.indexOf(b) : -1;
|
||||
if (indexA === -1 && indexB === -1) {
|
||||
const floorA = entries?.[a];
|
||||
const floorB = entries?.[b];
|
||||
if (floorA && floorB && floorA.level !== floorB.level) {
|
||||
return (floorB.level ?? -9999) - (floorA.level ?? -9999);
|
||||
}
|
||||
const nameA = floorA?.name ?? a;
|
||||
const nameB = floorB?.name ?? b;
|
||||
return stringCompare(nameA, nameB);
|
||||
}
|
||||
if (indexA === -1) {
|
||||
return 1;
|
||||
}
|
||||
if (indexB === -1) {
|
||||
return -1;
|
||||
}
|
||||
return indexA - indexB;
|
||||
};
|
||||
|
||||
@@ -47,8 +47,7 @@ export interface HassioFullBackupCreateParams {
|
||||
confirm_password?: string;
|
||||
background?: boolean;
|
||||
}
|
||||
export interface HassioPartialBackupCreateParams
|
||||
extends HassioFullBackupCreateParams {
|
||||
export interface HassioPartialBackupCreateParams extends HassioFullBackupCreateParams {
|
||||
folders?: string[];
|
||||
addons?: string[];
|
||||
homeassistant?: boolean;
|
||||
|
||||
@@ -18,6 +18,11 @@ export interface LabPreviewFeaturesResponse {
|
||||
features: LabPreviewFeature[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all lab features
|
||||
* @param hass - The Home Assistant instance
|
||||
* @returns A promise to fetch the lab features
|
||||
*/
|
||||
export const fetchLabFeatures = async (
|
||||
hass: HomeAssistant
|
||||
): Promise<LabPreviewFeature[]> => {
|
||||
@@ -27,6 +32,15 @@ export const fetchLabFeatures = async (
|
||||
return response.features;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a specific lab feature
|
||||
* @param hass - The Home Assistant instance
|
||||
* @param domain - The domain of the lab feature
|
||||
* @param preview_feature - The preview feature of the lab feature
|
||||
* @param enabled - Whether the lab feature is enabled
|
||||
* @param create_backup - Whether to create a backup of the lab feature
|
||||
* @returns A promise to update the lab feature
|
||||
*/
|
||||
export const labsUpdatePreviewFeature = (
|
||||
hass: HomeAssistant,
|
||||
domain: string,
|
||||
@@ -65,6 +79,12 @@ const subscribeLabUpdates = (
|
||||
"labs_updated"
|
||||
);
|
||||
|
||||
/**
|
||||
* Subscribe to a collection of lab features
|
||||
* @param conn - The connection to the Home Assistant instance
|
||||
* @param onChange - The function to call when the lab features change
|
||||
* @returns The unsubscribe function
|
||||
*/
|
||||
export const subscribeLabFeatures = (
|
||||
conn: Connection,
|
||||
onChange: (features: LabPreviewFeature[]) => void
|
||||
@@ -76,3 +96,27 @@ export const subscribeLabFeatures = (
|
||||
conn,
|
||||
onChange
|
||||
);
|
||||
|
||||
/**
|
||||
* Subscribe to a specific lab feature
|
||||
* @param conn - The connection to the Home Assistant instance
|
||||
* @param domain - The domain of the lab feature
|
||||
* @param previewFeature - The preview feature of the lab feature
|
||||
* @param onChange - The function to call when the lab feature changes
|
||||
* @returns The unsubscribe function
|
||||
*/
|
||||
export const subscribeLabFeature = (
|
||||
conn: Connection,
|
||||
domain: string,
|
||||
previewFeature: string,
|
||||
onChange: (enabled: boolean) => void
|
||||
) =>
|
||||
subscribeLabFeatures(conn, (features) => {
|
||||
const enabled =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === domain &&
|
||||
feature.preview_feature === previewFeature
|
||||
)?.enabled ?? false;
|
||||
onChange(enabled);
|
||||
});
|
||||
|
||||
@@ -18,8 +18,7 @@ export const enum LawnMowerEntityFeature {
|
||||
}
|
||||
|
||||
interface LawnMowerEntityAttributes
|
||||
extends HassEntityAttributeBase,
|
||||
Record<string, any> {}
|
||||
extends HassEntityAttributeBase, Record<string, any> {}
|
||||
|
||||
export interface LawnMowerEntity extends HassEntityBase {
|
||||
attributes: LawnMowerEntityAttributes;
|
||||
|
||||
@@ -18,8 +18,7 @@ export interface LovelaceSectionConfig extends LovelaceBaseSectionConfig {
|
||||
cards?: LovelaceCardConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceStrategySectionConfig
|
||||
extends LovelaceBaseSectionConfig {
|
||||
export interface LovelaceStrategySectionConfig extends LovelaceBaseSectionConfig {
|
||||
strategy: LovelaceStrategyConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,7 @@ export interface LovelaceConfig extends LovelaceDashboardBaseConfig {
|
||||
views: LovelaceViewRawConfig[];
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardStrategyConfig
|
||||
extends LovelaceDashboardBaseConfig {
|
||||
export interface LovelaceDashboardStrategyConfig extends LovelaceDashboardBaseConfig {
|
||||
strategy: LovelaceStrategyConfig;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ export interface LovelaceDashboardMutableParams {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardCreateParams
|
||||
extends LovelaceDashboardMutableParams {
|
||||
export interface LovelaceDashboardCreateParams extends LovelaceDashboardMutableParams {
|
||||
url_path: string;
|
||||
mode: "storage";
|
||||
}
|
||||
|
||||
@@ -106,8 +106,7 @@ export interface AutomationTrace extends BaseTrace {
|
||||
}
|
||||
|
||||
export interface AutomationTraceExtended
|
||||
extends AutomationTrace,
|
||||
BaseTraceExtended {
|
||||
extends AutomationTrace, BaseTraceExtended {
|
||||
config: ManualAutomationConfig;
|
||||
blueprint_inputs?: BlueprintAutomationConfig;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,12 @@ export interface WeatherEntity extends HassEntityBase {
|
||||
attributes: WeatherEntityAttributes;
|
||||
}
|
||||
|
||||
export const WEATHER_TEMPERATURE_ATTRIBUTES = new Set<string>([
|
||||
"temperature",
|
||||
"apparent_temperature",
|
||||
"dew_point",
|
||||
]);
|
||||
|
||||
export const weatherSVGs = new Set<string>([
|
||||
"clear-night",
|
||||
"cloudy",
|
||||
@@ -256,9 +262,15 @@ export const getWeatherUnit = (
|
||||
export const getSecondaryWeatherAttribute = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
forecast: ForecastAttribute[]
|
||||
forecast: ForecastAttribute[],
|
||||
temperatureFractionDigits?: number
|
||||
): TemplateResult | undefined => {
|
||||
const extrema = getWeatherExtrema(hass, stateObj, forecast);
|
||||
const extrema = getWeatherExtrema(
|
||||
hass,
|
||||
stateObj,
|
||||
forecast,
|
||||
temperatureFractionDigits
|
||||
);
|
||||
|
||||
if (extrema) {
|
||||
return extrema;
|
||||
@@ -298,7 +310,8 @@ export const getSecondaryWeatherAttribute = (
|
||||
const getWeatherExtrema = (
|
||||
hass: HomeAssistant,
|
||||
stateObj: WeatherEntity,
|
||||
forecast: ForecastAttribute[]
|
||||
forecast: ForecastAttribute[],
|
||||
temperatureFractionDigits?: number
|
||||
): TemplateResult | undefined => {
|
||||
if (!forecast?.length) {
|
||||
return undefined;
|
||||
@@ -313,13 +326,22 @@ const getWeatherExtrema = (
|
||||
break;
|
||||
}
|
||||
if (!tempHigh || fc.temperature > tempHigh) {
|
||||
tempHigh = fc.temperature;
|
||||
tempHigh =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.temperature
|
||||
: round(fc.temperature, temperatureFractionDigits);
|
||||
}
|
||||
if (!tempLow || (fc.templow && fc.templow < tempLow)) {
|
||||
tempLow = fc.templow;
|
||||
if (fc.templow !== undefined && (!tempLow || fc.templow < tempLow)) {
|
||||
tempLow =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.templow
|
||||
: round(fc.templow, temperatureFractionDigits);
|
||||
}
|
||||
if (!fc.templow && (!tempLow || fc.temperature < tempLow)) {
|
||||
tempLow = fc.temperature;
|
||||
tempLow =
|
||||
temperatureFractionDigits === undefined
|
||||
? fc.temperature
|
||||
: round(fc.temperature, temperatureFractionDigits);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { mdiAlertOutline, mdiClose } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-button";
|
||||
@@ -64,6 +65,7 @@ class DialogBox extends LitElement {
|
||||
<ha-wa-dialog
|
||||
.hass=${this.hass}
|
||||
.open=${this._open}
|
||||
type=${confirmPrompt ? "alert" : "standard"}
|
||||
?prevent-scrim-close=${confirmPrompt}
|
||||
@closed=${this._dialogClosed}
|
||||
aria-labelledby="dialog-box-title"
|
||||
@@ -79,7 +81,11 @@ class DialogBox extends LitElement {
|
||||
></ha-icon-button
|
||||
></slot>`
|
||||
: nothing}
|
||||
<span slot="title" id="dialog-box-title">
|
||||
<span
|
||||
class=${classMap({ title: true, alert: confirmPrompt })}
|
||||
slot="title"
|
||||
id="dialog-box-title"
|
||||
>
|
||||
${this._params.warning
|
||||
? html`<ha-svg-icon
|
||||
.path=${mdiAlertOutline}
|
||||
@@ -199,6 +205,14 @@ class DialogBox extends LitElement {
|
||||
ha-textfield {
|
||||
width: 100%;
|
||||
}
|
||||
.title.alert {
|
||||
padding: 0 var(--ha-space-2);
|
||||
}
|
||||
@media all and (min-width: 450px) and (min-height: 500px) {
|
||||
.title.alert {
|
||||
padding: 0 var(--ha-space-1);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,7 @@ export interface PromptDialogParams extends BaseDialogBoxParams {
|
||||
}
|
||||
|
||||
export interface DialogBoxParams
|
||||
extends ConfirmationDialogParams,
|
||||
PromptDialogParams {
|
||||
extends ConfirmationDialogParams, PromptDialogParams {
|
||||
confirm?: (out?: string) => void;
|
||||
confirmation?: boolean;
|
||||
prompt?: boolean;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||
import { ancestorsWithProperty } from "../common/dom/ancestors-with-property";
|
||||
import { deepActiveElement } from "../common/dom/deep-active-element";
|
||||
import type { HASSDomEvent, ValidHassDomEvent } from "../common/dom/fire_event";
|
||||
import { mainWindow } from "../common/dom/get_main_window";
|
||||
import { nextRender } from "../common/util/render-status";
|
||||
import type { ProvideHassElement } from "../mixins/provide-hass-lit-mixin";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
@@ -19,10 +19,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export interface HassDialog<T = HASSDomEvents[ValidHassDomEvent]>
|
||||
extends HTMLElement {
|
||||
export interface HassDialog<
|
||||
T = HASSDomEvents[ValidHassDomEvent],
|
||||
> extends HTMLElement {
|
||||
showDialog(params: T);
|
||||
closeDialog?: () => boolean;
|
||||
closeDialog?: (historyState?: any) => boolean;
|
||||
}
|
||||
|
||||
interface ShowDialogParams<T> {
|
||||
@@ -143,27 +144,32 @@ export const showDialog = async (
|
||||
return true;
|
||||
};
|
||||
|
||||
export const closeDialog = async (dialogTag: string): Promise<boolean> => {
|
||||
export const closeDialog = async (
|
||||
dialogTag: string,
|
||||
historyState?: any
|
||||
): Promise<boolean> => {
|
||||
if (!(dialogTag in LOADED)) {
|
||||
return true;
|
||||
}
|
||||
const dialogElement = await LOADED[dialogTag].element;
|
||||
if (dialogElement.closeDialog) {
|
||||
return dialogElement.closeDialog() !== false;
|
||||
return dialogElement.closeDialog(historyState) !== false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// called on back()
|
||||
export const closeLastDialog = async () => {
|
||||
export const closeLastDialog = async (historyState?: any) => {
|
||||
if (OPEN_DIALOG_STACK.length) {
|
||||
const lastDialog = OPEN_DIALOG_STACK.pop();
|
||||
const closed = await closeDialog(lastDialog!.dialogTag);
|
||||
const lastDialog = OPEN_DIALOG_STACK.pop() as DialogState;
|
||||
const closed = await closeDialog(lastDialog.dialogTag, historyState);
|
||||
if (!closed) {
|
||||
// if the dialog was not closed, put it back on the stack
|
||||
OPEN_DIALOG_STACK.push(lastDialog!);
|
||||
}
|
||||
if (OPEN_DIALOG_STACK.length && mainWindow.history.state?.opensDialog) {
|
||||
OPEN_DIALOG_STACK.push(lastDialog);
|
||||
} else if (
|
||||
OPEN_DIALOG_STACK.length &&
|
||||
mainWindow.history.state?.opensDialog
|
||||
) {
|
||||
// if there are more dialogs open, push a new state so back() will close the next top dialog
|
||||
mainWindow.history.pushState(
|
||||
{ dialog: OPEN_DIALOG_STACK[OPEN_DIALOG_STACK.length - 1].dialogTag },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../components/ha-camera-stream";
|
||||
@@ -9,6 +9,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { fileDownload } from "../../../util/file_download";
|
||||
import { showToast } from "../../../util/toast";
|
||||
|
||||
@customElement("more-info-camera")
|
||||
class MoreInfoCamera extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -112,8 +113,6 @@ class MoreInfoCamera extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("more-info-camera", MoreInfoCamera);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-camera": MoreInfoCamera;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-attribute-icon";
|
||||
@@ -32,6 +32,7 @@ import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
|
||||
type MainControl = "temperature" | "humidity";
|
||||
|
||||
@customElement("more-info-climate")
|
||||
class MoreInfoClimate extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -567,8 +568,6 @@ class MoreInfoClimate extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-climate", MoreInfoClimate);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-climate": MoreInfoClimate;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import type { GroupEntity } from "../../../data/group";
|
||||
import { computeGroupDomain } from "../../../data/group";
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
importMoreInfoControl,
|
||||
} from "../state_more_info_control";
|
||||
|
||||
@customElement("more-info-group")
|
||||
class MoreInfoGroup extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -106,8 +107,6 @@ class MoreInfoGroup extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-group", MoreInfoGroup);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-group": MoreInfoGroup;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiPower, mdiTuneVariant } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
@@ -15,6 +15,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
import "../components/ha-more-info-control-select-container";
|
||||
import { moreInfoControlStyle } from "../components/more-info-control-style";
|
||||
|
||||
@customElement("more-info-humidifier")
|
||||
class MoreInfoHumidifier extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -249,8 +250,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-humidifier", MoreInfoHumidifier);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"more-info-humidifier": MoreInfoHumidifier;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../components/ha-spinner";
|
||||
import "../components/ha-button";
|
||||
|
||||
@customElement("ha-init-page")
|
||||
class HaInitPage extends LitElement {
|
||||
@property({ type: Boolean }) public error = false;
|
||||
|
||||
@@ -120,8 +121,6 @@ class HaInitPage extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
customElements.define("ha-init-page", HaInitPage);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-init-page": HaInitPage;
|
||||
|
||||
@@ -128,6 +128,8 @@ class HassSubpage extends LitElement {
|
||||
ha-menu-button,
|
||||
ha-icon-button-arrow-prev,
|
||||
::slotted([slot="toolbar-icon"]) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
@@ -143,7 +145,6 @@ class HassSubpage extends LitElement {
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
@@ -621,9 +621,9 @@ export class HaTabsSubpageDataTable extends KeyboardShortcutMixin(LitElement) {
|
||||
} else if (this._sortDirection === "asc") {
|
||||
this._sortDirection = "desc";
|
||||
} else {
|
||||
this._sortDirection = null;
|
||||
this._sortDirection = "asc";
|
||||
}
|
||||
this._sortColumn = this._sortDirection === null ? undefined : columnId;
|
||||
this._sortColumn = columnId;
|
||||
|
||||
fireEvent(this, "sorting-changed", {
|
||||
column: columnId,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type { LocalizeKeys } from "../common/translations/localize";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-icon-button";
|
||||
@@ -26,6 +26,7 @@ export interface ToastActionParams {
|
||||
| { translationKey: LocalizeKeys; args?: Record<string, string> };
|
||||
}
|
||||
|
||||
@customElement("notification-manager")
|
||||
class NotificationManager extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -115,8 +116,6 @@ class NotificationManager extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("notification-manager", NotificationManager);
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"notification-manager": NotificationManager;
|
||||
|
||||
@@ -90,9 +90,7 @@ class OnboardingRestoreBackupCloudLogin extends LitElement {
|
||||
this._email = this._cloudLoginElement.emailField.value;
|
||||
}
|
||||
|
||||
await import(
|
||||
"../../panels/config/cloud/forgot-password/cloud-forgot-password-card"
|
||||
);
|
||||
await import("../../panels/config/cloud/forgot-password/cloud-forgot-password-card");
|
||||
this._view = "forgot-password";
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { TZDate } from "@date-fns/tz";
|
||||
import { addDays, isSameDay } from "date-fns";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { formatDate } from "../../common/datetime/format_date";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import { formatTime } from "../../common/datetime/format_time";
|
||||
@@ -26,6 +26,7 @@ import type { CalendarEventDetailDialogParams } from "./show-dialog-calendar-eve
|
||||
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
|
||||
import { resolveTimeZone } from "../../common/datetime/resolve-time-zone";
|
||||
|
||||
@customElement("dialog-calendar-event-detail")
|
||||
class DialogCalendarEventDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -271,8 +272,3 @@ declare global {
|
||||
"dialog-calendar-event-detail": DialogCalendarEventDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
"dialog-calendar-event-detail",
|
||||
DialogCalendarEventDetail
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { generateLovelaceViewStrategy } from "../lovelace/strategies/get-strateg
|
||||
import type { Lovelace } from "../lovelace/types";
|
||||
import "../lovelace/views/hui-view";
|
||||
import "../lovelace/views/hui-view-container";
|
||||
import "../lovelace/views/hui-view-background";
|
||||
|
||||
const CLIMATE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
|
||||
strategy: {
|
||||
@@ -115,6 +116,7 @@ class PanelClimate extends LitElement {
|
||||
this._lovelace
|
||||
? html`
|
||||
<hui-view-container .hass=${this.hass}>
|
||||
<hui-view-background .hass=${this.hass}> </hui-view-background>
|
||||
<hui-view
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||
@@ -40,6 +40,7 @@ const SENSOR_DOMAINS = ["sensor"];
|
||||
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
|
||||
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
|
||||
|
||||
@customElement("dialog-area-registry-detail")
|
||||
class DialogAreaDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -426,5 +427,3 @@ declare global {
|
||||
"dialog-area-registry-detail": DialogAreaDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-area-registry-detail", DialogAreaDetail);
|
||||
|
||||
496
src/panels/config/areas/dialog-areas-floors-order.ts
Normal file
496
src/panels/config/areas/dialog-areas-floors-order.ts
Normal file
@@ -0,0 +1,496 @@
|
||||
import { mdiClose, mdiDragHorizontalVariant, mdiTextureBox } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import {
|
||||
type AreasFloorHierarchy,
|
||||
getAreasFloorHierarchy,
|
||||
getAreasOrder,
|
||||
getFloorOrder,
|
||||
} from "../../../common/areas/areas-floor-hierarchy";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog-header";
|
||||
import "../../../components/ha-floor-icon";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-dialog";
|
||||
import type { HaMdDialog } from "../../../components/ha-md-dialog";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-sortable";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import type { AreaRegistryEntry } from "../../../data/area_registry";
|
||||
import {
|
||||
reorderAreaRegistryEntries,
|
||||
updateAreaRegistryEntry,
|
||||
} from "../../../data/area_registry";
|
||||
import { reorderFloorRegistryEntries } from "../../../data/floor_registry";
|
||||
import { haStyle, haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import type { AreasFloorsOrderDialogParams } from "./show-dialog-areas-floors-order";
|
||||
|
||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||
|
||||
interface FloorChange {
|
||||
areaId: string;
|
||||
floorId: string | null;
|
||||
}
|
||||
|
||||
@customElement("dialog-areas-floors-order")
|
||||
class DialogAreasFloorsOrder extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||
|
||||
@state() private _saving = false;
|
||||
|
||||
@query("ha-md-dialog") private _dialog?: HaMdDialog;
|
||||
|
||||
public async showDialog(
|
||||
_params: AreasFloorsOrderDialogParams
|
||||
): Promise<void> {
|
||||
this._open = true;
|
||||
this._computeHierarchy();
|
||||
}
|
||||
|
||||
private _computeHierarchy(): void {
|
||||
this._hierarchy = getAreasFloorHierarchy(
|
||||
Object.values(this.hass.floors),
|
||||
Object.values(this.hass.areas)
|
||||
);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._dialog?.close();
|
||||
}
|
||||
|
||||
private _dialogClosed(): void {
|
||||
this._open = false;
|
||||
this._hierarchy = undefined;
|
||||
this._saving = false;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._open || !this._hierarchy) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const hasFloors = this._hierarchy.floors.length > 0;
|
||||
const dialogTitle = this.hass.localize(
|
||||
hasFloors
|
||||
? "ui.panel.config.areas.dialog.reorder_floors_areas_title"
|
||||
: "ui.panel.config.areas.dialog.reorder_areas_title"
|
||||
);
|
||||
|
||||
return html`
|
||||
<ha-md-dialog open @closed=${this._dialogClosed}>
|
||||
<ha-dialog-header slot="headline">
|
||||
<ha-icon-button
|
||||
slot="navigationIcon"
|
||||
.label=${this.hass.localize("ui.common.close")}
|
||||
.path=${mdiClose}
|
||||
@click=${this.closeDialog}
|
||||
></ha-icon-button>
|
||||
<span slot="title" .title=${dialogTitle}>${dialogTitle}</span>
|
||||
</ha-dialog-header>
|
||||
<div slot="content" class="content">
|
||||
<ha-sortable
|
||||
handle-selector=".floor-handle"
|
||||
draggable-selector=".floor"
|
||||
@item-moved=${this._floorMoved}
|
||||
invert-swap
|
||||
>
|
||||
<div class="floors">
|
||||
${repeat(
|
||||
this._hierarchy.floors,
|
||||
(floor) => floor.id,
|
||||
(floor) => this._renderFloor(floor)
|
||||
)}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
${this._renderUnassignedAreas()}
|
||||
</div>
|
||||
<div slot="actions">
|
||||
<ha-button @click=${this.closeDialog} appearance="plain">
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button @click=${this._save} .disabled=${this._saving}>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-md-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderFloor(floor: { id: string; areas: string[] }) {
|
||||
const floorEntry = this.hass.floors[floor.id];
|
||||
if (!floorEntry) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="floor">
|
||||
<div class="floor-header">
|
||||
<ha-floor-icon .floor=${floorEntry}></ha-floor-icon>
|
||||
<span class="floor-name">${floorEntry.name}</span>
|
||||
<ha-svg-icon
|
||||
class="floor-handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
</div>
|
||||
<ha-sortable
|
||||
handle-selector=".area-handle"
|
||||
draggable-selector="ha-md-list-item"
|
||||
@item-moved=${this._areaMoved}
|
||||
@item-added=${this._areaAdded}
|
||||
group="areas"
|
||||
.floor=${floor.id}
|
||||
>
|
||||
<ha-md-list>
|
||||
${floor.areas.length > 0
|
||||
? floor.areas.map((areaId) => this._renderArea(areaId))
|
||||
: html`<p class="empty">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.dialog.empty_floor"
|
||||
)}
|
||||
</p>`}
|
||||
</ha-md-list>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderUnassignedAreas() {
|
||||
const hasFloors = this._hierarchy!.floors.length > 0;
|
||||
|
||||
return html`
|
||||
<div class="floor unassigned">
|
||||
${hasFloors
|
||||
? html`<div class="floor-header">
|
||||
<span class="floor-name">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.dialog.other_areas"
|
||||
)}
|
||||
</span>
|
||||
</div>`
|
||||
: nothing}
|
||||
<ha-sortable
|
||||
handle-selector=".area-handle"
|
||||
draggable-selector="ha-md-list-item"
|
||||
@item-moved=${this._areaMoved}
|
||||
@item-added=${this._areaAdded}
|
||||
group="areas"
|
||||
.floor=${UNASSIGNED_FLOOR}
|
||||
>
|
||||
<ha-md-list>
|
||||
${this._hierarchy!.areas.length > 0
|
||||
? this._hierarchy!.areas.map((areaId) => this._renderArea(areaId))
|
||||
: html`<p class="empty">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.dialog.empty_unassigned"
|
||||
)}
|
||||
</p>`}
|
||||
</ha-md-list>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderArea(areaId: string) {
|
||||
const area = this.hass.areas[areaId];
|
||||
if (!area) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-md-list-item .sortableData=${area}>
|
||||
${area.icon
|
||||
? html`<ha-icon slot="start" .icon=${area.icon}></ha-icon>`
|
||||
: html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiTextureBox}
|
||||
></ha-svg-icon>`}
|
||||
<span slot="headline">${area.name}</span>
|
||||
<ha-svg-icon
|
||||
class="area-handle"
|
||||
slot="end"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
</ha-md-list-item>
|
||||
`;
|
||||
}
|
||||
|
||||
private _floorMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._hierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
const newFloors = [...this._hierarchy.floors];
|
||||
const [movedFloor] = newFloors.splice(oldIndex, 1);
|
||||
newFloors.splice(newIndex, 0, movedFloor);
|
||||
|
||||
this._hierarchy = {
|
||||
...this._hierarchy,
|
||||
floors: newFloors,
|
||||
};
|
||||
}
|
||||
|
||||
private _areaMoved(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._hierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { floor } = ev.currentTarget as HTMLElement & { floor: string };
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
const floorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
||||
|
||||
if (floorId === null) {
|
||||
// Reorder unassigned areas
|
||||
const newAreas = [...this._hierarchy.areas];
|
||||
const [movedArea] = newAreas.splice(oldIndex, 1);
|
||||
newAreas.splice(newIndex, 0, movedArea);
|
||||
|
||||
this._hierarchy = {
|
||||
...this._hierarchy,
|
||||
areas: newAreas,
|
||||
};
|
||||
} else {
|
||||
// Reorder areas within a floor
|
||||
this._hierarchy = {
|
||||
...this._hierarchy,
|
||||
floors: this._hierarchy.floors.map((f) => {
|
||||
if (f.id === floorId) {
|
||||
const newAreas = [...f.areas];
|
||||
const [movedArea] = newAreas.splice(oldIndex, 1);
|
||||
newAreas.splice(newIndex, 0, movedArea);
|
||||
return { ...f, areas: newAreas };
|
||||
}
|
||||
return f;
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _areaAdded(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
if (!this._hierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { floor } = ev.currentTarget as HTMLElement & { floor: string };
|
||||
const { data: area, index } = ev.detail as {
|
||||
data: AreaRegistryEntry;
|
||||
index: number;
|
||||
};
|
||||
|
||||
const newFloorId = floor === UNASSIGNED_FLOOR ? null : floor;
|
||||
|
||||
// Update hierarchy
|
||||
const newUnassignedAreas = this._hierarchy.areas.filter(
|
||||
(id) => id !== area.area_id
|
||||
);
|
||||
if (newFloorId === null) {
|
||||
// Add to unassigned at the specified index
|
||||
newUnassignedAreas.splice(index, 0, area.area_id);
|
||||
}
|
||||
|
||||
this._hierarchy = {
|
||||
...this._hierarchy,
|
||||
floors: this._hierarchy.floors.map((f) => {
|
||||
if (f.id === newFloorId) {
|
||||
// Add to new floor at the specified index
|
||||
const newAreas = [...f.areas];
|
||||
newAreas.splice(index, 0, area.area_id);
|
||||
return { ...f, areas: newAreas };
|
||||
}
|
||||
// Remove from old floor
|
||||
return {
|
||||
...f,
|
||||
areas: f.areas.filter((id) => id !== area.area_id),
|
||||
};
|
||||
}),
|
||||
areas: newUnassignedAreas,
|
||||
};
|
||||
}
|
||||
|
||||
private _computeFloorChanges(): FloorChange[] {
|
||||
if (!this._hierarchy) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const changes: FloorChange[] = [];
|
||||
|
||||
// Check areas assigned to floors
|
||||
for (const floor of this._hierarchy.floors) {
|
||||
for (const areaId of floor.areas) {
|
||||
const originalFloorId = this.hass.areas[areaId]?.floor_id ?? null;
|
||||
if (floor.id !== originalFloorId) {
|
||||
changes.push({ areaId, floorId: floor.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check unassigned areas
|
||||
for (const areaId of this._hierarchy.areas) {
|
||||
const originalFloorId = this.hass.areas[areaId]?.floor_id ?? null;
|
||||
if (originalFloorId !== null) {
|
||||
changes.push({ areaId, floorId: null });
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
private async _save(): Promise<void> {
|
||||
if (!this._hierarchy || this._saving) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._saving = true;
|
||||
|
||||
try {
|
||||
const areaOrder = getAreasOrder(this._hierarchy);
|
||||
const floorOrder = getFloorOrder(this._hierarchy);
|
||||
|
||||
// Update floor assignments for areas that changed floors
|
||||
const floorChanges = this._computeFloorChanges();
|
||||
const floorChangePromises = floorChanges.map(({ areaId, floorId }) =>
|
||||
updateAreaRegistryEntry(this.hass, areaId, {
|
||||
floor_id: floorId,
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(floorChangePromises);
|
||||
|
||||
// Reorder areas and floors
|
||||
await reorderAreaRegistryEntries(this.hass, areaOrder);
|
||||
await reorderFloorRegistryEntries(this.hass, floorOrder);
|
||||
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
showToast(this, {
|
||||
message:
|
||||
err.message ||
|
||||
this.hass.localize("ui.panel.config.areas.dialog.reorder_failed"),
|
||||
});
|
||||
this._saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-md-dialog {
|
||||
min-width: 600px;
|
||||
max-height: 90%;
|
||||
--dialog-content-padding: 8px 24px;
|
||||
}
|
||||
|
||||
@media all and (max-width: 600px), all and (max-height: 500px) {
|
||||
ha-md-dialog {
|
||||
--md-dialog-container-shape: 0;
|
||||
min-width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.floors {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.floor {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: var(
|
||||
--ha-card-border-radius,
|
||||
var(--ha-border-radius-lg)
|
||||
);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.floor.unassigned {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.floor-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: var(--secondary-background-color);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.floor-name {
|
||||
flex: 1;
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
}
|
||||
|
||||
.floor-handle {
|
||||
cursor: grab;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
--md-list-item-leading-space: 16px;
|
||||
--md-list-item-trailing-space: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-list-item-one-line-container-height: 48px;
|
||||
--md-list-item-container-shape: 0;
|
||||
}
|
||||
|
||||
ha-md-list-item.sortable-ghost {
|
||||
border-radius: calc(
|
||||
var(--ha-card-border-radius, var(--ha-border-radius-lg)) - 1px
|
||||
);
|
||||
box-shadow: inset 0 0 0 2px var(--primary-color);
|
||||
}
|
||||
|
||||
.area-handle {
|
||||
cursor: grab;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.empty {
|
||||
text-align: center;
|
||||
color: var(--secondary-text-color);
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
ha-md-list:has(ha-md-list-item) .empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-areas-floors-order": DialogAreasFloorsOrder;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiTextureBox } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
@@ -27,6 +27,7 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { showAreaRegistryDetailDialog } from "./show-dialog-area-registry-detail";
|
||||
import type { FloorRegistryDetailDialogParams } from "./show-dialog-floor-registry-detail";
|
||||
|
||||
@customElement("dialog-floor-registry-detail")
|
||||
class DialogFloorDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -144,6 +145,10 @@ class DialogFloorDetail extends LitElement {
|
||||
"ui.panel.config.floors.editor.level"
|
||||
)}
|
||||
type="number"
|
||||
.helper=${this.hass.localize(
|
||||
"ui.panel.config.floors.editor.level_helper"
|
||||
)}
|
||||
helperPersistent
|
||||
></ha-textfield>
|
||||
|
||||
<ha-icon-picker
|
||||
@@ -357,5 +362,3 @@ declare global {
|
||||
"dialog-floor-registry-detail": DialogFloorDetail;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-floor-registry-detail", DialogFloorDetail);
|
||||
|
||||
@@ -2,10 +2,10 @@ import type { ActionDetail } from "@material/mwc-list";
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiDragHorizontalVariant,
|
||||
mdiHelpCircle,
|
||||
mdiPencil,
|
||||
mdiPlus,
|
||||
mdiSort,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
@@ -21,7 +21,6 @@ import memoizeOne from "memoize-one";
|
||||
import {
|
||||
getAreasFloorHierarchy,
|
||||
getAreasOrder,
|
||||
getFloorOrder,
|
||||
type AreasFloorHierarchy,
|
||||
} from "../../../common/areas/areas-floor-hierarchy";
|
||||
import { formatListWithAnds } from "../../../common/string/format-list";
|
||||
@@ -42,7 +41,6 @@ import type { FloorRegistryEntry } from "../../../data/floor_registry";
|
||||
import {
|
||||
createFloorRegistryEntry,
|
||||
deleteFloorRegistryEntry,
|
||||
reorderFloorRegistryEntries,
|
||||
updateFloorRegistryEntry,
|
||||
} from "../../../data/floor_registry";
|
||||
import {
|
||||
@@ -58,6 +56,7 @@ import {
|
||||
loadAreaRegistryDetailDialog,
|
||||
showAreaRegistryDetailDialog,
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { showAreasFloorsOrderDialog } from "./show-dialog-areas-floors-order";
|
||||
import { showFloorRegistryDetailDialog } from "./show-dialog-floor-registry-detail";
|
||||
|
||||
const UNASSIGNED_FLOOR = "__unassigned__";
|
||||
@@ -84,6 +83,8 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public route!: Route;
|
||||
|
||||
private _searchParms = new URLSearchParams(window.location.search);
|
||||
|
||||
@state() private _hierarchy?: AreasFloorHierarchy;
|
||||
|
||||
private _blockHierarchyUpdate = false;
|
||||
@@ -167,7 +168,9 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.isWide=${this.isWide}
|
||||
back-path="/config"
|
||||
.backPath=${this._searchParms.has("historyBack")
|
||||
? undefined
|
||||
: "/config"}
|
||||
.tabs=${configSections.areas}
|
||||
.route=${this.route}
|
||||
has-fab
|
||||
@@ -179,87 +182,84 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
@click=${this._showHelp}
|
||||
></ha-icon-button>
|
||||
<div class="container">
|
||||
<ha-sortable
|
||||
handle-selector=".handle"
|
||||
draggable-selector=".floor"
|
||||
@item-moved=${this._floorMoved}
|
||||
.options=${SORT_OPTIONS}
|
||||
group="floors"
|
||||
invert-swap
|
||||
>
|
||||
<div class="floors">
|
||||
${this._hierarchy.floors.map(({ areas, id }) => {
|
||||
const floor = this.hass.floors[id];
|
||||
if (!floor) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<div class="floor">
|
||||
<div class="header">
|
||||
<h2>
|
||||
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
||||
${floor.name}
|
||||
</h2>
|
||||
<div class="actions">
|
||||
<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDragHorizontalVariant}
|
||||
></ha-svg-icon>
|
||||
<ha-button-menu
|
||||
.floor=${floor}
|
||||
@action=${this._handleFloorAction}
|
||||
<div class="floors">
|
||||
${this._hierarchy.floors.map(({ areas, id }) => {
|
||||
const floor = this.hass.floors[id];
|
||||
if (!floor) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<div class="floor">
|
||||
<div class="header">
|
||||
<h2>
|
||||
<ha-floor-icon .floor=${floor}></ha-floor-icon>
|
||||
${floor.name}
|
||||
</h2>
|
||||
<div class="actions">
|
||||
<ha-button-menu
|
||||
.floor=${floor}
|
||||
@action=${this._handleFloorAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon"
|
||||
><ha-svg-icon
|
||||
.path=${mdiSort}
|
||||
slot="graphic"
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.reorder"
|
||||
)}</ha-list-item
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon"
|
||||
><ha-svg-icon
|
||||
.path=${mdiPencil}
|
||||
slot="graphic"
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.floor.edit_floor"
|
||||
)}</ha-list-item
|
||||
>
|
||||
<ha-list-item class="warning" graphic="icon"
|
||||
><ha-svg-icon
|
||||
class="warning"
|
||||
.path=${mdiDelete}
|
||||
slot="graphic"
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.floor.delete_floor"
|
||||
)}</ha-list-item
|
||||
>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item graphic="icon"
|
||||
><ha-svg-icon
|
||||
.path=${mdiPencil}
|
||||
slot="graphic"
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.floor.edit_floor"
|
||||
)}</ha-list-item
|
||||
>
|
||||
<ha-list-item class="warning" graphic="icon"
|
||||
><ha-svg-icon
|
||||
class="warning"
|
||||
.path=${mdiDelete}
|
||||
slot="graphic"
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.floor.delete_floor"
|
||||
)}</ha-list-item
|
||||
>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<ha-sortable
|
||||
handle-selector="a"
|
||||
draggable-selector="a"
|
||||
@item-added=${this._areaAdded}
|
||||
@item-moved=${this._areaMoved}
|
||||
group="areas"
|
||||
.options=${SORT_OPTIONS}
|
||||
.floor=${floor.floor_id}
|
||||
>
|
||||
<div class="areas">
|
||||
${areas.map((areaId) => {
|
||||
const area = this.hass.areas[areaId];
|
||||
if (!area) {
|
||||
return nothing;
|
||||
}
|
||||
const stats = areasStats.get(area.area_id);
|
||||
return this._renderArea(area, stats);
|
||||
})}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
<ha-sortable
|
||||
handle-selector="a"
|
||||
draggable-selector="a"
|
||||
@item-added=${this._areaAdded}
|
||||
@item-moved=${this._areaMoved}
|
||||
group="areas"
|
||||
.options=${SORT_OPTIONS}
|
||||
.floor=${floor.floor_id}
|
||||
>
|
||||
<div class="areas">
|
||||
${areas.map((areaId) => {
|
||||
const area = this.hass.areas[areaId];
|
||||
if (!area) {
|
||||
return nothing;
|
||||
}
|
||||
const stats = areasStats.get(area.area_id);
|
||||
return this._renderArea(area, stats);
|
||||
})}
|
||||
</div>
|
||||
</ha-sortable>
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
|
||||
${this._hierarchy.areas.length
|
||||
? html`
|
||||
@@ -267,9 +267,30 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
<div class="header">
|
||||
<h2>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.unassigned_areas"
|
||||
this._hierarchy.floors.length
|
||||
? "ui.panel.config.areas.picker.other_areas"
|
||||
: "ui.panel.config.areas.picker.header"
|
||||
)}
|
||||
</h2>
|
||||
<div class="actions">
|
||||
<ha-button-menu
|
||||
@action=${this._handleUnassignedAreasAction}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<ha-list-item graphic="icon"
|
||||
><ha-svg-icon
|
||||
.path=${mdiSort}
|
||||
slot="graphic"
|
||||
></ha-svg-icon
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.areas.picker.reorder"
|
||||
)}</ha-list-item
|
||||
>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
</div>
|
||||
<ha-sortable
|
||||
handle-selector="a"
|
||||
@@ -391,51 +412,6 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private async _floorMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.hass || !this._hierarchy) {
|
||||
return;
|
||||
}
|
||||
const { oldIndex, newIndex } = ev.detail;
|
||||
|
||||
const reorderFloors = (
|
||||
floors: AreasFloorHierarchy["floors"],
|
||||
oldIdx: number,
|
||||
newIdx: number
|
||||
) => {
|
||||
const newFloors = [...floors];
|
||||
const [movedFloor] = newFloors.splice(oldIdx, 1);
|
||||
newFloors.splice(newIdx, 0, movedFloor);
|
||||
return newFloors;
|
||||
};
|
||||
|
||||
// Optimistically update UI
|
||||
this._hierarchy = {
|
||||
...this._hierarchy,
|
||||
floors: reorderFloors(this._hierarchy.floors, oldIndex, newIndex),
|
||||
};
|
||||
|
||||
const areaOrder = getAreasOrder(this._hierarchy);
|
||||
const floorOrder = getFloorOrder(this._hierarchy);
|
||||
|
||||
// Block hierarchy updates for 500ms to avoid flickering
|
||||
// because of multiple async updates
|
||||
this._blockHierarchyUpdateFor(500);
|
||||
|
||||
try {
|
||||
await reorderAreaRegistryEntries(this.hass, areaOrder);
|
||||
await reorderFloorRegistryEntries(this.hass, floorOrder);
|
||||
} catch {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
"ui.panel.config.areas.picker.floor_reorder_failed"
|
||||
),
|
||||
});
|
||||
// Revert on error
|
||||
this._computeHierarchy();
|
||||
}
|
||||
}
|
||||
|
||||
private async _areaMoved(ev) {
|
||||
ev.stopPropagation();
|
||||
if (!this.hass || !this._hierarchy) {
|
||||
@@ -561,14 +537,23 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
const floor = (ev.currentTarget as any).floor;
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
this._editFloor(floor);
|
||||
this._showReorderDialog();
|
||||
break;
|
||||
case 1:
|
||||
this._editFloor(floor);
|
||||
break;
|
||||
case 2:
|
||||
this._deleteFloor(floor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleUnassignedAreasAction(ev: CustomEvent<ActionDetail>) {
|
||||
if (ev.detail.index === 0) {
|
||||
this._showReorderDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private _createFloor() {
|
||||
this._openFloorDialog();
|
||||
}
|
||||
@@ -598,6 +583,10 @@ export class HaConfigAreasDashboard extends LitElement {
|
||||
this._openAreaDialog();
|
||||
}
|
||||
|
||||
private _showReorderDialog() {
|
||||
showAreasFloorsOrderDialog(this, {});
|
||||
}
|
||||
|
||||
private _showHelp() {
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize("ui.panel.config.areas.caption"),
|
||||
|
||||
17
src/panels/config/areas/show-dialog-areas-floors-order.ts
Normal file
17
src/panels/config/areas/show-dialog-areas-floors-order.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export interface AreasFloorsOrderDialogParams {}
|
||||
|
||||
export const loadAreasFloorsOrderDialog = () =>
|
||||
import("./dialog-areas-floors-order");
|
||||
|
||||
export const showAreasFloorsOrderDialog = (
|
||||
element: HTMLElement,
|
||||
params: AreasFloorsOrderDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-areas-floors-order",
|
||||
dialogImport: loadAreasFloorsOrderDialog,
|
||||
dialogParams: params,
|
||||
});
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAlertCircleCheck,
|
||||
@@ -32,11 +33,11 @@ import { copyToClipboard } from "../../../../common/util/copy-clipboard";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-service-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import {
|
||||
@@ -288,15 +289,12 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -304,30 +302,24 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._runAction}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
<ha-dropdown-item value="run">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize("ui.panel.config.automation.editor.actions.run")
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -336,13 +328,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -351,7 +340,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -362,13 +350,10 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutAction}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -377,7 +362,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -388,51 +372,48 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.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}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.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-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
<ha-dropdown-item
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this._uiModeAvailable || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${this.action.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -443,15 +424,15 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${this.action.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -463,7 +444,6 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -478,8 +458,8 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
@@ -890,6 +870,47 @@ export default class HaAutomationActionRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "run":
|
||||
this._runAction();
|
||||
break;
|
||||
case "rename":
|
||||
this._renameAction();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateAction();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyAction();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutAction();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [rowStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { mainWindow } from "../../../common/dom/get_main_window";
|
||||
import { computeAreaName } from "../../../common/entity/compute_area_name";
|
||||
import { computeDeviceName } from "../../../common/entity/compute_device_name";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
@@ -96,7 +97,7 @@ import {
|
||||
fetchIntegrationManifests,
|
||||
} from "../../../data/integration";
|
||||
import type { LabelRegistryEntry } from "../../../data/label_registry";
|
||||
import { subscribeLabFeatures } from "../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../data/labs";
|
||||
import {
|
||||
TARGET_SEPARATOR,
|
||||
getConditionsForTarget,
|
||||
@@ -118,7 +119,6 @@ import type { HomeAssistant } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import "./add-automation-element/ha-automation-add-from-target";
|
||||
import type HaAutomationAddFromTarget from "./add-automation-element/ha-automation-add-from-target";
|
||||
import "./add-automation-element/ha-automation-add-items";
|
||||
import "./add-automation-element/ha-automation-add-search";
|
||||
import type { AddAutomationElementDialogParams } from "./show-add-automation-element-dialog";
|
||||
@@ -216,10 +216,6 @@ class DialogAddAutomationElement
|
||||
// #endregion state
|
||||
|
||||
// #region queries
|
||||
|
||||
@query("ha-automation-add-from-target")
|
||||
private _targetPickerElement?: HaAutomationAddFromTarget;
|
||||
|
||||
@query("ha-automation-add-items")
|
||||
private _itemsListElement?: HTMLDivElement;
|
||||
|
||||
@@ -285,22 +281,24 @@ class DialogAddAutomationElement
|
||||
this._fetchManifests();
|
||||
this._calculateUsedDomains();
|
||||
|
||||
this._unsubscribeLabFeatures = subscribeLabFeatures(
|
||||
this._unsubscribeLabFeatures = subscribeLabFeature(
|
||||
this.hass.connection,
|
||||
(features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
this._tab =
|
||||
this._newTriggersAndConditions && this._params?.type !== "condition"
|
||||
? "targets"
|
||||
: "groups";
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(enabled) => {
|
||||
this._newTriggersAndConditions = enabled;
|
||||
this._tab = this._newTriggersAndConditions ? "targets" : "groups";
|
||||
}
|
||||
);
|
||||
|
||||
// add initial dialog view state to history
|
||||
mainWindow.history.pushState(
|
||||
{
|
||||
dialogData: {},
|
||||
},
|
||||
""
|
||||
);
|
||||
|
||||
if (this._params?.type === "action") {
|
||||
this.hass.loadBackendTranslation("services");
|
||||
getServiceIcons(this.hass);
|
||||
@@ -321,7 +319,41 @@ class DialogAddAutomationElement
|
||||
this._bottomSheetMode = this._narrow;
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
public closeDialog(historyState?: any) {
|
||||
// prevent closing when come from popstate event and root level isn't active
|
||||
if (
|
||||
this._open &&
|
||||
historyState &&
|
||||
(this._selectedTarget || this._selectedGroup)
|
||||
) {
|
||||
if (historyState.dialogData?.target) {
|
||||
this._selectedTarget = historyState.dialogData.target;
|
||||
this._getItemsByTarget();
|
||||
this._tab = "targets";
|
||||
return false;
|
||||
}
|
||||
if (historyState.dialogData?.group) {
|
||||
this._selectedCollectionIndex = historyState.dialogData.collectionIndex;
|
||||
this._selectedGroup = historyState.dialogData.group;
|
||||
this._tab = "groups";
|
||||
return false;
|
||||
}
|
||||
|
||||
// return to home on mobile
|
||||
if (this._narrow) {
|
||||
this._selectedTarget = undefined;
|
||||
this._selectedGroup = undefined;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// if dialog is closed, but root level isn't active, clean up history state
|
||||
if (mainWindow.history.state?.dialogData) {
|
||||
this._open = false;
|
||||
mainWindow.history.back();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.removeKeyboardShortcuts();
|
||||
this._unsubscribe();
|
||||
if (this._params) {
|
||||
@@ -408,7 +440,7 @@ class DialogAddAutomationElement
|
||||
return html`
|
||||
<ha-bottom-sheet
|
||||
.open=${this._open}
|
||||
@closed=${this.closeDialog}
|
||||
@closed=${this._handleClosed}
|
||||
flexcontent
|
||||
>
|
||||
${this._renderContent()}
|
||||
@@ -420,7 +452,7 @@ class DialogAddAutomationElement
|
||||
<ha-wa-dialog
|
||||
width="large"
|
||||
.open=${this._open}
|
||||
@closed=${this.closeDialog}
|
||||
@closed=${this._handleClosed}
|
||||
flexcontent
|
||||
>
|
||||
${this._renderContent()}
|
||||
@@ -558,8 +590,7 @@ class DialogAddAutomationElement
|
||||
interactive
|
||||
type="button"
|
||||
class="paste"
|
||||
.value=${PASTE_VALUE}
|
||||
@click=${this._selected}
|
||||
@click=${this._paste}
|
||||
>
|
||||
<div class="shortcut-label">
|
||||
<div class="label">
|
||||
@@ -652,6 +683,7 @@ class DialogAddAutomationElement
|
||||
<ha-automation-add-items
|
||||
.hass=${this.hass}
|
||||
.items=${this._getItems()}
|
||||
.scrollable=${!this._narrow}
|
||||
.error=${this._tab === "targets" && this._loadItemsError
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.automation.editor.load_target_items_failed"
|
||||
@@ -730,15 +762,26 @@ class DialogAddAutomationElement
|
||||
);
|
||||
|
||||
if (targetId) {
|
||||
if (targetType === "area" && this.hass.areas[targetId]?.floor_id) {
|
||||
const floorId = this.hass.areas[targetId].floor_id;
|
||||
subtitle = computeFloorName(this.hass.floors[floorId]) || floorId;
|
||||
}
|
||||
if (targetType === "device" && this.hass.devices[targetId]?.area_id) {
|
||||
const areaId = this.hass.devices[targetId].area_id;
|
||||
subtitle = computeAreaName(this.hass.areas[areaId]) || areaId;
|
||||
}
|
||||
if (targetType === "entity" && this.hass.states[targetId]) {
|
||||
if (targetType === "area") {
|
||||
const floorId = this.hass.areas[targetId]?.floor_id;
|
||||
if (floorId) {
|
||||
subtitle = computeFloorName(this.hass.floors[floorId]) || floorId;
|
||||
} else {
|
||||
subtitle = this.hass.localize(
|
||||
"ui.panel.config.automation.editor.other_areas"
|
||||
);
|
||||
}
|
||||
} else if (targetType === "device") {
|
||||
const areaId = this.hass.devices[targetId]?.area_id;
|
||||
if (areaId) {
|
||||
subtitle = computeAreaName(this.hass.areas[areaId]) || areaId;
|
||||
} else {
|
||||
const device = this.hass.devices[targetId];
|
||||
subtitle = this.hass.localize(
|
||||
`ui.panel.config.automation.editor.${device?.entry_type === "service" ? "services" : "unassigned_devices"}`
|
||||
);
|
||||
}
|
||||
} else if (targetType === "entity" && this.hass.states[targetId]) {
|
||||
const entity = this.hass.entities[targetId];
|
||||
if (entity && !entity.device_id && !entity.area_id) {
|
||||
const domain = targetId.split(".", 2)[0];
|
||||
@@ -763,10 +806,10 @@ class DialogAddAutomationElement
|
||||
.join(computeRTL(this.hass) ? " ◂ " : " ▸ ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (subtitle) {
|
||||
return html`<span slot="subtitle">${subtitle}</span>`;
|
||||
if (subtitle) {
|
||||
return html`<span slot="subtitle">${subtitle}</span>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1353,6 +1396,61 @@ class DialogAddAutomationElement
|
||||
this._labelRegistry?.find(({ label_id }) => label_id === labelId)
|
||||
);
|
||||
|
||||
private _getDomainType(domain: string) {
|
||||
return ENTITY_DOMAINS_MAIN.has(domain) ||
|
||||
(this._manifests?.[domain].integration_type === "entity" &&
|
||||
!ENTITY_DOMAINS_OTHER.has(domain))
|
||||
? "dynamicGroups"
|
||||
: this._manifests?.[domain].integration_type === "helper"
|
||||
? "helpers"
|
||||
: "other";
|
||||
}
|
||||
|
||||
private _sortDomainsByCollection(
|
||||
type: AddAutomationElementDialogParams["type"],
|
||||
entries: [
|
||||
string,
|
||||
{ title: string; items: AddAutomationElementListItem[] },
|
||||
][]
|
||||
): { title: string; items: AddAutomationElementListItem[] }[] {
|
||||
const order: string[] = [];
|
||||
|
||||
TYPES[type].collections.forEach((collection) => {
|
||||
order.push(...Object.keys(collection.groups));
|
||||
});
|
||||
|
||||
return entries
|
||||
.sort((a, b) => {
|
||||
const domainA = a[0];
|
||||
const domainB = b[0];
|
||||
|
||||
if (order.includes(domainA) && order.includes(domainB)) {
|
||||
return order.indexOf(domainA) - order.indexOf(domainB);
|
||||
}
|
||||
|
||||
let typeA = domainA;
|
||||
let typeB = domainB;
|
||||
|
||||
if (!order.includes(domainA)) {
|
||||
typeA = this._getDomainType(domainA);
|
||||
}
|
||||
|
||||
if (!order.includes(domainB)) {
|
||||
typeB = this._getDomainType(domainB);
|
||||
}
|
||||
|
||||
if (typeA === typeB) {
|
||||
return stringCompare(
|
||||
a[1].title,
|
||||
b[1].title,
|
||||
this.hass.locale.language
|
||||
);
|
||||
}
|
||||
return order.indexOf(typeA) - order.indexOf(typeB);
|
||||
})
|
||||
.map((entry) => entry[1]);
|
||||
}
|
||||
|
||||
// #endregion data
|
||||
|
||||
// #region data memoize
|
||||
@@ -1368,12 +1466,12 @@ class DialogAddAutomationElement
|
||||
|
||||
private _getAreaEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getAreaEntityLookup(Object.values(entities), true)
|
||||
getAreaEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _getDeviceEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getDeviceEntityLookup(Object.values(entities), true)
|
||||
getDeviceEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _extractTypeAndIdFromTarget = memoizeOne(
|
||||
@@ -1438,8 +1536,9 @@ class DialogAddAutomationElement
|
||||
);
|
||||
});
|
||||
|
||||
return Object.values(items).sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
return this._sortDomainsByCollection(
|
||||
this._params!.type,
|
||||
Object.entries(items)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1548,8 +1647,9 @@ class DialogAddAutomationElement
|
||||
);
|
||||
});
|
||||
|
||||
return Object.values(items).sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
return this._sortDomainsByCollection(
|
||||
this._params!.type,
|
||||
Object.entries(items)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1580,8 +1680,9 @@ class DialogAddAutomationElement
|
||||
);
|
||||
});
|
||||
|
||||
return Object.values(items).sort((a, b) =>
|
||||
stringCompare(a.title, b.title, this.hass.locale.language)
|
||||
return this._sortDomainsByCollection(
|
||||
this._params!.type,
|
||||
Object.entries(items)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1594,11 +1695,7 @@ class DialogAddAutomationElement
|
||||
}
|
||||
|
||||
private _back() {
|
||||
if (this._selectedTarget) {
|
||||
this._targetPickerElement?.navigateBack();
|
||||
return;
|
||||
}
|
||||
this._selectedGroup = undefined;
|
||||
mainWindow.history.back();
|
||||
}
|
||||
|
||||
private _groupSelected(ev) {
|
||||
@@ -1610,11 +1707,26 @@ class DialogAddAutomationElement
|
||||
}
|
||||
this._selectedGroup = group.value;
|
||||
this._selectedCollectionIndex = ev.currentTarget.index;
|
||||
|
||||
mainWindow.history.pushState(
|
||||
{
|
||||
dialogData: {
|
||||
group: this._selectedGroup,
|
||||
collectionIndex: this._selectedCollectionIndex,
|
||||
},
|
||||
},
|
||||
""
|
||||
);
|
||||
requestAnimationFrame(() => {
|
||||
this._itemsListElement?.scrollTo(0, 0);
|
||||
});
|
||||
}
|
||||
|
||||
private _paste() {
|
||||
this._params!.add(PASTE_VALUE);
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _selected(ev: CustomEvent<{ value: string }>) {
|
||||
let target: HassServiceTarget | undefined;
|
||||
if (
|
||||
@@ -1634,6 +1746,14 @@ class DialogAddAutomationElement
|
||||
this._targetItems = undefined;
|
||||
this._loadItemsError = false;
|
||||
this._selectedTarget = ev.detail.value;
|
||||
mainWindow.history.pushState(
|
||||
{
|
||||
dialogData: {
|
||||
target: this._selectedTarget,
|
||||
},
|
||||
},
|
||||
""
|
||||
);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (this._narrow) {
|
||||
@@ -1678,14 +1798,19 @@ class DialogAddAutomationElement
|
||||
}
|
||||
|
||||
if (this._params!.type === "action") {
|
||||
const items = await getServicesForTarget(
|
||||
const items: string[] = await getServicesForTarget(
|
||||
this.hass.callWS,
|
||||
this._selectedTarget
|
||||
);
|
||||
|
||||
const filteredItems = items.filter(
|
||||
// homeassistant services are too generic to be applied on the selected target
|
||||
(service) => !service.startsWith("homeassistant.")
|
||||
);
|
||||
|
||||
this._targetItems = this._getDomainGroupedActionListItems(
|
||||
this.hass.localize,
|
||||
items
|
||||
filteredItems
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -1748,6 +1873,10 @@ class DialogAddAutomationElement
|
||||
this._tab = "targets";
|
||||
}
|
||||
|
||||
private _handleClosed() {
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
// #region interaction
|
||||
|
||||
// #region render helpers
|
||||
@@ -1913,7 +2042,7 @@ class DialogAddAutomationElement
|
||||
ha-wa-dialog {
|
||||
--dialog-content-padding: var(--ha-space-0);
|
||||
--ha-dialog-min-height: min(
|
||||
648px,
|
||||
800px,
|
||||
calc(
|
||||
100vh - max(
|
||||
var(--safe-area-inset-bottom),
|
||||
@@ -1922,7 +2051,7 @@ class DialogAddAutomationElement
|
||||
)
|
||||
);
|
||||
--ha-dialog-min-height: min(
|
||||
648px,
|
||||
800px,
|
||||
calc(
|
||||
100dvh - max(
|
||||
var(--safe-area-inset-bottom),
|
||||
@@ -2015,7 +2144,7 @@ class DialogAddAutomationElement
|
||||
min-height: 160px;
|
||||
}
|
||||
.content.column ha-automation-add-from-target {
|
||||
overflow: hidden;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
ha-wa-dialog ha-automation-add-items {
|
||||
|
||||
@@ -553,9 +553,6 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
area.icon,
|
||||
] as [string, string, string | undefined, string | undefined];
|
||||
})
|
||||
.sort(([, nameA], [, nameB]) =>
|
||||
stringCompare(nameA, nameB, this.hass.locale.language)
|
||||
)
|
||||
.map(([areaTargetId, areaName, floorId, areaIcon]) => {
|
||||
const { open, devices, entities } =
|
||||
this._entries[`floor${TARGET_SEPARATOR}${floorId || ""}`].areas![
|
||||
@@ -708,7 +705,11 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
this.floors
|
||||
);
|
||||
|
||||
const label = entityName || deviceName || entityId;
|
||||
let label = entityName || deviceName || entityId;
|
||||
|
||||
if (this.entities[entityId]?.hidden) {
|
||||
label += ` (${this.localize("ui.panel.config.automation.editor.entity_hidden")})`;
|
||||
}
|
||||
|
||||
return [entityId, label, stateObj] as [string, string, HassEntity];
|
||||
})
|
||||
@@ -837,12 +838,12 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
|
||||
private _getAreaEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getAreaEntityLookup(Object.values(entities), true)
|
||||
getAreaEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _getDeviceEntityLookupMemoized = memoizeOne(
|
||||
(entities: HomeAssistant["entities"]) =>
|
||||
getDeviceEntityLookup(Object.values(entities), true)
|
||||
getDeviceEntityLookup(Object.values(entities))
|
||||
);
|
||||
|
||||
private _getSelectedTargetId = memoizeOne(
|
||||
@@ -910,6 +911,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
const services: Record<string, Level3Entries> = {};
|
||||
|
||||
unassignedDevices.forEach(({ id: deviceId, entry_type }) => {
|
||||
const device = this.devices[deviceId];
|
||||
if (!device || device.disabled_by) {
|
||||
return;
|
||||
}
|
||||
const deviceEntry = {
|
||||
open: false,
|
||||
entities:
|
||||
@@ -1011,6 +1016,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
const devices: Record<string, Level3Entries> = {};
|
||||
|
||||
referenced_devices.forEach(({ id: deviceId }) => {
|
||||
const device = this.devices[deviceId];
|
||||
if (!device || device.disabled_by) {
|
||||
return;
|
||||
}
|
||||
devices[deviceId] = {
|
||||
open: false,
|
||||
entities:
|
||||
@@ -1382,92 +1391,6 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
);
|
||||
}
|
||||
|
||||
public navigateBack() {
|
||||
if (!this.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valueType = Object.keys(this.value)[0].replace("_id", "");
|
||||
const valueId = this.value[`${valueType}_id`];
|
||||
|
||||
if (
|
||||
valueType === "floor" ||
|
||||
valueType === "label" ||
|
||||
(!valueId &&
|
||||
(valueType === "device" ||
|
||||
valueType === "helper" ||
|
||||
valueType === "service" ||
|
||||
valueType === "area"))
|
||||
) {
|
||||
fireEvent(this, "value-changed", { value: undefined });
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType === "area") {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { floor_id: this.areas[valueId].floor_id || undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType === "device") {
|
||||
if (
|
||||
!this.devices[valueId].area_id &&
|
||||
this.devices[valueId].entry_type === "service"
|
||||
) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { service_id: undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { area_id: this.devices[valueId].area_id || undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueType === "entity" && valueId) {
|
||||
const deviceId = this.entities[valueId].device_id;
|
||||
if (deviceId) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { device_id: deviceId },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const areaId = this.entities[valueId].area_id;
|
||||
if (areaId) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { area_id: areaId },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const domain = valueId.split(".", 2)[0];
|
||||
const manifest = this.manifests ? this.manifests[domain] : undefined;
|
||||
if (manifest?.integration_type === "helper") {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { [`helper_${domain}_id`]: undefined },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { [`entity_${domain}_id`]: undefined },
|
||||
});
|
||||
}
|
||||
|
||||
if (valueType.startsWith("helper_") || valueType.startsWith("entity_")) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: {
|
||||
[`${valueType.startsWith("helper_") ? "helper" : "device"}_id`]:
|
||||
undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _expandHeight() {
|
||||
this._fullHeight = true;
|
||||
this.style.setProperty("--max-height", "none");
|
||||
|
||||
@@ -60,6 +60,8 @@ export class HaAutomationAddItems extends LitElement {
|
||||
@property({ type: Boolean, attribute: "tooltip-description" })
|
||||
public tooltipDescription = false;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) scrollable = false;
|
||||
|
||||
@state() private _itemsScrolled = false;
|
||||
|
||||
@query(".items")
|
||||
@@ -260,11 +262,12 @@ export class HaAutomationAddItems extends LitElement {
|
||||
:host {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:host([scrollable]) .items {
|
||||
overflow: auto;
|
||||
}
|
||||
.items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.items.blank {
|
||||
@@ -273,7 +276,7 @@ export class HaAutomationAddItems extends LitElement {
|
||||
align-items: center;
|
||||
color: var(--ha-color-text-secondary);
|
||||
padding: var(--ha-space-0);
|
||||
margin: var(--ha-space-3) var(--ha-space-4)
|
||||
margin: var(--ha-space-0) var(--ha-space-4)
|
||||
max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
||||
line-height: var(--ha-line-height-expanded);
|
||||
justify-content: center;
|
||||
@@ -306,7 +309,7 @@ export class HaAutomationAddItems extends LitElement {
|
||||
.items .item-headline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-1);
|
||||
gap: var(--ha-space-2);
|
||||
min-height: var(--ha-space-9);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -366,12 +369,16 @@ export class HaAutomationAddItems extends LitElement {
|
||||
}
|
||||
|
||||
.selected-target state-badge {
|
||||
--mdc-icon-size: 20px;
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.selected-target state-badge,
|
||||
.selected-target ha-floor-icon {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
align-items: center;
|
||||
}
|
||||
.selected-target ha-domain-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -33,11 +34,11 @@ import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-condition-icon";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import type {
|
||||
AutomationClipboard,
|
||||
Condition,
|
||||
@@ -194,15 +195,12 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -211,34 +209,28 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
>
|
||||
</ha-icon-button>
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
<ha-dropdown-item value="test">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFlask}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.test"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._renameCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.conditions.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -246,13 +238,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon
|
||||
>${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -261,7 +250,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -272,13 +260,10 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutCondition}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon
|
||||
>${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -287,7 +272,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -298,48 +282,45 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
.disabled=${this.disabled || this.first}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.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}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.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-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item .clickAction=${this._toggleYamlMode}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!this._yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="disable" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${this.condition.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -350,15 +331,15 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${this.condition.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
class="warning"
|
||||
.clickAction=${this._onDelete}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
variant="danger"
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -369,7 +350,6 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -384,8 +364,8 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -837,6 +817,47 @@ export default class HaAutomationConditionRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "test":
|
||||
this._testCondition();
|
||||
break;
|
||||
case "rename":
|
||||
this._renameCondition();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateCondition();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyCondition();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutCondition();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
CONDITION_BUILDING_BLOCKS,
|
||||
subscribeConditions,
|
||||
} from "../../../../data/condition";
|
||||
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../../data/labs";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
@@ -90,14 +90,14 @@ export default class HaAutomationCondition extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
}),
|
||||
subscribeLabFeature(
|
||||
this.hass!.connection,
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(enabled) => {
|
||||
this._newTriggersAndConditions = enabled;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -124,9 +124,9 @@ export class HaPlatformCondition extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
conditionDesc?.fields &&
|
||||
Object.values(conditionDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
Object.values(conditionDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
);
|
||||
|
||||
return html`
|
||||
@@ -393,6 +393,10 @@ export class HaPlatformCondition extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: 0px calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -23,20 +24,22 @@ import {
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { transform } from "../../../common/decorators/transform";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { goBack, navigate } from "../../../common/navigate";
|
||||
import { promiseTimeout } from "../../../common/util/promise-timeout";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-fade-in";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
@@ -74,7 +77,6 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { UndoRedoController } from "../../../common/controllers/undo-redo-controller";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
@@ -86,10 +88,10 @@ import {
|
||||
type EntityRegistryUpdate,
|
||||
showAutomationSaveDialog,
|
||||
} from "./automation-save-dialog/show-dialog-automation-save";
|
||||
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
import "./blueprint-automation-editor";
|
||||
import "./manual-automation-editor";
|
||||
import type { HaManualAutomationEditor } from "./manual-automation-editor";
|
||||
import { showAutomationSaveTimeoutDialog } from "./automation-save-timeout-dialog/show-dialog-automation-save-timeout";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -112,6 +114,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-automation-editor")
|
||||
export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
KeyboardShortcutMixin(LitElement)
|
||||
) {
|
||||
@@ -291,7 +294,10 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
@@ -299,99 +305,73 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
></ha-icon-button>
|
||||
|
||||
${this._mode === "gui" && this.narrow
|
||||
? html`<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._undo}
|
||||
? html`<ha-dropdown-item
|
||||
value="undo"
|
||||
.disabled=${!this._undoRedoController.canUndo}
|
||||
>
|
||||
${this.hass.localize("ui.common.undo")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._redo}
|
||||
<ha-svg-icon slot="icon" .path=${mdiUndo}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="redo"
|
||||
.disabled=${!this._undoRedoController.canRedo}
|
||||
>
|
||||
${this.hass.localize("ui.common.redo")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-list-item>`
|
||||
<ha-svg-icon slot="icon" .path=${mdiRedo}></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="info">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showSettings}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="settings">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.picker.show_settings"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiCog}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._editCategory}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="category">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.scene.picker.${this._registryEntry?.categories?.automation ? "edit_category" : "assign_category"}`
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiTag}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._runActions}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="run">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.run")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlay}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${stateObj && this.narrow
|
||||
? html`<a
|
||||
href="/config/automation/trace/${encodeURIComponent(
|
||||
this._config.id!
|
||||
)}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>`
|
||||
? html`<ha-dropdown-item value="trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.show_trace"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiTransitConnection}
|
||||
></ha-svg-icon>
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._promptAutomationAlias}
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
.disabled=${this._readOnly ||
|
||||
!this.automationId ||
|
||||
this._mode === "yaml"}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.editor.rename")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
${!useBlueprint
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
<ha-dropdown-item
|
||||
@click=${this._promptAutomationMode}
|
||||
.disabled=${this._readOnly || this._mode === "yaml"}
|
||||
>
|
||||
@@ -399,18 +379,17 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
"ui.panel.config.automation.editor.change_mode"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDebugStepOver}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
.disabled=${this._blueprintConfig ||
|
||||
<ha-dropdown-item
|
||||
.disabled=${!!this._blueprintConfig ||
|
||||
(!this._readOnly && !this.automationId)}
|
||||
graphic="icon"
|
||||
@click=${this._duplicate}
|
||||
value="duplicate"
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._readOnly
|
||||
@@ -418,74 +397,60 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
: "ui.panel.config.automation.editor.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${useBlueprint
|
||||
? html`
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._takeControl}
|
||||
<ha-dropdown-item
|
||||
value="take_control"
|
||||
.disabled=${this._readOnly}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.take_control"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiFileEdit}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiFileEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
@click=${this._mode === "gui"
|
||||
? this._switchYamlMode
|
||||
: this._switchUiMode}
|
||||
>
|
||||
<ha-dropdown-item value="toggle_yaml_mode">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${this._mode === "gui" ? "yaml" : "ui"}`
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._toggle}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="disable">
|
||||
${stateObj?.state === "off"
|
||||
? this.hass.localize("ui.panel.config.automation.editor.enable")
|
||||
: this.hass.localize("ui.panel.config.automation.editor.disable")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${stateObj?.state === "off"
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
<ha-dropdown-item
|
||||
.disabled=${!this.automationId}
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
graphic="icon"
|
||||
@click=${this._deleteConfirm}
|
||||
.variant=${this.automationId ? "danger" : "default"}
|
||||
value="delete"
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.automation.picker.delete")}
|
||||
<ha-svg-icon
|
||||
class=${classMap({ warning: Boolean(this.automationId) })}
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
>
|
||||
</ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<div
|
||||
class=${this._mode === "yaml" ? "yaml-mode" : ""}
|
||||
@subscribe-automation-config=${this._subscribeAutomationConfig}
|
||||
@@ -1248,6 +1213,63 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
this._undoRedoController.redo();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "undo":
|
||||
this._undo();
|
||||
break;
|
||||
case "redo":
|
||||
this._redo();
|
||||
break;
|
||||
case "info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "settings":
|
||||
this._showSettings();
|
||||
break;
|
||||
case "category":
|
||||
this._editCategory();
|
||||
break;
|
||||
case "run":
|
||||
this._runActions();
|
||||
break;
|
||||
case "rename":
|
||||
this._promptAutomationAlias();
|
||||
break;
|
||||
case "change_mode":
|
||||
this._promptAutomationMode();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicate();
|
||||
break;
|
||||
case "take_control":
|
||||
this._takeControl();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
if (this._mode === "gui") {
|
||||
this._switchYamlMode();
|
||||
break;
|
||||
}
|
||||
this._switchUiMode();
|
||||
break;
|
||||
case "disable":
|
||||
this._toggle();
|
||||
break;
|
||||
case "delete":
|
||||
this._deleteConfirm();
|
||||
break;
|
||||
case "trace":
|
||||
this._showTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -1300,13 +1322,6 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-start: initial;
|
||||
}
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
ha-button-menu a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -1339,5 +1354,3 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-automation-editor", HaAutomationEditor);
|
||||
|
||||
@@ -35,6 +35,8 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import "../../../components/ha-tooltip";
|
||||
import type { LocalizeFunc } from "../../../common/translations/localize";
|
||||
import {
|
||||
hasRejectedItems,
|
||||
@@ -327,14 +329,19 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
|
||||
const date = new Date(automation.last_triggered);
|
||||
const now = new Date();
|
||||
const dayDifference = differenceInDays(now, date);
|
||||
const formattedTime = formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
);
|
||||
const elementId = "last-triggered-" + slugify(automation.entity_id);
|
||||
return html`
|
||||
${dayDifference > 3
|
||||
? formatShortDateTimeWithConditionalYear(
|
||||
date,
|
||||
this.hass.locale,
|
||||
this.hass.config
|
||||
)
|
||||
: relativeTime(date, locale)}
|
||||
? formattedTime
|
||||
: html`
|
||||
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
|
||||
<span id=${elementId}>${relativeTime(date, locale)}</span>
|
||||
`}
|
||||
`;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiDotsVertical,
|
||||
mdiDownload,
|
||||
@@ -15,11 +16,13 @@ import { repeat } from "lit/directives/repeat";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { formatDateTimeWithSeconds } from "../../../common/datetime/format_date_time";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../components/ha-dropdown-item";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/trace/ha-trace-blueprint-config";
|
||||
import "../../../components/trace/ha-trace-config";
|
||||
import "../../../components/trace/ha-trace-logbook";
|
||||
@@ -104,9 +107,7 @@ export class HaAutomationTrace extends LitElement {
|
||||
appearance="plain"
|
||||
size="small"
|
||||
class="trace-link"
|
||||
href="/config/automation/edit/${encodeURIComponent(
|
||||
stateObj.attributes.id
|
||||
)}"
|
||||
@click=${this._navigateToAutomation}
|
||||
slot="toolbar-icon"
|
||||
>
|
||||
${this.hass.localize(
|
||||
@@ -114,65 +115,50 @@ export class HaAutomationTrace extends LitElement {
|
||||
)}
|
||||
</ha-button>
|
||||
`
|
||||
: ""}
|
||||
<ha-button-menu slot="toolbar-icon">
|
||||
: nothing}
|
||||
<ha-dropdown
|
||||
slot="toolbar-icon"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!stateObj}
|
||||
@click=${this._showInfo}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!stateObj} value="show_info">
|
||||
${this.hass.localize("ui.panel.config.automation.editor.show_info")}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
slot="icon"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${stateObj?.attributes.id && this.narrow
|
||||
? html`
|
||||
<a
|
||||
class="trace-link"
|
||||
href="/config/automation/edit/${encodeURIComponent(
|
||||
stateObj.attributes.id
|
||||
)}"
|
||||
>
|
||||
<ha-list-item graphic="icon">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.edit_automation"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${mdiPencil}
|
||||
></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</a>
|
||||
<ha-dropdown-item value="edit_automation">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.edit_automation"
|
||||
)}
|
||||
<ha-svg-icon slot="icon" .path=${mdiPencil}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
|
||||
<li divider role="separator"></li>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-list-item graphic="icon" @click=${this._refreshTraces}>
|
||||
<ha-dropdown-item value="refresh">
|
||||
${this.hass.localize("ui.panel.config.automation.trace.refresh")}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRefresh}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-list-item
|
||||
graphic="icon"
|
||||
.disabled=${!this._trace}
|
||||
@click=${this._downloadTrace}
|
||||
>
|
||||
<ha-dropdown-item .disabled=${!this._trace} value="download_trace">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.trace.download_trace"
|
||||
)}
|
||||
<ha-svg-icon slot="graphic" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-list-item>
|
||||
</ha-button-menu>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDownload}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
|
||||
<div class="toolbar">
|
||||
${this._traces && this._traces.length > 0
|
||||
@@ -520,6 +506,37 @@ export class HaAutomationTrace extends LitElement {
|
||||
fireEvent(this, "hass-more-info", { entityId: this._entityId });
|
||||
}
|
||||
|
||||
private _navigateToAutomation() {
|
||||
if (this._entityId && this.hass.states[this._entityId]) {
|
||||
navigate(
|
||||
`/config/automation/edit/${encodeURIComponent(this.hass.states[this._entityId].attributes.id)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "show_info":
|
||||
this._showInfo();
|
||||
break;
|
||||
case "refresh":
|
||||
this._refreshTraces();
|
||||
break;
|
||||
case "download_trace":
|
||||
this._downloadTrace();
|
||||
break;
|
||||
case "edit_automation":
|
||||
this._navigateToAutomation();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
|
||||
@@ -20,10 +20,11 @@ import { capitalizeFirstLetter } from "../../../../common/string/capitalize-firs
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type {
|
||||
Condition,
|
||||
@@ -36,6 +37,7 @@ import type { Action, Option } from "../../../../data/script";
|
||||
import { showPromptDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
import "../action/ha-automation-action";
|
||||
import type HaAutomationAction from "../action/ha-automation-action";
|
||||
import "../condition/ha-automation-condition";
|
||||
@@ -46,7 +48,6 @@ import {
|
||||
overflowStyles,
|
||||
rowStyles,
|
||||
} from "../styles";
|
||||
import { showToast } from "../../../../util/toast";
|
||||
|
||||
@customElement("ha-automation-option-row")
|
||||
export default class HaAutomationOptionRow extends LitElement {
|
||||
@@ -155,15 +156,12 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
|
||||
${this.option
|
||||
? html`
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -171,24 +169,18 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._renameOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-dropdown-item value="rename" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._duplicateOption}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -197,45 +189,42 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.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}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.disabled=${this.disabled || !!this.last}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.move_down"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiArrowDown}
|
||||
></ha-svg-icon
|
||||
></ha-md-menu-item>
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
@click=${this._removeOption}
|
||||
class="warning"
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -246,7 +235,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -261,8 +249,8 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
`
|
||||
: nothing}
|
||||
${!this.optionsInSidebar ? this._renderContent() : nothing}
|
||||
@@ -361,6 +349,32 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
fireEvent(this, "move-down");
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this._renameOption();
|
||||
break;
|
||||
case "delete":
|
||||
this._removeOption();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateOption();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _removeOption = () => {
|
||||
if (this.option) {
|
||||
fireEvent(this, "value-changed", {
|
||||
@@ -513,9 +527,6 @@ export default class HaAutomationOptionRow extends LitElement {
|
||||
overflowStyles,
|
||||
indentStyle,
|
||||
css`
|
||||
li[role="separator"] {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
h4 {
|
||||
color: var(--ha-color-text-secondary);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -16,8 +17,8 @@ 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";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import { ACTION_BUILDING_BLOCKS } from "../../../../data/action";
|
||||
import type { ActionSidebarConfig } from "../../../../data/automation";
|
||||
import { domainToName } from "../../../../data/integration";
|
||||
@@ -116,6 +117,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -126,38 +128,35 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
: ""}</span
|
||||
>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.run}>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlay}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="run">
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -166,9 +165,9 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -178,7 +177,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -190,13 +188,13 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
value="cut"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -206,7 +204,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -218,32 +215,29 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
value="disable"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -252,14 +246,14 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -269,7 +263,6 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -285,7 +278,7 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: keyed(
|
||||
@@ -341,6 +334,41 @@ export default class HaAutomationSidebarAction extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "run":
|
||||
this.config.run();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } 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 "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dialog-header";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
|
||||
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../ha-automation-editor-warning";
|
||||
import { ScrollableFadeMixin } from "../../../../mixins/scrollable-fade-mixin";
|
||||
|
||||
export interface SidebarOverflowMenuEntry {
|
||||
clickAction: () => void;
|
||||
@@ -36,6 +36,10 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
|
||||
@property({ attribute: false }) public warnings?: string[];
|
||||
|
||||
@property({ attribute: false }) public handleDropdownSelect!: (
|
||||
ev: CustomEvent
|
||||
) => void;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@query(".card-content") private _contentElement!: HTMLDivElement;
|
||||
@@ -63,14 +67,10 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
<slot slot="title" name="title"></slot>
|
||||
<slot slot="subtitle" name="subtitle"></slot>
|
||||
<slot name="overflow-menu" slot="actionItems">
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
@click=${this._openOverflowMenu}
|
||||
<ha-dropdown
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
.positioning=${this.narrow ? "absolute" : "fixed"}
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
placement="bottom-end"
|
||||
>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
@@ -78,7 +78,7 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
<slot name="menu-items"></slot>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown>
|
||||
</slot>
|
||||
</ha-dialog-header>
|
||||
${this.warnings
|
||||
@@ -100,11 +100,6 @@ export default class HaAutomationSidebarCard extends ScrollableFadeMixin(
|
||||
fireEvent(this, "close-sidebar");
|
||||
}
|
||||
|
||||
private _openOverflowMenu(ev: MouseEvent) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
...super.styles,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -16,9 +17,11 @@ 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 "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type {
|
||||
LegacyCondition,
|
||||
ConditionSidebarConfig,
|
||||
LegacyCondition,
|
||||
} from "../../../../data/automation";
|
||||
import { testCondition } from "../../../../data/automation";
|
||||
import {
|
||||
@@ -117,6 +120,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -124,42 +128,38 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
|
||||
: ""}</span
|
||||
>
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this._testCondition}>
|
||||
<ha-svg-icon slot="start" .path=${mdiFlask}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="test">
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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-dropdown-item>
|
||||
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -168,10 +168,10 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -181,7 +181,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -193,14 +192,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
value="cut"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -210,7 +209,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -222,32 +220,29 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
value="disable"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -256,14 +251,14 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -273,7 +268,6 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -289,7 +283,7 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${description && !this.yamlMode
|
||||
? html`<div class="description">${description}</div>`
|
||||
: keyed(
|
||||
@@ -419,6 +413,41 @@ export default class HaAutomationSidebarCondition extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "test":
|
||||
this._testCondition();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [
|
||||
sidebarEditorStyles,
|
||||
overflowStyles,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiDelete,
|
||||
@@ -6,8 +7,9 @@ import {
|
||||
} from "@mdi/js";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { OptionSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -50,33 +52,34 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.isWide=${this.isWide}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
${this.config.defaultOption
|
||||
? html`<span slot="overflow-menu"></span>`
|
||||
: html`
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${!!disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
@click=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -85,19 +88,15 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-divider
|
||||
</ha-dropdown-item>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.choose.remove_option"
|
||||
@@ -123,13 +122,33 @@ export default class HaAutomationSidebarOption extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
`}
|
||||
|
||||
<div class="description">${description}</div>
|
||||
</ha-automation-sidebar-card>`;
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ 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 "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
@@ -62,29 +64,30 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle">${subtitle}</span>
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -94,7 +97,6 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -110,7 +112,7 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-selector-editor
|
||||
@@ -160,6 +162,23 @@ export default class HaAutomationSidebarScriptFieldSelector extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = sidebarEditorStyles;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ 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 "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type { ScriptFieldSidebarConfig } from "../../../../data/automation";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { isMac } from "../../../../util/is_mac";
|
||||
@@ -56,28 +58,29 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -87,7 +90,6 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -103,7 +105,7 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-script-field-editor
|
||||
@@ -154,6 +156,23 @@ export default class HaAutomationSidebarScriptField extends LitElement {
|
||||
fireEvent(this, "toggle-yaml-mode");
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiContentCopy,
|
||||
@@ -15,6 +16,8 @@ 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 "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import type {
|
||||
LegacyTrigger,
|
||||
TriggerSidebarConfig,
|
||||
@@ -99,6 +102,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
.yamlMode=${this.yamlMode}
|
||||
.warnings=${this._warnings}
|
||||
.narrow=${this.narrow}
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
>
|
||||
<span slot="title">${title}</span>
|
||||
<span slot="subtitle"
|
||||
@@ -106,60 +110,56 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
? ` (${this.hass.localize("ui.panel.config.automation.editor.actions.disabled")})`
|
||||
: ""}</span
|
||||
>
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.rename}
|
||||
value="rename"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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-dropdown-item>
|
||||
|
||||
${!this.yamlMode &&
|
||||
!("id" in this.config.config) &&
|
||||
!this._requestShowId
|
||||
? html`<ha-md-menu-item
|
||||
? html`<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._showTriggerId}
|
||||
value="show_id"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiIdentifier}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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>`
|
||||
</ha-dropdown-item>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-divider
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.duplicate}
|
||||
value="duplicate"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.duplicate"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item slot="menu-items" .clickAction=${this.config.copy}>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item slot="menu-items" value="copy">
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -169,7 +169,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -181,14 +180,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.cut}
|
||||
value="cut"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -198,7 +197,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -210,32 +208,28 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!this.config.uiSupported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .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
|
||||
</ha-dropdown-item>
|
||||
<wa-divider slot="menu-items"></wa-divider>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
role="separator"
|
||||
tabindex="-1"
|
||||
></ha-md-divider>
|
||||
<ha-md-menu-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.disable}
|
||||
value="disable"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${rowDisabled ? mdiPlayCircleOutline : mdiStopCircleOutline}
|
||||
></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
@@ -244,14 +238,14 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
)}
|
||||
<span class="shortcut-placeholder ${isMac ? "mac" : ""}"></span>
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
slot="menu-items"
|
||||
.clickAction=${this.config.delete}
|
||||
value="delete"
|
||||
.disabled=${this.disabled}
|
||||
class="warning"
|
||||
variant="danger"
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiDelete}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiDelete}></ha-svg-icon>
|
||||
<div class="overflow-label">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.delete"
|
||||
@@ -261,7 +255,6 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -277,7 +270,7 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
${keyed(
|
||||
this.sidebarKey,
|
||||
html`<ha-automation-trigger-editor
|
||||
@@ -335,6 +328,41 @@ export default class HaAutomationSidebarTrigger extends LitElement {
|
||||
this._requestShowId = true;
|
||||
};
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this.config.rename();
|
||||
break;
|
||||
case "show_id":
|
||||
this._showTriggerId();
|
||||
break;
|
||||
case "duplicate":
|
||||
this.config.duplicate();
|
||||
break;
|
||||
case "copy":
|
||||
this.config.copy();
|
||||
break;
|
||||
case "cut":
|
||||
this.config.cut();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode();
|
||||
break;
|
||||
case "disable":
|
||||
this.config.disable();
|
||||
break;
|
||||
case "delete":
|
||||
this.config.delete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [sidebarEditorStyles, overflowStyles];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
@@ -34,11 +35,11 @@ import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-automation-row";
|
||||
import type { HaAutomationRow } from "../../../../components/ha-automation-row";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-dropdown";
|
||||
import "../../../../components/ha-dropdown-item";
|
||||
import type { HaDropdownItem } from "../../../../components/ha-dropdown-item";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-button-menu";
|
||||
import "../../../../components/ha-md-divider";
|
||||
import "../../../../components/ha-md-menu-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { TRIGGER_ICONS } from "../../../../components/ha-trigger-icon";
|
||||
import type {
|
||||
@@ -208,41 +209,35 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
|
||||
<slot name="icons" slot="icons"></slot>
|
||||
|
||||
<ha-md-button-menu
|
||||
quick
|
||||
<ha-dropdown
|
||||
slot="icons"
|
||||
@click=${preventDefaultStopPropagation}
|
||||
@keydown=${stopPropagation}
|
||||
@closed=${stopPropagation}
|
||||
positioning="fixed"
|
||||
anchor-corner="end-end"
|
||||
menu-corner="start-end"
|
||||
@wa-select=${this._handleDropdownSelect}
|
||||
placement="bottom-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}
|
||||
<ha-dropdown-item
|
||||
value="rename"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiRenameBox}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.rename"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._duplicateTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-dropdown-item value="duplicate" .disabled=${this.disabled}>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiPlusCircleMultipleOutline}
|
||||
></ha-svg-icon>
|
||||
|
||||
@@ -251,13 +246,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
"ui.panel.config.automation.editor.actions.duplicate"
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._copyTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
<ha-dropdown-item value="copy" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCopy}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.copy"
|
||||
@@ -266,7 +258,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -277,13 +268,10 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span>C</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._cutTrigger}
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiContentCut}></ha-svg-icon>
|
||||
<ha-dropdown-item value="cut" .disabled=${this.disabled}>
|
||||
<ha-svg-icon slot="icon" .path=${mdiContentCut}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.cut"
|
||||
@@ -292,7 +280,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -303,51 +290,51 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span>X</span>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
${!this.optionsInSidebar
|
||||
? html`
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._moveUp}
|
||||
<ha-dropdown-item
|
||||
value="move_up"
|
||||
.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}
|
||||
<ha-svg-icon slot="icon" .path=${mdiArrowUp}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="move_down"
|
||||
.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-svg-icon slot="icon" .path=${mdiArrowDown}></ha-svg-icon
|
||||
></ha-dropdown-item>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._toggleYamlMode}
|
||||
<ha-dropdown-item
|
||||
value="toggle_yaml_mode"
|
||||
.disabled=${!supported || !!this._warnings}
|
||||
>
|
||||
<ha-svg-icon slot="start" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
<ha-svg-icon slot="icon" .path=${mdiPlaylistEdit}></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
this.hass.localize(
|
||||
`ui.panel.config.automation.editor.edit_${!yamlMode ? "yaml" : "ui"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-dropdown-item>
|
||||
|
||||
<ha-md-divider role="separator" tabindex="-1"></ha-md-divider>
|
||||
<wa-divider></wa-divider>
|
||||
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDisable}
|
||||
<ha-dropdown-item
|
||||
value="disable"
|
||||
.disabled=${this.disabled || type === "list"}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${"enabled" in this.trigger && this.trigger.enabled === false
|
||||
? mdiPlayCircleOutline
|
||||
: mdiStopCircleOutline}
|
||||
@@ -358,15 +345,15 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
`ui.panel.config.automation.editor.actions.${"enabled" in this.trigger && this.trigger.enabled === false ? "enable" : "disable"}`
|
||||
)
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
<ha-md-menu-item
|
||||
.clickAction=${this._onDelete}
|
||||
class="warning"
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item
|
||||
value="delete"
|
||||
variant="danger"
|
||||
.disabled=${this.disabled}
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="warning"
|
||||
slot="start"
|
||||
slot="icon"
|
||||
.path=${mdiDelete}
|
||||
></ha-svg-icon>
|
||||
${this._renderOverflowLabel(
|
||||
@@ -377,7 +364,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<span
|
||||
>${isMac
|
||||
? html`<ha-svg-icon
|
||||
slot="start"
|
||||
.path=${mdiAppleKeyboardCommand}
|
||||
></ha-svg-icon>`
|
||||
: this.hass.localize(
|
||||
@@ -392,8 +378,8 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
>
|
||||
</span>`
|
||||
)}
|
||||
</ha-md-menu-item>
|
||||
</ha-md-button-menu>
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
${!this.optionsInSidebar
|
||||
? html`${this._warnings
|
||||
? html`<ha-automation-editor-warning
|
||||
@@ -804,6 +790,44 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
this._automationRowElement?.focus();
|
||||
}
|
||||
|
||||
private _handleDropdownSelect(ev: CustomEvent<{ item: HaDropdownItem }>) {
|
||||
const action = ev.detail?.item?.value;
|
||||
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "rename":
|
||||
this._renameTrigger();
|
||||
break;
|
||||
case "duplicate":
|
||||
this._duplicateTrigger();
|
||||
break;
|
||||
case "copy":
|
||||
this._copyTrigger();
|
||||
break;
|
||||
case "cut":
|
||||
this._cutTrigger();
|
||||
break;
|
||||
case "move_up":
|
||||
this._moveUp();
|
||||
break;
|
||||
case "move_down":
|
||||
this._moveDown();
|
||||
break;
|
||||
case "toggle_yaml_mode":
|
||||
this._toggleYamlMode(ev.target as HTMLElement);
|
||||
break;
|
||||
case "disable":
|
||||
this._onDisable();
|
||||
break;
|
||||
case "delete":
|
||||
this._onDelete();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
rowStyles,
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
type Trigger,
|
||||
type TriggerList,
|
||||
} from "../../../../data/automation";
|
||||
import { subscribeLabFeatures } from "../../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../../data/labs";
|
||||
import type { TriggerDescriptions } from "../../../../data/trigger";
|
||||
import { isTriggerList, subscribeTriggers } from "../../../../data/trigger";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
@@ -85,14 +85,14 @@ export default class HaAutomationTrigger extends SubscribeMixin(LitElement) {
|
||||
|
||||
protected hassSubscribe() {
|
||||
return [
|
||||
subscribeLabFeatures(this.hass!.connection, (features) => {
|
||||
this._newTriggersAndConditions =
|
||||
features.find(
|
||||
(feature) =>
|
||||
feature.domain === "automation" &&
|
||||
feature.preview_feature === "new_triggers_conditions"
|
||||
)?.enabled ?? false;
|
||||
}),
|
||||
subscribeLabFeature(
|
||||
this.hass!.connection,
|
||||
"automation",
|
||||
"new_triggers_conditions",
|
||||
(enabled) => {
|
||||
this._newTriggersAndConditions = enabled;
|
||||
}
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -160,9 +160,9 @@ export class HaPlatformTrigger extends LitElement {
|
||||
|
||||
const hasOptional = Boolean(
|
||||
triggerDesc?.fields &&
|
||||
Object.values(triggerDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
Object.values(triggerDesc.fields).some((field) =>
|
||||
showOptionalToggle(field)
|
||||
)
|
||||
);
|
||||
|
||||
return html`
|
||||
@@ -429,6 +429,10 @@ export class HaPlatformTrigger extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: 0px calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
ha-settings-row {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mdiOpenInNew } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { state } from "lit/decorators";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
@@ -13,6 +13,7 @@ import type { WebhookDialogParams } from "./show-dialog-manage-cloudhook";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-copy-textfield";
|
||||
|
||||
@customElement("dialog-manage-cloudhook")
|
||||
export class DialogManageCloudhook extends LitElement {
|
||||
protected hass?: HomeAssistant;
|
||||
|
||||
@@ -155,5 +156,3 @@ declare global {
|
||||
"dialog-manage-cloudhook": DialogManageCloudhook;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-manage-cloudhook", DialogManageCloudhook);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||
import { mdiDotsVertical, mdiRefresh } from "@mdi/js";
|
||||
import type { HassEntities } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult } from "lit";
|
||||
@@ -5,9 +6,13 @@ import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-bar";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-check-list-item";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-metric";
|
||||
import { extractApiErrorMessage } from "../../../data/hassio/common";
|
||||
import type {
|
||||
@@ -28,9 +33,6 @@ import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import "../dashboard/ha-config-updates";
|
||||
import { showJoinBetaDialog } from "./updates/show-dialog-join-beta";
|
||||
import "../../../components/ha-dropdown";
|
||||
import "../../../components/ha-dropdown-item";
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
|
||||
@customElement("ha-config-section-updates")
|
||||
class HaConfigSectionUpdates extends LitElement {
|
||||
@@ -71,25 +73,24 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
.path=${mdiRefresh}
|
||||
@click=${this._checkUpdates}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown @wa-select=${this._handleOverflowAction}>
|
||||
<ha-button-menu multi>
|
||||
<ha-icon-button
|
||||
slot="trigger"
|
||||
.label=${this.hass.localize("ui.common.menu")}
|
||||
.path=${mdiDotsVertical}
|
||||
></ha-icon-button>
|
||||
|
||||
<ha-dropdown-item
|
||||
type="checkbox"
|
||||
value="show_skipped"
|
||||
.checked=${this._showSkipped}
|
||||
<ha-check-list-item
|
||||
left
|
||||
@request-selected=${this._toggleSkipped}
|
||||
.selected=${this._showSkipped}
|
||||
>
|
||||
${this.hass.localize("ui.panel.config.updates.show_skipped")}
|
||||
</ha-dropdown-item>
|
||||
</ha-check-list-item>
|
||||
${this._supervisorInfo
|
||||
? html`
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item
|
||||
value="toggle_beta"
|
||||
<li divider role="separator"></li>
|
||||
<ha-list-item
|
||||
@request-selected=${this._toggleBeta}
|
||||
.disabled=${this._supervisorInfo.channel === "dev"}
|
||||
>
|
||||
${this._supervisorInfo.channel === "stable"
|
||||
@@ -97,10 +98,10 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.updates.leave_beta"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-list-item>
|
||||
`
|
||||
: ""}
|
||||
</ha-dropdown>
|
||||
</ha-button-menu>
|
||||
</div>
|
||||
<div class="content">
|
||||
<ha-card outlined>
|
||||
@@ -132,19 +133,27 @@ class HaConfigSectionUpdates extends LitElement {
|
||||
this._supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
|
||||
}
|
||||
|
||||
private async _handleOverflowAction(
|
||||
ev: CustomEvent<{ item: { value: string } }>
|
||||
private _toggleSkipped(ev: CustomEvent<RequestSelectedDetail>): void {
|
||||
if (ev.detail.source !== "property") {
|
||||
return;
|
||||
}
|
||||
|
||||
this._showSkipped = !this._showSkipped;
|
||||
}
|
||||
|
||||
private async _toggleBeta(
|
||||
ev: CustomEvent<RequestSelectedDetail>
|
||||
): Promise<void> {
|
||||
if (ev.detail.item.value === "toggle_beta") {
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
} else if (ev.detail.item.value === "show_skipped") {
|
||||
this._showSkipped = !this._showSkipped;
|
||||
if (!shouldHandleRequestSelectedEvent(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._supervisorInfo!.channel === "stable") {
|
||||
showJoinBetaDialog(this, {
|
||||
join: async () => this._setChannel("beta"),
|
||||
});
|
||||
} else {
|
||||
this._setChannel("stable");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1138,23 +1138,20 @@ export class HaConfigDevicePage extends LitElement {
|
||||
}
|
||||
|
||||
if (domains.includes("mqtt")) {
|
||||
const mqtt = await import(
|
||||
"./device-detail/integration-elements/mqtt/device-actions"
|
||||
);
|
||||
const mqtt =
|
||||
await import("./device-detail/integration-elements/mqtt/device-actions");
|
||||
const actions = mqtt.getMQTTDeviceActions(this, device);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("zha")) {
|
||||
const zha = await import(
|
||||
"./device-detail/integration-elements/zha/device-actions"
|
||||
);
|
||||
const zha =
|
||||
await import("./device-detail/integration-elements/zha/device-actions");
|
||||
const actions = await zha.getZHADeviceActions(this, this.hass, device);
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("zwave_js")) {
|
||||
const zwave = await import(
|
||||
"./device-detail/integration-elements/zwave_js/device-actions"
|
||||
);
|
||||
const zwave =
|
||||
await import("./device-detail/integration-elements/zwave_js/device-actions");
|
||||
const actions = await zwave.getZwaveDeviceActions(
|
||||
this,
|
||||
this.hass,
|
||||
@@ -1163,9 +1160,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("esphome")) {
|
||||
const esphome = await import(
|
||||
"./device-detail/integration-elements/esphome/device-actions"
|
||||
);
|
||||
const esphome =
|
||||
await import("./device-detail/integration-elements/esphome/device-actions");
|
||||
const actions = await esphome.getESPHomeDeviceActions(
|
||||
this,
|
||||
this.hass,
|
||||
@@ -1174,9 +1170,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
deviceActions.push(...actions);
|
||||
}
|
||||
if (domains.includes("matter")) {
|
||||
const matter = await import(
|
||||
"./device-detail/integration-elements/matter/device-actions"
|
||||
);
|
||||
const matter =
|
||||
await import("./device-detail/integration-elements/matter/device-actions");
|
||||
const defaultActions = matter.getMatterDeviceDefaultActions(
|
||||
this,
|
||||
this.hass,
|
||||
@@ -1220,9 +1215,8 @@ export class HaConfigDevicePage extends LitElement {
|
||||
).map((int) => int.domain);
|
||||
|
||||
if (domains.includes("zwave_js")) {
|
||||
const zwave = await import(
|
||||
"./device-detail/integration-elements/zwave_js/device-alerts"
|
||||
);
|
||||
const zwave =
|
||||
await import("./device-detail/integration-elements/zwave_js/device-alerts");
|
||||
|
||||
const alerts = await zwave.getZwaveDeviceAlerts(this.hass, device);
|
||||
deviceAlerts.push(...alerts);
|
||||
@@ -1304,9 +1298,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`);
|
||||
}
|
||||
if (domains.includes("zwave_js")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js"
|
||||
);
|
||||
import("./device-detail/integration-elements/zwave_js/ha-device-info-zwave_js");
|
||||
deviceInfo.push(html`
|
||||
<ha-device-info-zwave_js
|
||||
.hass=${this.hass}
|
||||
@@ -1315,9 +1307,7 @@ export class HaConfigDevicePage extends LitElement {
|
||||
`);
|
||||
}
|
||||
if (domains.includes("matter")) {
|
||||
import(
|
||||
"./device-detail/integration-elements/matter/ha-device-info-matter"
|
||||
);
|
||||
import("./device-detail/integration-elements/matter/ha-device-info-matter");
|
||||
deviceInfo.push(html`
|
||||
<ha-device-info-matter
|
||||
.hass=${this.hass}
|
||||
|
||||
@@ -1012,7 +1012,6 @@ export class EntityRegistrySettingsEditor extends LitElement {
|
||||
? html`<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
.value=${this._areaId}
|
||||
.placeholder=${this._device?.area_id}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._areaPicked}
|
||||
></ha-area-picker>`
|
||||
|
||||
@@ -116,8 +116,10 @@ import { showAddIntegrationDialog } from "../integrations/show-add-integration-d
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
|
||||
export interface StateEntity
|
||||
extends Omit<EntityRegistryEntry, "id" | "unique_id"> {
|
||||
export interface StateEntity extends Omit<
|
||||
EntityRegistryEntry,
|
||||
"id" | "unique_id"
|
||||
> {
|
||||
readonly?: boolean;
|
||||
selectable?: boolean;
|
||||
id?: string;
|
||||
|
||||
@@ -530,9 +530,7 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
||||
zha: {
|
||||
tag: "zha-config-dashboard-router",
|
||||
load: () =>
|
||||
import(
|
||||
"./integrations/integration-panels/zha/zha-config-dashboard-router"
|
||||
),
|
||||
import("./integrations/integration-panels/zha/zha-config-dashboard-router"),
|
||||
},
|
||||
mqtt: {
|
||||
tag: "mqtt-config-panel",
|
||||
@@ -542,30 +540,22 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
||||
zwave_js: {
|
||||
tag: "zwave_js-config-router",
|
||||
load: () =>
|
||||
import(
|
||||
"./integrations/integration-panels/zwave_js/zwave_js-config-router"
|
||||
),
|
||||
import("./integrations/integration-panels/zwave_js/zwave_js-config-router"),
|
||||
},
|
||||
matter: {
|
||||
tag: "matter-config-panel",
|
||||
load: () =>
|
||||
import(
|
||||
"./integrations/integration-panels/matter/matter-config-panel"
|
||||
),
|
||||
import("./integrations/integration-panels/matter/matter-config-panel"),
|
||||
},
|
||||
thread: {
|
||||
tag: "thread-config-panel",
|
||||
load: () =>
|
||||
import(
|
||||
"./integrations/integration-panels/thread/thread-config-panel"
|
||||
),
|
||||
import("./integrations/integration-panels/thread/thread-config-panel"),
|
||||
},
|
||||
bluetooth: {
|
||||
tag: "bluetooth-config-dashboard-router",
|
||||
load: () =>
|
||||
import(
|
||||
"./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"
|
||||
),
|
||||
import("./integrations/integration-panels/bluetooth/bluetooth-config-dashboard-router"),
|
||||
},
|
||||
dhcp: {
|
||||
tag: "dhcp-config-panel",
|
||||
@@ -580,9 +570,7 @@ class HaPanelConfig extends SubscribeMixin(HassRouterPage) {
|
||||
zeroconf: {
|
||||
tag: "zeroconf-config-panel",
|
||||
load: () =>
|
||||
import(
|
||||
"./integrations/integration-panels/zeroconf/zeroconf-config-panel"
|
||||
),
|
||||
import("./integrations/integration-panels/zeroconf/zeroconf-config-panel"),
|
||||
},
|
||||
application_credentials: {
|
||||
tag: "ha-config-application-credentials",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-form/ha-form";
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
} from "./show-dialog-schedule-block-info";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
|
||||
@customElement("dialog-schedule-block-info")
|
||||
class DialogScheduleBlockInfo extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@@ -156,5 +157,3 @@ declare global {
|
||||
"dialog-schedule-block-info": DialogScheduleBlockInfo;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("dialog-schedule-block-info", DialogScheduleBlockInfo);
|
||||
|
||||
@@ -9,10 +9,10 @@ export class MatterAddDevice extends HTMLElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
connectedCallback() {
|
||||
showMatterAddDeviceDialog(this);
|
||||
navigate(`/config/devices`, {
|
||||
navigate("/config/devices/dashboard", {
|
||||
replace: true,
|
||||
});
|
||||
showMatterAddDeviceDialog(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { stopPropagation } from "../../../../../common/dom/stop_propagation";
|
||||
import "../../../../../components/buttons/ha-call-service-button";
|
||||
import "../../../../../components/ha-card";
|
||||
@@ -15,6 +15,7 @@ import type { HomeAssistant } from "../../../../../types";
|
||||
import { formatAsPaddedHex } from "./functions";
|
||||
import type { IssueCommandServiceData } from "./types";
|
||||
|
||||
@customElement("zha-cluster-commands")
|
||||
export class ZHAClusterCommands extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@@ -259,5 +260,3 @@ declare global {
|
||||
"zha-cluster-commands": ZHAClusterCommands;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("zha-cluster-commands", ZHAClusterCommands);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user