Compare commits

..
44 Commits
Author SHA1 Message Date
Zack Arnett 2fef57f0b2 save it 2020-11-12 22:39:34 -06:00
Zack Arnett 9b279632f8 Updates 2020-11-09 11:37:23 -06:00
Zack BarettandGitHub 480e781364 Restore hui-card-options.ts 2020-11-03 23:04:19 -06:00
Zack BarettandGitHub adc10ff0c6 Restore hui-picture-header-footer.ts 2020-11-03 23:03:46 -06:00
Zack BarettandGitHub 57a718e409 Merge branch 'dev' into lit-grid-layout 2020-11-03 23:01:29 -06:00
Zack BarettandGitHub 23462bacfa Restore hui-weather-forecast-card.ts 2020-11-03 23:00:02 -06:00
Zack BarettandGitHub d99f8b0da8 Restore hui-vertical-stack-card.ts 2020-11-03 22:59:58 -06:00
Zack BarettandGitHub 991d1f0997 Restore hui-thermostat-card.ts 2020-11-03 22:59:54 -06:00
Zack BarettandGitHub 54e99357b5 Restore hui-shopping-list-card.ts 2020-11-03 22:59:50 -06:00
Zack BarettandGitHub d263b5550a Restore hui-plant-status-card.ts 2020-11-03 22:59:46 -06:00
Zack BarettandGitHub a2b6ce3437 Restore hui-media-control-card.ts 2020-11-03 22:59:43 -06:00
Zack BarettandGitHub 9458f7ae62 Restore hui-markdown-card.ts 2020-11-03 22:59:40 -06:00
Zack BarettandGitHub 4d2ae36b0d Restore hui-map-card.ts 2020-11-03 22:59:35 -06:00
Zack BarettandGitHub ebf0050e81 Restore hui-light-card.ts 2020-11-03 22:59:31 -06:00
Zack BarettandGitHub 3d0f492d62 Restore hui-iframe-card.ts 2020-11-03 22:59:27 -06:00
Zack BarettandGitHub f5d3237b06 Restore hui-humidifier-card.ts 2020-11-03 22:59:23 -06:00
Zack BarettandGitHub 07671ba1d4 Restore hui-horizontal-stack-card.ts 2020-11-03 22:59:20 -06:00
Zack BarettandGitHub 354d74ce1d Restore hui-history-graph-card.ts 2020-11-03 22:59:17 -06:00
Zack BarettandGitHub e892d14af0 Restore hui-glance-card.ts 2020-11-03 22:59:13 -06:00
Zack BarettandGitHub 8d034fb7e7 Restore hui-gauge-card.ts 2020-11-03 22:59:10 -06:00
Zack BarettandGitHub 249456e6a0 Restore hui-entity-filter-card.ts 2020-11-03 22:59:07 -06:00
Zack BarettandGitHub d3a6f31310 Restore hui-entity-card.ts 2020-11-03 22:59:03 -06:00
Zack BarettandGitHub 4c31aead8b Restore hui-entities-card.ts 2020-11-03 22:59:00 -06:00
Zack BarettandGitHub 3caf91c5d3 Restore hui-button-card.ts 2020-11-03 22:58:56 -06:00
Zack BarettandGitHub 9c3e754d53 Restore hui-alarm-panel-card.ts 2020-11-03 22:58:53 -06:00
Zack BarettandGitHub f48765c2b6 Restore ha-card.ts 2020-11-03 22:58:43 -06:00
Zack Arnett 236f26e652 SOME CHANGES 2020-11-03 22:57:54 -06:00
Zack Arnett 0fed96aba3 idk what I just did. I never commit just work 2020-10-09 16:33:22 -05:00
Zack Arnett 70b77833f1 add grid card options 2020-10-04 16:49:10 -05:00
Zack Arnett 307694a820 updates 2020-10-01 11:17:35 -05:00
Zack BarettandGitHub 402391c3e1 Restore hui-masonry-view.ts 2020-09-30 20:57:06 -05:00
Zack BarettandGitHub 81b5866e4d Restore dialog-entity-editor.ts 2020-09-30 20:55:52 -05:00
Zack BarettandGitHub 09985bf5f7 Restore ha-device-actions-ozw.ts 2020-09-30 20:55:47 -05:00
Zack BarettandGitHub 71d12a9e7a Restore ha-automation-action-wait_template.ts 2020-09-30 20:55:41 -05:00
Zack BarettandGitHub 5fd733b6c9 Restore ha-more-info-logbook.ts 2020-09-30 20:55:34 -05:00
Zack Arnett bc3f827b4a Merge branch 'dev' of https://github.com/home-assistant/frontend into lit-grid-layout 2020-09-30 20:54:25 -05:00
Zack Arnett a807a182e4 fix card options 2020-09-30 20:53:40 -05:00
Zack BarettandGitHub 0a1fb843ca Restore hui-view.ts 2020-09-30 20:50:04 -05:00
Zack Arnett fceb1568f5 add Grid view 2020-09-30 20:49:38 -05:00
Zack BarettandGitHub 74e93bbefc Restore ha-panel-lovelace.ts 2020-09-30 20:31:42 -05:00
Zack BarettandGitHub 5c0f4b564b Restore lovelace.ts 2020-09-30 20:31:11 -05:00
Zack BarettandGitHub 76734f7a0b Restore types.ts 2020-09-30 20:29:07 -05:00
Zack ArnettandGitHub 440d10e4cd Merge branch 'dev' into lit-grid-layout 2020-08-04 09:17:23 -05:00
Zack Arnett a901072695 First changes, alot of work to do 2020-08-04 08:53:07 -05:00
206 changed files with 3187 additions and 8743 deletions
+3 -3
View File
@@ -18,8 +18,8 @@
<!--
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
or resolves a feature request, be sure to link to that issue or discussion
in the additional information section.
or resolves a feature request, be sure to link to that issue in the
additional information section.
-->
## Type of change
@@ -56,7 +56,7 @@
-->
- This PR fixes or closes issue: fixes #
- This PR is related to issue or discussion:
- This PR is related to issue:
- Link to documentation pull request:
## Checklist
+27
View File
@@ -0,0 +1,27 @@
# 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
View File
@@ -0,0 +1,56 @@
# 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
View File
@@ -1,20 +0,0 @@
name: Lock
# yamllint disable-line rule:truthy
on:
schedule:
- cron: "0 * * * *"
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/[email protected]
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
View File
@@ -1,42 +0,0 @@
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/[email protected]
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.
-2
View File
@@ -23,8 +23,6 @@ dist
# vscode
.vscode/*
!.vscode/extensions.json
!.vscode/launch.json
!.vscode/tasks.json
# Cast dev settings
src/cast/dev_const.ts
-44
View File
@@ -1,44 +0,0 @@
{
// https://github.com/microsoft/vscode-js-debug/blob/master/OPTIONS.md
"configurations": [
{
"name": "Debug Frontend",
"request": "launch",
"type": "pwa-chrome",
"url": "http://localhost:8123/",
"webRoot": "${workspaceFolder}/hass_frontend",
"disableNetworkCache": true,
"preLaunchTask": "Develop Frontend",
"outFiles": [
"${workspaceFolder}/hass_frontend/frontend_latest/*.js"
]
},
{
"name": "Debug Gallery",
"request": "launch",
"type": "pwa-chrome",
"url": "http://localhost:8100/",
"webRoot": "${workspaceFolder}/gallery/dist",
"disableNetworkCache": true,
"preLaunchTask": "Develop Gallery"
},
{
"name": "Debug Demo",
"request": "launch",
"type": "pwa-chrome",
"url": "http://localhost:8090/",
"webRoot": "${workspaceFolder}/demo/dist",
"disableNetworkCache": true,
"preLaunchTask": "Develop Demo"
},
{
"name": "Debug Cast",
"request": "launch",
"type": "pwa-chrome",
"url": "http://localhost:8080/",
"webRoot": "${workspaceFolder}/cast/dist",
"disableNetworkCache": true,
"preLaunchTask": "Develop Cast"
},
]
}
-137
View File
@@ -1,137 +0,0 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Develop Frontend",
"type": "gulp",
"task": "develop-app",
// Sync changes here to other tasks until issue resolved
// https://github.com/Microsoft/vscode/issues/61497
"problemMatcher": {
"owner": "ha-build",
"source": "ha-build",
"fileLocation": "absolute",
"severity": "error",
"pattern": [
{
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
"severity": 1,
"file": 2,
"message": 3,
"line": 4,
"column": 5
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Changes detected. Starting compilation",
"endsPattern": "Build done @"
}
},
"isBackground": true,
"group": {
"kind": "build",
"isDefault": true
},
"runOptions": {
"instanceLimit": 1
}
},
{
"label": "Develop Gallery",
"type": "gulp",
"task": "develop-gallery",
"problemMatcher": {
"owner": "ha-build",
"source": "ha-build",
"fileLocation": "absolute",
"severity": "error",
"pattern": [
{
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
"severity": 1,
"file": 2,
"message": 3,
"line": 4,
"column": 5
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Changes detected. Starting compilation",
"endsPattern": "Build done @"
}
},
"isBackground": true,
"group": "build",
"runOptions": {
"instanceLimit": 1
}
},
{
"label": "Develop Demo",
"type": "gulp",
"task": "develop-demo",
"problemMatcher": {
"owner": "ha-build",
"source": "ha-build",
"fileLocation": "absolute",
"severity": "error",
"pattern": [
{
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
"severity": 1,
"file": 2,
"message": 3,
"line": 4,
"column": 5
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Changes detected. Starting compilation",
"endsPattern": "Build done @"
}
},
"isBackground": true,
"group": "build",
"runOptions": {
"instanceLimit": 1
}
},
{
"label": "Develop Cast",
"type": "gulp",
"task": "develop-cast",
"problemMatcher": {
"owner": "ha-build",
"source": "ha-build",
"fileLocation": "absolute",
"severity": "error",
"pattern": [
{
"regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)",
"severity": 1,
"file": 2,
"message": 3,
"line": 4,
"column": 5
}
],
"background": {
"activeOnStart": true,
"beginsPattern": "Changes detected. Starting compilation",
"endsPattern": "Build done @"
}
},
"isBackground": true,
"group": "build",
"runOptions": {
"instanceLimit": 1
}
},
]
}
-27
View File
@@ -1,27 +0,0 @@
# Vite
Vite is a new type of compiler that compiles each file as they come in.
## Running Vite
- Checkout this branch
- `yarn`
- Run `script/develop` until it finishes first webpack build. Then turn it off. We use this right now to prepare the static files + auth/onboarding pages.
- Update `hass_frontend/index.html`, find where we import the scripts and replace with:
```html
<script>
// Define in vite config doesn't work.
window.__DEV__ = true;
window.__DEMO__ = false;
window.__BACKWARDS_COMPAT__ = false;
window.__BUILD__ = "latest";
window.__VERSION__ = "dev";
// Temporary to stop an error
document.adoptedStyleSheets = [];
// Load scripts from Vite dev server
import("http://localhost:3000/src/entrypoints/core.ts");
import("http://localhost:3000/src/entrypoints/app.ts");
```
If Vite transforms would work correctly, we would just have to drop the "use-credentials" part in dev and update the import URLs to import from Vite dev server.
- Start vite `vite serve -c build-scripts/vite/vite.config.ts`
- Open Home Assistant as usual.
+5 -16
View File
@@ -18,14 +18,6 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }),
];
/**
* @param {{
* compiler: import("webpack").Compiler,
* contentBase: string,
* port: number,
* listenHost?: string
* }}
*/
const runDevServer = ({
compiler,
contentBase,
@@ -41,10 +33,7 @@ const runDevServer = ({
throw err;
}
// Server listening
log(
"[webpack-dev-server]",
`Project is running at http://localhost:${port}`
);
log("[webpack-dev-server]", `http://localhost:${port}`);
});
const handler = (done) => (err, stats) => {
@@ -56,12 +45,12 @@ const handler = (done) => (err, stats) => {
return;
}
if (stats.hasErrors() || stats.hasWarnings()) {
console.log(stats.toString("minimal"));
}
log(`Build done @ ${new Date().toLocaleTimeString()}`);
if (stats.hasErrors() || stats.hasWarnings()) {
log.warn(stats.toString("minimal"));
}
if (done) {
done();
}
+1 -1
View File
@@ -1,4 +1,4 @@
const path = require("path");
var path = require("path");
module.exports = {
polymer_dir: path.resolve(__dirname, ".."),
-61
View File
@@ -1,61 +0,0 @@
import * as path from "path";
import * as vite from "vite";
// https://github.com/vitejs/vite/blob/master/src/node/config.ts
const ignore = new Set(["/src/resources/compatibility.ts"]);
const conf: vite.ServerConfig = {
root: path.resolve(__dirname, "../.."),
optimizeDeps: {
// We don't automatically optimize dependencies because
// that causes duplicate imports of custom elements
auto: false,
},
resolvers: [
// This resolver is meant to filter out files that we don't
// need in latest build, like compatibility.
// But resolving it to an empty file doesn't yield expected
// results.
// {
// requestToFile(publicPath: string, root: string) {
// console.log("requestToFile", {
// publicPath,
// root,
// match: ignore.has(publicPath),
// resolved:
// ignore.has(publicPath) &&
// path.resolve(conf.root!, "src/util/empty.js"),
// });
// if (ignore.has(publicPath)) {
// return path.resolve(conf.root!, "src/util/empty.js");
// }
// return undefined;
// },
// fileToRequest(filePath: string, root: string) {
// if (!filePath.endsWith("/src/util/empty.js")) {
// return undefined;
// }
// console.log("fileToRequest", {
// filePath,
// root,
// match: filePath.endsWith("/src/util/empty.js"),
// });
// return "/src/util/empty.js";
// },
// },
],
// These don't seem to be picked up. Workaround is to manually
// add them to hass_frontend/index.html for now.
define: {
__DEV__: true,
__BUILD__: "latest",
__VERSION__: "dev",
__DEMO__: false,
__BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/",
},
cors: true,
};
console.log(conf);
export default conf;
+4 -17
View File
@@ -4,21 +4,6 @@ const TerserPlugin = require("terser-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const bundle = require("./bundle");
const log = require("fancy-log");
class LogStartCompilePlugin {
ignoredFirst = false;
apply(compiler) {
compiler.hooks.beforeCompile.tap("LogStartCompilePlugin", () => {
if (!this.ignoredFirst) {
this.ignoredFirst = true;
return;
}
log("Changes detected. Starting compilation");
});
}
}
const createWebpackConfig = ({
entry,
@@ -50,6 +35,9 @@ const createWebpackConfig = ({
loader: "babel-loader",
options: bundle.babelOptions({ latestBuild }),
},
resolve: {
fullySpecified: false,
},
},
{
test: /\.css$/,
@@ -119,8 +107,7 @@ const createWebpackConfig = ({
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean),
],
resolve: {
extensions: [".ts", ".js", ".json"],
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

+9 -90
View File
@@ -6,9 +6,7 @@ export const createMediaPlayerEntities = () => [
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
media_artist: "Technohead",
// Pause + Seek + Volume Set + Volume Mute + Previous Track + Next Track + Play Media +
// Select Source + Stop + Clear + Play + Shuffle Set + Browse Media
supported_features: 195135,
supported_features: 64063,
entity_picture: "/images/album_cover_2.jpg",
media_duration: 300,
media_position: 50,
@@ -16,15 +14,12 @@ export const createMediaPlayerEntities = () => [
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
}),
getEntity("media_player", "music_playing", "playing", {
friendly_name: "Playing The Music",
media_content_type: "music",
media_title: "I Wanna Be A Hippy (Flamman & Abraxas Radio Mix)",
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,
entity_picture: "/images/album_cover.jpg",
media_duration: 300,
@@ -33,7 +28,6 @@ export const createMediaPlayerEntities = () => [
// 23 seconds in
new Date().getTime() - 23000
).toISOString(),
volume_level: 0.5,
}),
getEntity("media_player", "stream_playing", "playing", {
friendly_name: "Playing the Stream",
@@ -41,125 +35,50 @@ export const createMediaPlayerEntities = () => [
media_title: "Epic sax guy 10 hours",
app_name: "YouTube",
entity_picture: "/images/frenck.jpg",
// Pause + Next Track + Play + Browse Media
supported_features: 147489,
supported_features: 33,
}),
getEntity("media_player", "stream_paused", "paused", {
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",
getEntity("media_player", "living_room", "playing", {
friendly_name: "Pause, No skip, tvshow",
media_content_type: "tvshow",
media_title: "Chapter 1",
media_series_title: "House of Cards",
app_name: "Netflix",
entity_picture: "/images/netflix.jpg",
// Pause
supported_features: 1,
}),
getEntity("media_player", "sonos_idle", "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,
volume_level: 0.33,
is_volume_muted: true,
}),
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", {
getEntity("media_player", "theater", "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,
}),
getEntity("media_player", "android_cast", "playing", {
friendly_name: "Casting App (no supported features)",
friendly_name: "Casting App",
media_title: "Android Screen Casting",
app_name: "Screen Mirroring",
}),
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,
// supported_features: 21437,
}),
getEntity("media_player", "unavailable", "unavailable", {
friendly_name: "Player Unavailable",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
supported_features: 21437,
}),
getEntity("media_player", "unknown", "unknown", {
friendly_name: "Player Unknown",
// Pause + Volume Set + Volume Mute + Previous Track + Next Track +
// Play Media + Stop + Play
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", {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
volume_level: 0.63,
is_volume_muted: false,
source: "TV",
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
friendly_name: "Receiver",
supported_features: 84364,
}),
getEntity("media_player", "receiver_off", "off", {
source_list: ["AirPlay", "Blu-Ray", "TV", "USB", "iPod (USB)"],
friendly_name: "Receiver (selectable sources)",
// Volume Set + Volume Mute + On + Off + Select Source + Play + Sound Mode
friendly_name: "Receiver",
supported_features: 84364,
}),
];
@@ -7,61 +7,40 @@ import { createMediaPlayerEntities } from "../data/media_players";
const CONFIGS = [
{
heading: "Paused Music",
heading: "Paused music",
config: `
- type: media-control
entity: media_player.music_paused
`,
},
{
heading: "Playing Music",
heading: "Playing music",
config: `
- type: media-control
entity: media_player.music_playing
`,
},
{
heading: "Playing Stream",
heading: "Playing stream",
config: `
- type: media-control
entity: media_player.stream_playing
`,
},
{
heading: "Paused Stream",
heading: "Pause, No skip, tvshow",
config: `
- type: media-control
entity: media_player.stream_paused
entity: media_player.living_room
`,
},
{
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",
heading: "Screen casting",
config: `
- type: media-control
entity: media_player.android_cast
`,
},
{
heading: "Digital Picture Frame",
config: `
- type: media-control
entity: media_player.image_display
`,
},
{
heading: "Sonos Idle",
config: `
@@ -69,53 +48,11 @@ const CONFIGS = [
entity: media_player.sonos_idle
`,
},
{
heading: "Idle waiting for Browse Media",
config: `
- type: media-control
entity: media_player.idle_browse_media
`,
},
{
heading: "Player Off",
config: `
- type: media-control
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
entity: media_player.theater
`,
},
{
@@ -133,14 +70,14 @@ const CONFIGS = [
`,
},
{
heading: "Receiver On (selectable sources)",
heading: "Receiver On",
config: `
- type: media-control
entity: media_player.receiver_on
`,
},
{
heading: "Receiver Off (selectable sources)",
heading: "Receiver Off",
config: `
- type: media-control
entity: media_player.receiver_off
@@ -12,45 +12,23 @@ const CONFIGS = [
- type: entities
entities:
- entity: media_player.music_paused
name: Paused Music
name: Paused music
- entity: media_player.music_playing
name: Playing Music
name: Playing music
- entity: media_player.stream_playing
name: Playing Stream
- entity: media_player.stream_paused
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
name: Paused, no play
- entity: media_player.living_room
name: Pause, No skip, tvshow
- entity: media_player.android_cast
name: Screen casting
- entity: media_player.image_display
name: Digital Picture Frame
- entity: media_player.sonos_idle
name: Sonos Idle
- entity: media_player.idle_browse_media
name: Idle waiting for Browse Media
- entity: media_player.theater_off
name: Chromcast Idle
- entity: media_player.theater
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
name: Player Unavailable
- entity: media_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)
`,
},
];
+6 -33
View File
@@ -13,10 +13,7 @@ import {
fetchHassioAddonInfo,
HassioAddonDetails,
} from "../../../src/data/hassio/addon";
import {
createHassioSession,
validateHassioSession,
} from "../../../src/data/hassio/ingress";
import { createHassioSession } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types";
@@ -38,17 +35,6 @@ class HassioIngressView extends LitElement {
@property({ type: Boolean })
public narrow = false;
private _sessionKeepAlive?: number;
public disconnectedCallback() {
super.disconnectedCallback();
if (this._sessionKeepAlive) {
clearInterval(this._sessionKeepAlive);
this._sessionKeepAlive = undefined;
}
}
protected render(): TemplateResult {
if (!this._addon) {
return html` <hass-loading-screen></hass-loading-screen> `;
@@ -58,7 +44,6 @@ class HassioIngressView extends LitElement {
if (!this.ingressPanel) {
return html`<hass-subpage
.hass=${this.hass}
.header=${this._addon.name}
.narrow=${this.narrow}
>
@@ -98,7 +83,10 @@ class HassioIngressView extends LitElement {
}
private async _fetchData(addonSlug: string) {
const createSessionPromise = createHassioSession(this.hass);
const createSessionPromise = createHassioSession(this.hass).then(
() => true,
() => false
);
let addon;
@@ -131,11 +119,7 @@ class HassioIngressView extends LitElement {
return;
}
let session;
try {
session = await createSessionPromise;
} catch (err) {
if (!(await createSessionPromise)) {
await showAlertDialog(this, {
text: "Unable to create an Ingress session",
title: addon.name,
@@ -144,17 +128,6 @@ class HassioIngressView extends LitElement {
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;
}
+3 -2
View File
@@ -90,6 +90,7 @@
"codemirror": "^5.49.0",
"comlink": "^4.3.0",
"core-js": "^3.6.5",
"cpx": "^1.5.0",
"cropperjs": "^1.5.7",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
@@ -104,6 +105,7 @@
"leaflet": "^1.4.0",
"leaflet-draw": "^1.0.4",
"lit-element": "^2.4.0",
"lit-grid-layout": "^1.1.11",
"lit-html": "^1.3.0",
"lit-virtualizer": "^0.4.2",
"marked": "^1.1.1",
@@ -120,6 +122,7 @@
"superstruct": "^0.10.12",
"tinykeys": "^1.1.1",
"unfetch": "^4.1.0",
"uuid": "^8.3.0",
"vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2",
@@ -161,7 +164,6 @@
"@typescript-eslint/parser": "^4.4.0",
"babel-loader": "^8.1.0",
"chai": "^4.2.0",
"cpx": "^1.5.0",
"del": "^4.0.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-typescript": "^7.2.1",
@@ -209,7 +211,6 @@
"typescript": "^4.0.3",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"vite": "^1.0.0-rc.9",
"webpack": "5.1.3",
"webpack-cli": "4.1.0",
"webpack-dev-server": "^3.11.0",
+1
View File
@@ -13,6 +13,7 @@
"src/panels/iframe/ha-panel-iframe.js",
"src/panels/logbook/ha-panel-logbook.js",
"src/panels/map/ha-panel-map.js",
"src/panels/shopping-list/ha-panel-shopping-list.js",
"src/panels/mailbox/ha-panel-mailbox.js",
"hassio/src/entrypoint.js"
],
Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

+1 -1
View File
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20201111.0",
version="20201021.1",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
-18
View File
@@ -1,18 +0,0 @@
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);
-22
View File
@@ -78,25 +78,3 @@ export const coverIcon = (state?: string, stateObj?: HassEntity): string => {
return "hass:window-open";
}
};
export const computeOpenIcon = (stateObj: HassEntity): string => {
switch (stateObj.attributes.device_class) {
case "awning":
case "door":
case "gate":
return "hass:arrow-expand-horizontal";
default:
return "hass:arrow-up";
}
};
export const computeCloseIcon = (stateObj: HassEntity): string => {
switch (stateObj.attributes.device_class) {
case "awning":
case "door":
case "gate":
return "hass:arrow-collapse-horizontal";
default:
return "hass:arrow-down";
}
};
-8
View File
@@ -1,8 +0,0 @@
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);
};
+31 -33
View File
@@ -1,5 +1,5 @@
import "@material/mwc-icon-button/mwc-icon-button";
import "@material/mwc-button/mwc-button";
import "../ha-icon-button";
import "@polymer/paper-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-item/paper-item-body";
@@ -38,8 +38,6 @@ import { SubscribeMixin } from "../../mixins/subscribe-mixin";
import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "./ha-devices-picker";
import "../ha-svg-icon";
import { mdiClose, mdiMenuDown, mdiMenuUp } from "@mdi/js";
interface DevicesByArea {
[areaId: string]: AreaDevices;
@@ -64,7 +62,7 @@ const rowRenderer = (
margin: -10px 0;
padding: 0;
}
mwc-icon-button {
ha-icon-button {
float: right;
}
.devices {
@@ -326,34 +324,36 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
autocorrect="off"
spellcheck="false"
>
<div class="suffix" slot="suffix">
${this.value
? html`<mwc-icon-button
class="clear-button"
.label=${this.hass.localize(
${this.value
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.clear"
)}
slot="suffix"
class="clear-button"
icon="hass:close"
@click=${this._clearValue}
no-ripple
>
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> `
: ""}
${areas.length > 0
? html`
<mwc-icon-button
.label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
class="toggle-button"
>
<ha-svg-icon
.path=${this._opened ? mdiMenuUp : mdiMenuDown}
></ha-svg-icon>
</mwc-icon-button>
`
: ""}
</div>
Clear
</ha-icon-button>
`
: ""}
${areas.length > 0
? html`
<ha-icon-button
aria-label=${this.hass.localize(
"ui.components.device-picker.show_devices"
)}
slot="suffix"
class="toggle-button"
.icon=${this._opened ? "hass:menu-up" : "hass:menu-down"}
>
Toggle
</ha-icon-button>
`
: ""}
</paper-input>
</vaadin-combo-box-light>
<mwc-button @click=${this._switchPicker}
@@ -409,12 +409,10 @@ export class HaAreaDevicesPicker extends SubscribeMixin(LitElement) {
static get styles(): CSSResult {
return css`
.suffix {
display: flex;
}
mwc-icon-button {
--mdc-icon-button-size: 24px;
padding: 0px 2px;
paper-input > ha-icon-button {
width: 24px;
height: 24px;
padding: 2px;
color: var(--secondary-text-color);
}
[hidden] {
@@ -19,7 +19,6 @@ import { PolymerChangedEvent } from "../../polymer-types";
import { HomeAssistant } from "../../types";
import "../ha-svg-icon";
import "./state-badge";
import { formatAttributeName } from "../../util/hass-attributes-util";
import "@material/mwc-icon-button/mwc-icon-button";
export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
@@ -36,9 +35,7 @@ const rowRenderer = (root: HTMLElement, _owner, model: { item: string }) => {
<paper-item></paper-item>
`;
}
root.querySelector("paper-item")!.textContent = formatAttributeName(
model.item
);
root.querySelector("paper-item")!.textContent = model.item;
};
@customElement("ha-entity-attribute-picker")
@@ -95,7 +92,7 @@ class HaEntityAttributePicker extends LitElement {
this.hass.localize(
"ui.components.entity.entity-attribute-picker.attribute"
)}
.value=${this._value ? formatAttributeName(this._value) : ""}
.value=${this._value}
.disabled=${this.disabled || !this.entityId}
class="input"
autocapitalize="none"
@@ -143,7 +140,7 @@ class HaEntityAttributePicker extends LitElement {
}
private get _value() {
return this.value;
return this.value || "";
}
private _openedChanged(ev: PolymerChangedEvent<boolean>) {
+152
View File
@@ -0,0 +1,152 @@
import { html } from "@polymer/polymer/lib/utils/html-tag";
import "@polymer/paper-tooltip/paper-tooltip";
/* eslint-plugin-disable lit */
import LocalizeMixin from "../../mixins/localize-mixin";
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeRTL } from "../../common/util/compute_rtl";
import "../ha-relative-time";
import "./state-badge";
class StateInfo extends LocalizeMixin(PolymerElement) {
static get template() {
return html`
${this.styleTemplate} ${this.stateBadgeTemplate} ${this.infoTemplate}
`;
}
static get styleTemplate() {
return html`
<style>
:host {
@apply --paper-font-body1;
min-width: 120px;
white-space: nowrap;
}
state-badge {
float: left;
}
:host([rtl]) state-badge {
float: right;
}
.info {
margin-left: 56px;
}
:host([rtl]) .info {
margin-right: 56px;
margin-left: 0;
text-align: right;
}
.name {
@apply --paper-font-common-nowrap;
color: var(--primary-text-color);
line-height: 40px;
}
.name[in-dialog],
:host([secondary-line]) .name {
line-height: 20px;
}
.time-ago,
.extra-info,
.extra-info > * {
@apply --paper-font-common-nowrap;
color: var(--secondary-text-color);
}
.row {
display: flex;
flex-direction: row;
flex-wrap: no-wrap;
width: 100%;
justify-content: space-between;
margin: 0 2px 4px 0;
}
.row:last-child {
margin-bottom: 0px;
}
</style>
`;
}
static get stateBadgeTemplate() {
return html` <state-badge state-obj="[[stateObj]]"></state-badge> `;
}
static get infoTemplate() {
return html`
<div class="info">
<div class="name" in-dialog$="[[inDialog]]">
[[computeStateName(stateObj)]]
</div>
<template is="dom-if" if="[[inDialog]]">
<div class="time-ago">
<ha-relative-time
id="last_changed"
hass="[[hass]]"
datetime="[[stateObj.last_changed]]"
></ha-relative-time>
<paper-tooltip animation-delay="0" for="last_changed">
<div>
<div class="row">
<span class="column-name">
[[localize('ui.dialogs.more_info_control.last_changed')]]:
</span>
<ha-relative-time
hass="[[hass]]"
datetime="[[stateObj.last_changed]]"
></ha-relative-time>
</div>
<div class="row">
<span>
[[localize('ui.dialogs.more_info_control.last_updated')]]:
</span>
<ha-relative-time
hass="[[hass]]"
datetime="[[stateObj.last_updated]]"
></ha-relative-time>
</div>
</div>
</paper-tooltip>
</div>
</template>
<template is="dom-if" if="[[!inDialog]]">
<div class="extra-info"><slot> </slot></div>
</template>
</div>
`;
}
static get properties() {
return {
hass: Object,
stateObj: Object,
inDialog: {
type: Boolean,
value: () => false,
},
rtl: {
type: Boolean,
reflectToAttribute: true,
computed: "computeRTL(hass)",
},
};
}
computeStateName(stateObj) {
return computeStateName(stateObj);
}
computeRTL(hass) {
return computeRTL(hass);
}
}
customElements.define("state-info", StateInfo);
-158
View File
@@ -1,158 +0,0 @@
import "@polymer/paper-tooltip/paper-tooltip";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import type { HassEntity } from "home-assistant-js-websocket";
import { computeStateName } from "../../common/entity/compute_state_name";
import { computeRTL } from "../../common/util/compute_rtl";
import type { HomeAssistant } from "../../types";
import "../ha-relative-time";
import "./state-badge";
@customElement("state-info")
class StateInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public stateObj?: HassEntity;
@property({ type: Boolean }) public inDialog = false;
// property used only in css
@property({ type: Boolean, reflect: true }) public rtl = false;
protected render(): TemplateResult {
if (!this.hass || !this.stateObj) {
return html``;
}
return html`<state-badge
.stateObj=${this.stateObj}
.stateColor=${true}
></state-badge>
<div class="info">
<div class="name" .inDialog=${this.inDialog}>
${computeStateName(this.stateObj)}
</div>
${this.inDialog
? html`<div class="time-ago">
<ha-relative-time
id="last_changed"
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
></ha-relative-time>
<paper-tooltip animation-delay="0" for="last_changed">
<div>
<div class="row">
<span class="column-name">
${this.hass.localize(
"ui.dialogs.more_info_control.last_changed"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_changed}
></ha-relative-time>
</div>
<div class="row">
<span>
${this.hass.localize(
"ui.dialogs.more_info_control.last_updated"
)}:
</span>
<ha-relative-time
.hass=${this.hass}
.datetime=${this.stateObj.last_updated}
></ha-relative-time>
</div>
</div>
</paper-tooltip>
</div>`
: html`<div class="extra-info"><slot> </slot></div>`}
</div>`;
}
protected updated(changedProps) {
super.updated(changedProps);
if (!changedProps.has("hass")) {
return;
}
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
if (!oldHass || oldHass.language !== this.hass.language) {
this.rtl = computeRTL(this.hass);
}
}
static get styles(): CSSResult {
return css`
:host {
@apply --paper-font-body1;
min-width: 120px;
white-space: nowrap;
}
state-badge {
float: left;
}
:host([rtl]) state-badge {
float: right;
}
.info {
margin-left: 56px;
}
:host([rtl]) .info {
margin-right: 56px;
margin-left: 0;
text-align: right;
}
.name {
@apply --paper-font-common-nowrap;
color: var(--primary-text-color);
line-height: 40px;
}
.name[in-dialog],
:host([secondary-line]) .name {
line-height: 20px;
}
.time-ago,
.extra-info,
.extra-info > * {
@apply --paper-font-common-nowrap;
color: var(--secondary-text-color);
}
.row {
display: flex;
flex-direction: row;
flex-wrap: no-wrap;
width: 100%;
justify-content: space-between;
margin: 0 2px 4px 0;
}
.row:last-child {
margin-bottom: 0px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"state-info": StateInfo;
}
}
+5 -7
View File
@@ -9,9 +9,7 @@ import {
TemplateResult,
} from "lit-element";
import { until } from "lit-html/directives/until";
import hassAttributeUtil, {
formatAttributeName,
} from "../util/hass-attributes-util";
import hassAttributeUtil from "../util/hass-attributes-util";
let jsYamlPromise: Promise<typeof import("js-yaml")>;
@@ -36,7 +34,7 @@ class HaAttributes extends LitElement {
(attribute) => html`
<div class="data-entry">
<div class="key">
${formatAttributeName(attribute)}
${attribute.replace(/_/g, " ").replace(/\bid\b/g, "ID")}
</div>
<div class="value">
${this.formatAttribute(attribute)}
@@ -63,12 +61,12 @@ class HaAttributes extends LitElement {
justify-content: space-between;
}
.data-entry .value {
max-width: 50%;
max-width: 200px;
overflow-wrap: break-word;
text-align: right;
}
.key {
flex-grow: 1;
.key:first-letter {
text-transform: capitalize;
}
.attribution {
color: var(--secondary-text-color);
+1 -3
View File
@@ -11,7 +11,7 @@ export class HaCircularProgress extends CircularProgress {
public alt = "Loading";
@property()
public size: "tiny" | "small" | "medium" | "large" = "medium";
public size: "small" | "medium" | "large" = "medium";
// @ts-ignore
public set density(_) {
@@ -20,8 +20,6 @@ export class HaCircularProgress extends CircularProgress {
public get density() {
switch (this.size) {
case "tiny":
return -8;
case "small":
return -5;
case "medium":
+1 -1
View File
@@ -63,7 +63,7 @@ class HaClimateState extends LitElement {
private _computeTarget(): string {
if (!this.hass || !this.stateObj) {
return "";
return "";
}
if (
+126
View File
@@ -0,0 +1,126 @@
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 HaCoverControls extends PolymerElement {
static get template() {
return html`
<style>
.state {
white-space: nowrap;
}
[invisible] {
visibility: hidden !important;
}
</style>
<div class="state">
<ha-icon-button
aria-label="Open cover"
icon="[[computeOpenIcon(stateObj)]]"
on-click="onOpenTap"
invisible$="[[!entityObj.supportsOpen]]"
disabled="[[computeOpenDisabled(stateObj, entityObj)]]"
></ha-icon-button>
<ha-icon-button
aria-label="Stop the cover from moving"
icon="hass:stop"
on-click="onStopTap"
invisible$="[[!entityObj.supportsStop]]"
disabled="[[computeStopDisabled(stateObj)]]"
></ha-icon-button>
<ha-icon-button
aria-label="Close cover"
icon="[[computeCloseIcon(stateObj)]]"
on-click="onCloseTap"
invisible$="[[!entityObj.supportsClose]]"
disabled="[[computeClosedDisabled(stateObj, entityObj)]]"
></ha-icon-button>
</div>
`;
}
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);
}
computeOpenIcon(stateObj) {
switch (stateObj.attributes.device_class) {
case "awning":
case "door":
case "gate":
return "hass:arrow-expand-horizontal";
default:
return "hass:arrow-up";
}
}
computeCloseIcon(stateObj) {
switch (stateObj.attributes.device_class) {
case "awning":
case "door":
case "gate":
return "hass:arrow-collapse-horizontal";
default:
return "hass:arrow-down";
}
}
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.isFullyOpen || entityObj.isOpening) && !assumedState;
}
computeClosedDisabled(stateObj, entityObj) {
if (stateObj.state === UNAVAILABLE) {
return true;
}
const assumedState = stateObj.attributes.assumed_state === true;
return (entityObj.isFullyClosed || entityObj.isClosing) && !assumedState;
}
onOpenTap(ev) {
ev.stopPropagation();
this.entityObj.openCover();
}
onCloseTap(ev) {
ev.stopPropagation();
this.entityObj.closeCover();
}
onStopTap(ev) {
ev.stopPropagation();
this.entityObj.stopCover();
}
}
customElements.define("ha-cover-controls", HaCoverControls);
-135
View File
@@ -1,135 +0,0 @@
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
PropertyValues,
TemplateResult,
} from "lit-element";
import { classMap } from "lit-html/directives/class-map";
import type { HassEntity } from "home-assistant-js-websocket";
import type { HomeAssistant } from "../types";
import { UNAVAILABLE } from "../data/entity";
import CoverEntity from "../util/cover-model";
import "./ha-icon-button";
import { computeCloseIcon, computeOpenIcon } from "../common/entity/cover_icon";
@customElement("ha-cover-controls")
class HaCoverControls extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public 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`
<div class="state">
<ha-icon-button
class=${classMap({
hidden: !this._entityObj.supportsOpen,
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.open_cover"
)}
.icon=${computeOpenIcon(this.stateObj)}
@click=${this._onOpenTap}
.disabled=${this._computeOpenDisabled()}
></ha-icon-button>
<ha-icon-button
class=${classMap({
hidden: !this._entityObj.supportsStop,
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.stop_cover"
)}
icon="hass:stop"
@click=${this._onStopTap}
.disabled=${this.stateObj.state === UNAVAILABLE}
></ha-icon-button>
<ha-icon-button
class=${classMap({
hidden: !this._entityObj.supportsClose,
})}
.label=${this.hass.localize(
"ui.dialogs.more_info_control.close_cover"
)}
.icon=${computeCloseIcon(this.stateObj)}
@click=${this._onCloseTap}
.disabled=${this._computeClosedDisabled()}
></ha-icon-button>
</div>
`;
}
private _computeOpenDisabled(): boolean {
if (this.stateObj.state === UNAVAILABLE) {
return true;
}
const assumedState = this.stateObj.attributes.assumed_state === true;
return (
(this._entityObj.isFullyOpen || this._entityObj.isOpening) &&
!assumedState
);
}
private _computeClosedDisabled(): boolean {
if (this.stateObj.state === UNAVAILABLE) {
return true;
}
const assumedState = this.stateObj.attributes.assumed_state === true;
return (
(this._entityObj.isFullyClosed || this._entityObj.isClosing) &&
!assumedState
);
}
private _onOpenTap(ev): void {
ev.stopPropagation();
this._entityObj.openCover();
}
private _onCloseTap(ev): void {
ev.stopPropagation();
this._entityObj.closeCover();
}
private _onStopTap(ev): void {
ev.stopPropagation();
this._entityObj.stopCover();
}
static get styles(): CSSResult {
return css`
.state {
white-space: nowrap;
}
.hidden {
visibility: hidden !important;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-cover-controls": HaCoverControls;
}
}
+106
View File
@@ -0,0 +1,106 @@
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
View File
@@ -1,122 +0,0 @@
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;
}
}
+109 -52
View File
@@ -1,70 +1,127 @@
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
const VaadinDatePicker = customElements.get("vaadin-date-picker");
@customElement("ha-date-input")
export class HaDateInput extends LitElement {
@property() public year?: string;
export class HaDateInput extends VaadinDatePicker {
constructor() {
super();
@property() public month?: string;
this.i18n.formatDate = this._formatISODate;
this.i18n.parseDate = this._parseISODate;
}
@property() public day?: string;
ready() {
super.ready();
const styleEl = document.createElement("style");
styleEl.innerHTML = `
@property({ type: Boolean }) public disabled = false;
static get styles() {
return css`
:host {
width: 12ex;
margin-top: -6px;
--material-body-font-size: 16px;
--_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);
display: block;
font-family: var(--paper-font-common-base_-_font-family);
-webkit-font-smoothing: var(
--paper-font-common-base_-_-webkit-font-smoothing
);
}
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";
}
private _formatISODate(d) {
return [
("0000" + String(d.year)).slice(-4),
("0" + String(d.month + 1)).slice(-2),
("0" + String(d.day)).slice(-2),
].join("-");
protected render(): TemplateResult {
return html`
<div class="date-input-wrap">
<paper-input
id="year"
type="number"
.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 _parseISODate(text) {
const parts = text.split("-");
const today = new Date();
let date;
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 _formatYear() {
const yearElement = this.shadowRoot!.getElementById(
"year"
) as PaperInputElement;
this.year = yearElement.value!;
}
if (date !== undefined) {
return { day: date, month, year };
}
return undefined;
private _formatMonth() {
const monthElement = this.shadowRoot!.getElementById(
"month"
) as PaperInputElement;
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 {
interface HTMLElementTagNameMap {
"ha-date-input": HaDateInput;
-1
View File
@@ -40,7 +40,6 @@ export class HaDialog extends MwcDialog {
.mdc-dialog {
--mdc-dialog-scroll-divider-color: var(--divider-color);
z-index: var(--dialog-z-index, 7);
backdrop-filter: var(--dialog-backdrop-filter, none);
}
.mdc-dialog__actions {
justify-content: var(--justify-action-buttons, flex-end);
+7 -23
View File
@@ -104,6 +104,7 @@ class HaHLSPlayer extends LitElement {
private async _startHls(): Promise<void> {
const videoEl = this._videoEl;
const playlist_url = this.url.replace("master_playlist", "playlist");
const useExoPlayerPromise = this._getUseExoPlayer();
const masterPlaylistPromise = fetch(this.url);
@@ -125,30 +126,13 @@ class HaHLSPlayer extends LitElement {
}
this._useExoPlayer = await useExoPlayerPromise;
const masterPlaylist = await (await masterPlaylistPromise).text();
// Parse playlist assuming it is a master playlist. Match group 1 is whether hevc, match group 2 is regular playlist url
// See https://tools.ietf.org/html/rfc8216 for HLS spec details
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;
let hevcRegexp: RegExp;
let masterPlaylist: string;
if (this._useExoPlayer) {
hevcRegexp = /CODECS=".*?((hev1)|(hvc1))\..*?"/;
masterPlaylist = await (await masterPlaylistPromise).text();
}
// If codec is HEVC and ExoPlayer is supported, use ExoPlayer.
if (
this._useExoPlayer &&
match !== null &&
match.groups!.isHevc !== undefined
) {
if (this._useExoPlayer && hevcRegexp!.test(masterPlaylist!)) {
this._renderHLSExoPlayer(playlist_url);
} else if (hls.isSupported()) {
this._renderHLSPolyfill(videoEl, hls, playlist_url);
+1 -9
View File
@@ -9,16 +9,11 @@ import {
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
import { HomeAssistant } from "../types";
@customElement("ha-icon-button-arrow-next")
export class HaIconButtonArrowNext extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public disabled = false;
@property() public label?: string;
@internalProperty() private _icon = mdiArrowRight;
public connectedCallback() {
@@ -34,10 +29,7 @@ export class HaIconButtonArrowNext extends LitElement {
}
protected render(): TemplateResult {
return html`<mwc-icon-button
.disabled=${this.disabled}
.label=${this.label || this.hass?.localize("ui.common.next") || "Next"}
>
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
+3 -13
View File
@@ -9,16 +9,11 @@ import {
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
import { HomeAssistant } from "../types";
@customElement("ha-icon-button-arrow-prev")
export class HaIconButtonArrowPrev extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public disabled = false;
@property() public label?: string;
@internalProperty() private _icon = mdiArrowLeft;
public connectedCallback() {
@@ -34,14 +29,9 @@ export class HaIconButtonArrowPrev extends LitElement {
}
protected render(): TemplateResult {
return html`
<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>
`;
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
+3 -13
View File
@@ -9,16 +9,11 @@ import {
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
import "@material/mwc-icon-button";
import "./ha-svg-icon";
import { HomeAssistant } from "../types";
@customElement("ha-icon-button-next")
export class HaIconButtonNext extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public disabled = false;
@property() public label?: string;
@internalProperty() private _icon = mdiChevronRight;
public connectedCallback() {
@@ -34,14 +29,9 @@ export class HaIconButtonNext extends LitElement {
}
protected render(): TemplateResult {
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>
</mwc-icon-button>
`;
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
+3 -13
View File
@@ -9,16 +9,11 @@ import {
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
import "@material/mwc-icon-button/mwc-icon-button";
import "./ha-svg-icon";
import { HomeAssistant } from "../types";
@customElement("ha-icon-button-prev")
export class HaIconButtonPrev extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property({ type: Boolean }) public disabled = false;
@property() public label?: string;
@internalProperty() private _icon = mdiChevronLeft;
public connectedCallback() {
@@ -34,14 +29,9 @@ export class HaIconButtonPrev extends LitElement {
}
protected render(): TemplateResult {
return html`
<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>
`;
return html`<mwc-icon-button .disabled=${this.disabled}>
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
</mwc-icon-button> `;
}
}
+1 -1
View File
@@ -14,7 +14,7 @@ class HaLabeledSlider extends PolymerElement {
}
.title {
margin: 4px 0 8px;
margin-bottom: 8px;
color: var(--primary-text-color);
}
+1
View File
@@ -47,6 +47,7 @@ class HaMarkdownElement extends UpdatingElement {
node.host !== document.location.host
) {
node.target = "_blank";
node.rel = "noreferrer";
// protect referrer on external links and deny window.opener access for security reasons
// (see https://mathiasbynens.github.io/rel-noopener/)
+26 -26
View File
@@ -320,11 +320,13 @@ class HaSidebar extends LitElement {
</mwc-icon-button>
`
: ""}
${this.editMode
? html`<mwc-button outlined @click=${this._closeEditMode}>
${this.hass.localize("ui.sidebar.done")}
</mwc-button>`
: html`<div class="title">Home Assistant</div>`}
<div class="title">
${this.editMode
? html`<mwc-button outlined @click=${this._closeEditMode}>
${this.hass.localize("ui.sidebar.done")}
</mwc-button>`
: "Home Assistant"}
</div>
</div>`;
}
@@ -754,7 +756,7 @@ class HaSidebar extends LitElement {
-moz-user-select: none;
border-right: 1px solid var(--divider-color);
background-color: var(--sidebar-background-color);
width: 56px;
width: 64px;
}
:host([expanded]) {
width: 256px;
@@ -766,9 +768,8 @@ class HaSidebar extends LitElement {
}
.menu {
height: var(--header-height);
box-sizing: border-box;
display: flex;
padding: 0 4px;
padding: 0 8.5px;
border-bottom: 1px solid transparent;
white-space: nowrap;
font-weight: 400;
@@ -777,11 +778,11 @@ class HaSidebar extends LitElement {
background-color: var(--primary-background-color);
font-size: 20px;
align-items: center;
padding-left: calc(4px + env(safe-area-inset-left));
padding-left: calc(8.5px + env(safe-area-inset-left));
}
:host([rtl]) .menu {
padding-left: 4px;
padding-right: calc(4px + env(safe-area-inset-right));
padding-left: 8.5px;
padding-right: calc(8.5px + env(safe-area-inset-right));
}
:host([expanded]) .menu {
width: calc(256px + env(safe-area-inset-left));
@@ -792,27 +793,26 @@ class HaSidebar extends LitElement {
.menu mwc-icon-button {
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 {
margin-left: 19px;
width: 100%;
display: none;
}
:host([rtl]) .title {
margin-left: 0;
margin-right: 19px;
}
:host([narrow]) .title {
margin: 0;
padding: 0 16px;
}
:host([expanded]) .title {
display: initial;
}
:host([expanded]) .menu mwc-button {
margin: 0 8px;
}
.menu mwc-button {
width: 100%;
.title mwc-button {
width: 90%;
}
#sortable,
.hidden-panel {
@@ -850,14 +850,14 @@ class HaSidebar extends LitElement {
paper-icon-item {
box-sizing: border-box;
margin: 4px;
margin: 4px 8px;
padding-left: 12px;
border-radius: 4px;
--paper-item-min-height: 40px;
width: 48px;
}
:host([expanded]) paper-icon-item {
width: 248px;
width: 240px;
}
:host([rtl]) paper-icon-item {
padding-left: auto;
@@ -874,9 +874,9 @@ class HaSidebar extends LitElement {
border-radius: 4px;
position: absolute;
top: 0;
right: 2px;
right: 0;
bottom: 0;
left: 2px;
left: 0;
pointer-events: none;
content: "";
transition: opacity 15ms linear;
+2 -2
View File
@@ -99,13 +99,13 @@ export class HaTab extends LitElement {
display: flex;
flex-direction: column;
text-align: center;
box-sizing: border-box;
align-items: center;
justify-content: center;
height: var(--header-height);
height: 64px;
cursor: pointer;
position: relative;
outline: none;
box-sizing: border-box;
}
.name {
+2 -2
View File
@@ -34,8 +34,8 @@ export class HaTabs extends PaperTabs {
superStyle!.appendChild(
document.createTextNode(`
#selectionBar {
box-sizing: border-box;
:host {
padding-top: .5px;
}
.not-visible {
display: none;
+1 -11
View File
@@ -58,18 +58,8 @@ export interface DataEntryFlowStepAbort {
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 =
| DataEntryFlowStepForm
| DataEntryFlowStepExternal
| DataEntryFlowStepCreateEntry
| DataEntryFlowStepAbort
| DataEntryFlowStepProgress;
| DataEntryFlowStepAbort;
+1 -7
View File
@@ -20,12 +20,6 @@ export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
original_icon?: string;
}
export interface UpdateEntityRegistryEntryResult {
entity_entry: ExtEntityRegistryEntry;
reload_delay?: number;
require_restart?: boolean;
}
export interface EntityRegistryEntryUpdateParams {
name?: string | null;
icon?: string | null;
@@ -78,7 +72,7 @@ export const updateEntityRegistryEntry = (
hass: HomeAssistant,
entityId: string,
updates: Partial<EntityRegistryEntryUpdateParams>
): Promise<UpdateEntityRegistryEntryResult> =>
): Promise<ExtEntityRegistryEntry> =>
hass.callWS({
type: "config/entity_registry/update",
entity_id: entityId,
-26
View File
@@ -1,26 +0,0 @@
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 }
);
+12
View File
@@ -111,6 +111,18 @@ export const fetchHassioLogs = async (
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 (
hass: HomeAssistant,
data: SupervisorOptions
+1 -86
View File
@@ -18,8 +18,6 @@ import {
} from "@mdi/js";
import type { HassEntity } from "home-assistant-js-websocket";
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_SEEK = 2;
@@ -33,7 +31,7 @@ export const SUPPORT_PLAY_MEDIA = 512;
export const SUPPORT_VOLUME_BUTTONS = 1024;
export const SUPPORT_SELECT_SOURCE = 2048;
export const SUPPORT_STOP = 4096;
export const SUPPORT_PLAY = 16384;
export const SUPPORTS_PLAY = 16384;
export const SUPPORT_SELECT_SOUND_MODE = 65536;
export const SUPPORT_BROWSE_MEDIA = 131072;
export const CONTRAST_RATIO = 4.5;
@@ -168,7 +166,6 @@ export const computeMediaDescription = (stateObj: HassEntity): string => {
switch (stateObj.attributes.media_content_type) {
case "music":
case "image":
secondaryTitle = stateObj.attributes.media_artist;
break;
case "playlist":
@@ -190,85 +187,3 @@ export const computeMediaDescription = (stateObj: HassEntity): string => {
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;
};
+7 -26
View File
@@ -21,18 +21,6 @@ export const getDefaultPanel = (hass: HomeAssistant): PanelInfo =>
? hass.panels[hass.defaultPanel]
: 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 => {
if (!hass.panels) {
return undefined;
@@ -46,20 +34,13 @@ export const getPanelTitle = (hass: HomeAssistant): string | undefined => {
return undefined;
}
const translationKey = getPanelNameTranslationKey(panel);
return hass.localize(translationKey) || panel.title || undefined;
};
export const getPanelIcon = (panel: PanelInfo): string | null => {
if (!panel.icon) {
switch (panel.component_name) {
case "profile":
return "hass:account";
case "lovelace":
return "hass:view-dashboard";
}
if (panel.url_path === "lovelace") {
return hass.localize("panel.states");
}
return panel.icon;
if (panel.url_path === "profile") {
return hass.localize("panel.profile");
}
return hass.localize(`panel.${panel.title}`) || panel.title || undefined;
};
+17 -104
View File
@@ -1,111 +1,24 @@
import { HomeAssistant } from "../types";
interface SystemCheckValueDateObject {
type: "date";
value: string;
export interface HomeAssistantSystemHealthInfo {
version: string;
dev: boolean;
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 {
[domain: string]: {
manage_url?: string;
info: {
[key: string]: SystemCheckValue;
};
};
[domain: string]: { [key: string]: string | number | boolean };
}
interface SystemHealthEventInitial {
type: "initial";
data: SystemHealthInfo;
}
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;
};
export const fetchSystemHealthInfo = (
hass: HomeAssistant
): Promise<SystemHealthInfo> =>
hass.callWS({
type: "system_health/info",
});
+1 -2
View File
@@ -17,8 +17,7 @@ export type TranslationCategory =
| "config"
| "options"
| "device_automation"
| "mfa_setup"
| "system_health";
| "mfa_setup";
export const fetchTranslationPreferences = (hass: HomeAssistant) =>
fetchFrontendUserData(hass.connection, "language");
@@ -36,7 +36,6 @@ import "./step-flow-external";
import "./step-flow-form";
import "./step-flow-loading";
import "./step-flow-pick-handler";
import "./step-flow-progress";
let instance = 0;
@@ -196,14 +195,6 @@ class DataEntryFlowDialog extends LitElement {
.hass=${this.hass}
></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
? // When it's a create entry result, we will fetch device & area registry
html` <step-flow-loading></step-flow-loading> `
@@ -160,21 +160,4 @@ export const showConfigFlowDialog = (
</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,7 +7,6 @@ import {
DataEntryFlowStepCreateEntry,
DataEntryFlowStepExternal,
DataEntryFlowStepForm,
DataEntryFlowStepProgress,
} from "../../data/data_entry_flow";
import { HomeAssistant } from "../../types";
@@ -69,16 +68,6 @@ export interface FlowConfig {
hass: HomeAssistant,
step: DataEntryFlowStepCreateEntry
): TemplateResult | "";
renderShowFormProgressHeader(
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): string;
renderShowFormProgressDescription(
hass: HomeAssistant,
step: DataEntryFlowStepProgress
): TemplateResult | "";
}
export interface DataEntryFlowDialogParams {
@@ -110,13 +110,5 @@ export const showOptionsFlowDialog = (
<p>${hass.localize(`ui.dialogs.options_flow.success.description`)}</p>
`;
},
renderShowFormProgressHeader(_hass, _step) {
return "";
},
renderShowFormProgressDescription(_hass, _step) {
return "";
},
}
);
@@ -23,7 +23,6 @@ import { HomeAssistant } from "../../types";
import { documentationUrl } from "../../util/documentation-url";
import { FlowConfig } from "./show-dialog-data-entry-flow";
import { configFlowContentStyles } from "./styles";
import { brandsUrl } from "../../util/brands-url";
interface HandlerObj {
name: string;
@@ -103,7 +102,7 @@ class StepFlowPickHandler extends LitElement {
<img
slot="item-icon"
loading="lazy"
src=${brandsUrl(handler.slug, "icon", true)}
src="https://brands.home-assistant.io/_/${handler.slug}/icon.png"
referrerpolicy="no-referrer"
/>
@@ -1,82 +0,0 @@
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,28 +1,26 @@
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-input/paper-input";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import "../../components/ha-dialog";
import "../../components/ha-area-picker";
import {
CSSResult,
LitElement,
TemplateResult,
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
import { HomeAssistant } from "../../types";
import { PolymerChangedEvent } from "../../polymer-types";
import "../../components/dialog/ha-paper-dialog";
import "../../components/ha-area-picker";
import { computeDeviceName } from "../../data/device_registry";
import { fireEvent } from "../../common/dom/fire_event";
import { PolymerChangedEvent } from "../../polymer-types";
import { haStyleDialog } from "../../resources/styles";
import { HomeAssistant } from "../../types";
import { DeviceRegistryDetailDialogParams } from "./show-dialog-device-registry-detail";
@customElement("dialog-device-registry-detail")
class DialogDeviceRegistryDetail extends LitElement {
@@ -36,7 +34,7 @@ class DialogDeviceRegistryDetail extends LitElement {
@internalProperty() private _areaId?: string;
@internalProperty() private _submitting?: boolean;
private _submitting?: boolean;
public async showDialog(
params: DeviceRegistryDetailDialogParams
@@ -48,24 +46,22 @@ class DialogDeviceRegistryDetail extends LitElement {
await this.updateComplete;
}
public closeDialog(): void {
this._error = "";
this._params = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._params) {
return html``;
}
const device = this._params.device;
return html`
<ha-dialog
open
@closed=${this.closeDialog}
.heading=${computeDeviceName(device, this.hass)}
<ha-paper-dialog
with-backdrop
opened
@opened-changed="${this._openedChanged}"
>
<div>
<h2>
${computeDeviceName(device, this.hass)}
</h2>
<paper-dialog-scrollable>
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
<div class="form">
<paper-input
@@ -81,22 +77,13 @@ class DialogDeviceRegistryDetail extends LitElement {
@value-changed=${this._areaPicked}
></ha-area-picker>
</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>
<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>
</ha-paper-dialog>
`;
}
@@ -126,10 +113,19 @@ class DialogDeviceRegistryDetail extends LitElement {
}
}
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
if (!(ev.detail as any).value) {
this._params = undefined;
}
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-paper-dialog {
min-width: 400px;
}
.form {
padding-bottom: 24px;
}
@@ -3,7 +3,7 @@ import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../components/ha-date-input";
import "@vaadin/vaadin-date-picker/theme/material/vaadin-date-picker";
import { attributeClassNames } from "../../../common/entity/attribute_class_names";
import "../../../components/ha-relative-time";
import "../../../components/paper-time-input";
@@ -14,12 +14,12 @@ class DatetimeInput extends PolymerElement {
<div class$="[[computeClassNames(stateObj)]]">
<template is="dom-if" if="[[doesHaveDate(stateObj)]]" restamp="">
<div>
<ha-date-input
<vaadin-date-picker
id="dateInput"
on-value-changed="dateTimeChanged"
label="Date"
value="{{selectedDate}}"
></ha-date-input>
></vaadin-date-picker>
</div>
</template>
<template is="dom-if" if="[[doesHaveTime(stateObj)]]" restamp="">
@@ -25,12 +25,19 @@ import "../../../components/ha-svg-icon";
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
import { UNAVAILABLE, UNAVAILABLE_STATES, UNKNOWN } from "../../../data/entity";
import {
computeMediaControls,
ControlButton,
MediaPickedEvent,
SUPPORTS_PLAY,
SUPPORT_BROWSE_MEDIA,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_SELECT_SOUND_MODE,
SUPPORT_SELECT_SOURCE,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
SUPPORT_TURN_ON,
SUPPORT_VOLUME_BUTTONS,
SUPPORT_VOLUME_MUTE,
SUPPORT_VOLUME_SET,
@@ -50,8 +57,8 @@ class MoreInfoMediaPlayer extends LitElement {
return html``;
}
const controls = this._getControls();
const stateObj = this.stateObj;
const controls = computeMediaControls(stateObj);
return html`
${!controls
@@ -247,6 +254,84 @@ 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 {
this.hass!.callService(
"media_player",
@@ -72,12 +72,6 @@ class MoreInfoSun extends LitElement {
flex-direction: row;
justify-content: space-between;
}
ha-relative-time {
display: inline-block;
}
ha-relative-time::first-letter {
text-transform: lowercase;
}
`;
}
}
+2 -2
View File
@@ -285,8 +285,8 @@ export class MoreInfoDialog extends LitElement {
text: this.hass.localize(
"ui.dialogs.more_info_control.restored.confirm_remove_text"
),
confirmText: this.hass.localize("ui.common.remove"),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.yes"),
dismissText: this.hass.localize("ui.common.no"),
confirm: () => {
removeEntityRegistryEntry(this.hass, entityId);
},
@@ -25,8 +25,8 @@ export class HuiNotificationDrawer extends EventsMixin(
color: var(--primary-text-color);
border-bottom: 1px solid var(--divider-color);
background-color: var(--primary-background-color);
height: var(--header-height);
box-sizing: border-box;
min-height: 64px;
width: calc(100% - 32px);
}
div[main-title] {
@@ -63,10 +63,7 @@ export class HuiNotificationDrawer extends EventsMixin(
<app-drawer id="drawer" opened="{{open}}" disable-swipe align="start">
<app-toolbar>
<div main-title>[[localize('ui.notification_drawer.title')]]</div>
<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>
<ha-icon-button-prev on-click="_closeDrawer" aria-label$="[[localize('ui.notification_drawer.close')]]"></ha-icon-button-prev>
</app-toolbar>
<div class="notifications">
<template is="dom-if" if="[[!_empty(notifications)]]">
+20 -142
View File
@@ -41,36 +41,19 @@ import {
showConfirmationDialog,
} from "../generic/show-dialog-box";
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 {
icon?: string;
iconPath?: string;
icon: string;
action(data?: any): void;
}
interface QuickBarNavigationItem extends QuickBarItem {
path: string;
}
interface NavigationInfo extends PageNavigation {
text: string;
}
@customElement("ha-quick-bar")
export class QuickBar extends LitElement {
@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[] = [];
@@ -90,7 +73,8 @@ export class QuickBar extends LitElement {
public async showDialog(params: QuickBarParams) {
this._commandMode = params.commandMode || this._toggleIfAlreadyOpened();
this._initializeItemsIfNeeded();
this._commandItems = this._generateCommandItems();
this._entityItems = this._generateEntityItems();
this._opened = true;
}
@@ -174,14 +158,6 @@ 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() {
this._setFilteredItems();
this.updateComplete.then(() => {
@@ -206,20 +182,14 @@ export class QuickBar extends LitElement {
.twoline=${Boolean(item.altText)}
.item=${item}
index=${ifDefined(index)}
graphic="icon"
hasMeta
graphic=${item.altText ? "avatar" : "icon"}
>
${item.iconPath
? html`<ha-svg-icon
.path=${item.iconPath}
slot="graphic"
></ha-svg-icon>`
: html`<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>`}
${item.text}
<ha-icon .icon=${item.icon} slot="graphic"></ha-icon>
<span>${item.text}</span>
${item.altText
? html`
<span slot="secondary" class="item-text secondary"
>${item.altText}</span
>
<span slot="secondary" class="secondary">${item.altText}</span>
`
: null}
</mwc-list-item>
@@ -282,8 +252,6 @@ export class QuickBar extends LitElement {
if (oldCommandMode !== this._commandMode) {
this._items = undefined;
this._focusSet = false;
this._initializeItemsIfNeeded();
}
}
@@ -311,22 +279,10 @@ 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[] {
return [
...this._generateReloadCommands(),
...this._generateServerControlCommands(),
...this._generateNavigationCommands(),
].sort((a, b) => compare(a.text.toLowerCase(), b.text.toLowerCase()));
}
@@ -359,7 +315,7 @@ export class QuickBar extends LitElement {
`ui.dialogs.quick-bar.commands.server_control.${action}`
)
),
icon: DEFAULT_SERVER_ICON,
icon: "hass:server",
action: () => this.hass.callService("homeassistant", action),
},
this.hass.localize("ui.dialogs.generic.ok")
@@ -367,79 +323,6 @@ 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(
item: QuickBarItem,
confirmText: ConfirmationDialogParams["confirmText"]
@@ -454,13 +337,15 @@ export class QuickBar extends LitElement {
};
}
private _withNavigationActions(items) {
return items.map(({ text, icon, iconPath, path }) => ({
text,
icon,
iconPath,
action: () => navigate(this, path),
}));
private _generateEntityItems(): QuickBarItem[] {
return Object.keys(this.hass.states)
.map((entityId) => ({
text: computeStateName(this.hass.states[entityId]) || 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 _toggleIfAlreadyOpened() {
@@ -502,14 +387,8 @@ export class QuickBar extends LitElement {
}
}
ha-icon,
ha-svg-icon {
margin-left: 20px;
}
ha-svg-icon.prefix {
margin: 8px;
color: var(--primary-text-color);
}
.uni-virtualizer-host {
@@ -526,7 +405,6 @@ export class QuickBar extends LitElement {
mwc-list-item {
width: 100%;
text-transform: capitalize;
}
`,
];
-2
View File
@@ -17,8 +17,6 @@
--primary-text-color: #e1e1e1;
--secondary-text-color: #9b9b9b;
--disabled-text-color: #6f6f6f;
--mdc-theme-surface: #1e1e1e;
--ha-card-background: #1e1e1e;
}
}
.content {
+1 -5
View File
@@ -9,12 +9,9 @@ import {
TemplateResult,
} from "lit-element";
import "./hass-subpage";
import { HomeAssistant } from "../types";
@customElement("hass-error-screen")
class HassErrorScreen extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ type: Boolean }) public toolbar = true;
@property() public error?: string;
@@ -24,7 +21,6 @@ class HassErrorScreen extends LitElement {
${this.toolbar
? html`<div class="toolbar">
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
</div>`
@@ -54,7 +50,7 @@ class HassErrorScreen extends LitElement {
display: flex;
align-items: center;
font-size: 20px;
height: var(--header-height);
height: 65px;
padding: 0 16px;
pointer-events: none;
background-color: var(--app-header-background-color);
+1 -2
View File
@@ -39,7 +39,6 @@ class HassLoadingScreen extends LitElement {
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
@click=${this._handleBack}
></ha-icon-button-arrow-prev>
`}
@@ -67,7 +66,7 @@ class HassLoadingScreen extends LitElement {
display: flex;
align-items: center;
font-size: 20px;
height: var(--header-height);
height: 65px;
padding: 0 16px;
pointer-events: none;
background-color: var(--app-header-background-color);
+8 -8
View File
@@ -12,17 +12,17 @@ import { classMap } from "lit-html/directives/class-map";
import "../components/ha-menu-button";
import "../components/ha-icon-button-arrow-prev";
import { restoreScroll } from "../common/decorators/restore-scroll";
import { HomeAssistant } from "../types";
@customElement("hass-subpage")
class HassSubpage extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property()
public header?: string;
@property() public header?: string;
@property({ type: Boolean })
public showBackButton = true;
@property({ type: Boolean }) public showBackButton = true;
@property({ type: Boolean }) public hassio = false;
@property({ type: Boolean })
public hassio = false;
// @ts-ignore
@restoreScroll(".content") private _savedScrollPos?: number;
@@ -31,7 +31,7 @@ class HassSubpage extends LitElement {
return html`
<div class="toolbar">
<ha-icon-button-arrow-prev
.hass=${this.hass}
aria-label="Back"
@click=${this._backTapped}
class=${classMap({ hidden: !this.showBackButton })}
></ha-icon-button-arrow-prev>
@@ -69,7 +69,7 @@ class HassSubpage extends LitElement {
display: flex;
align-items: center;
font-size: 20px;
height: var(--header-height);
height: 65px;
padding: 0 16px;
pointer-events: none;
background-color: var(--app-header-background-color);
+2 -2
View File
@@ -145,7 +145,7 @@ class HassTabsSubpage extends LitElement {
`
: html`
<ha-icon-button-arrow-prev
.hass=${this.hass}
aria-label="Back"
@click=${this._backTapped}
></ha-icon-button-arrow-prev>
`}
@@ -217,7 +217,7 @@ class HassTabsSubpage extends LitElement {
display: flex;
align-items: center;
font-size: 20px;
height: var(--header-height);
height: 65px;
background-color: var(--sidebar-background-color);
font-weight: 400;
color: var(--sidebar-text-color);
+1 -1
View File
@@ -192,7 +192,7 @@ class HomeAssistantMain extends LitElement {
color: var(--primary-text-color);
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
--app-drawer-width: 56px;
--app-drawer-width: 64px;
}
:host([expanded]) {
--app-drawer-width: calc(256px + env(safe-area-inset-left));
+1 -2
View File
@@ -8,7 +8,6 @@ import {
TemplateResult,
} from "lit-element";
import "../components/ha-icon";
import { brandsUrl } from "../util/brands-url";
@customElement("integration-badge")
class IntegrationBadge extends LitElement {
@@ -24,7 +23,7 @@ class IntegrationBadge extends LitElement {
return html`
<div class="icon">
<img
src=${brandsUrl(this.domain, "icon")}
src="https://brands.home-assistant.io/${this.domain}/icon.png"
referrerpolicy="no-referrer"
/>
${this.badgeIcon
+74 -7
View File
@@ -4,6 +4,7 @@ import {
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
@@ -14,6 +15,10 @@ import { showSnapshotUploadDialog } from "../../hassio/src/dialogs/snapshot/show
import { navigate } from "../common/navigate";
import type { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-card";
import {
extractApiErrorMessage,
ignoredStatusCodes,
} from "../data/hassio/common";
import { makeDialogManager } from "../dialogs/make-dialog-manager";
import { ProvideHassLitMixin } from "../mixins/provide-hass-lit-mixin";
import { haStyle } from "../resources/styles";
@@ -33,6 +38,10 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
@property({ type: Boolean }) public restoring = false;
@internalProperty() private _log = "";
@internalProperty() private _showFullLog = false;
protected render(): TemplateResult {
return this.restoring
? html`<ha-card
@@ -40,7 +49,22 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
"ui.panel.page-onboarding.restore.in_progress"
)}
>
<onboarding-loading></onboarding-loading>
${this._showFullLog
? 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>`
: html`
<button class="link" @click=${this._uploadSnapshot}>
@@ -49,6 +73,33 @@ 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 {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) => this._showSnapshotDialog(slug),
@@ -59,26 +110,42 @@ class OnboardingRestoreSnapshot extends ProvideHassLitMixin(LitElement) {
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
makeDialogManager(this, this.shadowRoot!);
setInterval(() => this._checkRestoreStatus(), 1000);
setInterval(() => this._getLogs(), 1000);
}
private async _checkRestoreStatus(): Promise<void> {
private async _getLogs(): Promise<void> {
if (this.restoring) {
try {
const response = await fetch("/api/hassio/supervisor/info", {
const response = await fetch("/api/hassio/supervisor/logs", {
method: "GET",
});
if (response.status === 401) {
// If we get a unauthorized response, the restore is done
navigate(this, "/", true);
location.reload();
this._restoreDone();
} else if (
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) {
// We fully expected issues with fetching info untill restore is complete.
this._log += this._filterLogs(err.toString());
}
}
}
private _restoreDone(): void {
navigate(this, "/", true);
location.reload();
}
private _showSnapshotDialog(slug: string): void {
showHassioSnapshotDialog(this, {
slug,
@@ -81,8 +81,7 @@ class HaConfigAreaPage extends LitElement {
if (!area) {
return html`
<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize("ui.panel.config.areas.area_not_found")}
error="${this.hass.localize("ui.panel.config.areas.area_not_found")}"
></hass-error-screen>
`;
}
@@ -313,8 +312,8 @@ class HaConfigAreaPage extends LitElement {
text: this.hass.localize(
"ui.panel.config.areas.delete.confirmation_text"
),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.no"),
confirmText: this.hass.localize("ui.common.yes"),
}))
) {
return false;
@@ -126,10 +126,9 @@ export class HaConfigAreasDashboard extends LitElement {
></ha-icon-button>
<mwc-fab
slot="fab"
.label=${this.hass.localize(
title="${this.hass.localize(
"ui.panel.config.areas.picker.create_area"
)}
extended
)}"
@click=${this._createArea}
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -276,8 +276,8 @@ export default class HaAutomationActionRow extends LitElement {
text: this.hass.localize(
"ui.panel.config.automation.editor.actions.delete_confirm"
),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.no"),
confirmText: this.hass.localize("ui.common.yes"),
confirm: () => {
fireEvent(this, "value-changed", { value: null });
},
@@ -123,8 +123,8 @@ export default class HaAutomationConditionRow extends LitElement {
text: this.hass.localize(
"ui.panel.config.automation.editor.conditions.delete_confirm"
),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.no"),
confirmText: this.hass.localize("ui.common.yes"),
confirm: () => {
fireEvent(this, "value-changed", { value: null });
},
@@ -472,8 +472,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
<mwc-fab
slot="fab"
class=${classMap({ dirty: this._dirty })}
.label=${this.hass.localize("ui.panel.config.automation.editor.save")}
extended
.title=${this.hass.localize("ui.panel.config.automation.editor.save")}
@click=${this._saveAutomation}
>
<ha-svg-icon slot="icon" .path=${mdiContentSave}></ha-svg-icon>
@@ -651,8 +650,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
text: this.hass!.localize(
"ui.panel.config.automation.editor.unsaved_confirm"
),
confirmText: this.hass!.localize("ui.common.leave"),
dismissText: this.hass!.localize("ui.common.stay"),
confirmText: this.hass!.localize("ui.common.yes"),
dismissText: this.hass!.localize("ui.common.no"),
confirm: () => history.back(),
});
} else {
@@ -667,8 +666,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
text: this.hass!.localize(
"ui.panel.config.automation.editor.unsaved_confirm"
),
confirmText: this.hass!.localize("ui.common.leave"),
dismissText: this.hass!.localize("ui.common.stay"),
confirmText: this.hass!.localize("ui.common.yes"),
dismissText: this.hass!.localize("ui.common.no"),
}))
) {
return;
@@ -690,8 +689,8 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
text: this.hass.localize(
"ui.panel.config.automation.picker.delete_confirm"
),
confirmText: this.hass!.localize("ui.common.delete"),
dismissText: this.hass!.localize("ui.common.cancel"),
confirmText: this.hass!.localize("ui.common.yes"),
dismissText: this.hass!.localize("ui.common.no"),
confirm: () => this._delete(),
});
}
@@ -176,10 +176,9 @@ class HaAutomationPicker extends LitElement {
</mwc-icon-button>
<mwc-fab
slot="fab"
.label=${this.hass.localize(
title=${this.hass.localize(
"ui.panel.config.automation.picker.add_automation"
)}
extended
@click=${this._createNew}
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -85,22 +85,13 @@ class DialogThingtalk extends LitElement {
.opened=${this._opened}
@opened-changed=${this._openedChanged}
>
<h2>
${this.hass.localize(
`ui.panel.config.automation.thingtalk.task_selection.header`
)}
</h2>
<h2>Create a new automation</h2>
<paper-dialog-scrollable>
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
${this.hass.localize(
`ui.panel.config.automation.thingtalk.task_selection.introduction`
)}<br /><br />
${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`
)}
Type below what this automation should do, and we will try to convert
it into a Home Assistant automation. (only English is supported for
now)<br /><br />
For example:
<ul @click=${this._handleExampleClick}>
<li>
<button class="link">
@@ -139,7 +130,7 @@ class DialogThingtalk extends LitElement {
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button class="left" @click="${this._skip}">
${this.hass.localize(`ui.common.skip`)}
Skip
</mwc-button>
<mwc-button @click="${this._generate}" .disabled=${this._submitting}>
${this._submitting
@@ -149,7 +140,7 @@ class DialogThingtalk extends LitElement {
title="Creating your automation..."
></ha-circular-progress>`
: ""}
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
Create automation
</mwc-button>
</div>
</ha-paper-dialog>
@@ -159,9 +150,7 @@ class DialogThingtalk extends LitElement {
private async _generate() {
this._value = this._input!.value as string;
if (!this._value) {
this._error = this.hass.localize(
`ui.panel.config.automation.thingtalk.task_selection.error_empty`
);
this._error = "Enter a command or tap skip.";
return;
}
this._submitting = true;
@@ -180,9 +169,7 @@ class DialogThingtalk extends LitElement {
this._submitting = false;
if (!Object.keys(config).length) {
this._error = this.hass.localize(
`ui.panel.config.automation.thingtalk.task_selection.error_unsupported`
);
this._error = "We couldn't create an automation for that (yet?).";
} else if (Object.keys(placeholders).length) {
this._config = config;
this._placeholders = placeholders;
@@ -131,11 +131,7 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
.opened=${this.opened}
@opened-changed="${this._openedChanged}"
>
<h2>
${this.hass.localize(
`ui.panel.config.automation.thingtalk.link_devices.header`
)}
</h2>
<h2>Great! Now we need to link some devices.</h2>
<paper-dialog-scrollable>
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
${Object.entries(this.placeholders).map(
@@ -172,9 +168,8 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
${extraInfo && extraInfo.manualEntity
? html`
<h3>
${this.hass.localize(
`ui.panel.config.automation.thingtalk.link_devices.ambiguous_entities`
)}
One or more devices have more than one matching
entity, please pick the one you want to use.
</h3>
${Object.keys(extraInfo.manualEntity).map(
(idx) => html`
@@ -231,9 +226,7 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
}
return html`
<div class="error">
${this.hass.localize(
`ui.panel.config.automation.thingtalk.link_devices.unknown_placeholder`
)}<br />
Unknown placeholder<br />
${placeholder.domains}<br />
${placeholder.fields.map(
(field) => html` ${field}<br /> `
@@ -246,10 +239,10 @@ export class ThingTalkPlaceholders extends SubscribeMixin(LitElement) {
</paper-dialog-scrollable>
<div class="paper-dialog-buttons">
<mwc-button class="left" @click="${this.skip}">
${this.hass.localize(`ui.common.skip`)}
Skip
</mwc-button>
<mwc-button @click="${this._done}" .disabled=${!this._isDone}>
${this.hass.localize(`ui.panel.config.automation.thingtalk.create`)}
Create automation
</mwc-button>
</div>
</ha-paper-dialog>
@@ -196,8 +196,8 @@ export default class HaAutomationTriggerRow extends LitElement {
text: this.hass.localize(
"ui.panel.config.automation.editor.triggers.delete_confirm"
),
dismissText: this.hass.localize("ui.common.cancel"),
confirmText: this.hass.localize("ui.common.delete"),
dismissText: this.hass.localize("ui.common.no"),
confirmText: this.hass.localize("ui.common.yes"),
confirm: () => {
fireEvent(this, "value-changed", { value: null });
},
@@ -61,10 +61,12 @@ class CloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
color: var(--primary-color);
}
</style>
<hass-subpage hass="[[hass]]" header="Home Assistant Cloud">
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
<div class="content">
<ha-config-section is-wide="[[isWide]]">
<span slot="header">Home Assistant Cloud</span>
<span slot="header"
>[[localize('ui.panel.config.cloud.caption')]]</span
>
<div slot="introduction">
<p>
[[localize('ui.panel.config.cloud.account.thank_you_note')]]
+3 -3
View File
@@ -214,9 +214,9 @@ class CloudAlexa extends LitElement {
}
return html`
<hass-subpage .hass=${this.hass} header="${this.hass!.localize(
"ui.panel.config.cloud.alexa.title"
)}">
<hass-subpage header="${this.hass!.localize(
"ui.panel.config.cloud.alexa.title"
)}">
${
emptyFilter
? html`
@@ -119,8 +119,8 @@ export class DialogManageCloudhook extends LitElement {
text: this.hass!.localize(
"ui.panel.config.cloud.dialog_cloudhook.confirm_disable"
),
dismissText: this.hass!.localize("ui.common.cancel"),
confirmText: this.hass!.localize("ui.common.disable"),
dismissText: this.hass!.localize("ui.common.no"),
confirmText: this.hass!.localize("ui.common.yes"),
confirm: () => {
this._params!.disableHook();
this._closeDialog();
@@ -46,7 +46,6 @@ class CloudForgotPassword extends LocalizeMixin(EventsMixin(PolymerElement)) {
}
</style>
<hass-subpage
hass="[[hass]]"
header="[[localize('ui.panel.config.cloud.forgot_password.title')]]"
>
<div class="content">
@@ -237,9 +237,9 @@ class CloudGoogleAssistant extends LitElement {
}
return html`
<hass-subpage
.hass=${this.hass}
.header=${this.hass!.localize("ui.panel.config.cloud.google.title")}>
<hass-subpage header="${this.hass!.localize(
"ui.panel.config.cloud.google.title"
)}">
${
emptyFilter
? html`
+4 -2
View File
@@ -76,10 +76,12 @@ class CloudLogin extends LocalizeMixin(
left: 8px;
}
</style>
<hass-subpage hass="[[hass]]" header="Home Assistant Cloud">
<hass-subpage header="[[localize('ui.panel.config.cloud.caption')]]">
<div class="content">
<ha-config-section is-wide="[[isWide]]">
<span slot="header">Home Assistant Cloud</span>
<span slot="header"
>[[localize('ui.panel.config.cloud.caption')]]</span
>
<div slot="introduction">
<p>
[[localize('ui.panel.config.cloud.login.introduction')]]
@@ -47,7 +47,7 @@ class CloudRegister extends LocalizeMixin(EventsMixin(PolymerElement)) {
display: none;
}
</style>
<hass-subpage hass="[[hass]]" header="[[localize('ui.panel.config.cloud.register.title')]]">
<hass-subpage header="[[localize('ui.panel.config.cloud.register.title')]]">
<div class="content">
<ha-config-section is-wide="[[isWide]]">
<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>
<paper-input
disabled="[[item.secondary]]"
label="Icon"
label="icon"
value="{{item.value}}"
>
</paper-input>
@@ -2,7 +2,6 @@ import "@polymer/paper-input/paper-input";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { formatAttributeName } from "../../../../util/hass-attributes-util";
class HaCustomizeString extends PolymerElement {
static get template() {
@@ -26,10 +25,7 @@ class HaCustomizeString extends PolymerElement {
}
getLabel(item) {
return (
formatAttributeName(item.description) +
(item.type === "json" ? " (JSON formatted)" : "")
);
return item.description + (item.type === "json" ? " (JSON formatted)" : "");
}
}
customElements.define("ha-customize-string", HaCustomizeString);
@@ -22,8 +22,6 @@ import { configSections } from "../ha-panel-config";
import "./ha-config-navigation";
import { mdiCloudLock } from "@mdi/js";
const CONF_HAPPENING = new Date() < new Date("2020-12-13T23:00:00Z");
@customElement("ha-config-dashboard")
class HaConfigDashboard extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -60,7 +58,7 @@ class HaConfigDashboard extends LitElement {
{
component: "cloud",
path: "/config/cloud",
name: "Home Assistant Cloud",
translationKey: "ui.panel.config.cloud.caption",
info: this.cloudStatus,
iconPath: mdiCloudLock,
},
@@ -69,19 +67,6 @@ class HaConfigDashboard extends LitElement {
</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(
(section) => html`
<ha-card>
@@ -180,22 +165,6 @@ class HaConfigDashboard extends LitElement {
text-decoration: none;
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 {
text-align: center;
color: var(--secondary-text-color);
@@ -9,7 +9,7 @@ import {
property,
TemplateResult,
} from "lit-element";
import { canShowPage } from "../../../common/config/can_show_page";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import "../../../components/ha-card";
import "../../../components/ha-icon-next";
import { CloudStatus, CloudStatusLoggedIn } from "../../../data/cloud";
@@ -27,7 +27,10 @@ class HaConfigNavigation extends LitElement {
protected render(): TemplateResult {
return html`
${this.pages.map((page) =>
canShowPage(this.hass, page)
(!page.component ||
page.core ||
isComponentLoaded(this.hass, page.component)) &&
(!page.advancedOnly || this.showAdvanced)
? html`
<a
href=${`/config/${page.component}`}
@@ -40,8 +43,7 @@ class HaConfigNavigation extends LitElement {
slot="item-icon"
></ha-svg-icon>
<paper-item-body two-line>
${page.name ||
this.hass.localize(
${this.hass.localize(
page.translationKey ||
`ui.panel.config.${page.component}.caption`
)}
@@ -45,7 +45,6 @@ import { configSections } from "../ha-panel-config";
import "./device-detail/ha-device-entities-card";
import "./device-detail/ha-device-info-card";
import { showDeviceAutomationDialog } from "./device-detail/show-dialog-device-automation";
import { brandsUrl } from "../../../util/brands-url";
export interface EntityRegistryStateEntry extends EntityRegistryEntry {
stateName?: string | null;
@@ -144,10 +143,9 @@ export class HaConfigDevicePage extends LitElement {
if (!device) {
return html`
<hass-error-screen
.hass=${this.hass}
.error=${this.hass.localize(
error="${this.hass.localize(
"ui.panel.config.devices.device_not_found"
)}
)}"
></hass-error-screen>
`;
}
@@ -224,19 +222,14 @@ export class HaConfigDevicePage extends LitElement {
`
: ""
}
${
integrations.length
? html`
<img
src=${brandsUrl(integrations[0], "logo")}
referrerpolicy="no-referrer"
@load=${this._onImageLoad}
@error=${this._onImageError}
/>
`
: ""
}
<img
src="https://brands.home-assistant.io/${
integrations[0]
}/logo.png"
referrerpolicy="no-referrer"
@load=${this._onImageLoad}
@error=${this._onImageError}
/>
</div>
</div>
<div class="column">
@@ -594,7 +587,7 @@ export class HaConfigDevicePage extends LitElement {
text: this.hass.localize(
"ui.panel.config.devices.confirm_rename_entity_ids_warning"
),
confirmText: this.hass.localize("ui.common.rename"),
confirmText: this.hass.localize("ui.common.yes"),
dismissText: this.hass.localize("ui.common.no"),
warning: true,
}));
@@ -17,7 +17,6 @@ import {
ExtEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
import type { PolymerChangedEvent } from "../../../polymer-types";
import type { HomeAssistant } from "../../../types";
@@ -44,27 +43,7 @@ export class HaEntityRegistryBasicEditor extends LitElement {
params.disabled_by = this._disabledBy;
}
try {
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
),
});
}
await updateEntityRegistryEntry(this.hass!, this._origEntityId, params);
} finally {
this._submitting = false;
}
@@ -23,10 +23,7 @@ import {
removeEntityRegistryEntry,
updateEntityRegistryEntry,
} from "../../../data/entity_registry";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../dialogs/generic/show-dialog-box";
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
import type { PolymerChangedEvent } from "../../../polymer-types";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant } from "../../../types";
@@ -194,27 +191,7 @@ export class EntityRegistrySettings extends LitElement {
params.disabled_by = this._disabledBy;
}
try {
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
),
});
}
await updateEntityRegistryEntry(this.hass!, this._origEntityId, params);
fireEvent(this as HTMLElement, "close-dialog");
} catch (err) {
this._error = err.message || "Unknown error";

Some files were not shown because too many files have changed in this diff Show More