mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-31 03:50:26 +00:00
Compare commits
82 Commits
lit-grid-l
...
20201111.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7e6153ba7d | ||
![]() |
34fddd5940 | ||
![]() |
0e5d6fe8d8 | ||
![]() |
e1342a0d9d | ||
![]() |
0cc2d3aaa7 | ||
![]() |
67814505b3 | ||
![]() |
bae29c6d62 | ||
![]() |
a0e67d4c03 | ||
![]() |
131bc5fbf7 | ||
![]() |
051218e29b | ||
![]() |
6ace8307d8 | ||
![]() |
e84bef44b7 | ||
![]() |
3186d762f2 | ||
![]() |
c97a3b0a56 | ||
![]() |
78f1bb3b91 | ||
![]() |
67707fbc90 | ||
![]() |
2a57ffa615 | ||
![]() |
216fce74f8 | ||
![]() |
6cd3e6652a | ||
![]() |
fe7d79cee6 | ||
![]() |
2f4e7b388b | ||
![]() |
2e289cd152 | ||
![]() |
21a3dcf06c | ||
![]() |
7f56add914 | ||
![]() |
88701c6167 | ||
![]() |
e4ce6117a1 | ||
![]() |
cec2a61bdf | ||
![]() |
8275ac5853 | ||
![]() |
b7bcf97365 | ||
![]() |
fa28b480f1 | ||
![]() |
4bb95b7396 | ||
![]() |
5a9bd73e8b | ||
![]() |
4fe0276914 | ||
![]() |
5e8bda55b4 | ||
![]() |
d09c4898c1 | ||
![]() |
6ae67ed299 | ||
![]() |
32ff166a74 | ||
![]() |
8feae04281 | ||
![]() |
129f9c147b | ||
![]() |
6e336dd207 | ||
![]() |
161561c48a | ||
![]() |
c162e84383 | ||
![]() |
dc8d80a6e5 | ||
![]() |
293f67968c | ||
![]() |
4dcf26236e | ||
![]() |
a0e8d69243 | ||
![]() |
33cd9bf516 | ||
![]() |
0132797f2f | ||
![]() |
7e2db0aa4e | ||
![]() |
cc1d50491b | ||
![]() |
461b86a04b | ||
![]() |
9a3a7c28f4 | ||
![]() |
1c9d0200ca | ||
![]() |
0037cd2e69 | ||
![]() |
028ae061da | ||
![]() |
744efa30f2 | ||
![]() |
bf4a94dc48 | ||
![]() |
ce4ba2f6f1 | ||
![]() |
5b232b5d35 | ||
![]() |
35151bbac7 | ||
![]() |
e37eebe4ad | ||
![]() |
0baaaefdf8 | ||
![]() |
bec0d9b00e | ||
![]() |
e6a4ab789b | ||
![]() |
36c1d3230c | ||
![]() |
30466ec3fe | ||
![]() |
ce414a5ca9 | ||
![]() |
e4e6edd573 | ||
![]() |
79927f4dc9 | ||
![]() |
603b833757 | ||
![]() |
48543a2dad | ||
![]() |
b22f5ae5c2 | ||
![]() |
2acb6a28fe | ||
![]() |
1064cdb79d | ||
![]() |
bd7cb1c877 | ||
![]() |
6c314982dc | ||
![]() |
d54710f113 | ||
![]() |
1346156ecd | ||
![]() |
a2d9f9b417 | ||
![]() |
3de78cca2d | ||
![]() |
a27680b8c0 | ||
![]() |
73be0fef75 |
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -18,8 +18,8 @@
|
|||||||
<!--
|
<!--
|
||||||
Describe the big picture of your changes here to communicate to the
|
Describe the big picture of your changes here to communicate to the
|
||||||
maintainers why we should accept this pull request. If it fixes a bug
|
maintainers why we should accept this pull request. If it fixes a bug
|
||||||
or resolves a feature request, be sure to link to that issue in the
|
or resolves a feature request, be sure to link to that issue or discussion
|
||||||
additional information section.
|
in the additional information section.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Type of change
|
## Type of change
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
- This PR fixes or closes issue: fixes #
|
- This PR fixes or closes issue: fixes #
|
||||||
- This PR is related to issue:
|
- This PR is related to issue or discussion:
|
||||||
- Link to documentation pull request:
|
- Link to documentation pull request:
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
27
.github/lock.yml
vendored
27
.github/lock.yml
vendored
@@ -1,27 +0,0 @@
|
|||||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
|
||||||
|
|
||||||
# Number of days of inactivity before a closed issue or pull request is locked
|
|
||||||
daysUntilLock: 1
|
|
||||||
|
|
||||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
|
||||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
|
||||||
skipCreatedBefore: 2020-01-01
|
|
||||||
|
|
||||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
|
||||||
exemptLabels: []
|
|
||||||
|
|
||||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
|
||||||
lockLabel: false
|
|
||||||
|
|
||||||
# Comment to post before locking. Set to `false` to disable
|
|
||||||
lockComment: false
|
|
||||||
|
|
||||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
|
||||||
setLockReason: false
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
only: pulls
|
|
||||||
|
|
||||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
|
||||||
issues:
|
|
||||||
daysUntilLock: 30
|
|
56
.github/stale.yml
vendored
56
.github/stale.yml
vendored
@@ -1,56 +0,0 @@
|
|||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
||||||
daysUntilStale: 90
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: 7
|
|
||||||
|
|
||||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
|
||||||
onlyLabels: []
|
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
||||||
exemptLabels:
|
|
||||||
- feature request
|
|
||||||
- Help wanted
|
|
||||||
- to do
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
|
||||||
exemptProjects: true
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
|
||||||
exemptMilestones: true
|
|
||||||
|
|
||||||
# Set to true to ignore issues with an assignee (defaults to false)
|
|
||||||
exemptAssignees: false
|
|
||||||
|
|
||||||
# Label to use when marking as stale
|
|
||||||
staleLabel: stale
|
|
||||||
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
There hasn't been any activity on this issue recently. Due to the high number
|
|
||||||
of incoming GitHub notifications, we have to clean some of the old issues,
|
|
||||||
as many of them have already been resolved with the latest updates.
|
|
||||||
|
|
||||||
Please make sure to update to the latest Home Assistant version and check
|
|
||||||
if that solves the issue. Let us know if that works for you by adding a
|
|
||||||
comment 👍
|
|
||||||
|
|
||||||
This issue now has been marked as stale and will be closed if no further
|
|
||||||
activity occurs. Thank you for your contributions.
|
|
||||||
|
|
||||||
# Comment to post when removing the stale label.
|
|
||||||
# unmarkComment: >
|
|
||||||
# Your comment here.
|
|
||||||
|
|
||||||
# Comment to post when closing a stale Issue or Pull Request.
|
|
||||||
# closeComment: >
|
|
||||||
# Your comment here.
|
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
|
||||||
limitPerRun: 30
|
|
||||||
|
|
||||||
# Limit to only `issues` or `pulls`
|
|
||||||
only: issues
|
|
20
.github/workflows/lock.yml
vendored
Normal file
20
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: Lock
|
||||||
|
|
||||||
|
# yamllint disable-line rule:truthy
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lock:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v2.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-lock-inactive-days: "30"
|
||||||
|
issue-exclude-created-before: "2020-10-01T00:00:00Z"
|
||||||
|
issue-lock-reason: ""
|
||||||
|
pr-lock-inactive-days: "1"
|
||||||
|
pr-exclude-created-before: "2020-11-01T00:00:00Z"
|
||||||
|
pr-lock-reason: ""
|
42
.github/workflows/stale.yml
vendored
Normal file
42
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Stale
|
||||||
|
|
||||||
|
# yamllint disable-line rule:truthy
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 90 days stale policy
|
||||||
|
uses: actions/stale@v3.0.13
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
days-before-stale: 90
|
||||||
|
days-before-close: 7
|
||||||
|
operations-per-run: 25
|
||||||
|
remove-stale-when-updated: true
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
exempt-issue-labels: "no-stale,Help%20wanted,help-wanted,feature-request,feature%20request"
|
||||||
|
stale-issue-message: >
|
||||||
|
There hasn't been any activity on this issue recently. Due to the
|
||||||
|
high number of incoming GitHub notifications, we have to clean some
|
||||||
|
of the old issues, as many of them have already been resolved with
|
||||||
|
the latest updates.
|
||||||
|
|
||||||
|
Please make sure to update to the latest Home Assistant version and
|
||||||
|
check if that solves the issue. Let us know if that works for you by
|
||||||
|
adding a comment 👍
|
||||||
|
|
||||||
|
This issue has now been marked as stale and will be closed if no
|
||||||
|
further activity occurs. Thank you for your contributions.
|
||||||
|
|
||||||
|
stale-pr-label: "stale"
|
||||||
|
exempt-pr-labels: "no-stale"
|
||||||
|
stale-pr-message: >
|
||||||
|
There hasn't been any activity on this pull request recently. This
|
||||||
|
pull request has been automatically marked as stale because of that
|
||||||
|
and will be closed if no further activity occurs within 7 days.
|
||||||
|
|
||||||
|
Thank you for your contributions.
|
@@ -35,9 +35,6 @@ const createWebpackConfig = ({
|
|||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: bundle.babelOptions({ latestBuild }),
|
options: bundle.babelOptions({ latestBuild }),
|
||||||
},
|
},
|
||||||
resolve: {
|
|
||||||
fullySpecified: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
|
BIN
gallery/public/images/sunflowers.jpg
Normal file
BIN
gallery/public/images/sunflowers.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 94 KiB |
@@ -6,7 +6,9 @@ export const createMediaPlayerEntities = () => [
|
|||||||
media_content_type: "music",
|
media_content_type: "music",
|
||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
media_artist: "Technohead",
|
media_artist: "Technohead",
|
||||||
supported_features: 64063,
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
|
||||||
|
supported_features: 195135,
|
||||||
entity_picture: "/images/album_cover_2.jpg",
|
entity_picture: "/images/album_cover_2.jpg",
|
||||||
media_duration: 300,
|
media_duration: 300,
|
||||||
media_position: 50,
|
media_position: 50,
|
||||||
@@ -14,12 +16,15 @@ export const createMediaPlayerEntities = () => [
|
|||||||
// 23 seconds in
|
// 23 seconds in
|
||||||
new Date().getTime() - 23000
|
new Date().getTime() - 23000
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
|
volume_level: 0.5,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "music_playing", "playing", {
|
getEntity("media_player", "music_playing", "playing", {
|
||||||
friendly_name: "Playing The Music",
|
friendly_name: "Playing The Music",
|
||||||
media_content_type: "music",
|
media_content_type: "music",
|
||||||
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
|
||||||
media_artist: "Technohead",
|
media_artist: "Technohead",
|
||||||
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Stop + Clear + Play + Shuffle Set
|
||||||
supported_features: 64063,
|
supported_features: 64063,
|
||||||
entity_picture: "/images/album_cover.jpg",
|
entity_picture: "/images/album_cover.jpg",
|
||||||
media_duration: 300,
|
media_duration: 300,
|
||||||
@@ -28,6 +33,7 @@ export const createMediaPlayerEntities = () => [
|
|||||||
// 23 seconds in
|
// 23 seconds in
|
||||||
new Date().getTime() - 23000
|
new Date().getTime() - 23000
|
||||||
).toISOString(),
|
).toISOString(),
|
||||||
|
volume_level: 0.5,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "stream_playing", "playing", {
|
getEntity("media_player", "stream_playing", "playing", {
|
||||||
friendly_name: "Playing the Stream",
|
friendly_name: "Playing the Stream",
|
||||||
@@ -35,50 +41,125 @@ export const createMediaPlayerEntities = () => [
|
|||||||
media_title: "Epic sax guy 10 hours",
|
media_title: "Epic sax guy 10 hours",
|
||||||
app_name: "YouTube",
|
app_name: "YouTube",
|
||||||
entity_picture: "/images/frenck.jpg",
|
entity_picture: "/images/frenck.jpg",
|
||||||
supported_features: 33,
|
// Pause + Next Track + Play + Browse Media
|
||||||
|
supported_features: 147489,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "living_room", "playing", {
|
getEntity("media_player", "stream_paused", "paused", {
|
||||||
friendly_name: "Pause, No skip, tvshow",
|
friendly_name: "Paused the Stream",
|
||||||
|
media_content_type: "movie",
|
||||||
|
media_title: "Epic sax guy 10 hours",
|
||||||
|
app_name: "YouTube",
|
||||||
|
entity_picture: "/images/frenck.jpg",
|
||||||
|
// Pause + Next Track + Play
|
||||||
|
supported_features: 16417,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "stream_playing_previous", "playing", {
|
||||||
|
friendly_name: 'Playing the Stream (with "previous" support)',
|
||||||
|
media_content_type: "movie",
|
||||||
|
media_title: "Epic sax guy 10 hours",
|
||||||
|
app_name: "YouTube",
|
||||||
|
entity_picture: "/images/frenck.jpg",
|
||||||
|
// Pause + Previous Track + Play
|
||||||
|
supported_features: 16401,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "tv_playing", "playing", {
|
||||||
|
friendly_name: "Playing non-skip TV Show",
|
||||||
media_content_type: "tvshow",
|
media_content_type: "tvshow",
|
||||||
media_title: "Chapter 1",
|
media_title: "Chapter 1",
|
||||||
media_series_title: "House of Cards",
|
media_series_title: "House of Cards",
|
||||||
app_name: "Netflix",
|
app_name: "Netflix",
|
||||||
entity_picture: "/images/netflix.jpg",
|
entity_picture: "/images/netflix.jpg",
|
||||||
|
// Pause
|
||||||
supported_features: 1,
|
supported_features: 1,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "sonos_idle", "idle", {
|
getEntity("media_player", "sonos_idle", "idle", {
|
||||||
friendly_name: "Sonos Idle",
|
friendly_name: "Sonos Idle",
|
||||||
|
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Stop + Clear + Play + Shuffle Set
|
||||||
supported_features: 64063,
|
supported_features: 64063,
|
||||||
|
volume_level: 0.33,
|
||||||
|
is_volume_muted: true,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "theater", "off", {
|
getEntity("media_player", "idle_browse_media", "idle", {
|
||||||
|
friendly_name: "Idle waiting for Browse Media (e.g. Spotify)",
|
||||||
|
// Pause + Seek + Volume Set + Previous Track + Next Track + Play Media +
|
||||||
|
// Select Source + Play + Shuffle Set + Browse Media
|
||||||
|
supported_features: 182839,
|
||||||
|
volume_level: 0.79,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_off", "off", {
|
||||||
friendly_name: "TV Off",
|
friendly_name: "TV Off",
|
||||||
|
// On + Off + Play + Next + Pause
|
||||||
|
supported_features: 16801,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_on", "on", {
|
||||||
|
friendly_name: "TV On",
|
||||||
|
// On + Off + Play + Next + Pause
|
||||||
|
supported_features: 16801,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_off_static", "off", {
|
||||||
|
friendly_name: "TV Off (cannot be switched on)",
|
||||||
|
// Off + Next + Pause
|
||||||
|
supported_features: 289,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "theater_on_static", "on", {
|
||||||
|
friendly_name: "TV On (cannot be switched off)",
|
||||||
|
// On + Next + Pause
|
||||||
supported_features: 161,
|
supported_features: 161,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "android_cast", "playing", {
|
getEntity("media_player", "android_cast", "playing", {
|
||||||
friendly_name: "Casting App",
|
friendly_name: "Casting App (no supported features)",
|
||||||
media_title: "Android Screen Casting",
|
media_title: "Android Screen Casting",
|
||||||
app_name: "Screen Mirroring",
|
app_name: "Screen Mirroring",
|
||||||
// supported_features: 21437,
|
}),
|
||||||
|
getEntity("media_player", "image_display", "playing", {
|
||||||
|
friendly_name: "Digital Picture Frame",
|
||||||
|
media_content_type: "image",
|
||||||
|
media_title: "Famous Painting",
|
||||||
|
media_artist: "Famous Artist",
|
||||||
|
entity_picture: "/images/sunflowers.jpg",
|
||||||
|
// On + Off + Browse Media
|
||||||
|
supported_features: 131456,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "unavailable", "unavailable", {
|
getEntity("media_player", "unavailable", "unavailable", {
|
||||||
friendly_name: "Player Unavailable",
|
friendly_name: "Player Unavailable",
|
||||||
|
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
supported_features: 21437,
|
supported_features: 21437,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "unknown", "unknown", {
|
getEntity("media_player", "unknown", "unknown", {
|
||||||
friendly_name: "Player Unknown",
|
friendly_name: "Player Unknown",
|
||||||
|
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
supported_features: 21437,
|
supported_features: 21437,
|
||||||
}),
|
}),
|
||||||
|
getEntity("media_player", "playing", "playing", {
|
||||||
|
friendly_name: "Player Playing (no Pause support)",
|
||||||
|
// Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
|
supported_features: 21436,
|
||||||
|
volume_level: 1,
|
||||||
|
}),
|
||||||
|
getEntity("media_player", "idle", "idle", {
|
||||||
|
friendly_name: "Player Idle",
|
||||||
|
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
|
||||||
|
// Play Media + Stop + Play
|
||||||
|
supported_features: 21437,
|
||||||
|
volume_level: 0,
|
||||||
|
}),
|
||||||
getEntity("media_player", "receiver_on", "on", {
|
getEntity("media_player", "receiver_on", "on", {
|
||||||
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
volume_level: 0.63,
|
volume_level: 0.63,
|
||||||
is_volume_muted: false,
|
is_volume_muted: false,
|
||||||
source: "TV",
|
source: "TV",
|
||||||
friendly_name: "Receiver",
|
friendly_name: "Receiver (selectable sources)",
|
||||||
|
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
||||||
supported_features: 84364,
|
supported_features: 84364,
|
||||||
}),
|
}),
|
||||||
getEntity("media_player", "receiver_off", "off", {
|
getEntity("media_player", "receiver_off", "off", {
|
||||||
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
|
||||||
friendly_name: "Receiver",
|
friendly_name: "Receiver (selectable sources)",
|
||||||
|
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
|
||||||
supported_features: 84364,
|
supported_features: 84364,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
@@ -7,40 +7,61 @@ import { createMediaPlayerEntities } from "../data/media_players";
|
|||||||
|
|
||||||
const CONFIGS = [
|
const CONFIGS = [
|
||||||
{
|
{
|
||||||
heading: "Paused music",
|
heading: "Paused Music",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.music_paused
|
entity: media_player.music_paused
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Playing music",
|
heading: "Playing Music",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.music_playing
|
entity: media_player.music_playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Playing stream",
|
heading: "Playing Stream",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.stream_playing
|
entity: media_player.stream_playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Pause, No skip, tvshow",
|
heading: "Paused Stream",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.living_room
|
entity: media_player.stream_paused
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Screen casting",
|
heading: 'Playing Stream (with "previous" support)',
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.stream_playing_previous
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Playing non-skip TV Show",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.tv_playing
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Screen Casting",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.android_cast
|
entity: media_player.android_cast
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Digital Picture Frame",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.image_display
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Sonos Idle",
|
heading: "Sonos Idle",
|
||||||
config: `
|
config: `
|
||||||
@@ -48,11 +69,53 @@ const CONFIGS = [
|
|||||||
entity: media_player.sonos_idle
|
entity: media_player.sonos_idle
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
heading: "Idle waiting for Browse Media",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.idle_browse_media
|
||||||
|
`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
heading: "Player Off",
|
heading: "Player Off",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.theater
|
entity: media_player.theater_off
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player On",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.theater_on
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player Off (cannot be switched on)",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.theater_off_static
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player On (cannot be switched off)",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.theater_on_static
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player Idle",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.idle
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
heading: "Player Playing",
|
||||||
|
config: `
|
||||||
|
- type: media-control
|
||||||
|
entity: media_player.playing
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,14 +133,14 @@ const CONFIGS = [
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Receiver On",
|
heading: "Receiver On (selectable sources)",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.receiver_on
|
entity: media_player.receiver_on
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
heading: "Receiver Off",
|
heading: "Receiver Off (selectable sources)",
|
||||||
config: `
|
config: `
|
||||||
- type: media-control
|
- type: media-control
|
||||||
entity: media_player.receiver_off
|
entity: media_player.receiver_off
|
||||||
|
@@ -12,23 +12,45 @@ const CONFIGS = [
|
|||||||
- type: entities
|
- type: entities
|
||||||
entities:
|
entities:
|
||||||
- entity: media_player.music_paused
|
- entity: media_player.music_paused
|
||||||
name: Paused music
|
name: Paused Music
|
||||||
- entity: media_player.music_playing
|
- entity: media_player.music_playing
|
||||||
name: Playing music
|
name: Playing Music
|
||||||
- entity: media_player.stream_playing
|
- entity: media_player.stream_playing
|
||||||
name: Paused, no play
|
name: Playing Stream
|
||||||
- entity: media_player.living_room
|
- entity: media_player.stream_paused
|
||||||
name: Pause, No skip, tvshow
|
name: Paused Stream
|
||||||
|
- entity: media_player.stream_playing_previous
|
||||||
|
name: Playing Stream (with "previous" support)
|
||||||
|
- entity: media_player.tv_playing
|
||||||
|
name: Playing non-skip TV Show
|
||||||
- entity: media_player.android_cast
|
- entity: media_player.android_cast
|
||||||
name: Screen casting
|
name: Screen casting
|
||||||
|
- entity: media_player.image_display
|
||||||
|
name: Digital Picture Frame
|
||||||
- entity: media_player.sonos_idle
|
- entity: media_player.sonos_idle
|
||||||
name: Chromcast Idle
|
name: Sonos Idle
|
||||||
- entity: media_player.theater
|
- entity: media_player.idle_browse_media
|
||||||
|
name: Idle waiting for Browse Media
|
||||||
|
- entity: media_player.theater_off
|
||||||
name: Player Off
|
name: Player Off
|
||||||
|
- entity: media_player.theater_on
|
||||||
|
name: Player On
|
||||||
|
- entity: media_player.theater_off_static
|
||||||
|
name: Player Off (cannot be switched on)
|
||||||
|
- entity: media_player.theater_on_static
|
||||||
|
name: Player On (cannot be switched off)
|
||||||
|
- entity: media_player.idle
|
||||||
|
name: Player Idle
|
||||||
|
- entity: media_player.playing
|
||||||
|
name: Player Playing
|
||||||
- entity: media_player.unavailable
|
- entity: media_player.unavailable
|
||||||
name: Player Unavailable
|
name: Player Unavailable
|
||||||
- entity: media_player.unknown
|
- entity: media_player.unknown
|
||||||
name: Player Unknown
|
name: Player Unknown
|
||||||
|
- entity: media_player.receiver_on
|
||||||
|
name: Receiver On (selectable sources)
|
||||||
|
- entity: media_player.receiver_off
|
||||||
|
name: Receiver Off (selectable sources)
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@@ -13,7 +13,10 @@ import {
|
|||||||
fetchHassioAddonInfo,
|
fetchHassioAddonInfo,
|
||||||
HassioAddonDetails,
|
HassioAddonDetails,
|
||||||
} from "../../../src/data/hassio/addon";
|
} from "../../../src/data/hassio/addon";
|
||||||
import { createHassioSession } from "../../../src/data/hassio/supervisor";
|
import {
|
||||||
|
createHassioSession,
|
||||||
|
validateHassioSession,
|
||||||
|
} from "../../../src/data/hassio/ingress";
|
||||||
import "../../../src/layouts/hass-loading-screen";
|
import "../../../src/layouts/hass-loading-screen";
|
||||||
import "../../../src/layouts/hass-subpage";
|
import "../../../src/layouts/hass-subpage";
|
||||||
import { HomeAssistant, Route } from "../../../src/types";
|
import { HomeAssistant, Route } from "../../../src/types";
|
||||||
@@ -35,6 +38,17 @@ class HassioIngressView extends LitElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
public narrow = false;
|
public narrow = false;
|
||||||
|
|
||||||
|
private _sessionKeepAlive?: number;
|
||||||
|
|
||||||
|
public disconnectedCallback() {
|
||||||
|
super.disconnectedCallback();
|
||||||
|
|
||||||
|
if (this._sessionKeepAlive) {
|
||||||
|
clearInterval(this._sessionKeepAlive);
|
||||||
|
this._sessionKeepAlive = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._addon) {
|
if (!this._addon) {
|
||||||
return html` <hass-loading-screen></hass-loading-screen> `;
|
return html` <hass-loading-screen></hass-loading-screen> `;
|
||||||
@@ -44,6 +58,7 @@ class HassioIngressView extends LitElement {
|
|||||||
|
|
||||||
if (!this.ingressPanel) {
|
if (!this.ingressPanel) {
|
||||||
return html`<hass-subpage
|
return html`<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
.header=${this._addon.name}
|
.header=${this._addon.name}
|
||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
>
|
>
|
||||||
@@ -83,10 +98,7 @@ class HassioIngressView extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchData(addonSlug: string) {
|
private async _fetchData(addonSlug: string) {
|
||||||
const createSessionPromise = createHassioSession(this.hass).then(
|
const createSessionPromise = createHassioSession(this.hass);
|
||||||
() => true,
|
|
||||||
() => false
|
|
||||||
);
|
|
||||||
|
|
||||||
let addon;
|
let addon;
|
||||||
|
|
||||||
@@ -119,7 +131,11 @@ class HassioIngressView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await createSessionPromise)) {
|
let session;
|
||||||
|
|
||||||
|
try {
|
||||||
|
session = await createSessionPromise;
|
||||||
|
} catch (err) {
|
||||||
await showAlertDialog(this, {
|
await showAlertDialog(this, {
|
||||||
text: "Unable to create an Ingress session",
|
text: "Unable to create an Ingress session",
|
||||||
title: addon.name,
|
title: addon.name,
|
||||||
@@ -128,6 +144,17 @@ class HassioIngressView extends LitElement {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._sessionKeepAlive) {
|
||||||
|
clearInterval(this._sessionKeepAlive);
|
||||||
|
}
|
||||||
|
this._sessionKeepAlive = window.setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await validateHassioSession(this.hass, session);
|
||||||
|
} catch (err) {
|
||||||
|
session = await createHassioSession(this.hass);
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
this._addon = addon;
|
this._addon = addon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -105,7 +105,6 @@
|
|||||||
"leaflet": "^1.4.0",
|
"leaflet": "^1.4.0",
|
||||||
"leaflet-draw": "^1.0.4",
|
"leaflet-draw": "^1.0.4",
|
||||||
"lit-element": "^2.4.0",
|
"lit-element": "^2.4.0",
|
||||||
"lit-grid-layout": "^1.1.11",
|
|
||||||
"lit-html": "^1.3.0",
|
"lit-html": "^1.3.0",
|
||||||
"lit-virtualizer": "^0.4.2",
|
"lit-virtualizer": "^0.4.2",
|
||||||
"marked": "^1.1.1",
|
"marked": "^1.1.1",
|
||||||
@@ -122,7 +121,6 @@
|
|||||||
"superstruct": "^0.10.12",
|
"superstruct": "^0.10.12",
|
||||||
"tinykeys": "^1.1.1",
|
"tinykeys": "^1.1.1",
|
||||||
"unfetch": "^4.1.0",
|
"unfetch": "^4.1.0",
|
||||||
"uuid": "^8.3.0",
|
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue2-daterange-picker": "^0.5.1",
|
"vue2-daterange-picker": "^0.5.1",
|
||||||
"web-animations-js": "^2.3.2",
|
"web-animations-js": "^2.3.2",
|
||||||
|
@@ -13,7 +13,6 @@
|
|||||||
"src/panels/iframe/ha-panel-iframe.js",
|
"src/panels/iframe/ha-panel-iframe.js",
|
||||||
"src/panels/logbook/ha-panel-logbook.js",
|
"src/panels/logbook/ha-panel-logbook.js",
|
||||||
"src/panels/map/ha-panel-map.js",
|
"src/panels/map/ha-panel-map.js",
|
||||||
"src/panels/shopping-list/ha-panel-shopping-list.js",
|
|
||||||
"src/panels/mailbox/ha-panel-mailbox.js",
|
"src/panels/mailbox/ha-panel-mailbox.js",
|
||||||
"hassio/src/entrypoint.js"
|
"hassio/src/entrypoint.js"
|
||||||
],
|
],
|
||||||
|
BIN
public/static/images/conference.png
Normal file
BIN
public/static/images/conference.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name="home-assistant-frontend",
|
name="home-assistant-frontend",
|
||||||
version="20201021.1",
|
version="20201111.0",
|
||||||
description="The Home Assistant frontend",
|
description="The Home Assistant frontend",
|
||||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||||
author="The Home Assistant Authors",
|
author="The Home Assistant Authors",
|
||||||
|
18
src/common/config/can_show_page.ts
Normal file
18
src/common/config/can_show_page.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { isComponentLoaded } from "./is_component_loaded";
|
||||||
|
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
|
export const canShowPage = (hass: HomeAssistant, page: PageNavigation) => {
|
||||||
|
return (
|
||||||
|
(isCore(page) || isLoadedIntegration(hass, page)) &&
|
||||||
|
!hideAdvancedPage(hass, page)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoadedIntegration = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
|
!page.component || isComponentLoaded(hass, page.component);
|
||||||
|
const isCore = (page: PageNavigation) => page.core;
|
||||||
|
const isAdvancedPage = (page: PageNavigation) => page.advancedOnly;
|
||||||
|
const userWantsAdvanced = (hass: HomeAssistant) => hass.userData?.showAdvanced;
|
||||||
|
const hideAdvancedPage = (hass: HomeAssistant, page: PageNavigation) =>
|
||||||
|
isAdvancedPage(page) && !userWantsAdvanced(hass);
|
8
src/common/util/copy-clipboard.ts
Normal file
8
src/common/util/copy-clipboard.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const copyToClipboard = (str) => {
|
||||||
|
const el = document.createElement("textarea");
|
||||||
|
el.value = str;
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(el);
|
||||||
|
};
|
@@ -1,5 +1,5 @@
|
|||||||
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "../ha-icon-button";
|
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-item/paper-item-body";
|
import "@polymer/paper-item/paper-item-body";
|
||||||
@@ -38,6 +38,8 @@ import { SubscribeMixin } from "../../mixins/subscribe-mixin";
|
|||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "./ha-devices-picker";
|
import "./ha-devices-picker";
|
||||||
|
import "../ha-svg-icon";
|
||||||
|
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
|
||||||
|
|
||||||
interface DevicesByArea {
|
interface DevicesByArea {
|
||||||
[areaId: string]: AreaDevices;
|
[areaId: string]: AreaDevices;
|
||||||
@@ -62,7 +64,7 @@ const rowRenderer = (
|
|||||||
margin: -10px 0;
|
margin: -10px 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
ha-icon-button {
|
mwc-icon-button {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
.devices {
|
.devices {
|
||||||
@@ -324,36 +326,34 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
spellcheck="false"
|
spellcheck="false"
|
||||||
>
|
>
|
||||||
${this.value
|
<div class="suffix" slot="suffix">
|
||||||
? html`
|
${this.value
|
||||||
<ha-icon-button
|
? html`<mwc-icon-button
|
||||||
aria-label=${this.hass.localize(
|
class="clear-button"
|
||||||
|
.label=${this.hass.localize(
|
||||||
"ui.components.device-picker.clear"
|
"ui.components.device-picker.clear"
|
||||||
)}
|
)}
|
||||||
slot="suffix"
|
|
||||||
class="clear-button"
|
|
||||||
icon="hass:close"
|
|
||||||
@click=${this._clearValue}
|
@click=${this._clearValue}
|
||||||
no-ripple
|
no-ripple
|
||||||
>
|
>
|
||||||
Clear
|
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
|
||||||
</ha-icon-button>
|
</mwc-icon-button> `
|
||||||
`
|
: ""}
|
||||||
: ""}
|
${areas.length > 0
|
||||||
${areas.length > 0
|
? html`
|
||||||
? html`
|
<mwc-icon-button
|
||||||
<ha-icon-button
|
.label=${this.hass.localize(
|
||||||
aria-label=${this.hass.localize(
|
"ui.components.device-picker.show_devices"
|
||||||
"ui.components.device-picker.show_devices"
|
)}
|
||||||
)}
|
class="toggle-button"
|
||||||
slot="suffix"
|
>
|
||||||
class="toggle-button"
|
<ha-svg-icon
|
||||||
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
|
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
|
||||||
>
|
></ha-svg-icon>
|
||||||
Toggle
|
</mwc-icon-button>
|
||||||
</ha-icon-button>
|
`
|
||||||
`
|
: ""}
|
||||||
: ""}
|
</div>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
</vaadin-combo-box-light>
|
</vaadin-combo-box-light>
|
||||||
<mwc-button @click=${this._switchPicker}
|
<mwc-button @click=${this._switchPicker}
|
||||||
@@ -409,10 +409,12 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
|
|||||||
|
|
||||||
static get styles(): CSSResult {
|
static get styles(): CSSResult {
|
||||||
return css`
|
return css`
|
||||||
paper-input > ha-icon-button {
|
.suffix {
|
||||||
width: 24px;
|
display: flex;
|
||||||
height: 24px;
|
}
|
||||||
padding: 2px;
|
mwc-icon-button {
|
||||||
|
--mdc-icon-button-size: 24px;
|
||||||
|
padding: 0px 2px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
[hidden] {
|
[hidden] {
|
||||||
|
@@ -19,6 +19,7 @@ import { PolymerChangedEvent } from "../../polymer-types";
|
|||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
import "../ha-svg-icon";
|
import "../ha-svg-icon";
|
||||||
import "./state-badge";
|
import "./state-badge";
|
||||||
|
import { formatAttributeName } from "../../util/hass-attributes-util";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
|
|
||||||
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
|
||||||
@@ -35,7 +36,9 @@ const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => {
|
|||||||
<paper-item></paper-item>
|
<paper-item></paper-item>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
root.querySelector("paper-item")!.textContent = model.item;
|
root.querySelector("paper-item")!.textContent = formatAttributeName(
|
||||||
|
model.item
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@customElement("ha-entity-attribute-picker")
|
@customElement("ha-entity-attribute-picker")
|
||||||
@@ -92,7 +95,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
"ui.components.entity.entity-attribute-picker.attribute"
|
"ui.components.entity.entity-attribute-picker.attribute"
|
||||||
)}
|
)}
|
||||||
.value=${this._value}
|
.value=${this._value ? formatAttributeName(this._value) : ""}
|
||||||
.disabled=${this.disabled || !this.entityId}
|
.disabled=${this.disabled || !this.entityId}
|
||||||
class="input"
|
class="input"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
@@ -140,7 +143,7 @@ class HaEntityAttributePicker extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get _value() {
|
private get _value() {
|
||||||
return this.value || "";
|
return this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
|
||||||
|
@@ -9,7 +9,9 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { until } from "lit-html/directives/until";
|
import { until } from "lit-html/directives/until";
|
||||||
import hassAttributeUtil from "../util/hass-attributes-util";
|
import hassAttributeUtil, {
|
||||||
|
formatAttributeName,
|
||||||
|
} from "../util/hass-attributes-util";
|
||||||
|
|
||||||
let jsYamlPromise: Promise<typeof import("js-yaml")>;
|
let jsYamlPromise: Promise<typeof import("js-yaml")>;
|
||||||
|
|
||||||
@@ -34,7 +36,7 @@ class HaAttributes extends LitElement {
|
|||||||
(attribute) => html`
|
(attribute) => html`
|
||||||
<div class="data-entry">
|
<div class="data-entry">
|
||||||
<div class="key">
|
<div class="key">
|
||||||
${attribute.replace(/_/g, " ").replace(/\bid\b/g, "ID")}
|
${formatAttributeName(attribute)}
|
||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
${this.formatAttribute(attribute)}
|
${this.formatAttribute(attribute)}
|
||||||
@@ -61,12 +63,12 @@ class HaAttributes extends LitElement {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.data-entry .value {
|
.data-entry .value {
|
||||||
max-width: 200px;
|
max-width: 50%;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
.key:first-letter {
|
.key {
|
||||||
text-transform: capitalize;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
.attribution {
|
.attribution {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@@ -11,7 +11,7 @@ export class HaCircularProgress extends CircularProgress {
|
|||||||
public alt = "Loading";
|
public alt = "Loading";
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public size: "small" | "medium" | "large" = "medium";
|
public size: "tiny" | "small" | "medium" | "large" = "medium";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
public set density(_) {
|
public set density(_) {
|
||||||
@@ -20,6 +20,8 @@ export class HaCircularProgress extends CircularProgress {
|
|||||||
|
|
||||||
public get density() {
|
public get density() {
|
||||||
switch (this.size) {
|
switch (this.size) {
|
||||||
|
case "tiny":
|
||||||
|
return -8;
|
||||||
case "small":
|
case "small":
|
||||||
return -5;
|
return -5;
|
||||||
case "medium":
|
case "medium":
|
||||||
|
@@ -63,7 +63,7 @@ class HaClimateState extends LitElement {
|
|||||||
|
|
||||||
private _computeTarget(): string {
|
private _computeTarget(): string {
|
||||||
if (!this.hass || !this.stateObj) {
|
if (!this.hass || !this.stateObj) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@@ -1,106 +0,0 @@
|
|||||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
|
||||||
import "./ha-icon-button";
|
|
||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
|
||||||
/* eslint-plugin-disable lit */
|
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
|
||||||
import { UNAVAILABLE } from "../data/entity";
|
|
||||||
import CoverEntity from "../util/cover-model";
|
|
||||||
|
|
||||||
class HaCoverTiltControls extends PolymerElement {
|
|
||||||
static get template() {
|
|
||||||
return html`
|
|
||||||
<style include="iron-flex"></style>
|
|
||||||
<style>
|
|
||||||
:host {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
[invisible] {
|
|
||||||
visibility: hidden !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Open cover tilt"
|
|
||||||
icon="hass:arrow-top-right"
|
|
||||||
on-click="onOpenTiltTap"
|
|
||||||
title="Open tilt"
|
|
||||||
invisible$="[[!entityObj.supportsOpenTilt]]"
|
|
||||||
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Stop cover from moving"
|
|
||||||
icon="hass:stop"
|
|
||||||
on-click="onStopTiltTap"
|
|
||||||
invisible$="[[!entityObj.supportsStopTilt]]"
|
|
||||||
disabled="[[computeStopDisabled(stateObj)]]"
|
|
||||||
title="Stop tilt"
|
|
||||||
></ha-icon-button>
|
|
||||||
<ha-icon-button
|
|
||||||
aria-label="Close cover tilt"
|
|
||||||
icon="hass:arrow-bottom-left"
|
|
||||||
on-click="onCloseTiltTap"
|
|
||||||
title="Close tilt"
|
|
||||||
invisible$="[[!entityObj.supportsCloseTilt]]"
|
|
||||||
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
|
|
||||||
></ha-icon-button>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get properties() {
|
|
||||||
return {
|
|
||||||
hass: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
stateObj: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
entityObj: {
|
|
||||||
type: Object,
|
|
||||||
computed: "computeEntityObj(hass, stateObj)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
computeEntityObj(hass, stateObj) {
|
|
||||||
return new CoverEntity(hass, stateObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
computeStopDisabled(stateObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeOpenDisabled(stateObj, entityObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
return entityObj.isFullyOpenTilt && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
computeClosedDisabled(stateObj, entityObj) {
|
|
||||||
if (stateObj.state === UNAVAILABLE) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const assumedState = stateObj.attributes.assumed_state === true;
|
|
||||||
return entityObj.isFullyClosedTilt && !assumedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
onOpenTiltTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.openCoverTilt();
|
|
||||||
}
|
|
||||||
|
|
||||||
onCloseTiltTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.closeCoverTilt();
|
|
||||||
}
|
|
||||||
|
|
||||||
onStopTiltTap(ev) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
this.entityObj.stopCoverTilt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("ha-cover-tilt-controls", HaCoverTiltControls);
|
|
122
src/components/ha-cover-tilt-controls.ts
Normal file
122
src/components/ha-cover-tilt-controls.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
import {
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
internalProperty,
|
||||||
|
CSSResult,
|
||||||
|
css,
|
||||||
|
customElement,
|
||||||
|
TemplateResult,
|
||||||
|
html,
|
||||||
|
PropertyValues,
|
||||||
|
} from "lit-element";
|
||||||
|
import { classMap } from "lit-html/directives/class-map";
|
||||||
|
|
||||||
|
import { UNAVAILABLE } from "../data/entity";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
import CoverEntity from "../util/cover-model";
|
||||||
|
|
||||||
|
import "./ha-icon-button";
|
||||||
|
|
||||||
|
@customElement("ha-cover-tilt-controls")
|
||||||
|
class HaCoverTiltControls extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false }) stateObj!: HassEntity;
|
||||||
|
|
||||||
|
@internalProperty() private _entityObj?: CoverEntity;
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if (changedProperties.has("stateObj")) {
|
||||||
|
this._entityObj = new CoverEntity(this.hass, this.stateObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._entityObj) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html` <ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
invisible: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.open_tilt_cover"
|
||||||
|
)}
|
||||||
|
icon="hass:arrow-top-right"
|
||||||
|
@click=${this._onOpenTiltTap}
|
||||||
|
.disabled=${this._computeOpenDisabled()}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
invisible: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
label=${this.hass.localize("ui.dialogs.more_info_control.stop_cover")}
|
||||||
|
icon="hass:stop"
|
||||||
|
@click=${this._onStopTiltTap}
|
||||||
|
.disabled=${this.stateObj.state === UNAVAILABLE}
|
||||||
|
></ha-icon-button>
|
||||||
|
<ha-icon-button
|
||||||
|
class=${classMap({
|
||||||
|
invisible: !this._entityObj.supportsStop,
|
||||||
|
})}
|
||||||
|
label=${this.hass.localize(
|
||||||
|
"ui.dialogs.more_info_control.open_tilt_cover"
|
||||||
|
)}
|
||||||
|
icon="hass:arrow-bottom-left"
|
||||||
|
@click=${this._onCloseTiltTap}
|
||||||
|
.disabled=${this._computeClosedDisabled()}
|
||||||
|
></ha-icon-button>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeOpenDisabled(): boolean {
|
||||||
|
if (this.stateObj.state === UNAVAILABLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
|
return this._entityObj.isFullyOpenTilt && !assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _computeClosedDisabled(): boolean {
|
||||||
|
if (this.stateObj.state === UNAVAILABLE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const assumedState = this.stateObj.attributes.assumed_state === true;
|
||||||
|
return this._entityObj.isFullyClosedTilt && !assumedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onOpenTiltTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.openCoverTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onCloseTiltTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.closeCoverTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onStopTiltTap(ev): void {
|
||||||
|
ev.stopPropagation();
|
||||||
|
this._entityObj.stopCoverTilt();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult {
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-cover-tilt-controls": HaCoverTiltControls;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,127 +1,70 @@
|
|||||||
import "@polymer/paper-input/paper-input";
|
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
|
||||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
|
||||||
import {
|
|
||||||
css,
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit-element";
|
|
||||||
|
|
||||||
@customElement("ha-date-input")
|
const VaadinDatePicker = customElements.get("vaadin-date-picker");
|
||||||
export class HaDateInput extends LitElement {
|
|
||||||
@property() public year?: string;
|
|
||||||
|
|
||||||
@property() public month?: string;
|
export class HaDateInput extends VaadinDatePicker {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
@property() public day?: string;
|
this.i18n.formatDate = this._formatISODate;
|
||||||
|
this.i18n.parseDate = this._parseISODate;
|
||||||
|
}
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
ready() {
|
||||||
|
super.ready();
|
||||||
static get styles() {
|
const styleEl = document.createElement("style");
|
||||||
return css`
|
styleEl.innerHTML = `
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
width: 12ex;
|
||||||
font-family: var(--paper-font-common-base_-_font-family);
|
margin-top: -6px;
|
||||||
-webkit-font-smoothing: var(
|
--material-body-font-size: 16px;
|
||||||
--paper-font-common-base_-_-webkit-font-smoothing
|
--_material-text-field-input-line-background-color: var(--primary-text-color);
|
||||||
);
|
--_material-text-field-input-line-opacity: 1;
|
||||||
}
|
--material-primary-color: var(--primary-text-color);
|
||||||
|
|
||||||
paper-input {
|
|
||||||
width: 30px;
|
|
||||||
text-align: center;
|
|
||||||
--paper-input-container-shared-input-style_-_-webkit-appearance: textfield;
|
|
||||||
--paper-input-container-input_-_-moz-appearance: textfield;
|
|
||||||
--paper-input-container-shared-input-style_-_appearance: textfield;
|
|
||||||
--paper-input-container-input-webkit-spinner_-_-webkit-appearance: none;
|
|
||||||
--paper-input-container-input-webkit-spinner_-_margin: 0;
|
|
||||||
--paper-input-container-input-webkit-spinner_-_display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
paper-input#year {
|
|
||||||
width: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.date-input-wrap {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
this.shadowRoot.appendChild(styleEl);
|
||||||
|
this._inputElement.querySelector("[part='toggle-button']").style.display =
|
||||||
|
"none";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
private _formatISODate(d) {
|
||||||
return html`
|
return [
|
||||||
<div class="date-input-wrap">
|
("0000" + String(d.year)).slice(-4),
|
||||||
<paper-input
|
("0" + String(d.month + 1)).slice(-2),
|
||||||
id="year"
|
("0" + String(d.day)).slice(-2),
|
||||||
type="number"
|
].join("-");
|
||||||
.value=${this.year}
|
|
||||||
@change=${this._formatYear}
|
|
||||||
maxlength="4"
|
|
||||||
max="9999"
|
|
||||||
min="0"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
<span suffix="" slot="suffix">-</span>
|
|
||||||
</paper-input>
|
|
||||||
<paper-input
|
|
||||||
id="month"
|
|
||||||
type="number"
|
|
||||||
.value=${this.month}
|
|
||||||
@change=${this._formatMonth}
|
|
||||||
maxlength="2"
|
|
||||||
max="12"
|
|
||||||
min="1"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
<span suffix="" slot="suffix">-</span>
|
|
||||||
</paper-input>
|
|
||||||
<paper-input
|
|
||||||
id="day"
|
|
||||||
type="number"
|
|
||||||
.value=${this.day}
|
|
||||||
@change=${this._formatDay}
|
|
||||||
maxlength="2"
|
|
||||||
max="31"
|
|
||||||
min="1"
|
|
||||||
.disabled=${this.disabled}
|
|
||||||
no-label-float
|
|
||||||
>
|
|
||||||
</paper-input>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _formatYear() {
|
private _parseISODate(text) {
|
||||||
const yearElement = this.shadowRoot!.getElementById(
|
const parts = text.split("-");
|
||||||
"year"
|
const today = new Date();
|
||||||
) as PaperInputElement;
|
let date;
|
||||||
this.year = yearElement.value!;
|
let month = today.getMonth();
|
||||||
}
|
let year = today.getFullYear();
|
||||||
|
if (parts.length === 3) {
|
||||||
|
year = parseInt(parts[0]);
|
||||||
|
if (parts[0].length < 3 && year >= 0) {
|
||||||
|
year += year < 50 ? 2000 : 1900;
|
||||||
|
}
|
||||||
|
month = parseInt(parts[1]) - 1;
|
||||||
|
date = parseInt(parts[2]);
|
||||||
|
} else if (parts.length === 2) {
|
||||||
|
month = parseInt(parts[0]) - 1;
|
||||||
|
date = parseInt(parts[1]);
|
||||||
|
} else if (parts.length === 1) {
|
||||||
|
date = parseInt(parts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
private _formatMonth() {
|
if (date !== undefined) {
|
||||||
const monthElement = this.shadowRoot!.getElementById(
|
return { day: date, month, year };
|
||||||
"month"
|
}
|
||||||
) as PaperInputElement;
|
return undefined;
|
||||||
this.month = ("0" + monthElement.value!).slice(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _formatDay() {
|
|
||||||
const dayElement = this.shadowRoot!.getElementById(
|
|
||||||
"day"
|
|
||||||
) as PaperInputElement;
|
|
||||||
this.day = ("0" + dayElement.value!).slice(-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
get value() {
|
|
||||||
return `${this.year}-${this.month}-${this.day}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customElements.define("ha-date-input", HaDateInput as any);
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
"ha-date-input": HaDateInput;
|
"ha-date-input": HaDateInput;
|
||||||
|
@@ -40,6 +40,7 @@ export class HaDialog extends MwcDialog {
|
|||||||
.mdc-dialog {
|
.mdc-dialog {
|
||||||
--mdc-dialog-scroll-divider-color: var(--divider-color);
|
--mdc-dialog-scroll-divider-color: var(--divider-color);
|
||||||
z-index: var(--dialog-z-index, 7);
|
z-index: var(--dialog-z-index, 7);
|
||||||
|
backdrop-filter: var(--dialog-backdrop-filter, none);
|
||||||
}
|
}
|
||||||
.mdc-dialog__actions {
|
.mdc-dialog__actions {
|
||||||
justify-content: var(--justify-action-buttons, flex-end);
|
justify-content: var(--justify-action-buttons, flex-end);
|
||||||
|
@@ -104,7 +104,6 @@ class HaHLSPlayer extends LitElement {
|
|||||||
|
|
||||||
private async _startHls(): Promise<void> {
|
private async _startHls(): Promise<void> {
|
||||||
const videoEl = this._videoEl;
|
const videoEl = this._videoEl;
|
||||||
const playlist_url = this.url.replace("master_playlist", "playlist");
|
|
||||||
const useExoPlayerPromise = this._getUseExoPlayer();
|
const useExoPlayerPromise = this._getUseExoPlayer();
|
||||||
const masterPlaylistPromise = fetch(this.url);
|
const masterPlaylistPromise = fetch(this.url);
|
||||||
|
|
||||||
@@ -126,13 +125,30 @@ class HaHLSPlayer extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._useExoPlayer = await useExoPlayerPromise;
|
this._useExoPlayer = await useExoPlayerPromise;
|
||||||
let hevcRegexp: RegExp;
|
const masterPlaylist = await (await masterPlaylistPromise).text();
|
||||||
let masterPlaylist: string;
|
|
||||||
if (this._useExoPlayer) {
|
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
|
||||||
hevcRegexp = /CODECS=".*?((hev1)|(hvc1))\..*?"/;
|
// See https://tools.ietf.org/html/rfc8216 for HLS spec details
|
||||||
masterPlaylist = await (await masterPlaylistPromise).text();
|
const playlistRegexp = /#EXT-X-STREAM-INF:.*?(?:CODECS=".*?(?<isHevc>hev1|hvc1)?\..*?".*?)?(?:\n|\r\n)(?<streamUrl>.+)/g;
|
||||||
|
const match = playlistRegexp.exec(masterPlaylist);
|
||||||
|
const matchTwice = playlistRegexp.exec(masterPlaylist);
|
||||||
|
|
||||||
|
// Get the regular playlist url from the input (master) playlist, falling back to the input playlist if necessary
|
||||||
|
// This avoids the player having to load and parse the master playlist again before loading the regular playlist
|
||||||
|
let playlist_url: string;
|
||||||
|
if (match !== null && matchTwice === null) {
|
||||||
|
// Only send the regular playlist url if we match exactly once
|
||||||
|
playlist_url = new URL(match.groups!.streamUrl, this.url).href;
|
||||||
|
} else {
|
||||||
|
playlist_url = this.url;
|
||||||
}
|
}
|
||||||
if (this._useExoPlayer && hevcRegexp!.test(masterPlaylist!)) {
|
|
||||||
|
// If codec is HEVC and ExoPlayer is supported, use ExoPlayer.
|
||||||
|
if (
|
||||||
|
this._useExoPlayer &&
|
||||||
|
match !== null &&
|
||||||
|
match.groups!.isHevc !== undefined
|
||||||
|
) {
|
||||||
this._renderHLSExoPlayer(playlist_url);
|
this._renderHLSExoPlayer(playlist_url);
|
||||||
} else if (hls.isSupported()) {
|
} else if (hls.isSupported()) {
|
||||||
this._renderHLSPolyfill(videoEl, hls, playlist_url);
|
this._renderHLSPolyfill(videoEl, hls, playlist_url);
|
||||||
|
@@ -9,11 +9,16 @@ import {
|
|||||||
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("ha-icon-button-arrow-next")
|
@customElement("ha-icon-button-arrow-next")
|
||||||
export class HaIconButtonArrowNext extends LitElement {
|
export class HaIconButtonArrowNext extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
@internalProperty() private _icon = mdiArrowRight;
|
@internalProperty() private _icon = mdiArrowRight;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@@ -29,7 +34,10 @@ export class HaIconButtonArrowNext extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
return html`<mwc-icon-button
|
||||||
|
.disabled=${this.disabled}
|
||||||
|
.label=${this.label || this.hass?.localize("ui.common.next") || "Next"}
|
||||||
|
>
|
||||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||||
</mwc-icon-button> `;
|
</mwc-icon-button> `;
|
||||||
}
|
}
|
||||||
|
@@ -9,11 +9,16 @@ import {
|
|||||||
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("ha-icon-button-arrow-prev")
|
@customElement("ha-icon-button-arrow-prev")
|
||||||
export class HaIconButtonArrowPrev extends LitElement {
|
export class HaIconButtonArrowPrev extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
@internalProperty() private _icon = mdiArrowLeft;
|
@internalProperty() private _icon = mdiArrowLeft;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@@ -29,9 +34,14 @@ export class HaIconButtonArrowPrev extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
return html`
|
||||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
<mwc-icon-button
|
||||||
</mwc-icon-button> `;
|
.disabled=${this.disabled}
|
||||||
|
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,11 +9,16 @@ import {
|
|||||||
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
||||||
import "@material/mwc-icon-button";
|
import "@material/mwc-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("ha-icon-button-next")
|
@customElement("ha-icon-button-next")
|
||||||
export class HaIconButtonNext extends LitElement {
|
export class HaIconButtonNext extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
@internalProperty() private _icon = mdiChevronRight;
|
@internalProperty() private _icon = mdiChevronRight;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@@ -29,9 +34,14 @@ export class HaIconButtonNext extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
return html`
|
||||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
<mwc-icon-button
|
||||||
</mwc-icon-button> `;
|
.disabled=${this.disabled}
|
||||||
|
.label=${this.label || this.hass?.localize("ui.common.next") || "Next"}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,11 +9,16 @@ import {
|
|||||||
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
||||||
import "@material/mwc-icon-button/mwc-icon-button";
|
import "@material/mwc-icon-button/mwc-icon-button";
|
||||||
import "./ha-svg-icon";
|
import "./ha-svg-icon";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("ha-icon-button-prev")
|
@customElement("ha-icon-button-prev")
|
||||||
export class HaIconButtonPrev extends LitElement {
|
export class HaIconButtonPrev extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public disabled = false;
|
@property({ type: Boolean }) public disabled = false;
|
||||||
|
|
||||||
|
@property() public label?: string;
|
||||||
|
|
||||||
@internalProperty() private _icon = mdiChevronLeft;
|
@internalProperty() private _icon = mdiChevronLeft;
|
||||||
|
|
||||||
public connectedCallback() {
|
public connectedCallback() {
|
||||||
@@ -29,9 +34,14 @@ export class HaIconButtonPrev extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
return html`
|
||||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
<mwc-icon-button
|
||||||
</mwc-icon-button> `;
|
.disabled=${this.disabled}
|
||||||
|
.label=${this.label || this.hass?.localize("ui.common.back") || "Back"}
|
||||||
|
>
|
||||||
|
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,7 +14,7 @@ class HaLabeledSlider extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 8px;
|
margin: 4px 0 8px;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -47,7 +47,6 @@ class HaMarkdownElement extends UpdatingElement {
|
|||||||
node.host !== document.location.host
|
node.host !== document.location.host
|
||||||
) {
|
) {
|
||||||
node.target = "_blank";
|
node.target = "_blank";
|
||||||
node.rel = "noreferrer";
|
|
||||||
|
|
||||||
// protect referrer on external links and deny window.opener access for security reasons
|
// protect referrer on external links and deny window.opener access for security reasons
|
||||||
// (see https://mathiasbynens.github.io/rel-noopener/)
|
// (see https://mathiasbynens.github.io/rel-noopener/)
|
||||||
|
@@ -320,13 +320,11 @@ class HaSidebar extends LitElement {
|
|||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
<div class="title">
|
${this.editMode
|
||||||
${this.editMode
|
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
||||||
? html`<mwc-button outlined @click=${this._closeEditMode}>
|
${this.hass.localize("ui.sidebar.done")}
|
||||||
${this.hass.localize("ui.sidebar.done")}
|
</mwc-button>`
|
||||||
</mwc-button>`
|
: html`<div class="title">Home Assistant</div>`}
|
||||||
: "Home Assistant"}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -756,7 +754,7 @@ class HaSidebar extends LitElement {
|
|||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
border-right: 1px solid var(--divider-color);
|
border-right: 1px solid var(--divider-color);
|
||||||
background-color: var(--sidebar-background-color);
|
background-color: var(--sidebar-background-color);
|
||||||
width: 64px;
|
width: 56px;
|
||||||
}
|
}
|
||||||
:host([expanded]) {
|
:host([expanded]) {
|
||||||
width: 256px;
|
width: 256px;
|
||||||
@@ -768,8 +766,9 @@ class HaSidebar extends LitElement {
|
|||||||
}
|
}
|
||||||
.menu {
|
.menu {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 8.5px;
|
padding: 0 4px;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -778,11 +777,11 @@ class HaSidebar extends LitElement {
|
|||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: calc(8.5px + env(safe-area-inset-left));
|
padding-left: calc(4px + env(safe-area-inset-left));
|
||||||
}
|
}
|
||||||
:host([rtl]) .menu {
|
:host([rtl]) .menu {
|
||||||
padding-left: 8.5px;
|
padding-left: 4px;
|
||||||
padding-right: calc(8.5px + env(safe-area-inset-right));
|
padding-right: calc(4px + env(safe-area-inset-right));
|
||||||
}
|
}
|
||||||
:host([expanded]) .menu {
|
:host([expanded]) .menu {
|
||||||
width: calc(256px + env(safe-area-inset-left));
|
width: calc(256px + env(safe-area-inset-left));
|
||||||
@@ -793,26 +792,27 @@ class HaSidebar extends LitElement {
|
|||||||
.menu mwc-icon-button {
|
.menu mwc-icon-button {
|
||||||
color: var(--sidebar-icon-color);
|
color: var(--sidebar-icon-color);
|
||||||
}
|
}
|
||||||
:host([expanded]) .menu mwc-icon-button {
|
|
||||||
margin-right: 23px;
|
|
||||||
}
|
|
||||||
:host([expanded][rtl]) .menu mwc-icon-button {
|
|
||||||
margin-right: 0px;
|
|
||||||
margin-left: 23px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
margin-left: 19px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
:host([rtl]) .title {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 19px;
|
||||||
|
}
|
||||||
:host([narrow]) .title {
|
:host([narrow]) .title {
|
||||||
|
margin: 0;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
}
|
}
|
||||||
:host([expanded]) .title {
|
:host([expanded]) .title {
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
.title mwc-button {
|
:host([expanded]) .menu mwc-button {
|
||||||
width: 90%;
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
.menu mwc-button {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
#sortable,
|
#sortable,
|
||||||
.hidden-panel {
|
.hidden-panel {
|
||||||
@@ -850,14 +850,14 @@ class HaSidebar extends LitElement {
|
|||||||
|
|
||||||
paper-icon-item {
|
paper-icon-item {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 4px 8px;
|
margin: 4px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
--paper-item-min-height: 40px;
|
--paper-item-min-height: 40px;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
}
|
}
|
||||||
:host([expanded]) paper-icon-item {
|
:host([expanded]) paper-icon-item {
|
||||||
width: 240px;
|
width: 248px;
|
||||||
}
|
}
|
||||||
:host([rtl]) paper-icon-item {
|
:host([rtl]) paper-icon-item {
|
||||||
padding-left: auto;
|
padding-left: auto;
|
||||||
@@ -874,9 +874,9 @@ class HaSidebar extends LitElement {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 2px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 2px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
content: "";
|
content: "";
|
||||||
transition: opacity 15ms linear;
|
transition: opacity 15ms linear;
|
||||||
|
@@ -99,13 +99,13 @@ export class HaTab extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 64px;
|
height: var(--header-height);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
|
@@ -34,8 +34,8 @@ export class HaTabs extends PaperTabs {
|
|||||||
|
|
||||||
superStyle!.appendChild(
|
superStyle!.appendChild(
|
||||||
document.createTextNode(`
|
document.createTextNode(`
|
||||||
:host {
|
#selectionBar {
|
||||||
padding-top: .5px;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.not-visible {
|
.not-visible {
|
||||||
display: none;
|
display: none;
|
||||||
|
@@ -58,8 +58,18 @@ export interface DataEntryFlowStepAbort {
|
|||||||
description_placeholders: { [key: string]: string };
|
description_placeholders: { [key: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DataEntryFlowStepProgress {
|
||||||
|
type: "progress";
|
||||||
|
flow_id: string;
|
||||||
|
handler: string;
|
||||||
|
step_id: string;
|
||||||
|
progress_action: string;
|
||||||
|
description_placeholders: { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
export type DataEntryFlowStep =
|
export type DataEntryFlowStep =
|
||||||
| DataEntryFlowStepForm
|
| DataEntryFlowStepForm
|
||||||
| DataEntryFlowStepExternal
|
| DataEntryFlowStepExternal
|
||||||
| DataEntryFlowStepCreateEntry
|
| DataEntryFlowStepCreateEntry
|
||||||
| DataEntryFlowStepAbort;
|
| DataEntryFlowStepAbort
|
||||||
|
| DataEntryFlowStepProgress;
|
||||||
|
@@ -20,6 +20,12 @@ export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
|||||||
original_icon?: string;
|
original_icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UpdateEntityRegistryEntryResult {
|
||||||
|
entity_entry: ExtEntityRegistryEntry;
|
||||||
|
reload_delay?: number;
|
||||||
|
require_restart?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EntityRegistryEntryUpdateParams {
|
export interface EntityRegistryEntryUpdateParams {
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
icon?: string | null;
|
icon?: string | null;
|
||||||
@@ -72,7 +78,7 @@ export const updateEntityRegistryEntry = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entityId: string,
|
entityId: string,
|
||||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||||
): Promise<ExtEntityRegistryEntry> =>
|
): Promise<UpdateEntityRegistryEntryResult> =>
|
||||||
hass.callWS({
|
hass.callWS({
|
||||||
type: "config/entity_registry/update",
|
type: "config/entity_registry/update",
|
||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
|
26
src/data/hassio/ingress.ts
Normal file
26
src/data/hassio/ingress.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { HassioResponse } from "./common";
|
||||||
|
import { CreateSessionResponse } from "./supervisor";
|
||||||
|
|
||||||
|
export const createHassioSession = async (hass: HomeAssistant) => {
|
||||||
|
const response = await hass.callApi<HassioResponse<CreateSessionResponse>>(
|
||||||
|
"POST",
|
||||||
|
"hassio/ingress/session"
|
||||||
|
);
|
||||||
|
document.cookie = `ingress_session=${
|
||||||
|
response.data.session
|
||||||
|
};path=/api/hassio_ingress/;SameSite=Strict${
|
||||||
|
location.protocol === "https:" ? ";Secure" : ""
|
||||||
|
}`;
|
||||||
|
return response.data.session;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateHassioSession = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
session: string
|
||||||
|
) =>
|
||||||
|
await hass.callApi<HassioResponse<null>>(
|
||||||
|
"POST",
|
||||||
|
"hassio/ingress/validate_session",
|
||||||
|
{ session }
|
||||||
|
);
|
@@ -111,18 +111,6 @@ export const fetchHassioLogs = async (
|
|||||||
return hass.callApi<string>("GET", `hassio/${provider}/logs`);
|
return hass.callApi<string>("GET", `hassio/${provider}/logs`);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createHassioSession = async (hass: HomeAssistant) => {
|
|
||||||
const response = await hass.callApi<HassioResponse<CreateSessionResponse>>(
|
|
||||||
"POST",
|
|
||||||
"hassio/ingress/session"
|
|
||||||
);
|
|
||||||
document.cookie = `ingress_session=${
|
|
||||||
response.data.session
|
|
||||||
};path=/api/hassio_ingress/;SameSite=Strict${
|
|
||||||
location.protocol === "https:" ? ";Secure" : ""
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setSupervisorOption = async (
|
export const setSupervisorOption = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
data: SupervisorOptions
|
data: SupervisorOptions
|
||||||
|
@@ -18,6 +18,8 @@ import {
|
|||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
import type { HassEntity } from "home-assistant-js-websocket";
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
import { UNAVAILABLE_STATES } from "./entity";
|
||||||
|
import { supportsFeature } from "../common/entity/supports-feature";
|
||||||
|
|
||||||
export const SUPPORT_PAUSE = 1;
|
export const SUPPORT_PAUSE = 1;
|
||||||
export const SUPPORT_SEEK = 2;
|
export const SUPPORT_SEEK = 2;
|
||||||
@@ -31,7 +33,7 @@ export const SUPPORT_PLAY_MEDIA = 512;
|
|||||||
export const SUPPORT_VOLUME_BUTTONS = 1024;
|
export const SUPPORT_VOLUME_BUTTONS = 1024;
|
||||||
export const SUPPORT_SELECT_SOURCE = 2048;
|
export const SUPPORT_SELECT_SOURCE = 2048;
|
||||||
export const SUPPORT_STOP = 4096;
|
export const SUPPORT_STOP = 4096;
|
||||||
export const SUPPORTS_PLAY = 16384;
|
export const SUPPORT_PLAY = 16384;
|
||||||
export const SUPPORT_SELECT_SOUND_MODE = 65536;
|
export const SUPPORT_SELECT_SOUND_MODE = 65536;
|
||||||
export const SUPPORT_BROWSE_MEDIA = 131072;
|
export const SUPPORT_BROWSE_MEDIA = 131072;
|
||||||
export const CONTRAST_RATIO = 4.5;
|
export const CONTRAST_RATIO = 4.5;
|
||||||
@@ -166,6 +168,7 @@ export const computeMediaDescription = (stateObj: HassEntity): string => {
|
|||||||
|
|
||||||
switch (stateObj.attributes.media_content_type) {
|
switch (stateObj.attributes.media_content_type) {
|
||||||
case "music":
|
case "music":
|
||||||
|
case "image":
|
||||||
secondaryTitle = stateObj.attributes.media_artist;
|
secondaryTitle = stateObj.attributes.media_artist;
|
||||||
break;
|
break;
|
||||||
case "playlist":
|
case "playlist":
|
||||||
@@ -187,3 +190,85 @@ export const computeMediaDescription = (stateObj: HassEntity): string => {
|
|||||||
|
|
||||||
return secondaryTitle;
|
return secondaryTitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const computeMediaControls = (
|
||||||
|
stateObj: HassEntity
|
||||||
|
): ControlButton[] | undefined => {
|
||||||
|
if (!stateObj) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = stateObj.state;
|
||||||
|
|
||||||
|
if (UNAVAILABLE_STATES.includes(state)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === "off") {
|
||||||
|
return supportsFeature(stateObj, SUPPORT_TURN_ON)
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
icon: "hass:power",
|
||||||
|
action: "turn_on",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttons: ControlButton[] = [];
|
||||||
|
|
||||||
|
if (supportsFeature(stateObj, SUPPORT_TURN_OFF)) {
|
||||||
|
buttons.push({
|
||||||
|
icon: "hass:power",
|
||||||
|
action: "turn_off",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(state === "playing" || state === "paused") &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)
|
||||||
|
) {
|
||||||
|
buttons.push({
|
||||||
|
icon: "hass:skip-previous",
|
||||||
|
action: "media_previous_track",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(state === "playing" &&
|
||||||
|
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
||||||
|
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
||||||
|
((state === "paused" || state === "idle") &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_PLAY)) ||
|
||||||
|
(state === "on" &&
|
||||||
|
(supportsFeature(stateObj, SUPPORT_PLAY) ||
|
||||||
|
supportsFeature(stateObj, SUPPORT_PAUSE)))
|
||||||
|
) {
|
||||||
|
buttons.push({
|
||||||
|
icon:
|
||||||
|
state === "on"
|
||||||
|
? "hass:play-pause"
|
||||||
|
: state !== "playing"
|
||||||
|
? "hass:play"
|
||||||
|
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||||
|
? "hass:pause"
|
||||||
|
: "hass:stop",
|
||||||
|
action:
|
||||||
|
state === "playing" && !supportsFeature(stateObj, SUPPORT_PAUSE)
|
||||||
|
? "media_stop"
|
||||||
|
: "media_play_pause",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(state === "playing" || state === "paused") &&
|
||||||
|
supportsFeature(stateObj, SUPPORT_NEXT_TRACK)
|
||||||
|
) {
|
||||||
|
buttons.push({
|
||||||
|
icon: "hass:skip-next",
|
||||||
|
action: "media_next_track",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons.length > 0 ? buttons : undefined;
|
||||||
|
};
|
||||||
|
@@ -21,6 +21,18 @@ export const getDefaultPanel = (hass: HomeAssistant): PanelInfo =>
|
|||||||
? hass.panels[hass.defaultPanel]
|
? hass.panels[hass.defaultPanel]
|
||||||
: hass.panels[DEFAULT_PANEL];
|
: hass.panels[DEFAULT_PANEL];
|
||||||
|
|
||||||
|
export const getPanelNameTranslationKey = (panel: PanelInfo): string => {
|
||||||
|
if (panel.url_path === "lovelace") {
|
||||||
|
return "panel.states";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panel.url_path === "profile") {
|
||||||
|
return "panel.profile";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `panel.${panel.title}`;
|
||||||
|
};
|
||||||
|
|
||||||
export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
|
export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
|
||||||
if (!hass.panels) {
|
if (!hass.panels) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -34,13 +46,20 @@ export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (panel.url_path === "lovelace") {
|
const translationKey = getPanelNameTranslationKey(panel);
|
||||||
return hass.localize("panel.states");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.url_path === "profile") {
|
return hass.localize(translationKey) || panel.title || undefined;
|
||||||
return hass.localize("panel.profile");
|
};
|
||||||
}
|
|
||||||
|
export const getPanelIcon = (panel: PanelInfo): string | null => {
|
||||||
return hass.localize(`panel.${panel.title}`) || panel.title || undefined;
|
if (!panel.icon) {
|
||||||
|
switch (panel.component_name) {
|
||||||
|
case "profile":
|
||||||
|
return "hass:account";
|
||||||
|
case "lovelace":
|
||||||
|
return "hass:view-dashboard";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel.icon;
|
||||||
};
|
};
|
||||||
|
@@ -1,24 +1,111 @@
|
|||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
export interface HomeAssistantSystemHealthInfo {
|
interface SystemCheckValueDateObject {
|
||||||
version: string;
|
type: "date";
|
||||||
dev: boolean;
|
value: string;
|
||||||
hassio: boolean;
|
|
||||||
virtualenv: string;
|
|
||||||
python_version: string;
|
|
||||||
docker: boolean;
|
|
||||||
arch: string;
|
|
||||||
timezone: string;
|
|
||||||
os_name: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SystemCheckValueErrorObject {
|
||||||
|
type: "failed";
|
||||||
|
error: string;
|
||||||
|
more_info?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SystemCheckValuePendingObject {
|
||||||
|
type: "pending";
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SystemCheckValueObject =
|
||||||
|
| SystemCheckValueDateObject
|
||||||
|
| SystemCheckValueErrorObject
|
||||||
|
| SystemCheckValuePendingObject;
|
||||||
|
|
||||||
|
export type SystemCheckValue =
|
||||||
|
| string
|
||||||
|
| number
|
||||||
|
| boolean
|
||||||
|
| SystemCheckValueObject;
|
||||||
|
|
||||||
export interface SystemHealthInfo {
|
export interface SystemHealthInfo {
|
||||||
[domain: string]: { [key: string]: string | number | boolean };
|
[domain: string]: {
|
||||||
|
manage_url?: string;
|
||||||
|
info: {
|
||||||
|
[key: string]: SystemCheckValue;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchSystemHealthInfo = (
|
interface SystemHealthEventInitial {
|
||||||
hass: HomeAssistant
|
type: "initial";
|
||||||
): Promise<SystemHealthInfo> =>
|
data: SystemHealthInfo;
|
||||||
hass.callWS({
|
}
|
||||||
type: "system_health/info",
|
interface SystemHealthEventUpdateSuccess {
|
||||||
});
|
type: "update";
|
||||||
|
success: true;
|
||||||
|
domain: string;
|
||||||
|
key: string;
|
||||||
|
data: SystemCheckValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SystemHealthEventUpdateError {
|
||||||
|
type: "update";
|
||||||
|
success: false;
|
||||||
|
domain: string;
|
||||||
|
key: string;
|
||||||
|
error: {
|
||||||
|
msg: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SystemHealthEventFinish {
|
||||||
|
type: "finish";
|
||||||
|
}
|
||||||
|
|
||||||
|
type SystemHealthEvent =
|
||||||
|
| SystemHealthEventInitial
|
||||||
|
| SystemHealthEventUpdateSuccess
|
||||||
|
| SystemHealthEventUpdateError
|
||||||
|
| SystemHealthEventFinish;
|
||||||
|
|
||||||
|
export const subscribeSystemHealthInfo = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
callback: (info: SystemHealthInfo) => void
|
||||||
|
) => {
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
const unsubProm = hass.connection.subscribeMessage<SystemHealthEvent>(
|
||||||
|
(updateEvent) => {
|
||||||
|
if (updateEvent.type === "initial") {
|
||||||
|
data = updateEvent.data;
|
||||||
|
callback(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (updateEvent.type === "finish") {
|
||||||
|
unsubProm.then((unsub) => unsub());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
...data,
|
||||||
|
[updateEvent.domain]: {
|
||||||
|
...data[updateEvent.domain],
|
||||||
|
info: {
|
||||||
|
...data[updateEvent.domain].info,
|
||||||
|
[updateEvent.key]: updateEvent.success
|
||||||
|
? updateEvent.data
|
||||||
|
: {
|
||||||
|
error: true,
|
||||||
|
value: updateEvent.error.msg,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
callback(data);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "system_health/info",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return unsubProm;
|
||||||
|
};
|
||||||
|
@@ -17,7 +17,8 @@ export type TranslationCategory =
|
|||||||
| "config"
|
| "config"
|
||||||
| "options"
|
| "options"
|
||||||
| "device_automation"
|
| "device_automation"
|
||||||
| "mfa_setup";
|
| "mfa_setup"
|
||||||
|
| "system_health";
|
||||||
|
|
||||||
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
|
||||||
fetchFrontendUserData(hass.connection, "language");
|
fetchFrontendUserData(hass.connection, "language");
|
||||||
|
@@ -36,6 +36,7 @@ import "./step-flow-external";
|
|||||||
import "./step-flow-form";
|
import "./step-flow-form";
|
||||||
import "./step-flow-loading";
|
import "./step-flow-loading";
|
||||||
import "./step-flow-pick-handler";
|
import "./step-flow-pick-handler";
|
||||||
|
import "./step-flow-progress";
|
||||||
|
|
||||||
let instance = 0;
|
let instance = 0;
|
||||||
|
|
||||||
@@ -195,6 +196,14 @@ class DataEntryFlowDialog extends LitElement {
|
|||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
></step-flow-abort>
|
></step-flow-abort>
|
||||||
`
|
`
|
||||||
|
: this._step.type === "progress"
|
||||||
|
? html`
|
||||||
|
<step-flow-progress
|
||||||
|
.flowConfig=${this._params.flowConfig}
|
||||||
|
.step=${this._step}
|
||||||
|
.hass=${this.hass}
|
||||||
|
></step-flow-progress>
|
||||||
|
`
|
||||||
: this._devices === undefined || this._areas === undefined
|
: this._devices === undefined || this._areas === undefined
|
||||||
? // When it's a create entry result, we will fetch device & area registry
|
? // When it's a create entry result, we will fetch device & area registry
|
||||||
html` <step-flow-loading></step-flow-loading> `
|
html` <step-flow-loading></step-flow-loading> `
|
||||||
|
@@ -160,4 +160,21 @@ export const showConfigFlowDialog = (
|
|||||||
</p>
|
</p>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderShowFormProgressHeader(hass, step) {
|
||||||
|
return hass.localize(`component.${step.handler}.title`);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormProgressDescription(hass, step) {
|
||||||
|
const description = localizeKey(
|
||||||
|
hass.localize,
|
||||||
|
`component.${step.handler}.config.progress.${step.progress_action}`,
|
||||||
|
step.description_placeholders
|
||||||
|
);
|
||||||
|
return description
|
||||||
|
? html`
|
||||||
|
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
||||||
|
`
|
||||||
|
: "";
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
DataEntryFlowStepCreateEntry,
|
DataEntryFlowStepCreateEntry,
|
||||||
DataEntryFlowStepExternal,
|
DataEntryFlowStepExternal,
|
||||||
DataEntryFlowStepForm,
|
DataEntryFlowStepForm,
|
||||||
|
DataEntryFlowStepProgress,
|
||||||
} from "../../data/data_entry_flow";
|
} from "../../data/data_entry_flow";
|
||||||
import { HomeAssistant } from "../../types";
|
import { HomeAssistant } from "../../types";
|
||||||
|
|
||||||
@@ -68,6 +69,16 @@ export interface FlowConfig {
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
step: DataEntryFlowStepCreateEntry
|
step: DataEntryFlowStepCreateEntry
|
||||||
): TemplateResult | "";
|
): TemplateResult | "";
|
||||||
|
|
||||||
|
renderShowFormProgressHeader(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
step: DataEntryFlowStepProgress
|
||||||
|
): string;
|
||||||
|
|
||||||
|
renderShowFormProgressDescription(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
step: DataEntryFlowStepProgress
|
||||||
|
): TemplateResult | "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataEntryFlowDialogParams {
|
export interface DataEntryFlowDialogParams {
|
||||||
|
@@ -110,5 +110,13 @@ export const showOptionsFlowDialog = (
|
|||||||
<p>${hass.localize(`ui.dialogs.options_flow.success.description`)}</p>
|
<p>${hass.localize(`ui.dialogs.options_flow.success.description`)}</p>
|
||||||
`;
|
`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderShowFormProgressHeader(_hass, _step) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
|
||||||
|
renderShowFormProgressDescription(_hass, _step) {
|
||||||
|
return "";
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@@ -23,6 +23,7 @@ import { HomeAssistant } from "../../types";
|
|||||||
import { documentationUrl } from "../../util/documentation-url";
|
import { documentationUrl } from "../../util/documentation-url";
|
||||||
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||||
import { configFlowContentStyles } from "./styles";
|
import { configFlowContentStyles } from "./styles";
|
||||||
|
import { brandsUrl } from "../../util/brands-url";
|
||||||
|
|
||||||
interface HandlerObj {
|
interface HandlerObj {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -102,7 +103,7 @@ class StepFlowPickHandler extends LitElement {
|
|||||||
<img
|
<img
|
||||||
slot="item-icon"
|
slot="item-icon"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
src="https://brands.home-assistant.io/_/${handler.slug}/icon.png"
|
src=${brandsUrl(handler.slug, "icon", true)}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
82
src/dialogs/config-flow/step-flow-progress.ts
Normal file
82
src/dialogs/config-flow/step-flow-progress.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import "@material/mwc-button";
|
||||||
|
import {
|
||||||
|
css,
|
||||||
|
CSSResult,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
LitElement,
|
||||||
|
property,
|
||||||
|
TemplateResult,
|
||||||
|
} from "lit-element";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import "../../components/ha-circular-progress";
|
||||||
|
import {
|
||||||
|
DataEntryFlowProgressedEvent,
|
||||||
|
DataEntryFlowStepProgress,
|
||||||
|
} from "../../data/data_entry_flow";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { FlowConfig } from "./show-dialog-data-entry-flow";
|
||||||
|
import { configFlowContentStyles } from "./styles";
|
||||||
|
|
||||||
|
@customElement("step-flow-progress")
|
||||||
|
class StepFlowProgress extends LitElement {
|
||||||
|
public flowConfig!: FlowConfig;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
private step!: DataEntryFlowStepProgress;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
return html`
|
||||||
|
<h2>
|
||||||
|
${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)}
|
||||||
|
</h2>
|
||||||
|
<div class="content">
|
||||||
|
<ha-circular-progress active></ha-circular-progress>
|
||||||
|
${this.flowConfig.renderShowFormProgressDescription(
|
||||||
|
this.hass,
|
||||||
|
this.step
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated(changedProps) {
|
||||||
|
super.firstUpdated(changedProps);
|
||||||
|
this.hass.connection.subscribeEvents<DataEntryFlowProgressedEvent>(
|
||||||
|
async (ev) => {
|
||||||
|
if (ev.data.flow_id !== this.step.flow_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fireEvent(this, "flow-update", {
|
||||||
|
stepPromise: this.flowConfig.fetchFlow(this.hass, this.step.flow_id),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"data_entry_flow_progressed"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResult[] {
|
||||||
|
return [
|
||||||
|
configFlowContentStyles,
|
||||||
|
css`
|
||||||
|
.content {
|
||||||
|
padding: 50px 100px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
ha-circular-progress {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"step-flow-progress": StepFlowProgress;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,26 +1,28 @@
|
|||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
|
||||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||||
import "@polymer/paper-input/paper-input";
|
import "@polymer/paper-input/paper-input";
|
||||||
import "@polymer/paper-item/paper-item";
|
import "@polymer/paper-item/paper-item";
|
||||||
import "@polymer/paper-listbox/paper-listbox";
|
import "@polymer/paper-listbox/paper-listbox";
|
||||||
|
import "../../components/ha-dialog";
|
||||||
|
import "../../components/ha-area-picker";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
css,
|
|
||||||
CSSResult,
|
CSSResult,
|
||||||
|
LitElement,
|
||||||
|
TemplateResult,
|
||||||
|
css,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
LitElement,
|
|
||||||
property,
|
|
||||||
internalProperty,
|
internalProperty,
|
||||||
TemplateResult,
|
property,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "../../components/dialog/ha-paper-dialog";
|
|
||||||
import "../../components/ha-area-picker";
|
|
||||||
import { computeDeviceName } from "../../data/device_registry";
|
|
||||||
import { PolymerChangedEvent } from "../../polymer-types";
|
|
||||||
import { haStyleDialog } from "../../resources/styles";
|
|
||||||
import { HomeAssistant } from "../../types";
|
|
||||||
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
|
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
|
||||||
|
import { HomeAssistant } from "../../types";
|
||||||
|
import { PolymerChangedEvent } from "../../polymer-types";
|
||||||
|
import { computeDeviceName } from "../../data/device_registry";
|
||||||
|
import { fireEvent } from "../../common/dom/fire_event";
|
||||||
|
import { haStyleDialog } from "../../resources/styles";
|
||||||
|
|
||||||
@customElement("dialog-device-registry-detail")
|
@customElement("dialog-device-registry-detail")
|
||||||
class DialogDeviceRegistryDetail extends LitElement {
|
class DialogDeviceRegistryDetail extends LitElement {
|
||||||
@@ -34,7 +36,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
|
|
||||||
@internalProperty() private _areaId?: string;
|
@internalProperty() private _areaId?: string;
|
||||||
|
|
||||||
private _submitting?: boolean;
|
@internalProperty() private _submitting?: boolean;
|
||||||
|
|
||||||
public async showDialog(
|
public async showDialog(
|
||||||
params: DeviceRegistryDetailDialogParams
|
params: DeviceRegistryDetailDialogParams
|
||||||
@@ -46,22 +48,24 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._error = "";
|
||||||
|
this._params = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._params) {
|
if (!this._params) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
const device = this._params.device;
|
const device = this._params.device;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-paper-dialog
|
<ha-dialog
|
||||||
with-backdrop
|
open
|
||||||
opened
|
@closed=${this.closeDialog}
|
||||||
@opened-changed="${this._openedChanged}"
|
.heading=${computeDeviceName(device, this.hass)}
|
||||||
>
|
>
|
||||||
<h2>
|
<div>
|
||||||
${computeDeviceName(device, this.hass)}
|
|
||||||
</h2>
|
|
||||||
<paper-dialog-scrollable>
|
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<paper-input
|
<paper-input
|
||||||
@@ -77,13 +81,22 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
@value-changed=${this._areaPicked}
|
@value-changed=${this._areaPicked}
|
||||||
></ha-area-picker>
|
></ha-area-picker>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog-scrollable>
|
|
||||||
<div class="paper-dialog-buttons">
|
|
||||||
<mwc-button @click="${this._updateEntry}">
|
|
||||||
${this.hass.localize("ui.panel.config.devices.update")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
</div>
|
||||||
</ha-paper-dialog>
|
<mwc-button
|
||||||
|
slot="secondaryAction"
|
||||||
|
@click=${this.closeDialog}
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
slot="primaryAction"
|
||||||
|
@click="${this._updateEntry}"
|
||||||
|
.disabled=${this._submitting}
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.panel.config.devices.update")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-dialog>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,19 +126,10 @@ class DialogDeviceRegistryDetail extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
|
||||||
if (!(ev.detail as any).value) {
|
|
||||||
this._params = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResult[] {
|
static get styles(): CSSResult[] {
|
||||||
return [
|
return [
|
||||||
haStyleDialog,
|
haStyleDialog,
|
||||||
css`
|
css`
|
||||||
ha-paper-dialog {
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
.form {
|
.form {
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input";
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
|
import "../../../components/ha-date-input";
|
||||||
import { attributeClassNames } from "../../../common/entity/attribute_class_names";
|
import { attributeClassNames } from "../../../common/entity/attribute_class_names";
|
||||||
import "../../../components/ha-relative-time";
|
import "../../../components/ha-relative-time";
|
||||||
import "../../../components/paper-time-input";
|
import "../../../components/paper-time-input";
|
||||||
@@ -14,12 +14,12 @@ class DatetimeInput extends PolymerElement {
|
|||||||
<div class$="[[computeClassNames(stateObj)]]">
|
<div class$="[[computeClassNames(stateObj)]]">
|
||||||
<template is="dom-if" if="[[doesHaveDate(stateObj)]]" restamp="">
|
<template is="dom-if" if="[[doesHaveDate(stateObj)]]" restamp="">
|
||||||
<div>
|
<div>
|
||||||
<vaadin-date-picker
|
<ha-date-input
|
||||||
id="dateInput"
|
id="dateInput"
|
||||||
on-value-changed="dateTimeChanged"
|
on-value-changed="dateTimeChanged"
|
||||||
label="Date"
|
label="Date"
|
||||||
value="{{selectedDate}}"
|
value="{{selectedDate}}"
|
||||||
></vaadin-date-picker>
|
></ha-date-input>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[doesHaveTime(stateObj)]]" restamp="">
|
<template is="dom-if" if="[[doesHaveTime(stateObj)]]" restamp="">
|
||||||
|
@@ -25,19 +25,12 @@ import "../../../components/ha-svg-icon";
|
|||||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||||
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
|
||||||
import {
|
import {
|
||||||
ControlButton,
|
computeMediaControls,
|
||||||
MediaPickedEvent,
|
MediaPickedEvent,
|
||||||
SUPPORTS_PLAY,
|
|
||||||
SUPPORT_BROWSE_MEDIA,
|
SUPPORT_BROWSE_MEDIA,
|
||||||
SUPPORT_NEXT_TRACK,
|
|
||||||
SUPPORT_PAUSE,
|
|
||||||
SUPPORT_PLAY_MEDIA,
|
SUPPORT_PLAY_MEDIA,
|
||||||
SUPPORT_PREVIOUS_TRACK,
|
|
||||||
SUPPORT_SELECT_SOUND_MODE,
|
SUPPORT_SELECT_SOUND_MODE,
|
||||||
SUPPORT_SELECT_SOURCE,
|
SUPPORT_SELECT_SOURCE,
|
||||||
SUPPORT_STOP,
|
|
||||||
SUPPORT_TURN_OFF,
|
|
||||||
SUPPORT_TURN_ON,
|
|
||||||
SUPPORT_VOLUME_BUTTONS,
|
SUPPORT_VOLUME_BUTTONS,
|
||||||
SUPPORT_VOLUME_MUTE,
|
SUPPORT_VOLUME_MUTE,
|
||||||
SUPPORT_VOLUME_SET,
|
SUPPORT_VOLUME_SET,
|
||||||
@@ -57,8 +50,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
const controls = this._getControls();
|
|
||||||
const stateObj = this.stateObj;
|
const stateObj = this.stateObj;
|
||||||
|
const controls = computeMediaControls(stateObj);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${!controls
|
${!controls
|
||||||
@@ -254,84 +247,6 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getControls(): ControlButton[] | undefined {
|
|
||||||
const stateObj = this.stateObj;
|
|
||||||
|
|
||||||
if (!stateObj) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = stateObj.state;
|
|
||||||
|
|
||||||
if (UNAVAILABLE_STATES.includes(state)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === "off") {
|
|
||||||
return supportsFeature(stateObj, SUPPORT_TURN_ON)
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
icon: "hass:power",
|
|
||||||
action: "turn_on",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === "idle") {
|
|
||||||
return supportsFeature(stateObj, SUPPORTS_PLAY)
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
icon: "hass:play",
|
|
||||||
action: "media_play",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttons: ControlButton[] = [];
|
|
||||||
|
|
||||||
if (supportsFeature(stateObj, SUPPORT_TURN_OFF)) {
|
|
||||||
buttons.push({
|
|
||||||
icon: "hass:power",
|
|
||||||
action: "turn_off",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsFeature(stateObj, SUPPORT_PREVIOUS_TRACK)) {
|
|
||||||
buttons.push({
|
|
||||||
icon: "hass:skip-previous",
|
|
||||||
action: "media_previous_track",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(state === "playing" &&
|
|
||||||
(supportsFeature(stateObj, SUPPORT_PAUSE) ||
|
|
||||||
supportsFeature(stateObj, SUPPORT_STOP))) ||
|
|
||||||
(state === "paused" && supportsFeature(stateObj, SUPPORTS_PLAY))
|
|
||||||
) {
|
|
||||||
buttons.push({
|
|
||||||
icon:
|
|
||||||
state !== "playing"
|
|
||||||
? "hass:play"
|
|
||||||
: supportsFeature(stateObj, SUPPORT_PAUSE)
|
|
||||||
? "hass:pause"
|
|
||||||
: "hass:stop",
|
|
||||||
action: "media_play_pause",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supportsFeature(stateObj, SUPPORT_NEXT_TRACK)) {
|
|
||||||
buttons.push({
|
|
||||||
icon: "hass:skip-next",
|
|
||||||
action: "media_next_track",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttons.length > 0 ? buttons : undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleClick(e: MouseEvent): void {
|
private _handleClick(e: MouseEvent): void {
|
||||||
this.hass!.callService(
|
this.hass!.callService(
|
||||||
"media_player",
|
"media_player",
|
||||||
|
@@ -72,6 +72,12 @@ class MoreInfoSun extends LitElement {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
ha-relative-time {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
ha-relative-time::first-letter {
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -285,8 +285,8 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.dialogs.more_info_control.restored.confirm_remove_text"
|
"ui.dialogs.more_info_control.restored.confirm_remove_text"
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.remove"),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
removeEntityRegistryEntry(this.hass, entityId);
|
removeEntityRegistryEntry(this.hass, entityId);
|
||||||
},
|
},
|
||||||
|
@@ -25,8 +25,8 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
background-color: var(--primary-background-color);
|
background-color: var(--primary-background-color);
|
||||||
min-height: 64px;
|
height: var(--header-height);
|
||||||
width: calc(100% - 32px);
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
div[main-title] {
|
div[main-title] {
|
||||||
@@ -63,7 +63,10 @@ export class HuiNotificationDrawer extends EventsMixin(
|
|||||||
<app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
|
<app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
|
||||||
<app-toolbar>
|
<app-toolbar>
|
||||||
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
|
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
|
||||||
<ha-icon-button-prev on-click="_closeDrawer" aria-label$="[[localize('ui.notification_drawer.close')]]"></ha-icon-button-prev>
|
<ha-icon-button-prev hass="[[hass]]" on-click="_closeDrawer"
|
||||||
|
title="[[localize('ui.notification_drawer.close')]]"
|
||||||
|
label="[[localize('ui.notification_drawer.close')]]">
|
||||||
|
</ha-icon-button-prev>
|
||||||
</app-toolbar>
|
</app-toolbar>
|
||||||
<div class="notifications">
|
<div class="notifications">
|
||||||
<template is="dom-if" if="[[!_empty(notifications)]]">
|
<template is="dom-if" if="[[!_empty(notifications)]]">
|
||||||
|
@@ -41,19 +41,36 @@ import {
|
|||||||
showConfirmationDialog,
|
showConfirmationDialog,
|
||||||
} from "../generic/show-dialog-box";
|
} from "../generic/show-dialog-box";
|
||||||
import { QuickBarParams } from "./show-dialog-quick-bar";
|
import { QuickBarParams } from "./show-dialog-quick-bar";
|
||||||
|
import { navigate } from "../../common/navigate";
|
||||||
|
import { configSections } from "../../panels/config/ha-panel-config";
|
||||||
|
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||||
|
import { canShowPage } from "../../common/config/can_show_page";
|
||||||
|
import { getPanelIcon, getPanelNameTranslationKey } from "../../data/panel";
|
||||||
|
|
||||||
|
const DEFAULT_NAVIGATION_ICON = "hass:arrow-right-circle";
|
||||||
|
const DEFAULT_SERVER_ICON = "hass:server";
|
||||||
|
|
||||||
interface QuickBarItem extends ScorableTextItem {
|
interface QuickBarItem extends ScorableTextItem {
|
||||||
icon: string;
|
icon?: string;
|
||||||
|
iconPath?: string;
|
||||||
action(data?: any): void;
|
action(data?: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface QuickBarNavigationItem extends QuickBarItem {
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NavigationInfo extends PageNavigation {
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
@customElement("ha-quick-bar")
|
@customElement("ha-quick-bar")
|
||||||
export class QuickBar extends LitElement {
|
export class QuickBar extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@internalProperty() private _commandItems: QuickBarItem[] = [];
|
@internalProperty() private _commandItems?: QuickBarItem[];
|
||||||
|
|
||||||
@internalProperty() private _entityItems: QuickBarItem[] = [];
|
@internalProperty() private _entityItems?: QuickBarItem[];
|
||||||
|
|
||||||
@internalProperty() private _items?: QuickBarItem[] = [];
|
@internalProperty() private _items?: QuickBarItem[] = [];
|
||||||
|
|
||||||
@@ -73,8 +90,7 @@ export class QuickBar extends LitElement {
|
|||||||
|
|
||||||
public async showDialog(params: QuickBarParams) {
|
public async showDialog(params: QuickBarParams) {
|
||||||
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
|
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
|
||||||
this._commandItems = this._generateCommandItems();
|
this._initializeItemsIfNeeded();
|
||||||
this._entityItems = this._generateEntityItems();
|
|
||||||
this._opened = true;
|
this._opened = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +174,14 @@ export class QuickBar extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _initializeItemsIfNeeded() {
|
||||||
|
if (this._commandMode) {
|
||||||
|
this._commandItems = this._commandItems || this._generateCommandItems();
|
||||||
|
} else {
|
||||||
|
this._entityItems = this._entityItems || this._generateEntityItems();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _handleOpened() {
|
private _handleOpened() {
|
||||||
this._setFilteredItems();
|
this._setFilteredItems();
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
@@ -182,14 +206,20 @@ export class QuickBar extends LitElement {
|
|||||||
.twoline=${Boolean(item.altText)}
|
.twoline=${Boolean(item.altText)}
|
||||||
.item=${item}
|
.item=${item}
|
||||||
index=${ifDefined(index)}
|
index=${ifDefined(index)}
|
||||||
hasMeta
|
graphic="icon"
|
||||||
graphic=${item.altText ? "avatar" : "icon"}
|
|
||||||
>
|
>
|
||||||
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
|
${item.iconPath
|
||||||
<span>${item.text}</span>
|
? html`<ha-svg-icon
|
||||||
|
.path=${item.iconPath}
|
||||||
|
slot="graphic"
|
||||||
|
></ha-svg-icon>`
|
||||||
|
: html`<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>`}
|
||||||
|
${item.text}
|
||||||
${item.altText
|
${item.altText
|
||||||
? html`
|
? html`
|
||||||
<span slot="secondary" class="secondary">${item.altText}</span>
|
<span slot="secondary" class="item-text secondary"
|
||||||
|
>${item.altText}</span
|
||||||
|
>
|
||||||
`
|
`
|
||||||
: null}
|
: null}
|
||||||
</mwc-list-item>
|
</mwc-list-item>
|
||||||
@@ -252,6 +282,8 @@ export class QuickBar extends LitElement {
|
|||||||
if (oldCommandMode !== this._commandMode) {
|
if (oldCommandMode !== this._commandMode) {
|
||||||
this._items = undefined;
|
this._items = undefined;
|
||||||
this._focusSet = false;
|
this._focusSet = false;
|
||||||
|
|
||||||
|
this._initializeItemsIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,10 +311,22 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _generateEntityItems(): QuickBarItem[] {
|
||||||
|
return Object.keys(this.hass.states)
|
||||||
|
.map((entityId) => ({
|
||||||
|
text: computeStateName(this.hass.states[entityId]),
|
||||||
|
altText: entityId,
|
||||||
|
icon: domainIcon(computeDomain(entityId), this.hass.states[entityId]),
|
||||||
|
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()));
|
||||||
|
}
|
||||||
|
|
||||||
private _generateCommandItems(): QuickBarItem[] {
|
private _generateCommandItems(): QuickBarItem[] {
|
||||||
return [
|
return [
|
||||||
...this._generateReloadCommands(),
|
...this._generateReloadCommands(),
|
||||||
...this._generateServerControlCommands(),
|
...this._generateServerControlCommands(),
|
||||||
|
...this._generateNavigationCommands(),
|
||||||
].sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()));
|
].sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +359,7 @@ export class QuickBar extends LitElement {
|
|||||||
`ui.dialogs.quick-bar.commands.server_control.${action}`
|
`ui.dialogs.quick-bar.commands.server_control.${action}`
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
icon: "hass:server",
|
icon: DEFAULT_SERVER_ICON,
|
||||||
action: () => this.hass.callService("homeassistant", action),
|
action: () => this.hass.callService("homeassistant", action),
|
||||||
},
|
},
|
||||||
this.hass.localize("ui.dialogs.generic.ok")
|
this.hass.localize("ui.dialogs.generic.ok")
|
||||||
@@ -323,6 +367,79 @@ export class QuickBar extends LitElement {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _generateNavigationCommands(): QuickBarItem[] {
|
||||||
|
const panelItems = this._generateNavigationPanelCommands();
|
||||||
|
const sectionItems = this._generateNavigationConfigSectionCommands();
|
||||||
|
|
||||||
|
return this._withNavigationActions([...panelItems, ...sectionItems]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _generateNavigationPanelCommands(): Omit<
|
||||||
|
QuickBarNavigationItem,
|
||||||
|
"action"
|
||||||
|
>[] {
|
||||||
|
return Object.keys(this.hass.panels).map((panelKey) => {
|
||||||
|
const panel = this.hass.panels[panelKey];
|
||||||
|
const translationKey = getPanelNameTranslationKey(panel);
|
||||||
|
|
||||||
|
const text = this.hass.localize(
|
||||||
|
"ui.dialogs.quick-bar.commands.navigation.navigate_to",
|
||||||
|
"panel",
|
||||||
|
this.hass.localize(translationKey) || panel.title || panel.url_path
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
text,
|
||||||
|
icon: getPanelIcon(panel) || DEFAULT_NAVIGATION_ICON,
|
||||||
|
path: `/${panel.url_path}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _generateNavigationConfigSectionCommands(): Partial<
|
||||||
|
QuickBarNavigationItem
|
||||||
|
>[] {
|
||||||
|
const items: NavigationInfo[] = [];
|
||||||
|
|
||||||
|
for (const sectionKey of Object.keys(configSections)) {
|
||||||
|
for (const page of configSections[sectionKey]) {
|
||||||
|
if (canShowPage(this.hass, page)) {
|
||||||
|
if (page.component) {
|
||||||
|
const info = this._getNavigationInfoFromConfig(page);
|
||||||
|
|
||||||
|
if (info) {
|
||||||
|
items.push(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getNavigationInfoFromConfig(
|
||||||
|
page: PageNavigation
|
||||||
|
): NavigationInfo | undefined {
|
||||||
|
if (page.component) {
|
||||||
|
const shortCaption = this.hass.localize(
|
||||||
|
`ui.dialogs.quick-bar.commands.navigation.${page.component}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (page.translationKey) {
|
||||||
|
const caption = this.hass.localize(
|
||||||
|
"ui.dialogs.quick-bar.commands.navigation.navigate_to_config",
|
||||||
|
"panel",
|
||||||
|
shortCaption
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...page, text: caption };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
private _generateConfirmationCommand(
|
private _generateConfirmationCommand(
|
||||||
item: QuickBarItem,
|
item: QuickBarItem,
|
||||||
confirmText: ConfirmationDialogParams["confirmText"]
|
confirmText: ConfirmationDialogParams["confirmText"]
|
||||||
@@ -337,15 +454,13 @@ export class QuickBar extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _generateEntityItems(): QuickBarItem[] {
|
private _withNavigationActions(items) {
|
||||||
return Object.keys(this.hass.states)
|
return items.map(({ text, icon, iconPath, path }) => ({
|
||||||
.map((entityId) => ({
|
text,
|
||||||
text: computeStateName(this.hass.states[entityId]) || entityId,
|
icon,
|
||||||
altText: entityId,
|
iconPath,
|
||||||
icon: domainIcon(computeDomain(entityId), this.hass.states[entityId]),
|
action: () => navigate(this, path),
|
||||||
action: () => fireEvent(this, "hass-more-info", { entityId }),
|
}));
|
||||||
}))
|
|
||||||
.sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggleIfAlreadyOpened() {
|
private _toggleIfAlreadyOpened() {
|
||||||
@@ -387,8 +502,14 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ha-icon,
|
||||||
|
ha-svg-icon {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
ha-svg-icon.prefix {
|
ha-svg-icon.prefix {
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.uni-virtualizer-host {
|
.uni-virtualizer-host {
|
||||||
@@ -405,6 +526,7 @@ export class QuickBar extends LitElement {
|
|||||||
|
|
||||||
mwc-list-item {
|
mwc-list-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
@@ -17,6 +17,8 @@
|
|||||||
--primary-text-color: #e1e1e1;
|
--primary-text-color: #e1e1e1;
|
||||||
--secondary-text-color: #9b9b9b;
|
--secondary-text-color: #9b9b9b;
|
||||||
--disabled-text-color: #6f6f6f;
|
--disabled-text-color: #6f6f6f;
|
||||||
|
--mdc-theme-surface: #1e1e1e;
|
||||||
|
--ha-card-background: #1e1e1e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
|
@@ -9,9 +9,12 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "./hass-subpage";
|
import "./hass-subpage";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("hass-error-screen")
|
@customElement("hass-error-screen")
|
||||||
class HassErrorScreen extends LitElement {
|
class HassErrorScreen extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ type: Boolean }) public toolbar = true;
|
@property({ type: Boolean }) public toolbar = true;
|
||||||
|
|
||||||
@property() public error?: string;
|
@property() public error?: string;
|
||||||
@@ -21,6 +24,7 @@ class HassErrorScreen extends LitElement {
|
|||||||
${this.toolbar
|
${this.toolbar
|
||||||
? html`<div class="toolbar">
|
? html`<div class="toolbar">
|
||||||
<ha-icon-button-arrow-prev
|
<ha-icon-button-arrow-prev
|
||||||
|
.hass=${this.hass}
|
||||||
@click=${this._handleBack}
|
@click=${this._handleBack}
|
||||||
></ha-icon-button-arrow-prev>
|
></ha-icon-button-arrow-prev>
|
||||||
</div>`
|
</div>`
|
||||||
@@ -50,7 +54,7 @@ class HassErrorScreen extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 65px;
|
height: var(--header-height);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: var(--app-header-background-color);
|
background-color: var(--app-header-background-color);
|
||||||
|
@@ -39,6 +39,7 @@ class HassLoadingScreen extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-icon-button-arrow-prev
|
<ha-icon-button-arrow-prev
|
||||||
|
.hass=${this.hass}
|
||||||
@click=${this._handleBack}
|
@click=${this._handleBack}
|
||||||
></ha-icon-button-arrow-prev>
|
></ha-icon-button-arrow-prev>
|
||||||
`}
|
`}
|
||||||
@@ -66,7 +67,7 @@ class HassLoadingScreen extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 65px;
|
height: var(--header-height);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: var(--app-header-background-color);
|
background-color: var(--app-header-background-color);
|
||||||
|
@@ -12,17 +12,17 @@ import { classMap } from "lit-html/directives/class-map";
|
|||||||
import "../components/ha-menu-button";
|
import "../components/ha-menu-button";
|
||||||
import "../components/ha-icon-button-arrow-prev";
|
import "../components/ha-icon-button-arrow-prev";
|
||||||
import { restoreScroll } from "../common/decorators/restore-scroll";
|
import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||||
|
import { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@customElement("hass-subpage")
|
@customElement("hass-subpage")
|
||||||
class HassSubpage extends LitElement {
|
class HassSubpage extends LitElement {
|
||||||
@property()
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
public header?: string;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property() public header?: string;
|
||||||
public showBackButton = true;
|
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean }) public showBackButton = true;
|
||||||
public hassio = false;
|
|
||||||
|
@property({ type: Boolean }) public hassio = false;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@restoreScroll(".content") private _savedScrollPos?: number;
|
@restoreScroll(".content") private _savedScrollPos?: number;
|
||||||
@@ -31,7 +31,7 @@ class HassSubpage extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<ha-icon-button-arrow-prev
|
<ha-icon-button-arrow-prev
|
||||||
aria-label="Back"
|
.hass=${this.hass}
|
||||||
@click=${this._backTapped}
|
@click=${this._backTapped}
|
||||||
class=${classMap({ hidden: !this.showBackButton })}
|
class=${classMap({ hidden: !this.showBackButton })}
|
||||||
></ha-icon-button-arrow-prev>
|
></ha-icon-button-arrow-prev>
|
||||||
@@ -69,7 +69,7 @@ class HassSubpage extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 65px;
|
height: var(--header-height);
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
background-color: var(--app-header-background-color);
|
background-color: var(--app-header-background-color);
|
||||||
|
@@ -145,7 +145,7 @@ class HassTabsSubpage extends LitElement {
|
|||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<ha-icon-button-arrow-prev
|
<ha-icon-button-arrow-prev
|
||||||
aria-label="Back"
|
.hass=${this.hass}
|
||||||
@click=${this._backTapped}
|
@click=${this._backTapped}
|
||||||
></ha-icon-button-arrow-prev>
|
></ha-icon-button-arrow-prev>
|
||||||
`}
|
`}
|
||||||
@@ -217,7 +217,7 @@ class HassTabsSubpage extends LitElement {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 65px;
|
height: var(--header-height);
|
||||||
background-color: var(--sidebar-background-color);
|
background-color: var(--sidebar-background-color);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
color: var(--sidebar-text-color);
|
color: var(--sidebar-text-color);
|
||||||
|
@@ -192,7 +192,7 @@ class HomeAssistantMain extends LitElement {
|
|||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
||||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
--app-drawer-width: 64px;
|
--app-drawer-width: 56px;
|
||||||
}
|
}
|
||||||
:host([expanded]) {
|
:host([expanded]) {
|
||||||
--app-drawer-width: calc(256px + env(safe-area-inset-left));
|
--app-drawer-width: calc(256px + env(safe-area-inset-left));
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import "../components/ha-icon";
|
import "../components/ha-icon";
|
||||||
|
import { brandsUrl } from "../util/brands-url";
|
||||||
|
|
||||||
@customElement("integration-badge")
|
@customElement("integration-badge")
|
||||||
class IntegrationBadge extends LitElement {
|
class IntegrationBadge extends LitElement {
|
||||||
@@ -23,7 +24,7 @@ class IntegrationBadge extends LitElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<img
|
<img
|
||||||
src="https://brands.home-assistant.io/${this.domain}/icon.png"
|
src=${brandsUrl(this.domain, "icon")}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
${this.badgeIcon
|
${this.badgeIcon
|
||||||
|
@@ -4,7 +4,6 @@ import {
|
|||||||
CSSResult,
|
CSSResult,
|
||||||
customElement,
|
customElement,
|
||||||
html,
|
html,
|
||||||
internalProperty,
|
|
||||||
LitElement,
|
LitElement,
|
||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
@@ -15,10 +14,6 @@ import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show
|
|||||||
import { navigate } from "../common/navigate";
|
import { navigate } from "../common/navigate";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import "../components/ha-card";
|
import "../components/ha-card";
|
||||||
import {
|
|
||||||
extractApiErrorMessage,
|
|
||||||
ignoredStatusCodes,
|
|
||||||
} from "../data/hassio/common";
|
|
||||||
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
import { makeDialogManager } from "../dialogs/make-dialog-manager";
|
||||||
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
|
||||||
import { haStyle } from "../resources/styles";
|
import { haStyle } from "../resources/styles";
|
||||||
@@ -38,10 +33,6 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public restoring = false;
|
@property({ type: Boolean }) public restoring = false;
|
||||||
|
|
||||||
@internalProperty() private _log = "";
|
|
||||||
|
|
||||||
@internalProperty() private _showFullLog = false;
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return this.restoring
|
return this.restoring
|
||||||
? html`<ha-card
|
? html`<ha-card
|
||||||
@@ -49,22 +40,7 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
|||||||
"ui.panel.page-onboarding.restore.in_progress"
|
"ui.panel.page-onboarding.restore.in_progress"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
${this._showFullLog
|
<onboarding-loading></onboarding-loading>
|
||||||
? html`<hassio-ansi-to-html .content=${this._log}>
|
|
||||||
</hassio-ansi-to-html>`
|
|
||||||
: html`<onboarding-loading></onboarding-loading>
|
|
||||||
<hassio-ansi-to-html
|
|
||||||
class="logentry"
|
|
||||||
.content=${this._lastLogEntry(this._log)}
|
|
||||||
>
|
|
||||||
</hassio-ansi-to-html>`}
|
|
||||||
<div class="card-actions">
|
|
||||||
<mwc-button @click=${this._toggeFullLog}>
|
|
||||||
${this._showFullLog
|
|
||||||
? this.localize("ui.panel.page-onboarding.restore.hide_log")
|
|
||||||
: this.localize("ui.panel.page-onboarding.restore.show_log")}
|
|
||||||
</mwc-button>
|
|
||||||
</div>
|
|
||||||
</ha-card>`
|
</ha-card>`
|
||||||
: html`
|
: html`
|
||||||
<button class="link" @click=${this._uploadSnapshot}>
|
<button class="link" @click=${this._uploadSnapshot}>
|
||||||
@@ -73,33 +49,6 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _toggeFullLog(): void {
|
|
||||||
this._showFullLog = !this._showFullLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _filterLogs(logs: string): string {
|
|
||||||
// Filter out logs that is not relevant to show during the restore
|
|
||||||
return logs
|
|
||||||
.split("\n")
|
|
||||||
.filter(
|
|
||||||
(entry) =>
|
|
||||||
!entry.includes("/supervisor/logs") &&
|
|
||||||
!entry.includes("/supervisor/ping") &&
|
|
||||||
!entry.includes("DEBUG") &&
|
|
||||||
!entry.includes("TypeError: Failed to fetch")
|
|
||||||
)
|
|
||||||
.join("\n")
|
|
||||||
.replace(/\s[A-Z]+\s\(\w+\)\s\[[\w.]+\]/gi, "")
|
|
||||||
.replace(/\d{2}-\d{2}-\d{2}\s/gi, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _lastLogEntry(logs: string): string {
|
|
||||||
return logs
|
|
||||||
.split("\n")
|
|
||||||
.slice(-2)[0]
|
|
||||||
.replace(/\d{2}:\d{2}:\d{2}\s/gi, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private _uploadSnapshot(): void {
|
private _uploadSnapshot(): void {
|
||||||
showSnapshotUploadDialog(this, {
|
showSnapshotUploadDialog(this, {
|
||||||
showSnapshot: (slug: string) => this._showSnapshotDialog(slug),
|
showSnapshot: (slug: string) => this._showSnapshotDialog(slug),
|
||||||
@@ -110,42 +59,26 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
|
|||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
makeDialogManager(this, this.shadowRoot!);
|
makeDialogManager(this, this.shadowRoot!);
|
||||||
setInterval(() => this._getLogs(), 1000);
|
setInterval(() => this._checkRestoreStatus(), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getLogs(): Promise<void> {
|
private async _checkRestoreStatus(): Promise<void> {
|
||||||
if (this.restoring) {
|
if (this.restoring) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/hassio/supervisor/logs", {
|
const response = await fetch("/api/hassio/supervisor/info", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
});
|
});
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
// If we get a unauthorized response, the restore is done
|
// If we get a unauthorized response, the restore is done
|
||||||
this._restoreDone();
|
navigate(this, "/", true);
|
||||||
} else if (
|
location.reload();
|
||||||
response.status &&
|
|
||||||
!ignoredStatusCodes.has(response.status)
|
|
||||||
) {
|
|
||||||
// Handle error responses
|
|
||||||
this._log += this._filterLogs(extractApiErrorMessage(response));
|
|
||||||
}
|
|
||||||
const logs = await response.text();
|
|
||||||
this._log = this._filterLogs(logs);
|
|
||||||
if (this._log.match(/\d{2}:\d{2}:\d{2}\s.*Restore\s\w+\sdone/)) {
|
|
||||||
// The log indicates that the restore done, navigate the user back to base
|
|
||||||
this._restoreDone();
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._log += this._filterLogs(err.toString());
|
// We fully expected issues with fetching info untill restore is complete.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _restoreDone(): void {
|
|
||||||
navigate(this, "/", true);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _showSnapshotDialog(slug: string): void {
|
private _showSnapshotDialog(slug: string): void {
|
||||||
showHassioSnapshotDialog(this, {
|
showHassioSnapshotDialog(this, {
|
||||||
slug,
|
slug,
|
||||||
|
@@ -81,7 +81,8 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
if (!area) {
|
if (!area) {
|
||||||
return html`
|
return html`
|
||||||
<hass-error-screen
|
<hass-error-screen
|
||||||
error="${this.hass.localize("ui.panel.config.areas.area_not_found")}"
|
.hass=${this.hass}
|
||||||
|
.error=${this.hass.localize("ui.panel.config.areas.area_not_found")}
|
||||||
></hass-error-screen>
|
></hass-error-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -312,8 +313,8 @@ class HaConfigAreaPage extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.areas.delete.confirmation_text"
|
"ui.panel.config.areas.delete.confirmation_text"
|
||||||
),
|
),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -126,9 +126,10 @@ export class HaConfigAreasDashboard extends LitElement {
|
|||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
<mwc-fab
|
<mwc-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
title="${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.areas.picker.create_area"
|
"ui.panel.config.areas.picker.create_area"
|
||||||
)}"
|
)}
|
||||||
|
extended
|
||||||
@click=${this._createArea}
|
@click=${this._createArea}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
@@ -276,8 +276,8 @@ export default class HaAutomationActionRow extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.actions.delete_confirm"
|
"ui.panel.config.automation.editor.actions.delete_confirm"
|
||||||
),
|
),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
fireEvent(this, "value-changed", { value: null });
|
fireEvent(this, "value-changed", { value: null });
|
||||||
},
|
},
|
||||||
|
@@ -123,8 +123,8 @@ export default class HaAutomationConditionRow extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.conditions.delete_confirm"
|
"ui.panel.config.automation.editor.conditions.delete_confirm"
|
||||||
),
|
),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
fireEvent(this, "value-changed", { value: null });
|
fireEvent(this, "value-changed", { value: null });
|
||||||
},
|
},
|
||||||
|
@@ -472,7 +472,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
<mwc-fab
|
<mwc-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
class=${classMap({ dirty: this._dirty })}
|
class=${classMap({ dirty: this._dirty })}
|
||||||
.title=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
.label=${this.hass.localize("ui.panel.config.automation.editor.save")}
|
||||||
|
extended
|
||||||
@click=${this._saveAutomation}
|
@click=${this._saveAutomation}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
|
||||||
@@ -650,8 +651,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
text: this.hass!.localize(
|
text: this.hass!.localize(
|
||||||
"ui.panel.config.automation.editor.unsaved_confirm"
|
"ui.panel.config.automation.editor.unsaved_confirm"
|
||||||
),
|
),
|
||||||
confirmText: this.hass!.localize("ui.common.yes"),
|
confirmText: this.hass!.localize("ui.common.leave"),
|
||||||
dismissText: this.hass!.localize("ui.common.no"),
|
dismissText: this.hass!.localize("ui.common.stay"),
|
||||||
confirm: () => history.back(),
|
confirm: () => history.back(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -666,8 +667,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
text: this.hass!.localize(
|
text: this.hass!.localize(
|
||||||
"ui.panel.config.automation.editor.unsaved_confirm"
|
"ui.panel.config.automation.editor.unsaved_confirm"
|
||||||
),
|
),
|
||||||
confirmText: this.hass!.localize("ui.common.yes"),
|
confirmText: this.hass!.localize("ui.common.leave"),
|
||||||
dismissText: this.hass!.localize("ui.common.no"),
|
dismissText: this.hass!.localize("ui.common.stay"),
|
||||||
}))
|
}))
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -689,8 +690,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.automation.picker.delete_confirm"
|
"ui.panel.config.automation.picker.delete_confirm"
|
||||||
),
|
),
|
||||||
confirmText: this.hass!.localize("ui.common.yes"),
|
confirmText: this.hass!.localize("ui.common.delete"),
|
||||||
dismissText: this.hass!.localize("ui.common.no"),
|
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||||
confirm: () => this._delete(),
|
confirm: () => this._delete(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -176,9 +176,10 @@ class HaAutomationPicker extends LitElement {
|
|||||||
</mwc-icon-button>
|
</mwc-icon-button>
|
||||||
<mwc-fab
|
<mwc-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
title=${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.automation.picker.add_automation"
|
"ui.panel.config.automation.picker.add_automation"
|
||||||
)}
|
)}
|
||||||
|
extended
|
||||||
@click=${this._createNew}
|
@click=${this._createNew}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
@@ -85,13 +85,22 @@ class DialogThingtalk extends LitElement {
|
|||||||
.opened=${this._opened}
|
.opened=${this._opened}
|
||||||
@opened-changed=${this._openedChanged}
|
@opened-changed=${this._openedChanged}
|
||||||
>
|
>
|
||||||
<h2>Create a new automation</h2>
|
<h2>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.task_selection.header`
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
<paper-dialog-scrollable>
|
<paper-dialog-scrollable>
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
Type below what this automation should do, and we will try to convert
|
${this.hass.localize(
|
||||||
it into a Home Assistant automation. (only English is supported for
|
`ui.panel.config.automation.thingtalk.task_selection.introduction`
|
||||||
now)<br /><br />
|
)}<br /><br />
|
||||||
For example:
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.task_selection.language_note`
|
||||||
|
)}<br /><br />
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.task_selection.for_example`
|
||||||
|
)}
|
||||||
<ul @click=${this._handleExampleClick}>
|
<ul @click=${this._handleExampleClick}>
|
||||||
<li>
|
<li>
|
||||||
<button class="link">
|
<button class="link">
|
||||||
@@ -130,7 +139,7 @@ class DialogThingtalk extends LitElement {
|
|||||||
</paper-dialog-scrollable>
|
</paper-dialog-scrollable>
|
||||||
<div class="paper-dialog-buttons">
|
<div class="paper-dialog-buttons">
|
||||||
<mwc-button class="left" @click="${this._skip}">
|
<mwc-button class="left" @click="${this._skip}">
|
||||||
Skip
|
${this.hass.localize(`ui.common.skip`)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
<mwc-button @click="${this._generate}" .disabled=${this._submitting}>
|
<mwc-button @click="${this._generate}" .disabled=${this._submitting}>
|
||||||
${this._submitting
|
${this._submitting
|
||||||
@@ -140,7 +149,7 @@ class DialogThingtalk extends LitElement {
|
|||||||
title="Creating your automation..."
|
title="Creating your automation..."
|
||||||
></ha-circular-progress>`
|
></ha-circular-progress>`
|
||||||
: ""}
|
: ""}
|
||||||
Create automation
|
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</ha-paper-dialog>
|
</ha-paper-dialog>
|
||||||
@@ -150,7 +159,9 @@ class DialogThingtalk extends LitElement {
|
|||||||
private async _generate() {
|
private async _generate() {
|
||||||
this._value = this._input!.value as string;
|
this._value = this._input!.value as string;
|
||||||
if (!this._value) {
|
if (!this._value) {
|
||||||
this._error = "Enter a command or tap skip.";
|
this._error = this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.task_selection.error_empty`
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._submitting = true;
|
this._submitting = true;
|
||||||
@@ -169,7 +180,9 @@ class DialogThingtalk extends LitElement {
|
|||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
|
|
||||||
if (!Object.keys(config).length) {
|
if (!Object.keys(config).length) {
|
||||||
this._error = "We couldn't create an automation for that (yet?).";
|
this._error = this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.task_selection.error_unsupported`
|
||||||
|
);
|
||||||
} else if (Object.keys(placeholders).length) {
|
} else if (Object.keys(placeholders).length) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
this._placeholders = placeholders;
|
this._placeholders = placeholders;
|
||||||
|
@@ -131,7 +131,11 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
|||||||
.opened=${this.opened}
|
.opened=${this.opened}
|
||||||
@opened-changed="${this._openedChanged}"
|
@opened-changed="${this._openedChanged}"
|
||||||
>
|
>
|
||||||
<h2>Great! Now we need to link some devices.</h2>
|
<h2>
|
||||||
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.link_devices.header`
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
<paper-dialog-scrollable>
|
<paper-dialog-scrollable>
|
||||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||||
${Object.entries(this.placeholders).map(
|
${Object.entries(this.placeholders).map(
|
||||||
@@ -168,8 +172,9 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
|||||||
${extraInfo && extraInfo.manualEntity
|
${extraInfo && extraInfo.manualEntity
|
||||||
? html`
|
? html`
|
||||||
<h3>
|
<h3>
|
||||||
One or more devices have more than one matching
|
${this.hass.localize(
|
||||||
entity, please pick the one you want to use.
|
`ui.panel.config.automation.thingtalk.link_devices.ambiguous_entities`
|
||||||
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
${Object.keys(extraInfo.manualEntity).map(
|
${Object.keys(extraInfo.manualEntity).map(
|
||||||
(idx) => html`
|
(idx) => html`
|
||||||
@@ -226,7 +231,9 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
|||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<div class="error">
|
<div class="error">
|
||||||
Unknown placeholder<br />
|
${this.hass.localize(
|
||||||
|
`ui.panel.config.automation.thingtalk.link_devices.unknown_placeholder`
|
||||||
|
)}<br />
|
||||||
${placeholder.domains}<br />
|
${placeholder.domains}<br />
|
||||||
${placeholder.fields.map(
|
${placeholder.fields.map(
|
||||||
(field) => html` ${field}<br /> `
|
(field) => html` ${field}<br /> `
|
||||||
@@ -239,10 +246,10 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
|
|||||||
</paper-dialog-scrollable>
|
</paper-dialog-scrollable>
|
||||||
<div class="paper-dialog-buttons">
|
<div class="paper-dialog-buttons">
|
||||||
<mwc-button class="left" @click="${this.skip}">
|
<mwc-button class="left" @click="${this.skip}">
|
||||||
Skip
|
${this.hass.localize(`ui.common.skip`)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
<mwc-button @click="${this._done}" .disabled=${!this._isDone}>
|
<mwc-button @click="${this._done}" .disabled=${!this._isDone}>
|
||||||
Create automation
|
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
|
||||||
</mwc-button>
|
</mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</ha-paper-dialog>
|
</ha-paper-dialog>
|
||||||
|
@@ -196,8 +196,8 @@ export default class HaAutomationTriggerRow extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.automation.editor.triggers.delete_confirm"
|
"ui.panel.config.automation.editor.triggers.delete_confirm"
|
||||||
),
|
),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.delete"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
fireEvent(this, "value-changed", { value: null });
|
fireEvent(this, "value-changed", { value: null });
|
||||||
},
|
},
|
||||||
|
@@ -61,12 +61,10 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
|||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
|
<hass-subpage hass="[[hass]]" header="Home Assistant Cloud">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
<ha-config-section is-wide="[[isWide]]">
|
||||||
<span slot="header"
|
<span slot="header">Home Assistant Cloud</span>
|
||||||
>[[localize('ui.panel.config.cloud.caption')]]</span
|
|
||||||
>
|
|
||||||
<div slot="introduction">
|
<div slot="introduction">
|
||||||
<p>
|
<p>
|
||||||
[[localize('ui.panel.config.cloud.account.thank_you_note')]]
|
[[localize('ui.panel.config.cloud.account.thank_you_note')]]
|
||||||
|
@@ -214,9 +214,9 @@ class CloudAlexa extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage header="${this.hass!.localize(
|
<hass-subpage .hass=${this.hass} header="${this.hass!.localize(
|
||||||
"ui.panel.config.cloud.alexa.title"
|
"ui.panel.config.cloud.alexa.title"
|
||||||
)}">
|
)}">
|
||||||
${
|
${
|
||||||
emptyFilter
|
emptyFilter
|
||||||
? html`
|
? html`
|
||||||
|
@@ -119,8 +119,8 @@ export class DialogManageCloudhook extends LitElement {
|
|||||||
text: this.hass!.localize(
|
text: this.hass!.localize(
|
||||||
"ui.panel.config.cloud.dialog_cloudhook.confirm_disable"
|
"ui.panel.config.cloud.dialog_cloudhook.confirm_disable"
|
||||||
),
|
),
|
||||||
dismissText: this.hass!.localize("ui.common.no"),
|
dismissText: this.hass!.localize("ui.common.cancel"),
|
||||||
confirmText: this.hass!.localize("ui.common.yes"),
|
confirmText: this.hass!.localize("ui.common.disable"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
this._params!.disableHook();
|
this._params!.disableHook();
|
||||||
this._closeDialog();
|
this._closeDialog();
|
||||||
|
@@ -46,6 +46,7 @@ class CloudForgotPassword extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<hass-subpage
|
<hass-subpage
|
||||||
|
hass="[[hass]]"
|
||||||
header="[[localize('ui.panel.config.cloud.forgot_password.title')]]"
|
header="[[localize('ui.panel.config.cloud.forgot_password.title')]]"
|
||||||
>
|
>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@@ -237,9 +237,9 @@ class CloudGoogleAssistant extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage header="${this.hass!.localize(
|
<hass-subpage
|
||||||
"ui.panel.config.cloud.google.title"
|
.hass=${this.hass}
|
||||||
)}">
|
.header=${this.hass!.localize("ui.panel.config.cloud.google.title")}>
|
||||||
${
|
${
|
||||||
emptyFilter
|
emptyFilter
|
||||||
? html`
|
? html`
|
||||||
|
@@ -76,12 +76,10 @@ class CloudLogin extends LocalizeMixin(
|
|||||||
left: 8px;
|
left: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
|
<hass-subpage hass="[[hass]]" header="Home Assistant Cloud">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
<ha-config-section is-wide="[[isWide]]">
|
||||||
<span slot="header"
|
<span slot="header">Home Assistant Cloud</span>
|
||||||
>[[localize('ui.panel.config.cloud.caption')]]</span
|
|
||||||
>
|
|
||||||
<div slot="introduction">
|
<div slot="introduction">
|
||||||
<p>
|
<p>
|
||||||
[[localize('ui.panel.config.cloud.login.introduction')]]
|
[[localize('ui.panel.config.cloud.login.introduction')]]
|
||||||
|
@@ -47,7 +47,7 @@ class CloudRegister extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<hass-subpage header="[[localize('ui.panel.config.cloud.register.title')]]">
|
<hass-subpage hass="[[hass]]" header="[[localize('ui.panel.config.cloud.register.title')]]">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ha-config-section is-wide="[[isWide]]">
|
<ha-config-section is-wide="[[isWide]]">
|
||||||
<span slot="header">[[localize('ui.panel.config.cloud.register.headline')]]</span>
|
<span slot="header">[[localize('ui.panel.config.cloud.register.headline')]]</span>
|
||||||
|
@@ -21,7 +21,7 @@ class HaCustomizeIcon extends PolymerElement {
|
|||||||
<ha-icon class="icon-image" icon="[[item.value]]"></ha-icon>
|
<ha-icon class="icon-image" icon="[[item.value]]"></ha-icon>
|
||||||
<paper-input
|
<paper-input
|
||||||
disabled="[[item.secondary]]"
|
disabled="[[item.secondary]]"
|
||||||
label="icon"
|
label="Icon"
|
||||||
value="{{item.value}}"
|
value="{{item.value}}"
|
||||||
>
|
>
|
||||||
</paper-input>
|
</paper-input>
|
||||||
|
@@ -2,6 +2,7 @@ import "@polymer/paper-input/paper-input";
|
|||||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||||
/* eslint-plugin-disable lit */
|
/* eslint-plugin-disable lit */
|
||||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||||
|
import { formatAttributeName } from "../../../../util/hass-attributes-util";
|
||||||
|
|
||||||
class HaCustomizeString extends PolymerElement {
|
class HaCustomizeString extends PolymerElement {
|
||||||
static get template() {
|
static get template() {
|
||||||
@@ -25,7 +26,10 @@ class HaCustomizeString extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLabel(item) {
|
getLabel(item) {
|
||||||
return item.description + (item.type === "json" ? " (JSON formatted)" : "");
|
return (
|
||||||
|
formatAttributeName(item.description) +
|
||||||
|
(item.type === "json" ? " (JSON formatted)" : "")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define("ha-customize-string", HaCustomizeString);
|
customElements.define("ha-customize-string", HaCustomizeString);
|
||||||
|
@@ -22,6 +22,8 @@ import { configSections } from "../ha-panel-config";
|
|||||||
import "./ha-config-navigation";
|
import "./ha-config-navigation";
|
||||||
import { mdiCloudLock } from "@mdi/js";
|
import { mdiCloudLock } from "@mdi/js";
|
||||||
|
|
||||||
|
const CONF_HAPPENING = new Date() < new Date("2020-12-13T23:00:00Z");
|
||||||
|
|
||||||
@customElement("ha-config-dashboard")
|
@customElement("ha-config-dashboard")
|
||||||
class HaConfigDashboard extends LitElement {
|
class HaConfigDashboard extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@@ -58,7 +60,7 @@ class HaConfigDashboard extends LitElement {
|
|||||||
{
|
{
|
||||||
component: "cloud",
|
component: "cloud",
|
||||||
path: "/config/cloud",
|
path: "/config/cloud",
|
||||||
translationKey: "ui.panel.config.cloud.caption",
|
name: "Home Assistant Cloud",
|
||||||
info: this.cloudStatus,
|
info: this.cloudStatus,
|
||||||
iconPath: mdiCloudLock,
|
iconPath: mdiCloudLock,
|
||||||
},
|
},
|
||||||
@@ -67,6 +69,19 @@ class HaConfigDashboard extends LitElement {
|
|||||||
</ha-card>
|
</ha-card>
|
||||||
`
|
`
|
||||||
: ""}
|
: ""}
|
||||||
|
${CONF_HAPPENING
|
||||||
|
? html`
|
||||||
|
<ha-card class="conf-card"
|
||||||
|
><a
|
||||||
|
target="_blank"
|
||||||
|
href="https://www.home-assistant.io/conference"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<img src="/static/images/conference.png" />
|
||||||
|
<div class="carrot"><ha-icon-next></ha-icon-next></div></a
|
||||||
|
></ha-card>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
${Object.values(configSections).map(
|
${Object.values(configSections).map(
|
||||||
(section) => html`
|
(section) => html`
|
||||||
<ha-card>
|
<ha-card>
|
||||||
@@ -165,6 +180,22 @@ class HaConfigDashboard extends LitElement {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
}
|
}
|
||||||
|
.conf-card {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.conf-card img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.conf-card .carrot {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
.promo-advanced {
|
.promo-advanced {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
property,
|
property,
|
||||||
TemplateResult,
|
TemplateResult,
|
||||||
} from "lit-element";
|
} from "lit-element";
|
||||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
import { canShowPage } from "../../../common/config/can_show_page";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import "../../../components/ha-icon-next";
|
import "../../../components/ha-icon-next";
|
||||||
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
|
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
|
||||||
@@ -27,10 +27,7 @@ class HaConfigNavigation extends LitElement {
|
|||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
${this.pages.map((page) =>
|
${this.pages.map((page) =>
|
||||||
(!page.component ||
|
canShowPage(this.hass, page)
|
||||||
page.core ||
|
|
||||||
isComponentLoaded(this.hass, page.component)) &&
|
|
||||||
(!page.advancedOnly || this.showAdvanced)
|
|
||||||
? html`
|
? html`
|
||||||
<a
|
<a
|
||||||
href=${`/config/${page.component}`}
|
href=${`/config/${page.component}`}
|
||||||
@@ -43,7 +40,8 @@ class HaConfigNavigation extends LitElement {
|
|||||||
slot="item-icon"
|
slot="item-icon"
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
<paper-item-body two-line>
|
<paper-item-body two-line>
|
||||||
${this.hass.localize(
|
${page.name ||
|
||||||
|
this.hass.localize(
|
||||||
page.translationKey ||
|
page.translationKey ||
|
||||||
`ui.panel.config.${page.component}.caption`
|
`ui.panel.config.${page.component}.caption`
|
||||||
)}
|
)}
|
||||||
|
@@ -45,6 +45,7 @@ import { configSections } from "../ha-panel-config";
|
|||||||
import "./device-detail/ha-device-entities-card";
|
import "./device-detail/ha-device-entities-card";
|
||||||
import "./device-detail/ha-device-info-card";
|
import "./device-detail/ha-device-info-card";
|
||||||
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
|
||||||
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
|
||||||
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
|
||||||
stateName?: string | null;
|
stateName?: string | null;
|
||||||
@@ -143,9 +144,10 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
if (!device) {
|
if (!device) {
|
||||||
return html`
|
return html`
|
||||||
<hass-error-screen
|
<hass-error-screen
|
||||||
error="${this.hass.localize(
|
.hass=${this.hass}
|
||||||
|
.error=${this.hass.localize(
|
||||||
"ui.panel.config.devices.device_not_found"
|
"ui.panel.config.devices.device_not_found"
|
||||||
)}"
|
)}
|
||||||
></hass-error-screen>
|
></hass-error-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -222,14 +224,19 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
`
|
`
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
<img
|
${
|
||||||
src="https://brands.home-assistant.io/${
|
integrations.length
|
||||||
integrations[0]
|
? html`
|
||||||
}/logo.png"
|
<img
|
||||||
referrerpolicy="no-referrer"
|
src=${brandsUrl(integrations[0], "logo")}
|
||||||
@load=${this._onImageLoad}
|
referrerpolicy="no-referrer"
|
||||||
@error=${this._onImageError}
|
@load=${this._onImageLoad}
|
||||||
/>
|
@error=${this._onImageError}
|
||||||
|
/>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column">
|
<div class="column">
|
||||||
@@ -587,7 +594,7 @@ export class HaConfigDevicePage extends LitElement {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.devices.confirm_rename_entity_ids_warning"
|
"ui.panel.config.devices.confirm_rename_entity_ids_warning"
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.rename"),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.no"),
|
||||||
warning: true,
|
warning: true,
|
||||||
}));
|
}));
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
ExtEntityRegistryEntry,
|
ExtEntityRegistryEntry,
|
||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
|
|
||||||
@@ -43,7 +44,27 @@ export class HaEntityRegistryBasicEditor extends LitElement {
|
|||||||
params.disabled_by = this._disabledBy;
|
params.disabled_by = this._disabledBy;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await updateEntityRegistryEntry(this.hass!, this._origEntityId, params);
|
const result = await updateEntityRegistryEntry(
|
||||||
|
this.hass!,
|
||||||
|
this._origEntityId,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
if (result.require_restart) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.enabled_restart_confirm"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (result.reload_delay) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.enabled_delay_confirm",
|
||||||
|
"delay",
|
||||||
|
result.reload_delay
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this._submitting = false;
|
this._submitting = false;
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,10 @@ import {
|
|||||||
removeEntityRegistryEntry,
|
removeEntityRegistryEntry,
|
||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
} from "../../../dialogs/generic/show-dialog-box";
|
||||||
import type { PolymerChangedEvent } from "../../../polymer-types";
|
import type { PolymerChangedEvent } from "../../../polymer-types";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../types";
|
import type { HomeAssistant } from "../../../types";
|
||||||
@@ -191,7 +194,27 @@ export class EntityRegistrySettings extends LitElement {
|
|||||||
params.disabled_by = this._disabledBy;
|
params.disabled_by = this._disabledBy;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await updateEntityRegistryEntry(this.hass!, this._origEntityId, params);
|
const result = await updateEntityRegistryEntry(
|
||||||
|
this.hass!,
|
||||||
|
this._origEntityId,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
if (result.require_restart) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.enabled_restart_confirm"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (result.reload_delay) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.enabled_delay_confirm",
|
||||||
|
"delay",
|
||||||
|
result.reload_delay
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
fireEvent(this as HTMLElement, "close-dialog");
|
fireEvent(this as HTMLElement, "close-dialog");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._error = err.message || "Unknown error";
|
this._error = err.message || "Unknown error";
|
||||||
|
@@ -46,7 +46,10 @@ import {
|
|||||||
updateEntityRegistryEntry,
|
updateEntityRegistryEntry,
|
||||||
} from "../../../data/entity_registry";
|
} from "../../../data/entity_registry";
|
||||||
import { domainToName } from "../../../data/integration";
|
import { domainToName } from "../../../data/integration";
|
||||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
import {
|
||||||
|
showAlertDialog,
|
||||||
|
showConfirmationDialog,
|
||||||
|
} from "../../../dialogs/generic/show-dialog-box";
|
||||||
import "../../../layouts/hass-loading-screen";
|
import "../../../layouts/hass-loading-screen";
|
||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
|
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
@@ -685,7 +688,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
this._selectedEntities = ev.detail.value;
|
this._selectedEntities = ev.detail.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _enableSelected() {
|
private async _enableSelected() {
|
||||||
showConfirmationDialog(this, {
|
showConfirmationDialog(this, {
|
||||||
title: this.hass.localize(
|
title: this.hass.localize(
|
||||||
"ui.panel.config.entities.picker.enable_selected.confirm_title",
|
"ui.panel.config.entities.picker.enable_selected.confirm_title",
|
||||||
@@ -695,15 +698,42 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.entities.picker.enable_selected.confirm_text"
|
"ui.panel.config.entities.picker.enable_selected.confirm_text"
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.enable"),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirm: () => {
|
confirm: async () => {
|
||||||
this._selectedEntities.forEach((entity) =>
|
let require_restart = false;
|
||||||
updateEntityRegistryEntry(this.hass, entity, {
|
let reload_delay = 0;
|
||||||
disabled_by: null,
|
await Promise.all(
|
||||||
|
this._selectedEntities.map(async (entity) => {
|
||||||
|
const result = await updateEntityRegistryEntry(this.hass, entity, {
|
||||||
|
disabled_by: null,
|
||||||
|
});
|
||||||
|
if (result.require_restart) {
|
||||||
|
require_restart = true;
|
||||||
|
}
|
||||||
|
if (result.reload_delay) {
|
||||||
|
reload_delay = Math.max(reload_delay, result.reload_delay);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
this._clearSelection();
|
this._clearSelection();
|
||||||
|
// If restart is required by any entity, show a dialog.
|
||||||
|
// Otherwise, show a dialog explaining that some patience is needed
|
||||||
|
if (require_restart) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.enabled_restart_confirm"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} else if (reload_delay) {
|
||||||
|
showAlertDialog(this, {
|
||||||
|
text: this.hass.localize(
|
||||||
|
"ui.dialogs.entity_registry.editor.enabled_delay_confirm",
|
||||||
|
"delay",
|
||||||
|
reload_delay
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -718,8 +748,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
text: this.hass.localize(
|
text: this.hass.localize(
|
||||||
"ui.panel.config.entities.picker.disable_selected.confirm_text"
|
"ui.panel.config.entities.picker.disable_selected.confirm_text"
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.disable"),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
this._selectedEntities.forEach((entity) =>
|
this._selectedEntities.forEach((entity) =>
|
||||||
updateEntityRegistryEntry(this.hass, entity, {
|
updateEntityRegistryEntry(this.hass, entity, {
|
||||||
@@ -758,8 +788,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
|||||||
"selected",
|
"selected",
|
||||||
this._selectedEntities.length
|
this._selectedEntities.length
|
||||||
),
|
),
|
||||||
confirmText: this.hass.localize("ui.common.yes"),
|
confirmText: this.hass.localize("ui.common.remove"),
|
||||||
dismissText: this.hass.localize("ui.common.no"),
|
dismissText: this.hass.localize("ui.common.cancel"),
|
||||||
confirm: () => {
|
confirm: () => {
|
||||||
removeableEntities.forEach((entity) =>
|
removeableEntities.forEach((entity) =>
|
||||||
removeEntityRegistryEntry(this.hass, entity)
|
removeEntityRegistryEntry(this.hass, entity)
|
||||||
|
@@ -160,9 +160,10 @@ export class HaConfigHelpers extends LitElement {
|
|||||||
>
|
>
|
||||||
<mwc-fab
|
<mwc-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
title="${this.hass.localize(
|
.label=${this.hass.localize(
|
||||||
"ui.panel.config.helpers.picker.add_helper"
|
"ui.panel.config.helpers.picker.add_helper"
|
||||||
)}"
|
)}
|
||||||
|
extended
|
||||||
@click=${this._createHelpler}
|
@click=${this._createHelpler}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
IntegrationManifest,
|
IntegrationManifest,
|
||||||
} from "../../../data/integration";
|
} from "../../../data/integration";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
|
||||||
@customElement("integrations-card")
|
@customElement("integrations-card")
|
||||||
class IntegrationsCard extends LitElement {
|
class IntegrationsCard extends LitElement {
|
||||||
@@ -50,7 +51,7 @@ class IntegrationsCard extends LitElement {
|
|||||||
<td>
|
<td>
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
src="https://brands.home-assistant.io/_/${domain}/icon.png"
|
src=${brandsUrl(domain, "icon", true)}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "@material/mwc-icon-button";
|
||||||
import "../../../components/ha-circular-progress";
|
import "../../../components/ha-circular-progress";
|
||||||
import { mdiContentCopy } from "@mdi/js";
|
import { mdiContentCopy } from "@mdi/js";
|
||||||
import {
|
import {
|
||||||
@@ -15,10 +17,13 @@ import "@polymer/paper-tooltip/paper-tooltip";
|
|||||||
import type { PaperTooltipElement } from "@polymer/paper-tooltip/paper-tooltip";
|
import type { PaperTooltipElement } from "@polymer/paper-tooltip/paper-tooltip";
|
||||||
import { domainToName } from "../../../data/integration";
|
import { domainToName } from "../../../data/integration";
|
||||||
import {
|
import {
|
||||||
fetchSystemHealthInfo,
|
subscribeSystemHealthInfo,
|
||||||
SystemHealthInfo,
|
SystemHealthInfo,
|
||||||
|
SystemCheckValueObject,
|
||||||
} from "../../../data/system_health";
|
} from "../../../data/system_health";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
|
import { formatDateTime } from "../../../common/datetime/format_date_time";
|
||||||
|
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||||
|
|
||||||
const sortKeys = (a: string, b: string) => {
|
const sortKeys = (a: string, b: string) => {
|
||||||
if (a === "homeassistant") {
|
if (a === "homeassistant") {
|
||||||
@@ -60,19 +65,77 @@ class SystemHealthCard extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
const domains = Object.keys(this._info).sort(sortKeys);
|
const domains = Object.keys(this._info).sort(sortKeys);
|
||||||
for (const domain of domains) {
|
for (const domain of domains) {
|
||||||
|
const domainInfo = this._info[domain];
|
||||||
const keys: TemplateResult[] = [];
|
const keys: TemplateResult[] = [];
|
||||||
|
|
||||||
for (const key of Object.keys(this._info[domain]).sort()) {
|
for (const key of Object.keys(domainInfo.info)) {
|
||||||
|
let value: unknown;
|
||||||
|
|
||||||
|
if (
|
||||||
|
domainInfo.info[key] &&
|
||||||
|
typeof domainInfo.info[key] === "object"
|
||||||
|
) {
|
||||||
|
const info = domainInfo.info[key] as SystemCheckValueObject;
|
||||||
|
|
||||||
|
if (info.type === "pending") {
|
||||||
|
value = html`
|
||||||
|
<ha-circular-progress active size="tiny"></ha-circular-progress>
|
||||||
|
`;
|
||||||
|
} else if (info.type === "failed") {
|
||||||
|
value = html`
|
||||||
|
<span class="error">${info.error}</span>${!info.more_info
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
–
|
||||||
|
<a
|
||||||
|
href=${info.more_info}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.info.system_health.more_info"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
} else if (info.type === "date") {
|
||||||
|
value = formatDateTime(new Date(info.value), this.hass.language);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = domainInfo.info[key];
|
||||||
|
}
|
||||||
|
|
||||||
keys.push(html`
|
keys.push(html`
|
||||||
<tr>
|
<tr>
|
||||||
<td>${key}</td>
|
<td>
|
||||||
<td>${this._info[domain][key]}</td>
|
${this.hass.localize(
|
||||||
|
`component.${domain}.system_health.info.${key}`
|
||||||
|
) || key}
|
||||||
|
</td>
|
||||||
|
<td>${value}</td>
|
||||||
</tr>
|
</tr>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
if (domain !== "homeassistant") {
|
if (domain !== "homeassistant") {
|
||||||
sections.push(
|
sections.push(
|
||||||
html`<h3>${domainToName(this.hass.localize, domain)}</h3>`
|
html`
|
||||||
|
<div class="card-header">
|
||||||
|
<h3>
|
||||||
|
${domainToName(this.hass.localize, domain)}
|
||||||
|
</h3>
|
||||||
|
${!domainInfo.manage_url
|
||||||
|
? ""
|
||||||
|
: html`
|
||||||
|
<a class="manage" href=${domainInfo.manage_url}>
|
||||||
|
<mwc-button>
|
||||||
|
${this.hass.localize(
|
||||||
|
"ui.panel.config.info.system_health.manage"
|
||||||
|
)}
|
||||||
|
</mwc-button>
|
||||||
|
</a>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
sections.push(html`
|
sections.push(html`
|
||||||
@@ -109,45 +172,65 @@ class SystemHealthCard extends LitElement {
|
|||||||
|
|
||||||
protected firstUpdated(changedProps) {
|
protected firstUpdated(changedProps) {
|
||||||
super.firstUpdated(changedProps);
|
super.firstUpdated(changedProps);
|
||||||
this._fetchInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _fetchInfo() {
|
this.hass!.loadBackendTranslation("system_health");
|
||||||
try {
|
|
||||||
if (!this.hass!.config.components.includes("system_health")) {
|
if (!this.hass!.config.components.includes("system_health")) {
|
||||||
throw new Error();
|
|
||||||
}
|
|
||||||
this._info = await fetchSystemHealthInfo(this.hass!);
|
|
||||||
} catch (err) {
|
|
||||||
this._info = {
|
this._info = {
|
||||||
system_health: {
|
system_health: {
|
||||||
error: this.hass.localize("ui.panel.config.info.system_health_error"),
|
info: {
|
||||||
|
error: this.hass.localize(
|
||||||
|
"ui.panel.config.info.system_health_error"
|
||||||
|
),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subscribeSystemHealthInfo(this.hass!, (info) => {
|
||||||
|
this._info = info;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _copyInfo(): void {
|
private _copyInfo(): void {
|
||||||
const copyElement = this.shadowRoot?.querySelector(
|
let haContent: string | undefined;
|
||||||
".card-content"
|
const domainParts: string[] = [];
|
||||||
) as HTMLElement;
|
|
||||||
|
|
||||||
// Add temporary heading (fixed in EN since usually executed to provide support data)
|
for (const domain of Object.keys(this._info!).sort(sortKeys)) {
|
||||||
const tempTitle = document.createElement("h3");
|
const domainInfo = this._info![domain];
|
||||||
tempTitle.innerText = "System Health";
|
const parts = [`${domainToName(this.hass.localize, domain)}\n`];
|
||||||
copyElement.insertBefore(tempTitle, copyElement.firstElementChild);
|
|
||||||
|
|
||||||
const selection = window.getSelection()!;
|
for (const key of Object.keys(domainInfo.info)) {
|
||||||
selection.removeAllRanges();
|
let value: unknown;
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNodeContents(copyElement);
|
|
||||||
selection.addRange(range);
|
|
||||||
|
|
||||||
document.execCommand("copy");
|
if (typeof domainInfo.info[key] === "object") {
|
||||||
window.getSelection()!.removeAllRanges();
|
const info = domainInfo.info[key] as SystemCheckValueObject;
|
||||||
|
|
||||||
// Remove temporary heading again
|
if (info.type === "pending") {
|
||||||
copyElement.removeChild(tempTitle);
|
value = "pending";
|
||||||
|
} else if (info.type === "failed") {
|
||||||
|
value = `failed to load: ${info.error}`;
|
||||||
|
} else if (info.type === "date") {
|
||||||
|
value = formatDateTime(new Date(info.value), this.hass.language);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = domainInfo.info[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.push(`${key}: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (domain === "homeassistant") {
|
||||||
|
haContent = parts.join("\n");
|
||||||
|
} else {
|
||||||
|
domainParts.push(parts.join("\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyToClipboard(
|
||||||
|
`System Health\n\n${haContent}\n\n${domainParts.join("\n\n")}`
|
||||||
|
);
|
||||||
|
|
||||||
this._toolTip!.show();
|
this._toolTip!.show();
|
||||||
setTimeout(() => this._toolTip?.hide(), 3000);
|
setTimeout(() => this._toolTip?.hide(), 3000);
|
||||||
@@ -160,7 +243,7 @@ class SystemHealthCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
td:first-child {
|
td:first-child {
|
||||||
width: 33%;
|
width: 45%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-container {
|
.loading-container {
|
||||||
@@ -172,6 +255,19 @@ class SystemHealthCard extends LitElement {
|
|||||||
.card-header {
|
.card-header {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.manage {
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,7 @@ import type {
|
|||||||
ConfigEntryUpdatedEvent,
|
ConfigEntryUpdatedEvent,
|
||||||
HaIntegrationCard,
|
HaIntegrationCard,
|
||||||
} from "./ha-integration-card";
|
} from "./ha-integration-card";
|
||||||
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
|
||||||
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
interface DataEntryFlowProgressExtended extends DataEntryFlowProgress {
|
||||||
localized_title?: string;
|
localized_title?: string;
|
||||||
@@ -330,7 +331,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img
|
<img
|
||||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
src=${brandsUrl(item.domain, "logo")}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
@error=${this._onImageError}
|
@error=${this._onImageError}
|
||||||
@load=${this._onImageLoad}
|
@load=${this._onImageLoad}
|
||||||
@@ -378,7 +379,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img
|
<img
|
||||||
src="https://brands.home-assistant.io/${flow.handler}/logo.png"
|
src=${brandsUrl(flow.handler, "logo")}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
@error=${this._onImageError}
|
@error=${this._onImageError}
|
||||||
@load=${this._onImageLoad}
|
@load=${this._onImageLoad}
|
||||||
@@ -475,8 +476,10 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
|||||||
</div>
|
</div>
|
||||||
<mwc-fab
|
<mwc-fab
|
||||||
slot="fab"
|
slot="fab"
|
||||||
aria-label=${this.hass.localize("ui.panel.config.integrations.new")}
|
.label=${this.hass.localize(
|
||||||
title=${this.hass.localize("ui.panel.config.integrations.new")}
|
"ui.panel.config.integrations.add_integration"
|
||||||
|
)}
|
||||||
|
extended
|
||||||
@click=${this._createFlow}
|
@click=${this._createFlow}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
@@ -31,6 +31,7 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
|||||||
import { mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
import { mdiDotsVertical, mdiOpenInNew } from "@mdi/js";
|
||||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||||
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event";
|
||||||
|
import { brandsUrl } from "../../../util/brands-url";
|
||||||
|
|
||||||
export interface ConfigEntryUpdatedEvent {
|
export interface ConfigEntryUpdatedEvent {
|
||||||
entry: ConfigEntry;
|
entry: ConfigEntry;
|
||||||
@@ -107,7 +108,7 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
<ha-card outlined class="group">
|
<ha-card outlined class="group">
|
||||||
<div class="group-header">
|
<div class="group-header">
|
||||||
<img
|
<img
|
||||||
src="https://brands.home-assistant.io/${this.domain}/icon.png"
|
src=${brandsUrl(this.domain, "icon")}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
@error=${this._onImageError}
|
@error=${this._onImageError}
|
||||||
@load=${this._onImageLoad}
|
@load=${this._onImageLoad}
|
||||||
@@ -157,7 +158,7 @@ export class HaIntegrationCard extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img
|
<img
|
||||||
src="https://brands.home-assistant.io/${item.domain}/logo.png"
|
src=${brandsUrl(item.domain, "logo")}
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
@error=${this._onImageError}
|
@error=${this._onImageError}
|
||||||
@load=${this._onImageLoad}
|
@load=${this._onImageLoad}
|
||||||
|
@@ -41,7 +41,7 @@ class HaPanelDevMqtt extends LitElement {
|
|||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage>
|
<hass-subpage .hass=${this.hass}>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<ha-card header="MQTT settings">
|
<ha-card header="MQTT settings">
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
|
@@ -58,9 +58,10 @@ class OZWConfigDashboard extends LitElement {
|
|||||||
|
|
||||||
if (this._instances.length === 0) {
|
if (this._instances.length === 0) {
|
||||||
return html`<hass-error-screen
|
return html`<hass-error-screen
|
||||||
.error="${this.hass.localize(
|
.hass=${this.hass}
|
||||||
|
.error=${this.hass.localize(
|
||||||
"ui.panel.config.ozw.select_instance.none_found"
|
"ui.panel.config.ozw.select_instance.none_found"
|
||||||
)}"
|
)}
|
||||||
></hass-error-screen>`;
|
></hass-error-screen>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,9 +68,10 @@ class OZWNodeConfig extends LitElement {
|
|||||||
if (this._error) {
|
if (this._error) {
|
||||||
return html`
|
return html`
|
||||||
<hass-error-screen
|
<hass-error-screen
|
||||||
.error="${this.hass.localize(
|
.hass=${this.hass}
|
||||||
|
.error=${this.hass.localize(
|
||||||
"ui.panel.config.ozw.node." + this._error
|
"ui.panel.config.ozw.node." + this._error
|
||||||
)}"
|
)}
|
||||||
></hass-error-screen>
|
></hass-error-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -64,7 +64,8 @@ class OZWNodeDashboard extends LitElement {
|
|||||||
if (this._not_found) {
|
if (this._not_found) {
|
||||||
return html`
|
return html`
|
||||||
<hass-error-screen
|
<hass-error-screen
|
||||||
.error="${this.hass.localize("ui.panel.config.ozw.node.not_found")}"
|
.hass=${this.hass}
|
||||||
|
.error=${this.hass.localize("ui.panel.config.ozw.node.not_found")}
|
||||||
></hass-error-screen>
|
></hass-error-screen>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@@ -67,6 +67,7 @@ export class ZHAAddGroupPage extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<hass-subpage
|
<hass-subpage
|
||||||
|
.hass=${this.hass}
|
||||||
.header=${this.hass.localize("ui.panel.config.zha.groups.create_group")}
|
.header=${this.hass.localize("ui.panel.config.zha.groups.create_group")}
|
||||||
>
|
>
|
||||||
<ha-config-section .isWide=${!this.narrow}>
|
<ha-config-section .isWide=${!this.narrow}>
|
||||||
|
@@ -84,7 +84,8 @@ class ZHAConfigDashboard extends LitElement {
|
|||||||
</ha-card>
|
</ha-card>
|
||||||
<a href="/config/zha/add" slot="fab">
|
<a href="/config/zha/add" slot="fab">
|
||||||
<mwc-fab
|
<mwc-fab
|
||||||
title=${this.hass.localize("ui.panel.config.zha.add_device")}
|
.label=${this.hass.localize("ui.panel.config.zha.add_device")}
|
||||||
|
extended
|
||||||
?rtl=${computeRTL(this.hass)}
|
?rtl=${computeRTL(this.hass)}
|
||||||
>
|
>
|
||||||
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user