mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-18 23:06:40 +00:00
commit
484b1c8444
51
build-scripts/gulp/app.js
Normal file
51
build-scripts/gulp/app.js
Normal file
@ -0,0 +1,51 @@
|
||||
// Run HA develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-app",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel(
|
||||
"gen-service-worker-dev",
|
||||
"gen-icons",
|
||||
"gen-pages-dev",
|
||||
"gen-index-html-dev",
|
||||
"build-translations"
|
||||
),
|
||||
"copy-static",
|
||||
"webpack-watch-app"
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"build-app",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel("gen-icons", "build-translations"),
|
||||
"copy-static",
|
||||
gulp.parallel(
|
||||
"webpack-prod-app",
|
||||
// Do not compress static files in CI, it's SLOW.
|
||||
...(process.env.CI === "true" ? [] : ["compress-static"])
|
||||
),
|
||||
gulp.parallel(
|
||||
"gen-pages-prod",
|
||||
"gen-index-html-prod",
|
||||
"gen-service-worker-prod"
|
||||
)
|
||||
)
|
||||
);
|
@ -3,3 +3,4 @@ const gulp = require("gulp");
|
||||
const config = require("../paths");
|
||||
|
||||
gulp.task("clean", () => del([config.root, config.build_dir]));
|
||||
gulp.task("clean-demo", () => del([config.demo_root, config.build_dir]));
|
||||
|
36
build-scripts/gulp/demo.js
Normal file
36
build-scripts/gulp/demo.js
Normal file
@ -0,0 +1,36 @@
|
||||
// Run HA develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
|
||||
gulp.task(
|
||||
"develop-demo",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean-demo",
|
||||
gulp.parallel("gen-icons", "gen-icons-demo", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-dev-server-demo"
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task(
|
||||
"build-demo",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean-demo",
|
||||
gulp.parallel("gen-icons", "gen-icons-demo", "build-translations"),
|
||||
"copy-static-demo",
|
||||
"webpack-prod-demo"
|
||||
)
|
||||
);
|
@ -1,29 +0,0 @@
|
||||
// Run HA develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
|
||||
gulp.task(
|
||||
"develop",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "development";
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel(
|
||||
"copy-static",
|
||||
"gen-service-worker-dev",
|
||||
"gen-icons",
|
||||
"gen-pages-dev",
|
||||
"gen-index-html-dev",
|
||||
gulp.series("build-translations", "copy-translations")
|
||||
),
|
||||
"webpack-watch"
|
||||
)
|
||||
);
|
@ -5,17 +5,21 @@ const path = require("path");
|
||||
const fs = require("fs-extra");
|
||||
const zopfli = require("gulp-zopfli-green");
|
||||
const merge = require("merge-stream");
|
||||
const config = require("../paths");
|
||||
const paths = require("../paths");
|
||||
|
||||
const npmPath = (...parts) =>
|
||||
path.resolve(config.polymer_dir, "node_modules", ...parts);
|
||||
const polyPath = (...parts) => path.resolve(config.polymer_dir, ...parts);
|
||||
const staticPath = (...parts) => path.resolve(config.root, "static", ...parts);
|
||||
path.resolve(paths.polymer_dir, "node_modules", ...parts);
|
||||
const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
|
||||
|
||||
const copyFileDir = (fromFile, toDir) =>
|
||||
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
|
||||
|
||||
function copyTranslations() {
|
||||
const genStaticPath = (staticDir) => (...parts) =>
|
||||
path.resolve(staticDir, ...parts);
|
||||
|
||||
function copyTranslations(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// Translation output
|
||||
fs.copySync(
|
||||
polyPath("build-translations/output"),
|
||||
@ -23,9 +27,8 @@ function copyTranslations() {
|
||||
);
|
||||
}
|
||||
|
||||
function copyStatic() {
|
||||
// Basic static files
|
||||
fs.copySync(polyPath("public"), config.root);
|
||||
function copyPolyfills(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// Web Component polyfills and adapters
|
||||
copyFileDir(
|
||||
@ -40,31 +43,16 @@ function copyStatic() {
|
||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js.map"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
|
||||
// Local fonts
|
||||
fs.copySync(npmPath("@polymer/font-roboto-local/fonts"), staticPath("fonts"));
|
||||
|
||||
// External dependency assets
|
||||
copyFileDir(
|
||||
npmPath("react-big-calendar/lib/css/react-big-calendar.css"),
|
||||
staticPath("panels/calendar/")
|
||||
);
|
||||
copyFileDir(
|
||||
npmPath("leaflet/dist/leaflet.css"),
|
||||
staticPath("images/leaflet/")
|
||||
);
|
||||
fs.copySync(
|
||||
npmPath("leaflet/dist/images"),
|
||||
staticPath("images/leaflet/images/")
|
||||
);
|
||||
}
|
||||
|
||||
gulp.task("copy-static", (done) => {
|
||||
copyStatic();
|
||||
done();
|
||||
});
|
||||
function copyFonts(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
// Local fonts
|
||||
fs.copySync(npmPath("@polymer/font-roboto-local/fonts"), staticPath("fonts"));
|
||||
}
|
||||
|
||||
gulp.task("compress-static", () => {
|
||||
function compressStatic(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
const fonts = gulp
|
||||
.src(staticPath("fonts/**/*.ttf"))
|
||||
.pipe(zopfli())
|
||||
@ -79,9 +67,44 @@ gulp.task("compress-static", () => {
|
||||
.pipe(gulp.dest(staticPath("translations")));
|
||||
|
||||
return merge(fonts, polyfills, translations);
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task("copy-translations", (done) => {
|
||||
copyTranslations();
|
||||
gulp.task("copy-static", (done) => {
|
||||
const staticDir = paths.static;
|
||||
const staticPath = genStaticPath(paths.static);
|
||||
// Basic static files
|
||||
fs.copySync(polyPath("public"), paths.root);
|
||||
|
||||
copyPolyfills(staticDir);
|
||||
copyFonts(staticDir);
|
||||
copyTranslations(staticDir);
|
||||
|
||||
// Panel assets
|
||||
copyFileDir(
|
||||
npmPath("react-big-calendar/lib/css/react-big-calendar.css"),
|
||||
staticPath("panels/calendar/")
|
||||
);
|
||||
copyFileDir(
|
||||
npmPath("leaflet/dist/leaflet.css"),
|
||||
staticPath("images/leaflet/")
|
||||
);
|
||||
fs.copySync(
|
||||
npmPath("leaflet/dist/images"),
|
||||
staticPath("images/leaflet/images/")
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
gulp.task("compress-static", () => compressStatic(paths.static));
|
||||
|
||||
gulp.task("copy-static-demo", (done) => {
|
||||
// Copy app static files
|
||||
fs.copySync(polyPath("public"), paths.demo_root);
|
||||
// Copy demo static files
|
||||
fs.copySync(path.resolve(paths.demo_dir, "public"), paths.demo_root);
|
||||
|
||||
copyPolyfills(paths.demo_static);
|
||||
copyFonts(paths.demo_static);
|
||||
copyTranslations(paths.demo_static);
|
||||
done();
|
||||
});
|
||||
|
@ -1,6 +1,7 @@
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const paths = require("../paths");
|
||||
|
||||
const ICON_PACKAGE_PATH = path.resolve(
|
||||
__dirname,
|
||||
@ -118,6 +119,15 @@ gulp.task("gen-icons-hass", (done) => {
|
||||
});
|
||||
gulp.task("gen-icons", gulp.series("gen-icons-hass", "gen-icons-mdi"));
|
||||
|
||||
gulp.task("gen-icons-demo", (done) => {
|
||||
const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo");
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.demo_dir, "hademo-icons.html"),
|
||||
generateIconset("hademo", iconNames)
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
findIcons,
|
||||
generateIconset,
|
||||
|
@ -1,31 +0,0 @@
|
||||
// Run HA develop mode
|
||||
const gulp = require("gulp");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
require("./gen-icons.js");
|
||||
require("./gather-static.js");
|
||||
require("./webpack.js");
|
||||
require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
|
||||
gulp.task(
|
||||
"build-release",
|
||||
gulp.series(
|
||||
async function setEnv() {
|
||||
process.env.NODE_ENV = "production";
|
||||
},
|
||||
"clean",
|
||||
gulp.parallel(
|
||||
"copy-static",
|
||||
"gen-icons",
|
||||
gulp.series("build-translations", "copy-translations")
|
||||
),
|
||||
gulp.parallel("webpack-prod", "compress-static"),
|
||||
gulp.parallel(
|
||||
"gen-pages-prod",
|
||||
"gen-index-html-prod",
|
||||
"gen-service-worker-prod"
|
||||
)
|
||||
)
|
||||
);
|
@ -1,7 +1,11 @@
|
||||
// Tasks to run webpack.
|
||||
const gulp = require("gulp");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { createAppConfig } = require("../webpack");
|
||||
const WebpackDevServer = require("webpack-dev-server");
|
||||
const log = require("fancy-log");
|
||||
const paths = require("../paths");
|
||||
const { createAppConfig, createDemoConfig } = require("../webpack");
|
||||
|
||||
const handler = (done) => (err, stats) => {
|
||||
if (err) {
|
||||
@ -12,7 +16,7 @@ const handler = (done) => (err, stats) => {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||
log(`Build done @ ${new Date().toLocaleTimeString()}`);
|
||||
|
||||
if (stats.hasErrors() || stats.hasWarnings()) {
|
||||
console.log(stats.toString("minimal"));
|
||||
@ -23,7 +27,7 @@ const handler = (done) => (err, stats) => {
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task("webpack-watch", () => {
|
||||
gulp.task("webpack-watch-app", () => {
|
||||
const compiler = webpack([
|
||||
createAppConfig({
|
||||
isProdBuild: false,
|
||||
@ -41,7 +45,7 @@ gulp.task("webpack-watch", () => {
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"webpack-prod",
|
||||
"webpack-prod-app",
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
webpack(
|
||||
@ -61,3 +65,52 @@ gulp.task(
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("webpack-dev-server-demo", () => {
|
||||
const compiler = webpack([
|
||||
createDemoConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: false,
|
||||
isStatsBuild: false,
|
||||
}),
|
||||
createDemoConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
||||
isStatsBuild: false,
|
||||
}),
|
||||
]);
|
||||
|
||||
new WebpackDevServer(compiler, {
|
||||
open: true,
|
||||
watchContentBase: true,
|
||||
contentBase: path.resolve(paths.demo_dir, "dist"),
|
||||
}).listen(8080, "localhost", function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
// Server listening
|
||||
log("[webpack-dev-server]", "http://localhost:8080");
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"webpack-prod-demo",
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
webpack(
|
||||
[
|
||||
createDemoConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
isStatsBuild: false,
|
||||
}),
|
||||
createDemoConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: true,
|
||||
isStatsBuild: false,
|
||||
}),
|
||||
],
|
||||
handler(resolve)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -2,9 +2,16 @@ var path = require("path");
|
||||
|
||||
module.exports = {
|
||||
polymer_dir: path.resolve(__dirname, ".."),
|
||||
|
||||
build_dir: path.resolve(__dirname, "../build"),
|
||||
root: path.resolve(__dirname, "../hass_frontend"),
|
||||
static: path.resolve(__dirname, "../hass_frontend/static"),
|
||||
output: path.resolve(__dirname, "../hass_frontend/frontend_latest"),
|
||||
output_es5: path.resolve(__dirname, "../hass_frontend/frontend_es5"),
|
||||
|
||||
demo_dir: path.resolve(__dirname, "../demo"),
|
||||
demo_root: path.resolve(__dirname, "../demo/dist"),
|
||||
demo_static: path.resolve(__dirname, "../demo/dist/static"),
|
||||
demo_output: path.resolve(__dirname, "../demo/dist/frontend_latest"),
|
||||
demo_output_es5: path.resolve(__dirname, "../demo/frontend_es5"),
|
||||
};
|
||||
|
@ -17,6 +17,12 @@ if (!version) {
|
||||
}
|
||||
version = version[0];
|
||||
|
||||
const genMode = (isProdBuild) => (isProdBuild ? "production" : "development");
|
||||
const genDevTool = (isProdBuild) =>
|
||||
isProdBuild ? "cheap-source-map" : "inline-cheap-module-source-map";
|
||||
const genChunkFilename = (isProdBuild, isStatsBuild) =>
|
||||
isProdBuild && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
|
||||
const resolve = {
|
||||
extensions: [".ts", ".js", ".json", ".tsx"],
|
||||
alias: {
|
||||
@ -29,6 +35,20 @@ const resolve = {
|
||||
},
|
||||
};
|
||||
|
||||
const cssLoader = {
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
};
|
||||
const htmlLoader = {
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const plugins = [
|
||||
// Ignore moment.js locales
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
@ -75,8 +95,6 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
] = `build-translations/output/${key}.json`;
|
||||
});
|
||||
|
||||
const publicPath = latestBuild ? "/frontend_latest/" : "/frontend_es5/";
|
||||
|
||||
const entry = {
|
||||
app: "./src/entrypoints/app.ts",
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
@ -88,28 +106,11 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
};
|
||||
|
||||
return {
|
||||
mode: isProdBuild ? "production" : "development",
|
||||
devtool: isProdBuild
|
||||
? "cheap-source-map "
|
||||
: "inline-cheap-module-source-map",
|
||||
mode: genMode(isProdBuild),
|
||||
devtool: genDevTool(isProdBuild),
|
||||
entry,
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader],
|
||||
},
|
||||
optimization: optimization(latestBuild),
|
||||
plugins: [
|
||||
@ -165,20 +166,56 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
if (!isProdBuild || dontHash.has(chunk.name)) return `${chunk.name}.js`;
|
||||
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
|
||||
},
|
||||
chunkFilename:
|
||||
isProdBuild && !isStatsBuild
|
||||
? "chunk.[chunkhash].js"
|
||||
: "[name].chunk.js",
|
||||
chunkFilename: genChunkFilename(isProdBuild, isStatsBuild),
|
||||
path: latestBuild ? paths.output : paths.output_es5,
|
||||
publicPath,
|
||||
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
|
||||
},
|
||||
resolve,
|
||||
};
|
||||
};
|
||||
|
||||
const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
|
||||
return {
|
||||
mode: genMode(isProdBuild),
|
||||
devtool: genDevTool(isProdBuild),
|
||||
entry: {
|
||||
main: "./demo/src/entrypoint.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.ts",
|
||||
},
|
||||
module: {
|
||||
rules: [babelLoaderConfig({ latestBuild }), cssLoader, htmlLoader],
|
||||
},
|
||||
optimization: optimization(latestBuild),
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: !isProdBuild,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify("DEMO"),
|
||||
__DEMO__: true,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProdBuild ? "production" : "development"
|
||||
),
|
||||
}),
|
||||
...plugins,
|
||||
].filter(Boolean),
|
||||
resolve,
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
chunkFilename: genChunkFilename(isProdBuild, isStatsBuild),
|
||||
path: path.resolve(
|
||||
paths.demo_root,
|
||||
latestBuild ? "frontend_latest" : "frontend_es5"
|
||||
),
|
||||
publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
resolve,
|
||||
plugins,
|
||||
optimization,
|
||||
createAppConfig,
|
||||
createDemoConfig,
|
||||
};
|
||||
|
@ -74,9 +74,6 @@
|
||||
content="https://www.home-assistant.io/images/default-social.png"
|
||||
/>
|
||||
<title>Home Assistant Demo</title>
|
||||
<script src="./custom-elements-es5-adapter.js"></script>
|
||||
<script src="./compatibility.js"></script>
|
||||
<script src="./main.js" async></script>
|
||||
<style>
|
||||
body {
|
||||
font-family: Roboto, Noto, sans-serif;
|
||||
@ -98,6 +95,47 @@
|
||||
<body>
|
||||
<div id="ha-init-skeleton"></div>
|
||||
<ha-demo></ha-demo>
|
||||
<script>
|
||||
function _ls(src) {
|
||||
var doc = document.documentElement;
|
||||
var script = doc.insertBefore(
|
||||
document.createElement("script"),
|
||||
doc.lastChild
|
||||
);
|
||||
script.type = "text/javascript";
|
||||
script.src = src;
|
||||
}
|
||||
window.Polymer = {
|
||||
lazyRegister: true,
|
||||
useNativeCSSProperties: true,
|
||||
dom: "shadow",
|
||||
suppressTemplateNotifications: true,
|
||||
suppressBindingNotifications: true,
|
||||
};
|
||||
var webComponentsSupported =
|
||||
"customElements" in window &&
|
||||
"content" in document.createElement("template");
|
||||
if (!webComponentsSupported) {
|
||||
_ls("/static/polyfills/webcomponents-bundle.js");
|
||||
}
|
||||
var isS101 = /\s+Version\/10\.1(?:\.\d+)?\s+Safari\//.test(
|
||||
navigator.userAgent
|
||||
);
|
||||
</script>
|
||||
|
||||
<script type="module" src="./frontend_latest/main.js"></script>
|
||||
|
||||
<script nomodule>
|
||||
(function() {
|
||||
// // Safari 10.1 supports type=module but ignores nomodule, so we add this check.
|
||||
if (!isS101) {
|
||||
_ls("./static/polyfills/custom-elements-es5-adapter.js");
|
||||
_ls("./frontend_es5/compatibility.js");
|
||||
_ls("./frontend_es5/main.js");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
||||
(function(d, t) {
|
||||
|
0
demo/public/service_worker.js
Normal file
0
demo/public/service_worker.js
Normal file
@ -4,16 +4,6 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
OUTPUT_DIR=dist
|
||||
|
||||
rm -rf $OUTPUT_DIR
|
||||
mkdir $OUTPUT_DIR
|
||||
node script/gen-icons.js
|
||||
|
||||
cd ..
|
||||
DEMO=1 ./node_modules/.bin/gulp build-translations gen-icons
|
||||
cd demo
|
||||
|
||||
NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js
|
||||
./node_modules/.bin/gulp build-demo
|
||||
|
@ -4,12 +4,6 @@
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
node script/gen-icons.js
|
||||
|
||||
cd ..
|
||||
DEMO=1 ./node_modules/.bin/gulp build-translations gen-icons
|
||||
cd demo
|
||||
|
||||
../node_modules/.bin/webpack-dev-server
|
||||
./node_modules/.bin/gulp develop-demo
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { HomeAssistantAppEl } from "../../src/layouts/app/home-assistant";
|
||||
import { HomeAssistantAppEl } from "../../src/layouts/home-assistant";
|
||||
import {
|
||||
provideHass,
|
||||
MockHomeAssistant,
|
||||
@ -18,7 +18,7 @@ import { HomeAssistant } from "../../src/types";
|
||||
import { mockFrontend } from "./stubs/frontend";
|
||||
|
||||
class HaDemo extends HomeAssistantAppEl {
|
||||
protected async _handleConnProm() {
|
||||
protected async _initialize() {
|
||||
const initial: Partial<MockHomeAssistant> = {
|
||||
panelUrl: (this as any).panelUrl,
|
||||
// Override updateHass so that the correct hass lifecycle methods are called
|
||||
@ -26,7 +26,7 @@ class HaDemo extends HomeAssistantAppEl {
|
||||
this._updateHass(hassUpdate),
|
||||
};
|
||||
|
||||
const hass = provideHass(this, initial);
|
||||
const hass = (this.hass = provideHass(this, initial));
|
||||
mockLovelace(hass);
|
||||
mockAuth(hass);
|
||||
mockTranslations(hass);
|
||||
|
@ -1,90 +1,13 @@
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const WorkboxPlugin = require("workbox-webpack-plugin");
|
||||
const { babelLoaderConfig } = require("../build-scripts/babel.js");
|
||||
const webpackBase = require("../build-scripts/webpack.js");
|
||||
const { createDemoConfig } = require("../build-scripts/webpack.js");
|
||||
|
||||
const isProd = process.env.NODE_ENV === "production";
|
||||
// This file exists because we haven't migrated the stats script yet
|
||||
|
||||
const isProdBuild = process.env.NODE_ENV === "production";
|
||||
const isStatsBuild = process.env.STATS === "1";
|
||||
const chunkFilename =
|
||||
isProd && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js";
|
||||
const buildPath = path.resolve(__dirname, "dist");
|
||||
const publicPath = "/";
|
||||
|
||||
const latestBuild = false;
|
||||
|
||||
module.exports = {
|
||||
mode: isProd ? "production" : "development",
|
||||
devtool: isProd ? "cheap-source-map" : "inline-source-map",
|
||||
entry: {
|
||||
main: "./src/entrypoint.ts",
|
||||
compatibility: "../src/entrypoints/compatibility.ts",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
babelLoaderConfig({ latestBuild }),
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: "raw-loader",
|
||||
},
|
||||
{
|
||||
test: /\.(html)$/,
|
||||
use: {
|
||||
loader: "html-loader",
|
||||
options: {
|
||||
exportAsEs6Default: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
optimization: webpackBase.optimization(latestBuild),
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__DEV__: false,
|
||||
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
|
||||
__VERSION__: JSON.stringify("DEMO"),
|
||||
__DEMO__: true,
|
||||
__STATIC_PATH__: "/static/",
|
||||
"process.env.NODE_ENV": JSON.stringify(
|
||||
isProd ? "production" : "development"
|
||||
),
|
||||
}),
|
||||
new CopyWebpackPlugin([
|
||||
"public",
|
||||
"../node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js",
|
||||
{ from: "../public", to: "static" },
|
||||
{ from: "../build-translations/output", to: "static/translations" },
|
||||
{
|
||||
from: "../node_modules/leaflet/dist/leaflet.css",
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
{
|
||||
from: "../node_modules/@polymer/font-roboto-local/fonts",
|
||||
to: "static/fonts",
|
||||
},
|
||||
{
|
||||
from: "../node_modules/leaflet/dist/images",
|
||||
to: "static/images/leaflet/",
|
||||
},
|
||||
]),
|
||||
...webpackBase.plugins,
|
||||
isProd &&
|
||||
new WorkboxPlugin.GenerateSW({
|
||||
swDest: "service_worker.js",
|
||||
importWorkboxFrom: "local",
|
||||
include: [],
|
||||
}),
|
||||
].filter(Boolean),
|
||||
resolve: webpackBase.resolve,
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
chunkFilename: chunkFilename,
|
||||
path: buildPath,
|
||||
publicPath,
|
||||
},
|
||||
devServer: {
|
||||
contentBase: "./public",
|
||||
},
|
||||
};
|
||||
module.exports = createDemoConfig({
|
||||
isProdBuild,
|
||||
isStatsBuild,
|
||||
latestBuild,
|
||||
});
|
||||
|
@ -9,7 +9,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../src/resources/ha-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioAddonAudio extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -10,7 +10,7 @@ import "../../../src/components/ha-label-badge";
|
||||
import "../../../src/components/ha-markdown";
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/resources/ha-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../src/mixins/events-mixin";
|
||||
import { navigate } from "../../../src/common/navigate";
|
||||
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
|
@ -5,7 +5,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import "../../../src/resources/ha-style";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioAddonNetwork extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -4,7 +4,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../src/mixins/events-mixin";
|
||||
|
||||
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../src/components/buttons/ha-call-api-button";
|
||||
import EventsMixin from "../../../src/mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../src/mixins/events-mixin";
|
||||
|
||||
class HassioSupervisorInfo extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -75,7 +75,7 @@
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"fecha": "^3.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^3.4.0",
|
||||
"home-assistant-js-websocket": "^4.1.1",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"jquery": "^3.3.1",
|
||||
"js-yaml": "^3.13.0",
|
||||
|
@ -6,4 +6,4 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
./node_modules/.bin/gulp build-release
|
||||
./node_modules/.bin/gulp build-app
|
||||
|
@ -6,4 +6,4 @@ set -e
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
./node_modules/.bin/gulp develop
|
||||
./node_modules/.bin/gulp develop-app
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20190502.0",
|
||||
version="20190507.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -3,7 +3,7 @@ import "@polymer/paper-item/paper-item-body";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
import { localizeLiteMixin } from "../mixins/localize-lite-mixin";
|
||||
import "../components/ha-icon-next";
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import { fetchThumbnailUrlWithCache } from "../data/camera";
|
||||
|
||||
|
@ -10,7 +10,7 @@ import computeStateDomain from "../common/entity/compute_state_domain";
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import stateMoreInfoType from "../common/entity/state_more_info_type";
|
||||
import canToggleState from "../common/entity/can_toggle_state";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
|
||||
class HaEntitiesCard extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
|
@ -6,7 +6,7 @@ import "../components/state-history-charts";
|
||||
import "../data/ha-state-history-data";
|
||||
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -9,7 +9,7 @@ import HassMediaPlayerEntity from "../util/hass-media-player-model";
|
||||
import { fetchMediaPlayerThumbnailWithCache } from "../data/media-player";
|
||||
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
|
@ -5,7 +5,7 @@ import "../components/ha-card";
|
||||
import "../components/ha-icon";
|
||||
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
class HaPlantCard extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -6,7 +6,7 @@ import computeStateName from "../common/entity/compute_state_name";
|
||||
import "../components/ha-card";
|
||||
import "../components/ha-icon";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
import LocalizeMixin from "../mixins/localize-mixin";
|
||||
import { computeRTL } from "../common/util/compute_rtl";
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default (a, b) => {
|
||||
export const compare = (a: string, b: string) => {
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
@ -8,3 +8,6 @@ export default (a, b) => {
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const caseInsensitiveCompare = (a: string, b: string) =>
|
||||
compare(a.toLowerCase(), b.toLowerCase());
|
||||
|
@ -4,8 +4,14 @@
|
||||
// be triggered. The function will be called after it stops being called for
|
||||
// N milliseconds. If `immediate` is passed, trigger the function on the
|
||||
// leading edge, instead of the trailing.
|
||||
export default function debounce(func, wait, immediate) {
|
||||
// tslint:disable-next-line: ban-types
|
||||
export const debounce = <T extends Function>(
|
||||
func: T,
|
||||
wait,
|
||||
immediate = false
|
||||
): T => {
|
||||
let timeout;
|
||||
// @ts-ignore
|
||||
return function(...args) {
|
||||
// tslint:disable:no-this-assignment
|
||||
// @ts-ignore
|
||||
@ -23,4 +29,4 @@ export default function debounce(func, wait, immediate) {
|
||||
func.apply(context, args);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "./ha-progress-button";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { forwardHaptic } from "../../util/haptics";
|
||||
import { forwardHaptic } from "../../data/haptics";
|
||||
|
||||
const isOn = (stateObj?: HassEntity) =>
|
||||
stateObj !== undefined && !STATES_OFF.includes(stateObj.state);
|
||||
@ -90,7 +90,7 @@ class HaEntityToggle extends LitElement {
|
||||
if (!this.hass || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
forwardHaptic(this, "light");
|
||||
forwardHaptic("light");
|
||||
const stateDomain = computeStateDomain(this.stateObj);
|
||||
let serviceDomain;
|
||||
let service;
|
||||
|
@ -27,9 +27,11 @@ class HaCard extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
transition: all 0.3s ease-out;
|
||||
position: relative;
|
||||
}
|
||||
.header:not(:empty),
|
||||
.header::slotted(*) {
|
||||
|
||||
.card-header,
|
||||
:host ::slotted(.card-header) {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
font-family: var(--ha-card-header-font-family, inherit);
|
||||
font-size: var(--ha-card-header-font-size, 24px);
|
||||
@ -38,12 +40,31 @@ class HaCard extends LitElement {
|
||||
padding: 24px 16px 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-content:not(:first-child)),
|
||||
slot:not(:first-child)::slotted(.card-content) {
|
||||
padding-top: 0px;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-content) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
:host ::slotted(.card-actions) {
|
||||
border-top: 1px solid #e8e8e8;
|
||||
padding: 5px 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<slot class="header" name="header">${this.header}</slot>
|
||||
${this.header
|
||||
? html`
|
||||
<div class="card-header">${this.header}</div>
|
||||
`
|
||||
: html``}
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/**
|
||||
* Color-picker custom element
|
||||
|
@ -5,7 +5,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "@vaadin/vaadin-combo-box/vaadin-combo-box-light";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
class HaComboBox extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
|
@ -31,7 +31,9 @@ export class HaDateInput extends LitElement {
|
||||
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;
|
||||
|
@ -8,7 +8,7 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "./ha-paper-slider";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
let loaded = null;
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import { getAppKey } from "../data/notify_html5";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
export const pushSupported =
|
||||
"serviceWorker" in navigator &&
|
||||
|
@ -2,7 +2,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
import isComponentLoaded from "../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
@ -3,7 +3,7 @@ import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../mixins/events-mixin";
|
||||
import { EventsMixin } from "../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -48,6 +48,7 @@ export class PaperTimeInput extends PolymerElement {
|
||||
margin: 0;
|
||||
display: none;
|
||||
}
|
||||
--paper-input-container-shared-input-style_-_-webkit-appearance: textfield;
|
||||
}
|
||||
|
||||
paper-dropdown-menu {
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { User, fetchUsers } from "../../data/user";
|
||||
import compare from "../../common/string/compare";
|
||||
import { compare } from "../../common/string/compare";
|
||||
|
||||
class HaEntityPicker extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { compare } from "../common/string/compare";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
|
||||
export interface AreaRegistryEntry {
|
||||
area_id: string;
|
||||
@ -9,9 +12,6 @@ export interface AreaRegistryEntryMutableParams {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const fetchAreaRegistry = (hass: HomeAssistant) =>
|
||||
hass.callWS<AreaRegistryEntry[]>({ type: "config/area_registry/list" });
|
||||
|
||||
export const createAreaRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
values: AreaRegistryEntryMutableParams
|
||||
@ -37,3 +37,33 @@ export const deleteAreaRegistryEntry = (hass: HomeAssistant, areaId: string) =>
|
||||
type: "config/area_registry/delete",
|
||||
area_id: areaId,
|
||||
});
|
||||
|
||||
const fetchAreaRegistry = (conn) =>
|
||||
conn
|
||||
.sendMessagePromise({
|
||||
type: "config/area_registry/list",
|
||||
})
|
||||
.then((areas) => areas.sort((ent1, ent2) => compare(ent1.name, ent2.name)));
|
||||
|
||||
const subscribeAreaRegistryUpdates = (conn, store) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchAreaRegistry(conn).then((areas) => store.setState(areas, true)),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"area_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeAreaRegistry = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (areas: AreaRegistryEntry[]) => void
|
||||
) =>
|
||||
createCollection<AreaRegistryEntry[]>(
|
||||
"_areaRegistry",
|
||||
fetchAreaRegistry,
|
||||
subscribeAreaRegistryUpdates,
|
||||
hass.connection,
|
||||
onChange
|
||||
);
|
||||
|
@ -14,6 +14,8 @@ export interface SignedPath {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export const hassUrl = `${location.protocol}//${location.host}`;
|
||||
|
||||
export const getSignedPath = (
|
||||
hass: HomeAssistant,
|
||||
path: string
|
||||
|
@ -1,4 +1,17 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
|
||||
export interface ConfigEntry {
|
||||
entry_id: string;
|
||||
domain: string;
|
||||
title: string;
|
||||
source: string;
|
||||
state: string;
|
||||
connection_class: string;
|
||||
supports_options: boolean;
|
||||
}
|
||||
|
||||
export interface FieldSchema {
|
||||
name: string;
|
||||
@ -9,7 +22,10 @@ export interface FieldSchema {
|
||||
export interface ConfigFlowProgress {
|
||||
flow_id: string;
|
||||
handler: string;
|
||||
context: { [key: string]: any };
|
||||
context: {
|
||||
title_placeholders: { [key: string]: string };
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ConfigFlowStepForm {
|
||||
@ -74,3 +90,53 @@ export const getConfigFlowsInProgress = (hass: HomeAssistant) =>
|
||||
|
||||
export const getConfigFlowHandlers = (hass: HomeAssistant) =>
|
||||
hass.callApi<string[]>("GET", "config/config_entries/flow_handlers");
|
||||
|
||||
const fetchConfigFlowInProgress = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
type: "config/entity_registry/list",
|
||||
});
|
||||
|
||||
const subscribeConfigFlowInProgressUpdates = (conn, store) =>
|
||||
debounce(
|
||||
conn.subscribeEvents(
|
||||
() =>
|
||||
fetchConfigFlowInProgress(conn).then((flows) =>
|
||||
store.setState(flows, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"config_entry_discovered"
|
||||
);
|
||||
|
||||
export const subscribeConfigFlowInProgress = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (flows: ConfigFlowProgress[]) => void
|
||||
) =>
|
||||
createCollection<ConfigFlowProgress[]>(
|
||||
"_configFlowProgress",
|
||||
fetchConfigFlowInProgress,
|
||||
subscribeConfigFlowInProgressUpdates,
|
||||
hass.connection,
|
||||
onChange
|
||||
);
|
||||
|
||||
export const getConfigEntries = (hass: HomeAssistant) =>
|
||||
hass.callApi<ConfigEntry[]>("GET", "config/config_entries/entry");
|
||||
|
||||
export const localizeConfigFlowTitle = (
|
||||
localize: LocalizeFunc,
|
||||
flow: ConfigFlowProgress
|
||||
) => {
|
||||
const placeholders = flow.context.title_placeholders || {};
|
||||
const placeholderKeys = Object.keys(placeholders);
|
||||
if (placeholderKeys.length === 0) {
|
||||
return localize(`component.${flow.handler}.config.title`);
|
||||
}
|
||||
const args: string[] = [];
|
||||
placeholderKeys.forEach((key) => {
|
||||
args.push(key);
|
||||
args.push(placeholders[key]);
|
||||
});
|
||||
return localize(`component.${flow.handler}.config.flow_title`, ...args);
|
||||
};
|
||||
|
22
src/data/connection-status.ts
Normal file
22
src/data/connection-status.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Broadcast connection status updates
|
||||
*/
|
||||
|
||||
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||
|
||||
export type ConnectionStatus = "connected" | "auth-invalid" | "disconnected";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"connection-status": ConnectionStatus;
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"connection-status": HASSDomEvent<ConnectionStatus>;
|
||||
}
|
||||
}
|
||||
|
||||
export const broadcastConnectionStatus = (status: ConnectionStatus) => {
|
||||
fireEvent(window, "connection-status", status);
|
||||
};
|
@ -1,4 +1,6 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
|
||||
export interface DeviceRegistryEntry {
|
||||
id: string;
|
||||
@ -18,9 +20,6 @@ export interface DeviceRegistryEntryMutableParams {
|
||||
name_by_user?: string;
|
||||
}
|
||||
|
||||
export const fetchDeviceRegistry = (hass: HomeAssistant) =>
|
||||
hass.callWS<DeviceRegistryEntry[]>({ type: "config/device_registry/list" });
|
||||
|
||||
export const updateDeviceRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
deviceId: string,
|
||||
@ -31,3 +30,33 @@ export const updateDeviceRegistryEntry = (
|
||||
device_id: deviceId,
|
||||
...updates,
|
||||
});
|
||||
|
||||
const fetchDeviceRegistry = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
type: "config/device_registry/list",
|
||||
});
|
||||
|
||||
const subscribeDeviceRegistryUpdates = (conn, store) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchDeviceRegistry(conn).then((devices) =>
|
||||
store.setState(devices, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"device_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeDeviceRegistry = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (devices: DeviceRegistryEntry[]) => void
|
||||
) =>
|
||||
createCollection<DeviceRegistryEntry[]>(
|
||||
"_dr",
|
||||
fetchDeviceRegistry,
|
||||
subscribeDeviceRegistryUpdates,
|
||||
hass.connection,
|
||||
onChange
|
||||
);
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { createCollection } from "home-assistant-js-websocket";
|
||||
import { HomeAssistant } from "../types";
|
||||
import computeStateName from "../common/entity/compute_state_name";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
|
||||
export interface EntityRegistryEntry {
|
||||
entity_id: string;
|
||||
@ -26,11 +28,6 @@ export const computeEntityRegistryName = (
|
||||
return state ? computeStateName(state) : null;
|
||||
};
|
||||
|
||||
export const fetchEntityRegistry = (
|
||||
hass: HomeAssistant
|
||||
): Promise<EntityRegistryEntry[]> =>
|
||||
hass.callWS<EntityRegistryEntry[]>({ type: "config/entity_registry/list" });
|
||||
|
||||
export const updateEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@ -50,3 +47,33 @@ export const removeEntityRegistryEntry = (
|
||||
type: "config/entity_registry/remove",
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
||||
const fetchEntityRegistry = (conn) =>
|
||||
conn.sendMessagePromise({
|
||||
type: "config/entity_registry/list",
|
||||
});
|
||||
|
||||
const subscribeEntityRegistryUpdates = (conn, store) =>
|
||||
conn.subscribeEvents(
|
||||
debounce(
|
||||
() =>
|
||||
fetchEntityRegistry(conn).then((entities) =>
|
||||
store.setState(entities, true)
|
||||
),
|
||||
500,
|
||||
true
|
||||
),
|
||||
"entity_registry_updated"
|
||||
);
|
||||
|
||||
export const subscribeEntityRegistry = (
|
||||
hass: HomeAssistant,
|
||||
onChange: (entities: EntityRegistryEntry[]) => void
|
||||
) =>
|
||||
createCollection<EntityRegistryEntry[]>(
|
||||
"_entityRegistry",
|
||||
fetchEntityRegistry,
|
||||
subscribeEntityRegistryUpdates,
|
||||
hass.connection,
|
||||
onChange
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Utility function that enables haptic feedback
|
||||
* Broadcast haptic feedback requests
|
||||
*/
|
||||
|
||||
import { fireEvent, HASSDomEvent } from "../common/dom/fire_event";
|
||||
@ -27,6 +27,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export const forwardHaptic = (el: HTMLElement, hapticType: HapticType) => {
|
||||
fireEvent(el, "haptic", hapticType);
|
||||
export const forwardHaptic = (hapticType: HapticType) => {
|
||||
fireEvent(window, "haptic", hapticType);
|
||||
};
|
@ -1,14 +1,26 @@
|
||||
import { handleFetchPromise } from "../util/hass-call-api";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface OnboardingStep {
|
||||
step: string;
|
||||
done: boolean;
|
||||
export interface OnboardingUserStepResponse {
|
||||
auth_code: string;
|
||||
}
|
||||
|
||||
interface UserStepResponse {
|
||||
export interface OnboardingIntegrationStepResponse {
|
||||
auth_code: string;
|
||||
}
|
||||
|
||||
export interface OnboardingResponses {
|
||||
user: OnboardingUserStepResponse;
|
||||
integration: OnboardingIntegrationStepResponse;
|
||||
}
|
||||
|
||||
export type ValidOnboardingStep = keyof OnboardingResponses;
|
||||
|
||||
export interface OnboardingStep {
|
||||
step: ValidOnboardingStep;
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
export const fetchOnboardingOverview = () =>
|
||||
fetch("/api/onboarding", { credentials: "same-origin" });
|
||||
|
||||
@ -17,11 +29,22 @@ export const onboardUserStep = (params: {
|
||||
name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
language: string;
|
||||
}) =>
|
||||
handleFetchPromise<UserStepResponse>(
|
||||
handleFetchPromise<OnboardingUserStepResponse>(
|
||||
fetch("/api/onboarding/users", {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify(params),
|
||||
})
|
||||
);
|
||||
|
||||
export const onboardIntegrationStep = (
|
||||
hass: HomeAssistant,
|
||||
params: { client_id: string }
|
||||
) =>
|
||||
hass.callApi<OnboardingIntegrationStepResponse>(
|
||||
"POST",
|
||||
"onboarding/integration",
|
||||
params
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
import "../../components/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
@ -37,10 +38,14 @@ import "./step-flow-abort";
|
||||
import "./step-flow-create-entry";
|
||||
import {
|
||||
DeviceRegistryEntry,
|
||||
fetchDeviceRegistry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import { AreaRegistryEntry, fetchAreaRegistry } from "../../data/area_registry";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../data/area_registry";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { caseInsensitiveCompare } from "../../common/string/compare";
|
||||
|
||||
let instance = 0;
|
||||
|
||||
@ -57,30 +62,19 @@ declare global {
|
||||
@customElement("dialog-config-flow")
|
||||
class ConfigFlowDialog extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property()
|
||||
private _params?: HaConfigFlowParams;
|
||||
|
||||
@property()
|
||||
private _loading = true;
|
||||
|
||||
@property() private _params?: HaConfigFlowParams;
|
||||
@property() private _loading = true;
|
||||
private _instance = instance;
|
||||
|
||||
@property()
|
||||
private _step:
|
||||
@property() private _step:
|
||||
| ConfigFlowStep
|
||||
| undefined
|
||||
// Null means we need to pick a config flow
|
||||
| null;
|
||||
|
||||
@property()
|
||||
private _devices?: DeviceRegistryEntry[];
|
||||
|
||||
@property()
|
||||
private _areas?: AreaRegistryEntry[];
|
||||
|
||||
@property()
|
||||
private _handlers?: string[];
|
||||
@property() private _devices?: DeviceRegistryEntry[];
|
||||
@property() private _areas?: AreaRegistryEntry[];
|
||||
@property() private _handlers?: string[];
|
||||
private _unsubAreas?: UnsubscribeFunc;
|
||||
private _unsubDevices?: UnsubscribeFunc;
|
||||
|
||||
public async showDialog(params: HaConfigFlowParams): Promise<void> {
|
||||
this._params = params;
|
||||
@ -95,7 +89,13 @@ class ConfigFlowDialog extends LitElement {
|
||||
this._loading = true;
|
||||
this.updateComplete.then(() => this._scheduleCenterDialog());
|
||||
try {
|
||||
this._handlers = await getConfigFlowHandlers(this.hass);
|
||||
this._handlers = (await getConfigFlowHandlers(this.hass)).sort(
|
||||
(handlerA, handlerB) =>
|
||||
caseInsensitiveCompare(
|
||||
this.hass.localize(`component.${handlerA}.config.title`),
|
||||
this.hass.localize(`component.${handlerB}.config.title`)
|
||||
)
|
||||
);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
@ -196,6 +196,10 @@ class ConfigFlowDialog extends LitElement {
|
||||
this._fetchDevices(this._step.result);
|
||||
this._fetchAreas();
|
||||
}
|
||||
|
||||
if (changedProps.has("_devices") && this._dialog) {
|
||||
this._scheduleCenterDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private _scheduleCenterDialog() {
|
||||
@ -207,16 +211,17 @@ class ConfigFlowDialog extends LitElement {
|
||||
}
|
||||
|
||||
private async _fetchDevices(configEntryId) {
|
||||
// Wait 5 seconds to give integrations time to find devices
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
const devices = await fetchDeviceRegistry(this.hass);
|
||||
this._devices = devices.filter((device) =>
|
||||
device.config_entries.includes(configEntryId)
|
||||
);
|
||||
this._unsubDevices = subscribeDeviceRegistry(this.hass, (devices) => {
|
||||
this._devices = devices.filter((device) =>
|
||||
device.config_entries.includes(configEntryId)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async _fetchAreas() {
|
||||
this._areas = await fetchAreaRegistry(this.hass);
|
||||
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
|
||||
this._areas = areas;
|
||||
});
|
||||
}
|
||||
|
||||
private async _processStep(
|
||||
@ -261,6 +266,14 @@ class ConfigFlowDialog extends LitElement {
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
this._devices = undefined;
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
this._unsubAreas = undefined;
|
||||
}
|
||||
if (this._unsubDevices) {
|
||||
this._unsubDevices();
|
||||
this._unsubDevices = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
|
@ -169,6 +169,18 @@ class StepFlowCreateEntry extends LitElement {
|
||||
.buttons > *:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
paper-dropdown-menu-light {
|
||||
cursor: pointer;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.device {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
class MoreInfoAlarmControlPanel extends LocalizeMixin(
|
||||
|
@ -15,7 +15,7 @@ import attributeClassNames from "../../../common/entity/attribute_class_names";
|
||||
import featureClassNames from "../../../common/entity/feature_class_names";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
|
||||
|
@ -10,7 +10,7 @@ import "../../../components/ha-attributes";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
|
||||
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
|
@ -10,7 +10,7 @@ import "../../../components/ha-labeled-slider";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
|
||||
import featureClassNames from "../../../common/entity/feature_class_names";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
const FEATURE_CLASS_NAMES = {
|
||||
@ -182,12 +182,17 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
dynamic-align=""
|
||||
label="[[localize('ui.card.light.effect')]]"
|
||||
>
|
||||
<paper-listbox slot="dropdown-content" selected="{{effectIndex}}">
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="[[stateObj.attributes.effect]]"
|
||||
on-selected-changed="effectChanged"
|
||||
attr-for-selected="item-name"
|
||||
>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[stateObj.attributes.effect_list]]"
|
||||
>
|
||||
<paper-item>[[item]]</paper-item>
|
||||
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
@ -212,12 +217,6 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
observer: "stateObjChanged",
|
||||
},
|
||||
|
||||
effectIndex: {
|
||||
type: Number,
|
||||
value: -1,
|
||||
observer: "effectChanged",
|
||||
},
|
||||
|
||||
brightnessSliderValue: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
@ -264,13 +263,6 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
s: newVal.attributes.hs_color[1] / 100,
|
||||
};
|
||||
}
|
||||
if (newVal.attributes.effect_list) {
|
||||
props.effectIndex = newVal.attributes.effect_list.indexOf(
|
||||
newVal.attributes.effect
|
||||
);
|
||||
} else {
|
||||
props.effectIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
this.setProperties(props);
|
||||
@ -293,17 +285,15 @@ class MoreInfoLight extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
effectChanged(effectIndex) {
|
||||
var effectInput;
|
||||
// Selected Option will transition to '' before transitioning to new value
|
||||
if (effectIndex === "" || effectIndex === -1) return;
|
||||
effectChanged(ev) {
|
||||
var oldVal = this.stateObj.attributes.effect;
|
||||
var newVal = ev.detail.value;
|
||||
|
||||
effectInput = this.stateObj.attributes.effect_list[effectIndex];
|
||||
if (effectInput === this.stateObj.attributes.effect) return;
|
||||
if (!newVal || oldVal === newVal) return;
|
||||
|
||||
this.hass.callService("light", "turn_on", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
effect: effectInput,
|
||||
effect: newVal,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import HassMediaPlayerEntity from "../../../util/hass-media-player-model";
|
||||
|
||||
import attributeClassNames from "../../../common/entity/attribute_class_names";
|
||||
import isComponentLoaded from "../../../common/config/is_component_loaded";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
|
||||
|
@ -109,12 +109,17 @@ class MoreInfoVacuum extends PolymerElement {
|
||||
dynamic-align=""
|
||||
label="Fan speed"
|
||||
>
|
||||
<paper-listbox slot="dropdown-content" selected="{{fanSpeedIndex}}">
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="[[stateObj.attributes.fan_speed]]"
|
||||
on-selected-changed="fanSpeedChanged"
|
||||
attr-for-selected="item-name"
|
||||
>
|
||||
<template
|
||||
is="dom-repeat"
|
||||
items="[[stateObj.attributes.fan_speed_list]]"
|
||||
>
|
||||
<paper-item>[[item]]</paper-item>
|
||||
<paper-item item-name$="[[item]]">[[item]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
@ -150,12 +155,6 @@ class MoreInfoVacuum extends PolymerElement {
|
||||
stateObj: {
|
||||
type: Object,
|
||||
},
|
||||
|
||||
fanSpeedIndex: {
|
||||
type: Number,
|
||||
value: -1,
|
||||
observer: "fanSpeedChanged",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -206,17 +205,15 @@ class MoreInfoVacuum extends PolymerElement {
|
||||
);
|
||||
}
|
||||
|
||||
fanSpeedChanged(fanSpeedIndex) {
|
||||
var fanSpeedInput;
|
||||
// Selected Option will transition to '' before transitioning to new value
|
||||
if (fanSpeedIndex === "" || fanSpeedIndex === -1) return;
|
||||
fanSpeedChanged(ev) {
|
||||
var oldVal = this.stateObj.attributes.fan_speed;
|
||||
var newVal = ev.detail.value;
|
||||
|
||||
fanSpeedInput = this.stateObj.attributes.fan_speed_list[fanSpeedIndex];
|
||||
if (fanSpeedInput === this.stateObj.attributes.fan_speed) return;
|
||||
if (!newVal || oldVal === newVal) return;
|
||||
|
||||
this.hass.callService("vacuum", "set_fan_speed", {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
fan_speed: fanSpeedInput,
|
||||
fan_speed: newVal,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import "../../../components/ha-paper-dropdown-menu";
|
||||
import featureClassNames from "../../../common/entity/feature_class_names";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
/*
|
||||
|
@ -15,7 +15,7 @@ import computeStateName from "../../common/entity/compute_state_name";
|
||||
import computeStateDomain from "../../common/entity/compute_state_domain";
|
||||
import isComponentLoaded from "../../common/config/is_component_loaded";
|
||||
import { DOMAINS_MORE_INFO_NO_HISTORY } from "../../common/const";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
||||
|
@ -5,7 +5,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
|
||||
import computeStateName from "../../common/entity/compute_state_name";
|
||||
|
@ -11,7 +11,7 @@ import "../resources/roboto";
|
||||
// properly into iron-meta, which is used to transfer iconsets to iron-icon.
|
||||
import "../components/ha-iconset-svg";
|
||||
|
||||
import "../layouts/app/home-assistant";
|
||||
import "../layouts/home-assistant";
|
||||
|
||||
setPassiveTouchGestures(true);
|
||||
/* LastPass createElement workaround. See #428 */
|
||||
|
@ -14,6 +14,7 @@ import { subscribePanels } from "../data/ws-panels";
|
||||
import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { hassUrl } from "../data/auth";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -21,7 +22,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const hassUrl = `${location.protocol}//${location.host}`;
|
||||
const isExternal = location.search.includes("external_auth=1");
|
||||
|
||||
const authProm = isExternal
|
||||
|
@ -15,7 +15,7 @@ function initRouting() {
|
||||
|
||||
// Get api from network.
|
||||
workbox.routing.registerRoute(
|
||||
new RegExp(`${location.host}/api/.*`),
|
||||
new RegExp(`${location.host}/(api|auth)/.*`),
|
||||
new workbox.strategies.NetworkOnly()
|
||||
);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ExternalMessaging } from "./external_messaging";
|
||||
|
||||
export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
||||
document.addEventListener("connection-status", (ev) =>
|
||||
window.addEventListener("connection-status", (ev) =>
|
||||
bus.fireMessage({
|
||||
type: "connection-status",
|
||||
payload: { event: ev.detail },
|
||||
@ -10,6 +10,6 @@ export const externalForwardConnectionEvents = (bus: ExternalMessaging) => {
|
||||
};
|
||||
|
||||
export const externalForwardHaptics = (bus: ExternalMessaging) =>
|
||||
document.addEventListener("haptic", (ev) =>
|
||||
window.addEventListener("haptic", (ev) =>
|
||||
bus.fireMessage({ type: "haptic", payload: { hapticType: ev.detail } })
|
||||
);
|
||||
|
@ -44,7 +44,7 @@
|
||||
Home Assistant
|
||||
</div>
|
||||
|
||||
<ha-onboarding>Initializing</ha-onboarding>
|
||||
<ha-onboarding></ha-onboarding>
|
||||
</div>
|
||||
|
||||
<%= renderTemplate('_js_base') %>
|
||||
|
@ -1,161 +0,0 @@
|
||||
import {
|
||||
ERR_INVALID_AUTH,
|
||||
subscribeEntities,
|
||||
subscribeConfig,
|
||||
subscribeServices,
|
||||
callService,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
import { translationMetadata } from "../../resources/translations-metadata";
|
||||
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
|
||||
import { getState } from "../../util/ha-pref-storage";
|
||||
import { getLocalLanguage } from "../../util/hass-translation";
|
||||
import { fetchWithAuth } from "../../util/fetch-with-auth";
|
||||
import hassCallApi from "../../util/hass-call-api";
|
||||
import { subscribePanels } from "../../data/ws-panels";
|
||||
import { forwardHaptic } from "../../util/haptics";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export default (superClass) =>
|
||||
class extends EventsMixin(LocalizeMixin(superClass)) {
|
||||
firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._handleConnProm();
|
||||
}
|
||||
|
||||
async _handleConnProm() {
|
||||
let auth;
|
||||
let conn;
|
||||
try {
|
||||
const result = await window.hassConnection;
|
||||
auth = result.auth;
|
||||
conn = result.conn;
|
||||
} catch (err) {
|
||||
this._error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass = Object.assign(
|
||||
{
|
||||
auth,
|
||||
connection: conn,
|
||||
connected: true,
|
||||
states: null,
|
||||
config: null,
|
||||
themes: null,
|
||||
panels: null,
|
||||
services: null,
|
||||
user: null,
|
||||
panelUrl: this._panelUrl,
|
||||
|
||||
language: getLocalLanguage(),
|
||||
// If resources are already loaded, don't discard them
|
||||
resources: (this.hass && this.hass.resources) || null,
|
||||
localize: () => "",
|
||||
|
||||
translationMetadata: translationMetadata,
|
||||
dockedSidebar: false,
|
||||
moreInfoEntityId: null,
|
||||
callService: async (domain, service, serviceData = {}) => {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Calling service", domain, service, serviceData);
|
||||
}
|
||||
try {
|
||||
await callService(conn, domain, service, serviceData);
|
||||
} catch (err) {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line
|
||||
console.error(
|
||||
"Error calling service",
|
||||
domain,
|
||||
service,
|
||||
serviceData,
|
||||
err
|
||||
);
|
||||
}
|
||||
forwardHaptic(this, "error");
|
||||
const message =
|
||||
this.hass.localize(
|
||||
"ui.notification_toast.service_call_failed",
|
||||
"service",
|
||||
`${domain}/${service}`
|
||||
) + ` ${err.message}`;
|
||||
this.fire("hass-notification", { message });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
callApi: async (method, path, parameters) =>
|
||||
hassCallApi(auth, method, path, parameters),
|
||||
fetchWithAuth: (path, init) =>
|
||||
fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
|
||||
// For messages that do not get a response
|
||||
sendWS: (msg) => {
|
||||
if (__DEV__) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Sending", msg);
|
||||
}
|
||||
conn.sendMessage(msg);
|
||||
},
|
||||
// For messages that expect a response
|
||||
callWS: (msg) => {
|
||||
if (__DEV__) {
|
||||
/* eslint-disable no-console */
|
||||
console.log("Sending", msg);
|
||||
}
|
||||
|
||||
const resp = conn.sendMessagePromise(msg);
|
||||
|
||||
if (__DEV__) {
|
||||
resp.then(
|
||||
(result) => console.log("Received", result),
|
||||
(err) => console.error("Error", err)
|
||||
);
|
||||
}
|
||||
return resp;
|
||||
},
|
||||
},
|
||||
getState()
|
||||
);
|
||||
|
||||
this.hassConnected();
|
||||
}
|
||||
|
||||
hassConnected() {
|
||||
super.hassConnected();
|
||||
|
||||
const conn = this.hass.connection;
|
||||
|
||||
fireEvent(document, "connection-status", "connected");
|
||||
|
||||
conn.addEventListener("ready", () => this.hassReconnected());
|
||||
conn.addEventListener("disconnected", () => this.hassDisconnected());
|
||||
// If we reconnect after losing connection and auth is no longer valid.
|
||||
conn.addEventListener("reconnect-error", (_conn, err) => {
|
||||
if (err === ERR_INVALID_AUTH) {
|
||||
fireEvent(document, "connection-status", "auth-invalid");
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
subscribeEntities(conn, (states) => this._updateHass({ states }));
|
||||
subscribeConfig(conn, (config) => this._updateHass({ config }));
|
||||
subscribeServices(conn, (services) => this._updateHass({ services }));
|
||||
subscribePanels(conn, (panels) => this._updateHass({ panels }));
|
||||
}
|
||||
|
||||
hassReconnected() {
|
||||
super.hassReconnected();
|
||||
this._updateHass({ connected: true });
|
||||
fireEvent(document, "connection-status", "connected");
|
||||
}
|
||||
|
||||
hassDisconnected() {
|
||||
super.hassDisconnected();
|
||||
this._updateHass({ connected: false });
|
||||
fireEvent(document, "connection-status", "disconnected");
|
||||
}
|
||||
};
|
@ -1,45 +1,20 @@
|
||||
import "@polymer/app-route/app-location";
|
||||
import { html, LitElement, PropertyValues, css, property } from "lit-element";
|
||||
|
||||
import "../home-assistant-main";
|
||||
import "../ha-init-page";
|
||||
import "../../resources/ha-style";
|
||||
import { registerServiceWorker } from "../../util/register-service-worker";
|
||||
import { DEFAULT_PANEL } from "../../common/const";
|
||||
import "./home-assistant-main";
|
||||
import "./ha-init-page";
|
||||
import "../resources/ha-style";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import { DEFAULT_PANEL } from "../common/const";
|
||||
|
||||
import HassBaseMixin from "./hass-base-mixin";
|
||||
import AuthMixin from "./auth-mixin";
|
||||
import TranslationsMixin from "./translations-mixin";
|
||||
import ThemesMixin from "./themes-mixin";
|
||||
import MoreInfoMixin from "./more-info-mixin";
|
||||
import SidebarMixin from "./sidebar-mixin";
|
||||
import { dialogManagerMixin } from "./dialog-manager-mixin";
|
||||
import ConnectionMixin from "./connection-mixin";
|
||||
import NotificationMixin from "./notification-mixin";
|
||||
import DisconnectToastMixin from "./disconnect-toast-mixin";
|
||||
import { urlSyncMixin } from "./url-sync-mixin";
|
||||
|
||||
import { Route, HomeAssistant } from "../../types";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { Route, HomeAssistant } from "../types";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { HassElement } from "../state/hass-element";
|
||||
|
||||
(LitElement.prototype as any).html = html;
|
||||
(LitElement.prototype as any).css = css;
|
||||
|
||||
const ext = <T>(baseClass: T, mixins): T =>
|
||||
mixins.reduceRight((base, mixin) => mixin(base), baseClass);
|
||||
|
||||
export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
|
||||
AuthMixin,
|
||||
ThemesMixin,
|
||||
TranslationsMixin,
|
||||
MoreInfoMixin,
|
||||
SidebarMixin,
|
||||
DisconnectToastMixin,
|
||||
ConnectionMixin,
|
||||
NotificationMixin,
|
||||
dialogManagerMixin,
|
||||
urlSyncMixin,
|
||||
]) {
|
||||
export class HomeAssistantAppEl extends HassElement {
|
||||
@property() private _route?: Route;
|
||||
@property() private _error?: boolean;
|
||||
@property() private _panelUrl?: string;
|
||||
@ -69,6 +44,7 @@ export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._initialize();
|
||||
setTimeout(registerServiceWorker, 1000);
|
||||
/* polyfill for paper-dropdown */
|
||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
|
||||
@ -86,6 +62,16 @@ export class HomeAssistantAppEl extends ext(HassBaseMixin(LitElement), [
|
||||
}
|
||||
}
|
||||
|
||||
protected async _initialize() {
|
||||
try {
|
||||
const { auth, conn } = await window.hassConnection;
|
||||
this.initializeHass(auth, conn);
|
||||
} catch (err) {
|
||||
this._error = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private _routeChanged(ev) {
|
||||
const route = ev.detail.value as Route;
|
||||
// If it's the first route that we process,
|
@ -1,7 +1,7 @@
|
||||
import { dedupingMixin } from "@polymer/polymer/lib/utils/mixin";
|
||||
import { PaperDialogBehavior } from "@polymer/paper-dialog-behavior/paper-dialog-behavior";
|
||||
import { mixinBehaviors } from "@polymer/polymer/lib/legacy/class";
|
||||
import EventsMixin from "./events-mixin";
|
||||
import { EventsMixin } from "./events-mixin";
|
||||
/**
|
||||
* @polymerMixin
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -33,7 +33,7 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/* @polymerMixin */
|
||||
export default dedupingMixin(
|
||||
export const EventsMixin = dedupingMixin(
|
||||
(superClass) =>
|
||||
class extends superClass {
|
||||
/**
|
||||
|
@ -31,10 +31,10 @@ export const localizeLiteBaseMixin = (superClass) =>
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateResources();
|
||||
this._downloadResources();
|
||||
}
|
||||
|
||||
private async _updateResources() {
|
||||
private async _downloadResources() {
|
||||
const { language, data } = await getTranslation(
|
||||
this.translationFragment,
|
||||
this.language
|
||||
|
@ -1,146 +1,95 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-button";
|
||||
import {
|
||||
LitElement,
|
||||
CSSResult,
|
||||
css,
|
||||
html,
|
||||
PropertyValues,
|
||||
property,
|
||||
customElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import {
|
||||
getAuth,
|
||||
createConnection,
|
||||
genClientId,
|
||||
Auth,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { OnboardingStep, onboardUserStep } from "../data/onboarding";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import {
|
||||
OnboardingStep,
|
||||
ValidOnboardingStep,
|
||||
OnboardingResponses,
|
||||
fetchOnboardingOverview,
|
||||
} from "../data/onboarding";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import "./onboarding-create-user";
|
||||
import "./onboarding-loading";
|
||||
import { hassUrl } from "../data/auth";
|
||||
import { HassElement } from "../state/hass-element";
|
||||
|
||||
interface OnboardingEvent<T extends ValidOnboardingStep> {
|
||||
type: T;
|
||||
result: OnboardingResponses[T];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"onboarding-step": OnboardingEvent<ValidOnboardingStep>;
|
||||
}
|
||||
|
||||
interface GlobalEventHandlersEventMap {
|
||||
"onboarding-step": HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ha-onboarding")
|
||||
class HaOnboarding extends litLocalizeLiteMixin(LitElement) {
|
||||
class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
public translationFragment = "page-onboarding";
|
||||
|
||||
@property() private _name = "";
|
||||
@property() private _username = "";
|
||||
@property() private _password = "";
|
||||
@property() private _passwordConfirm = "";
|
||||
@property() private _loading = false;
|
||||
@property() private _errorMsg?: string = undefined;
|
||||
@property() private _steps?: OnboardingStep[];
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<p>
|
||||
${this.localize("ui.panel.page-onboarding.intro")}
|
||||
</p>
|
||||
const step = this._curStep()!;
|
||||
|
||||
<p>
|
||||
${this.localize("ui.panel.page-onboarding.user.intro")}
|
||||
</p>
|
||||
|
||||
${
|
||||
this._errorMsg
|
||||
? html`
|
||||
<p class="error">
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.user.error.${this._errorMsg}`
|
||||
) || this._errorMsg}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
if (this._loading || !step) {
|
||||
return html`
|
||||
<onboarding-loading></onboarding-loading>
|
||||
`;
|
||||
} else if (step.step === "user") {
|
||||
return html`
|
||||
<onboarding-create-user
|
||||
.localize=${this.localize}
|
||||
.language=${this.language}
|
||||
></onboarding-create-user>
|
||||
`;
|
||||
} else if (step.step === "integration") {
|
||||
return html`
|
||||
<onboarding-integrations
|
||||
.hass=${this.hass}
|
||||
.onboardingLocalize=${this.localize}
|
||||
></onboarding-integrations>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
<form>
|
||||
<paper-input
|
||||
autofocus
|
||||
name="name"
|
||||
label="${this.localize("ui.panel.page-onboarding.user.data.name")}"
|
||||
.value=${this._name}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize='on'
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.required_field"
|
||||
)}"
|
||||
@blur=${this._maybePopulateUsername}
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
name="username"
|
||||
label="${this.localize("ui.panel.page-onboarding.user.data.username")}"
|
||||
value=${this._username}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize='none'
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.required_field"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
name="password"
|
||||
label="${this.localize("ui.panel.page-onboarding.user.data.password")}"
|
||||
value=${this._password}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
type='password'
|
||||
auto-validate
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.required_field"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
name="passwordConfirm"
|
||||
label="${this.localize(
|
||||
"ui.panel.page-onboarding.user.data.password_confirm"
|
||||
)}"
|
||||
value=${this._passwordConfirm}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
type='password'
|
||||
.invalid=${this._password !== "" &&
|
||||
this._passwordConfirm !== "" &&
|
||||
this._passwordConfirm !== this._password}
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.error.password_not_match"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<p class="action">
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._submitForm}
|
||||
.disabled=${this._loading}
|
||||
>
|
||||
${this.localize("ui.panel.page-onboarding.user.create_account")}
|
||||
</mwc-button>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
if (ev.keyCode === 13) {
|
||||
this._submitForm();
|
||||
}
|
||||
});
|
||||
this._fetchOnboardingSteps();
|
||||
import("./onboarding-integrations");
|
||||
registerServiceWorker(false);
|
||||
this.addEventListener("onboarding-step", (ev) => this._handleStepDone(ev));
|
||||
}
|
||||
|
||||
private _curStep() {
|
||||
return this._steps ? this._steps.find((stp) => !stp.done) : undefined;
|
||||
}
|
||||
|
||||
private async _fetchOnboardingSteps() {
|
||||
try {
|
||||
const response = await window.stepsPromise;
|
||||
const response = await (window.stepsPromise || fetchOnboardingOverview());
|
||||
|
||||
if (response.status === 404) {
|
||||
// We don't load the component when onboarding is done
|
||||
document.location.href = "/";
|
||||
document.location.assign("/");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -148,83 +97,73 @@ class HaOnboarding extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
if (steps.every((step) => step.done)) {
|
||||
// Onboarding is done!
|
||||
document.location.href = "/";
|
||||
document.location.assign("/");
|
||||
return;
|
||||
}
|
||||
|
||||
if (steps[0].done) {
|
||||
// First step is already done, so we need to get auth somewhere else.
|
||||
const auth = await getAuth({
|
||||
hassUrl,
|
||||
});
|
||||
await this._connectHass(auth);
|
||||
}
|
||||
|
||||
this._steps = steps;
|
||||
} catch (err) {
|
||||
alert("Something went wrong loading loading onboarding, try refreshing");
|
||||
}
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev: PolymerChangedEvent<string>): void {
|
||||
const name = (ev.target as any).name;
|
||||
this[`_${name}`] = ev.detail.value;
|
||||
}
|
||||
private async _handleStepDone(
|
||||
ev: HASSDomEvent<OnboardingEvent<ValidOnboardingStep>>
|
||||
) {
|
||||
const stepResult = ev.detail;
|
||||
this._steps = this._steps!.map((step) =>
|
||||
step.step === stepResult.type ? { ...step, done: true } : step
|
||||
);
|
||||
|
||||
private _maybePopulateUsername(): void {
|
||||
if (this._username) {
|
||||
return;
|
||||
}
|
||||
if (stepResult.type === "user") {
|
||||
const result = stepResult.result as OnboardingResponses["user"];
|
||||
this._loading = true;
|
||||
try {
|
||||
const auth = await getAuth({
|
||||
hassUrl,
|
||||
authCode: result.auth_code,
|
||||
});
|
||||
await this._connectHass(auth);
|
||||
} catch (err) {
|
||||
alert("Ah snap, something went wrong!");
|
||||
location.reload();
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
} else if (stepResult.type === "integration") {
|
||||
const result = stepResult.result as OnboardingResponses["integration"];
|
||||
this._loading = true;
|
||||
|
||||
const parts = this._name.split(" ");
|
||||
|
||||
if (parts.length) {
|
||||
this._username = parts[0].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
private async _submitForm(): Promise<void> {
|
||||
if (!this._name || !this._username || !this._password) {
|
||||
this._errorMsg = "required_fields";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._password !== this._passwordConfirm) {
|
||||
this._errorMsg = "password_not_match";
|
||||
return;
|
||||
}
|
||||
|
||||
this._loading = true;
|
||||
this._errorMsg = "";
|
||||
|
||||
try {
|
||||
const clientId = genClientId();
|
||||
|
||||
const { auth_code } = await onboardUserStep({
|
||||
client_id: clientId,
|
||||
name: this._name,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
});
|
||||
// Revoke current auth token.
|
||||
await this.hass!.auth.revoke();
|
||||
|
||||
const state = btoa(
|
||||
JSON.stringify({
|
||||
hassUrl: `${location.protocol}//${location.host}`,
|
||||
clientId,
|
||||
clientId: genClientId(),
|
||||
})
|
||||
);
|
||||
|
||||
document.location.href = `/?auth_callback=1&code=${encodeURIComponent(
|
||||
auth_code
|
||||
)}&state=${state}`;
|
||||
} catch (err) {
|
||||
// tslint:disable-next-line
|
||||
console.error(err);
|
||||
this._loading = false;
|
||||
this._errorMsg = err.message;
|
||||
document.location.assign(
|
||||
`/?auth_callback=1&code=${encodeURIComponent(
|
||||
result.auth_code
|
||||
)}&state=${state}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.action {
|
||||
margin: 32px 0;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
private async _connectHass(auth: Auth) {
|
||||
const conn = await createConnection({ auth });
|
||||
this.initializeHass(auth, conn);
|
||||
// Load config strings for integrations
|
||||
(this as any)._loadFragmentTranslations(this.hass!.language, "config");
|
||||
}
|
||||
}
|
||||
|
||||
|
86
src/onboarding/integration-badge.ts
Normal file
86
src/onboarding/integration-badge.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "../components/ha-icon";
|
||||
|
||||
@customElement("integration-badge")
|
||||
class IntegrationBadge extends LitElement {
|
||||
@property() public icon!: string;
|
||||
@property() public title!: string;
|
||||
@property() public badgeIcon?: string;
|
||||
@property({ type: Boolean, reflect: true }) public clickable = false;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<div class="icon">
|
||||
<iron-icon .icon=${this.icon}></iron-icon>
|
||||
${this.badgeIcon
|
||||
? html`
|
||||
<ha-icon class="badge" .icon=${this.badgeIcon}></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<div class="title">${this.title}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
:host([clickable]) {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
margin: 0 auto 8px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--secondary-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:host([clickable]) .icon {
|
||||
border-color: var(--primary-color);
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
color: var(--primary-color);
|
||||
bottom: -5px;
|
||||
right: -5px;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
display: block;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
min-height: 2.3em;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"integration-badge": IntegrationBadge;
|
||||
}
|
||||
}
|
210
src/onboarding/onboarding-create-user.ts
Normal file
210
src/onboarding/onboarding-create-user.ts
Normal file
@ -0,0 +1,210 @@
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-button";
|
||||
import {
|
||||
LitElement,
|
||||
CSSResult,
|
||||
css,
|
||||
html,
|
||||
PropertyValues,
|
||||
property,
|
||||
customElement,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import { onboardUserStep } from "../data/onboarding";
|
||||
import { PolymerChangedEvent } from "../polymer-types";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
@customElement("onboarding-create-user")
|
||||
class OnboardingCreateUser extends LitElement {
|
||||
@property() public localize!: LocalizeFunc;
|
||||
@property() public language!: string;
|
||||
|
||||
@property() private _name = "";
|
||||
@property() private _username = "";
|
||||
@property() private _password = "";
|
||||
@property() private _passwordConfirm = "";
|
||||
@property() private _loading = false;
|
||||
@property() private _errorMsg?: string = undefined;
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<p>
|
||||
${this.localize("ui.panel.page-onboarding.intro")}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
${this.localize("ui.panel.page-onboarding.user.intro")}
|
||||
</p>
|
||||
|
||||
${
|
||||
this._errorMsg
|
||||
? html`
|
||||
<p class="error">
|
||||
${this.localize(
|
||||
`ui.panel.page-onboarding.user.error.${this._errorMsg}`
|
||||
) || this._errorMsg}
|
||||
</p>
|
||||
`
|
||||
: ""
|
||||
}
|
||||
|
||||
<form>
|
||||
<paper-input
|
||||
name="name"
|
||||
label="${this.localize("ui.panel.page-onboarding.user.data.name")}"
|
||||
.value=${this._name}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize='on'
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.required_field"
|
||||
)}"
|
||||
@blur=${this._maybePopulateUsername}
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
name="username"
|
||||
label="${this.localize("ui.panel.page-onboarding.user.data.username")}"
|
||||
value=${this._username}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
auto-validate
|
||||
autocapitalize='none'
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.required_field"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
name="password"
|
||||
label="${this.localize("ui.panel.page-onboarding.user.data.password")}"
|
||||
value=${this._password}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
type='password'
|
||||
auto-validate
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.required_field"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<paper-input
|
||||
name="passwordConfirm"
|
||||
label="${this.localize(
|
||||
"ui.panel.page-onboarding.user.data.password_confirm"
|
||||
)}"
|
||||
value=${this._passwordConfirm}
|
||||
@value-changed=${this._handleValueChanged}
|
||||
required
|
||||
type='password'
|
||||
.invalid=${this._password !== "" &&
|
||||
this._passwordConfirm !== "" &&
|
||||
this._passwordConfirm !== this._password}
|
||||
.errorMessage="${this.localize(
|
||||
"ui.panel.page-onboarding.user.error.password_not_match"
|
||||
)}"
|
||||
></paper-input>
|
||||
|
||||
<p class="action">
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._submitForm}
|
||||
.disabled=${this._loading}
|
||||
>
|
||||
${this.localize("ui.panel.page-onboarding.user.create_account")}
|
||||
</mwc-button>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
setTimeout(
|
||||
() => this.shadowRoot!.querySelector("paper-input")!.focus(),
|
||||
100
|
||||
);
|
||||
this.addEventListener("keypress", (ev) => {
|
||||
if (ev.keyCode === 13) {
|
||||
this._submitForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _handleValueChanged(ev: PolymerChangedEvent<string>): void {
|
||||
const name = (ev.target as any).name;
|
||||
this[`_${name}`] = ev.detail.value;
|
||||
}
|
||||
|
||||
private _maybePopulateUsername(): void {
|
||||
if (this._username) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = this._name.split(" ");
|
||||
|
||||
if (parts.length) {
|
||||
this._username = parts[0].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
private async _submitForm(): Promise<void> {
|
||||
if (!this._name || !this._username || !this._password) {
|
||||
this._errorMsg = "required_fields";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._password !== this._passwordConfirm) {
|
||||
this._errorMsg = "password_not_match";
|
||||
return;
|
||||
}
|
||||
|
||||
this._loading = true;
|
||||
this._errorMsg = "";
|
||||
|
||||
try {
|
||||
const clientId = genClientId();
|
||||
|
||||
const result = await onboardUserStep({
|
||||
client_id: clientId,
|
||||
name: this._name,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
language: this.language,
|
||||
});
|
||||
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "user",
|
||||
result,
|
||||
});
|
||||
} catch (err) {
|
||||
// tslint:disable-next-line
|
||||
console.error(err);
|
||||
this._loading = false;
|
||||
this._errorMsg = err.body.message;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.action {
|
||||
margin: 32px 0;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-create-user": OnboardingCreateUser;
|
||||
}
|
||||
}
|
196
src/onboarding/onboarding-integrations.ts
Normal file
196
src/onboarding/onboarding-integrations.ts
Normal file
@ -0,0 +1,196 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
property,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import {
|
||||
loadConfigFlowDialog,
|
||||
showConfigFlowDialog,
|
||||
} from "../dialogs/config-flow/show-dialog-config-flow";
|
||||
import { HomeAssistant } from "../types";
|
||||
import {
|
||||
getConfigFlowsInProgress,
|
||||
getConfigEntries,
|
||||
ConfigEntry,
|
||||
ConfigFlowProgress,
|
||||
localizeConfigFlowTitle,
|
||||
} from "../data/config_entries";
|
||||
import { compare } from "../common/string/compare";
|
||||
import "./integration-badge";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { debounce } from "../common/util/debounce";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { onboardIntegrationStep } from "../data/onboarding";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
|
||||
@customElement("onboarding-integrations")
|
||||
class OnboardingIntegrations extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public onboardingLocalize!: LocalizeFunc;
|
||||
@property() private _entries?: ConfigEntry[];
|
||||
@property() private _discovered?: ConfigFlowProgress[];
|
||||
private _unsubEvents?: () => void;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.hass.connection
|
||||
.subscribeEvents(
|
||||
debounce(() => this._loadData(), 500),
|
||||
"config_entry_discovered"
|
||||
)
|
||||
.then((unsub) => {
|
||||
this._unsubEvents = unsub;
|
||||
});
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubEvents) {
|
||||
this._unsubEvents();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this._entries || !this._discovered) {
|
||||
return html``;
|
||||
}
|
||||
// Render discovered and existing entries together sorted by localized title.
|
||||
const entries: Array<[string, TemplateResult]> = this._entries.map(
|
||||
(entry) => {
|
||||
const title = this.hass.localize(
|
||||
`component.${entry.domain}.config.title`
|
||||
);
|
||||
return [
|
||||
title,
|
||||
html`
|
||||
<integration-badge
|
||||
.title=${title}
|
||||
icon="hass:check"
|
||||
></integration-badge>
|
||||
`,
|
||||
];
|
||||
}
|
||||
);
|
||||
const discovered: Array<[string, TemplateResult]> = this._discovered.map(
|
||||
(flow) => {
|
||||
const title = localizeConfigFlowTitle(this.hass.localize, flow);
|
||||
return [
|
||||
title,
|
||||
html`
|
||||
<button .flowId=${flow.flow_id} @click=${this._continueFlow}>
|
||||
<integration-badge
|
||||
clickable
|
||||
.title=${title}
|
||||
icon="hass:plus"
|
||||
></integration-badge>
|
||||
</button>
|
||||
`,
|
||||
];
|
||||
}
|
||||
);
|
||||
const content = [...entries, ...discovered]
|
||||
.sort((a, b) => compare(a[0], b[0]))
|
||||
.map((item) => item[1]);
|
||||
|
||||
return html`
|
||||
<p>
|
||||
${this.onboardingLocalize("ui.panel.page-onboarding.integration.intro")}
|
||||
</p>
|
||||
<div class="badges">
|
||||
${content}
|
||||
<button @click=${this._createFlow}>
|
||||
<integration-badge
|
||||
clickable
|
||||
title=${this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.integration.more_integrations"
|
||||
)}
|
||||
icon="hass:dots-horizontal"
|
||||
></integration-badge>
|
||||
</button>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<mwc-button @click=${this._finish}>
|
||||
${this.onboardingLocalize(
|
||||
"ui.panel.page-onboarding.integration.finish"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
loadConfigFlowDialog();
|
||||
this._loadData();
|
||||
/* polyfill for paper-dropdown */
|
||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ "web-animations-js/web-animations-next-lite.min");
|
||||
}
|
||||
|
||||
private _createFlow() {
|
||||
showConfigFlowDialog(this, {
|
||||
dialogClosedCallback: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
private _continueFlow(ev) {
|
||||
showConfigFlowDialog(this, {
|
||||
continueFlowId: ev.currentTarget.flowId,
|
||||
dialogClosedCallback: () => this._loadData(),
|
||||
});
|
||||
}
|
||||
|
||||
private async _loadData() {
|
||||
const [discovered, entries] = await Promise.all([
|
||||
getConfigFlowsInProgress(this.hass!),
|
||||
getConfigEntries(this.hass!),
|
||||
]);
|
||||
this._discovered = discovered;
|
||||
this._entries = entries;
|
||||
}
|
||||
|
||||
private async _finish() {
|
||||
const result = await onboardIntegrationStep(this.hass, {
|
||||
client_id: genClientId(),
|
||||
});
|
||||
fireEvent(this, "onboarding-step", {
|
||||
type: "integration",
|
||||
result,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.badges {
|
||||
margin-top: 24px;
|
||||
}
|
||||
.badges > * {
|
||||
width: 24%;
|
||||
min-width: 90px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
button {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: 0;
|
||||
font: inherit;
|
||||
}
|
||||
.footer {
|
||||
text-align: right;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-integrations": OnboardingIntegrations;
|
||||
}
|
||||
}
|
70
src/onboarding/onboarding-loading.ts
Normal file
70
src/onboarding/onboarding-loading.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
|
||||
@customElement("onboarding-loading")
|
||||
class OnboardingLoading extends LitElement {
|
||||
protected render(): TemplateResult | void {
|
||||
return html`
|
||||
<div class="loader"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
/* MIT License (MIT). Copyright (c) 2014 Luke Haas */
|
||||
.loader,
|
||||
.loader:after {
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.loader {
|
||||
margin: 60px auto;
|
||||
font-size: 4px;
|
||||
position: relative;
|
||||
text-indent: -9999em;
|
||||
border-top: 1.1em solid rgba(3, 169, 244, 0.2);
|
||||
border-right: 1.1em solid rgba(3, 169, 244, 0.2);
|
||||
border-bottom: 1.1em solid rgba(3, 169, 244, 0.2);
|
||||
border-left: 1.1em solid rgb(3, 168, 244);
|
||||
-webkit-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-animation: load8 1.4s infinite linear;
|
||||
animation: load8 1.4s infinite linear;
|
||||
}
|
||||
@-webkit-keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes load8 {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"onboarding-loading": OnboardingLoading;
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import React from "react";
|
||||
/* eslint-enable */
|
||||
import BigCalendar from "react-big-calendar";
|
||||
import moment from "moment";
|
||||
import EventsMixin from "../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
|
||||
import "../../resources/ha-style";
|
||||
|
||||
|
@ -4,24 +4,23 @@ import {
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
PropertyDeclarations,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-fab/paper-fab";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
AreaRegistryEntry,
|
||||
fetchAreaRegistry,
|
||||
updateAreaRegistryEntry,
|
||||
deleteAreaRegistryEntry,
|
||||
createAreaRegistryEntry,
|
||||
subscribeAreaRegistry,
|
||||
} from "../../../data/area_registry";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import compare from "../../../common/string/compare";
|
||||
import "../ha-config-section";
|
||||
import {
|
||||
showAreaRegistryDetailDialog,
|
||||
@ -29,22 +28,23 @@ import {
|
||||
} from "./show-dialog-area-registry-detail";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
|
||||
class HaConfigAreaRegistry extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public isWide?: boolean;
|
||||
private _items?: AreaRegistryEntry[];
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
@property() private _areas?: AreaRegistryEntry[];
|
||||
private _unsubAreas?: UnsubscribeFunc;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
isWide: {},
|
||||
_items: {},
|
||||
};
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubAreas) {
|
||||
this._unsubAreas();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass || this._items === undefined) {
|
||||
if (!this.hass || this._areas === undefined) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
@ -72,8 +72,8 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<paper-card>
|
||||
${this._items.map((entry) => {
|
||||
<ha-card>
|
||||
${this._areas.map((entry) => {
|
||||
return html`
|
||||
<paper-item @click=${this._openEditEntry} .entry=${entry}>
|
||||
<paper-item-body>
|
||||
@ -82,7 +82,7 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
</paper-item>
|
||||
`;
|
||||
})}
|
||||
${this._items.length === 0
|
||||
${this._areas.length === 0
|
||||
? html`
|
||||
<div class="empty">
|
||||
${this.hass.localize(
|
||||
@ -96,7 +96,7 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
|
||||
@ -116,14 +116,16 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchData();
|
||||
loadAreaRegistryDetailDialog();
|
||||
}
|
||||
|
||||
private async _fetchData() {
|
||||
this._items = (await fetchAreaRegistry(this.hass!)).sort((ent1, ent2) =>
|
||||
compare(ent1.name, ent2.name)
|
||||
);
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
if (!this._unsubAreas) {
|
||||
this._unsubAreas = subscribeAreaRegistry(this.hass, (areas) => {
|
||||
this._areas = areas;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _createArea() {
|
||||
@ -137,22 +139,10 @@ class HaConfigAreaRegistry extends LitElement {
|
||||
private _openDialog(entry?: AreaRegistryEntry) {
|
||||
showAreaRegistryDetailDialog(this, {
|
||||
entry,
|
||||
createEntry: async (values) => {
|
||||
const created = await createAreaRegistryEntry(this.hass!, values);
|
||||
this._items = this._items!.concat(created).sort((ent1, ent2) =>
|
||||
compare(ent1.name, ent2.name)
|
||||
);
|
||||
},
|
||||
updateEntry: async (values) => {
|
||||
const updated = await updateAreaRegistryEntry(
|
||||
this.hass!,
|
||||
entry!.area_id,
|
||||
values
|
||||
);
|
||||
this._items = this._items!.map((ent) =>
|
||||
ent === entry ? updated : ent
|
||||
);
|
||||
},
|
||||
createEntry: async (values) =>
|
||||
createAreaRegistryEntry(this.hass!, values),
|
||||
updateEntry: async (values) =>
|
||||
updateAreaRegistryEntry(this.hass!, entry!.area_id, values),
|
||||
removeEntry: async () => {
|
||||
if (
|
||||
!confirm(`Are you sure you want to delete this area?
|
||||
@ -164,7 +154,6 @@ All devices in this area will become unassigned.`)
|
||||
|
||||
try {
|
||||
await deleteAreaRegistryEntry(this.hass!, entry!.area_id);
|
||||
this._items = this._items!.filter((ent) => ent !== entry);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
@ -178,10 +167,10 @@ All devices in this area will become unassigned.`)
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 16px auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
|
@ -226,6 +226,9 @@ class HaAutomationEditor extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
.errors {
|
||||
padding: 20px;
|
||||
font-weight: bold;
|
||||
@ -234,15 +237,12 @@ class HaAutomationEditor extends LitElement {
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.triggers,
|
||||
.script {
|
||||
margin-top: -16px;
|
||||
}
|
||||
.triggers paper-card,
|
||||
.script paper-card {
|
||||
.triggers ha-card,
|
||||
.script ha-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.add-card mwc-button {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-fab/paper-fab";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -8,6 +7,7 @@ import "@polymer/paper-item/paper-item";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-paper-icon-button-arrow-prev";
|
||||
import "../../../layouts/ha-app-layout";
|
||||
|
||||
@ -32,6 +32,10 @@ class HaAutomationPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -82,13 +86,16 @@ class HaAutomationPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
<div slot="introduction">
|
||||
[[localize('ui.panel.config.automation.picker.introduction')]]
|
||||
<p>
|
||||
<a href="https://home-assistant.io/docs/automation/editor/">
|
||||
<a
|
||||
href="https://home-assistant.io/docs/automation/editor/"
|
||||
target="_blank"
|
||||
>
|
||||
[[localize('ui.panel.config.automation.picker.learn_more')]]
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<paper-card
|
||||
<ha-card
|
||||
heading="[[localize('ui.panel.config.automation.picker.pick_automation')]]"
|
||||
>
|
||||
<template is="dom-if" if="[[!automations.length]]">
|
||||
@ -107,7 +114,7 @@ class HaAutomationPicker extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</template>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<paper-fab
|
||||
|
@ -7,11 +7,12 @@ import {
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
// tslint:disable-next-line
|
||||
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./cloud-exposed-entities";
|
||||
@ -36,7 +37,7 @@ export class CloudAlexaPref extends LitElement {
|
||||
const enabled = this.cloudStatus!.prefs.alexa_enabled;
|
||||
|
||||
return html`
|
||||
<paper-card heading="Alexa">
|
||||
<ha-card header="Alexa">
|
||||
<paper-toggle-button
|
||||
.checked="${enabled}"
|
||||
@change="${this._toggleChanged}"
|
||||
@ -73,7 +74,7 @@ export class CloudAlexaPref extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -92,10 +93,11 @@ export class CloudAlexaPref extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card > paper-toggle-button {
|
||||
ha-card > paper-toggle-button {
|
||||
margin: -4px 0;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 16px;
|
||||
top: 32px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -7,12 +7,13 @@ import {
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
// tslint:disable-next-line
|
||||
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
import "../../../components/buttons/ha-call-api-button";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./cloud-exposed-entities";
|
||||
@ -41,7 +42,7 @@ export class CloudGooglePref extends LitElement {
|
||||
} = this.cloudStatus.prefs;
|
||||
|
||||
return html`
|
||||
<paper-card heading="Google Assistant">
|
||||
<ha-card header="Google Assistant">
|
||||
<paper-toggle-button
|
||||
id="google_enabled"
|
||||
.checked="${google_enabled}"
|
||||
@ -105,7 +106,7 @@ export class CloudGooglePref extends LitElement {
|
||||
>Sync devices</ha-call-api-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -137,10 +138,11 @@ export class CloudGooglePref extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card > paper-toggle-button {
|
||||
ha-card > paper-toggle-button {
|
||||
margin: -4px 0;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 16px;
|
||||
top: 32px;
|
||||
}
|
||||
ha-call-api-button {
|
||||
color: var(--primary-color);
|
||||
|
@ -8,12 +8,13 @@ import {
|
||||
css,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
// tslint:disable-next-line
|
||||
import { PaperToggleButtonElement } from "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
@ -48,16 +49,16 @@ export class CloudRemotePref extends LitElement {
|
||||
|
||||
if (!remote_certificate) {
|
||||
return html`
|
||||
<paper-card heading="Remote Control">
|
||||
<ha-card header="Remote Control">
|
||||
<div class="preparing">
|
||||
Remote access is being prepared. We will notify you when it's ready.
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<paper-card heading="Remote Control">
|
||||
<ha-card header="Remote Control">
|
||||
<paper-toggle-button
|
||||
.checked="${remote_connected}"
|
||||
@change="${this._toggleChanged}"
|
||||
@ -83,7 +84,7 @@ export class CloudRemotePref extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -111,19 +112,17 @@ export class CloudRemotePref extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.preparing {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card > paper-toggle-button {
|
||||
ha-card > paper-toggle-button {
|
||||
margin: -4px 0;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 16px;
|
||||
top: 32px;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
|
@ -51,19 +51,17 @@ export class CloudWebhooks extends LitElement {
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
<ha-card header="Webhooks">
|
||||
<div class="body">
|
||||
<div class="card-content">
|
||||
Anything that is configured to be triggered by a webhook can be given
|
||||
a publicly accessible URL to allow you to send data back to Home
|
||||
Assistant from anywhere, without exposing your instance to the
|
||||
internet.
|
||||
</div>
|
||||
internet. ${this._renderBody()}
|
||||
|
||||
${this._renderBody()}
|
||||
|
||||
<div class="footer">
|
||||
<a href="https://www.nabucasa.com/config/webhooks" target="_blank">
|
||||
Learn more about creating webhook-powered automations.
|
||||
</a>
|
||||
<div class="footer">
|
||||
<a href="https://www.nabucasa.com/config/webhooks" target="_blank">
|
||||
Learn more about creating webhook-powered automations.
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
@ -194,15 +192,12 @@ export class CloudWebhooks extends LitElement {
|
||||
private renderStyle() {
|
||||
return html`
|
||||
<style>
|
||||
.body {
|
||||
padding: 0 16px 8px;
|
||||
}
|
||||
.body-text {
|
||||
padding: 0 16px;
|
||||
padding: 8px 0;
|
||||
}
|
||||
.webhook {
|
||||
display: flex;
|
||||
padding: 4px 16px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.progress {
|
||||
margin-right: 16px;
|
||||
@ -211,7 +206,7 @@ export class CloudWebhooks extends LitElement {
|
||||
justify-content: center;
|
||||
}
|
||||
.footer {
|
||||
padding: 16px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
.body-text a,
|
||||
.footer a {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-toggle-button/paper-toggle-button";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/buttons/ha-call-api-button";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
@ -13,7 +13,7 @@ import "../ha-config-section";
|
||||
import "./cloud-webhooks";
|
||||
|
||||
import formatDateTime from "../../../common/datetime/format_date_time";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { fetchCloudSubscriptionInfo } from "../../../data/cloud";
|
||||
@ -41,9 +41,6 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
padding-bottom: 24px;
|
||||
direction: ltr;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.account-row {
|
||||
display: flex;
|
||||
padding: 0 16px;
|
||||
@ -82,7 +79,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<paper-card heading="Nabu Casa Account">
|
||||
<ha-card header="Nabu Casa Account">
|
||||
<div class="account-row">
|
||||
<paper-item-body two-line="">
|
||||
[[cloudStatus.email]]
|
||||
@ -105,7 +102,7 @@ class HaConfigCloudAccount extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
>Sign out</mwc-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
|
@ -1,12 +1,12 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
@ -20,8 +20,7 @@ class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
paper-card {
|
||||
display: block;
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
margin-top: 24px;
|
||||
@ -47,9 +46,8 @@ class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
|
||||
</style>
|
||||
<hass-subpage header="Forgot Password">
|
||||
<div class="content">
|
||||
<paper-card>
|
||||
<ha-card header="Forgot your password">
|
||||
<div class="card-content">
|
||||
<h1>Forgot your password?</h1>
|
||||
<p>
|
||||
Enter your email address and we will send you a link to reset
|
||||
your password.
|
||||
@ -72,7 +70,7 @@ class HaConfigCloudForgotPassword extends EventsMixin(PolymerElement) {
|
||||
>Send reset email</ha-progress-button
|
||||
>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
`;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
@ -8,12 +7,13 @@ import "@polymer/paper-ripple/paper-ripple";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
|
||||
import "../ha-config-section";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
import NavigateMixin from "../../../mixins/navigate-mixin";
|
||||
import "../../../components/ha-icon-next";
|
||||
/*
|
||||
@ -34,14 +34,14 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
[slot="introduction"] a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
paper-card:last-child {
|
||||
margin-top: 24px;
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-card .card-header {
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
h1 {
|
||||
@apply --paper-font-headline;
|
||||
@ -97,7 +97,7 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<paper-card hidden$="[[!flashMessage]]">
|
||||
<ha-card hidden$="[[!flashMessage]]">
|
||||
<div class="card-content flash-msg">
|
||||
[[flashMessage]]
|
||||
<paper-icon-button icon="hass:close" on-click="_dismissFlash"
|
||||
@ -105,11 +105,10 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
>
|
||||
<paper-ripple id="flashRipple" noink=""></paper-ripple>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
|
||||
<paper-card>
|
||||
<ha-card header="Sign in">
|
||||
<div class="card-content">
|
||||
<h1>Sign In</h1>
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
<paper-input
|
||||
label="Email"
|
||||
@ -142,9 +141,9 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
forgot password?
|
||||
</button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<paper-item on-click="_handleRegister">
|
||||
<paper-item-body two-line="">
|
||||
Start your free 1 month trial
|
||||
@ -152,7 +151,7 @@ class HaConfigCloudLogin extends NavigateMixin(EventsMixin(PolymerElement)) {
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/buttons/ha-progress-button";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../resources/ha-style";
|
||||
import "../ha-config-section";
|
||||
import EventsMixin from "../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
@ -29,15 +29,9 @@ class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
paper-card:last-child {
|
||||
margin-top: 24px;
|
||||
}
|
||||
h1 {
|
||||
@apply --paper-font-headline;
|
||||
margin: 0;
|
||||
@ -84,10 +78,9 @@ class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<paper-card>
|
||||
<ha-card header="Create Account">
|
||||
<div class="card-content">
|
||||
<div class="header">
|
||||
<h1>Create Account</h1>
|
||||
<div class="error" hidden$="[[!_error]]">[[_error]]</div>
|
||||
</div>
|
||||
<paper-input autofocus="" id="email" label="Email address" type="email" value="{{email}}" on-keydown="_keyDown" error-message="Invalid email"></paper-input>
|
||||
@ -97,7 +90,7 @@ class HaConfigCloudRegister extends EventsMixin(PolymerElement) {
|
||||
<ha-progress-button on-click="_handleRegister" progress="[[_requestInProgress]]">Start trial</ha-progress-button>
|
||||
<button class="link" hidden="[[_requestInProgress]]" on-click="_handleResendVerifyEmail">Resend confirmation email</button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</div>
|
||||
</hass-subpage>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/buttons/ha-call-service-button";
|
||||
import "../../../resources/ha-style";
|
||||
|
||||
@ -57,8 +57,8 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
||||
>[[localize('ui.panel.config.core.section.core.introduction')]]</span
|
||||
>
|
||||
|
||||
<paper-card
|
||||
heading="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.core.section.core.validation.heading')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.config.core.section.core.validation.introduction')]]
|
||||
@ -91,10 +91,10 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
||||
<div id="configLog" class="validate-log">[[validateLog]]</div>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
|
||||
<paper-card
|
||||
heading="[[localize('ui.panel.config.core.section.core.reloading.heading')]]"
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.core.section.core.reloading.heading')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.config.core.section.core.reloading.introduction')]]
|
||||
@ -128,10 +128,10 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
||||
>[[localize('ui.panel.config.core.section.core.reloading.script')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
|
||||
<paper-card
|
||||
heading="[[localize('ui.panel.config.core.section.core.server_management.heading')]]"
|
||||
<ha-card
|
||||
header="[[localize('ui.panel.config.core.section.core.server_management.heading')]]"
|
||||
>
|
||||
<div class="card-content">
|
||||
[[localize('ui.panel.config.core.section.core.server_management.introduction')]]
|
||||
@ -152,7 +152,7 @@ class HaConfigSectionCore extends LocalizeMixin(PolymerElement) {
|
||||
>[[localize('ui.panel.config.core.section.core.server_management.stop')]]
|
||||
</ha-call-service-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
`;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import EventsMixin from "../../../../mixins/events-mixin";
|
||||
import { EventsMixin } from "../../../../mixins/events-mixin";
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
|
@ -2,12 +2,12 @@ import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-menu-button";
|
||||
import "../../../components/ha-icon-next";
|
||||
|
||||
@ -25,12 +25,12 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--primary-text-color);
|
||||
@ -51,7 +51,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
||||
<span slot="introduction">[[localize('ui.panel.config.introduction')]]</span>
|
||||
|
||||
<template is="dom-if" if="[[computeIsLoaded(hass, 'cloud')]]">
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<a href='/config/cloud' tabindex="-1">
|
||||
<paper-item>
|
||||
<paper-item-body two-line="">
|
||||
@ -69,11 +69,11 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
||||
</paper-item-body>
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<a href='/config/integrations/dashboard' tabindex="-1">
|
||||
<paper-item>
|
||||
<paper-item-body two-line>
|
||||
@ -97,7 +97,7 @@ class HaConfigDashboard extends NavigateMixin(LocalizeMixin(PolymerElement)) {
|
||||
<ha-icon-next></ha-icon-next>
|
||||
</paper-item>
|
||||
</a>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
|
||||
<ha-config-navigation hass="[[hass]]"></ha-config-navigation>
|
||||
</ha-config-section>
|
||||
|
@ -1,5 +1,4 @@
|
||||
import "@polymer/iron-icon/iron-icon";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
@ -10,6 +9,7 @@ import LocalizeMixin from "../../../mixins/localize-mixin";
|
||||
|
||||
import isComponentLoaded from "../../../common/config/is_component_loaded";
|
||||
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon-next";
|
||||
|
||||
const CORE_PAGES = ["core", "customize", "entity_registry", "area_registry"];
|
||||
@ -21,14 +21,14 @@ class HaConfigNavigation extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex">
|
||||
paper-card {
|
||||
display: block;
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<template is="dom-repeat" items="[[pages]]">
|
||||
<template is="dom-if" if="[[_computeLoaded(hass, item)]]">
|
||||
<paper-item on-click="_navigate">
|
||||
@ -40,7 +40,7 @@ class HaConfigNavigation extends LocalizeMixin(NavigateMixin(PolymerElement)) {
|
||||
</paper-item>
|
||||
</template>
|
||||
</template>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -4,24 +4,23 @@ import {
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
PropertyDeclarations,
|
||||
property,
|
||||
} from "lit-element";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
fetchEntityRegistry,
|
||||
computeEntityRegistryName,
|
||||
updateEntityRegistryEntry,
|
||||
removeEntityRegistryEntry,
|
||||
subscribeEntityRegistry,
|
||||
} from "../../../data/entity_registry";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-icon";
|
||||
import compare from "../../../common/string/compare";
|
||||
import domainIcon from "../../../common/entity/domain_icon";
|
||||
import stateIcon from "../../../common/entity/state_icon";
|
||||
import computeDomain from "../../../common/entity/compute_domain";
|
||||
@ -30,22 +29,24 @@ import {
|
||||
showEntityRegistryDetailDialog,
|
||||
loadEntityRegistryDetailDialog,
|
||||
} from "./show-dialog-entity-registry-detail";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
|
||||
class HaConfigEntityRegistry extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
public isWide?: boolean;
|
||||
private _items?: EntityRegistryEntry[];
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide?: boolean;
|
||||
@property() private _entities?: EntityRegistryEntry[];
|
||||
private _unsubEntities?: UnsubscribeFunc;
|
||||
|
||||
static get properties(): PropertyDeclarations {
|
||||
return {
|
||||
hass: {},
|
||||
isWide: {},
|
||||
_items: {},
|
||||
};
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (this._unsubEntities) {
|
||||
this._unsubEntities();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.hass || this._items === undefined) {
|
||||
if (!this.hass || this._entities === undefined) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
@ -77,8 +78,8 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
)}
|
||||
</a>
|
||||
</span>
|
||||
<paper-card>
|
||||
${this._items.map((entry) => {
|
||||
<ha-card>
|
||||
${this._entities.map((entry) => {
|
||||
const state = this.hass!.states[entry.entity_id];
|
||||
return html`
|
||||
<paper-icon-item @click=${this._openEditEntry} .entry=${entry}>
|
||||
@ -103,7 +104,7 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
</paper-icon-item>
|
||||
`;
|
||||
})}
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
</ha-config-section>
|
||||
</hass-subpage>
|
||||
`;
|
||||
@ -111,14 +112,18 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._fetchData();
|
||||
loadEntityRegistryDetailDialog();
|
||||
}
|
||||
|
||||
private async _fetchData(): Promise<void> {
|
||||
this._items = (await fetchEntityRegistry(this.hass!)).sort((ent1, ent2) =>
|
||||
compare(ent1.entity_id, ent2.entity_id)
|
||||
);
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
if (!this._unsubEntities) {
|
||||
this._unsubEntities = subscribeEntityRegistry(this.hass, (entities) => {
|
||||
this._entities = entities.sort((ent1, ent2) =>
|
||||
compare(ent1.entity_id, ent2.entity_id)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _openEditEntry(ev: MouseEvent): void {
|
||||
@ -131,7 +136,7 @@ class HaConfigEntityRegistry extends LitElement {
|
||||
entry.entity_id,
|
||||
updates
|
||||
);
|
||||
this._items = this._items!.map((ent) =>
|
||||
this._entities = this._entities!.map((ent) =>
|
||||
ent === entry ? updated : ent
|
||||
);
|
||||
},
|
||||
@ -148,7 +153,7 @@ Deleting an entry will not remove the entity from Home Assistant. To do this, yo
|
||||
|
||||
try {
|
||||
await removeEntityRegistryEntry(this.hass!, entry.entity_id);
|
||||
this._items = this._items!.filter((ent) => ent !== entry);
|
||||
this._entities = this._entities!.filter((ent) => ent !== entry);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
@ -162,9 +167,9 @@ Deleting an entry will not remove the entity from Home Assistant. To do this, yo
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-card {
|
||||
display: block;
|
||||
ha-card {
|
||||
direction: ltr;
|
||||
overflow: hidden;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-card/paper-card";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../components/ha-card";
|
||||
|
||||
import computeStateName from "../../common/entity/compute_state_name";
|
||||
|
||||
@ -13,8 +13,7 @@ class HaEntityConfig extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
paper-card {
|
||||
display: block;
|
||||
ha-card {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
@ -38,7 +37,7 @@ class HaEntityConfig extends PolymerElement {
|
||||
@apply --layout-justified;
|
||||
}
|
||||
</style>
|
||||
<paper-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu
|
||||
@ -89,7 +88,7 @@ class HaEntityConfig extends PolymerElement {
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user