mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 18:29:27 +00:00
Compare commits
1 Commits
20200623.1
...
restore-co
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3272c32f87 |
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
hass_frontend
|
||||
hass_frontend_es5
|
||||
.git
|
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -34,8 +34,10 @@ jobs:
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build resources
|
||||
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
|
||||
- name: Build icons
|
||||
run: ./node_modules/.bin/gulp gen-icons-json
|
||||
- name: Build translations
|
||||
run: ./node_modules/.bin/gulp build-translations
|
||||
- name: Run eslint
|
||||
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
|
||||
- name: Run tsc
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,7 +24,7 @@ dist
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
# Cast dev settings
|
||||
# Cast dev settings
|
||||
src/cast/dev_const.ts
|
||||
|
||||
# Secrets
|
||||
|
31
Dockerfile
Normal file
31
Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM node:8.11.1-alpine
|
||||
|
||||
# install yarn
|
||||
ENV PATH /root/.yarn/bin:$PATH
|
||||
|
||||
## Install/force base tools
|
||||
RUN apk update \
|
||||
&& apk add make g++ curl bash binutils tar git python2 python3 \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& /bin/bash \
|
||||
&& touch ~/.bashrc
|
||||
|
||||
## Install yarn
|
||||
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
|
||||
## Setup the project
|
||||
RUN mkdir -p /frontend
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
|
||||
|
||||
RUN chmod +x /usr/bin/docker_entrypoint.sh
|
||||
|
||||
CMD [ "docker_entrypoint.sh" ]
|
@@ -22,6 +22,15 @@ This is the repository for the official [Home Assistant](https://home-assistant.
|
||||
|
||||
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
|
||||
|
||||
### Docker environment
|
||||
|
||||
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
|
||||
|
||||
- `sh ./script/docker_run.sh build` Build all the project with one command
|
||||
- `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the _classic environment_) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
|
||||
|
||||
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
|
||||
|
||||
## License
|
||||
|
||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||
|
@@ -20,13 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
|
||||
// Loads stuff from a CDN
|
||||
require.resolve("@polymer/font-roboto/roboto.js"),
|
||||
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
|
||||
// Compatibility not needed for latest builds
|
||||
latestBuild &&
|
||||
// wrapped in require.resolve so it blows up if file no longer exists
|
||||
require.resolve(
|
||||
path.resolve(paths.polymer_dir, "src/resources/compatibility.ts")
|
||||
),
|
||||
// This polyfill is loaded in workers to support ES5, filter it out.
|
||||
// Polyfill only needed for ES5 workers so filter out in latestBuild
|
||||
latestBuild && require.resolve("proxy-polyfill/src/index.js"),
|
||||
].filter(Boolean);
|
||||
|
||||
@@ -57,7 +51,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
].filter(Boolean),
|
||||
plugins: [
|
||||
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
|
||||
!latestBuild && [
|
||||
[
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
@@ -73,7 +67,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
|
||||
require("@babel/plugin-proposal-class-properties").default,
|
||||
{ loose: true },
|
||||
],
|
||||
].filter(Boolean),
|
||||
],
|
||||
});
|
||||
|
||||
// Are already ES5, cause warnings when babelified.
|
||||
@@ -85,8 +79,8 @@ module.exports.babelExclude = () => [
|
||||
const outputPath = (outputRoot, latestBuild) =>
|
||||
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
|
||||
|
||||
const publicPath = (latestBuild, root = "") =>
|
||||
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
|
||||
const publicPath = (latestBuild) =>
|
||||
latestBuild ? "/frontend_latest/" : "/frontend_es5/";
|
||||
|
||||
/*
|
||||
BundleConfig {
|
||||
@@ -118,6 +112,7 @@ module.exports.config = {
|
||||
authorize: "./src/entrypoints/authorize.ts",
|
||||
onboarding: "./src/entrypoints/onboarding.ts",
|
||||
core: "./src/entrypoints/core.ts",
|
||||
compatibility: "./src/entrypoints/compatibility.ts",
|
||||
"custom-panel": "./src/entrypoints/custom-panel.ts",
|
||||
},
|
||||
outputPath: outputPath(paths.app_output_root, latestBuild),
|
||||
@@ -132,6 +127,10 @@ module.exports.config = {
|
||||
return {
|
||||
entry: {
|
||||
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"),
|
||||
compatibility: path.resolve(
|
||||
paths.polymer_dir,
|
||||
"src/entrypoints/compatibility.ts"
|
||||
),
|
||||
},
|
||||
outputPath: outputPath(paths.demo_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild),
|
||||
@@ -170,12 +169,15 @@ module.exports.config = {
|
||||
},
|
||||
|
||||
hassio({ isProdBuild, latestBuild }) {
|
||||
if (latestBuild) {
|
||||
throw new Error("Hass.io does not support latest build!");
|
||||
}
|
||||
return {
|
||||
entry: {
|
||||
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
|
||||
},
|
||||
outputPath: outputPath(paths.hassio_output_root, latestBuild),
|
||||
publicPath: publicPath(latestBuild, paths.hassio_publicPath),
|
||||
outputPath: paths.hassio_output_root,
|
||||
publicPath: paths.hassio_publicPath,
|
||||
isProdBuild,
|
||||
latestBuild,
|
||||
dontHash: new Set(["entrypoint"]),
|
||||
|
@@ -20,7 +20,6 @@ gulp.task(
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-cast",
|
||||
"gen-index-cast-dev",
|
||||
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
|
||||
)
|
||||
);
|
||||
|
@@ -6,32 +6,30 @@ const merge = require("merge-stream");
|
||||
const path = require("path");
|
||||
const paths = require("../paths");
|
||||
|
||||
const zopfliOptions = { threshold: 150 };
|
||||
|
||||
gulp.task("compress-app", function compressApp() {
|
||||
const jsLatest = gulp
|
||||
.src(path.resolve(paths.app_output_latest, "**/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(paths.app_output_latest));
|
||||
|
||||
const jsEs5 = gulp
|
||||
.src(path.resolve(paths.app_output_es5, "**/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(paths.app_output_es5));
|
||||
|
||||
const polyfills = gulp
|
||||
.src(path.resolve(paths.app_output_static, "polyfills/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
|
||||
|
||||
const translations = gulp
|
||||
.src(path.resolve(paths.app_output_static, "translations/**/*.json"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
|
||||
|
||||
const icons = gulp
|
||||
.src(path.resolve(paths.app_output_static, "mdi/*.json"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(zopfli({ threshold: 150 }))
|
||||
.pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
|
||||
|
||||
return merge(jsLatest, jsEs5, polyfills, translations, icons);
|
||||
@@ -40,6 +38,6 @@ gulp.task("compress-app", function compressApp() {
|
||||
gulp.task("compress-hassio", function compressApp() {
|
||||
return gulp
|
||||
.src(path.resolve(paths.hassio_output_root, "**/*.js"))
|
||||
.pipe(zopfli(zopfliOptions))
|
||||
.pipe(zopfli())
|
||||
.pipe(gulp.dest(paths.hassio_output_root));
|
||||
});
|
||||
|
@@ -53,6 +53,7 @@ gulp.task("gen-pages-dev", (done) => {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: `/frontend_latest/${page}.js`,
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5PageJS: `/frontend_es5/${page}.js`,
|
||||
});
|
||||
|
||||
@@ -78,6 +79,7 @@ gulp.task("gen-pages-prod", (done) => {
|
||||
const content = renderTemplate(page, {
|
||||
latestPageJS: latestManifest[`${page}.js`],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5PageJS: es5Manifest[`${page}.js`],
|
||||
});
|
||||
|
||||
@@ -97,6 +99,7 @@ gulp.task("gen-index-app-dev", (done) => {
|
||||
latestCoreJS: "/frontend_latest/core.js",
|
||||
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5AppJS: "/frontend_es5/app.js",
|
||||
es5CoreJS: "/frontend_es5/core.js",
|
||||
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
|
||||
@@ -120,6 +123,7 @@ gulp.task("gen-index-app-prod", (done) => {
|
||||
latestCoreJS: latestManifest["core.js"],
|
||||
latestCustomPanelJS: latestManifest["custom-panel.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5AppJS: es5Manifest["app.js"],
|
||||
es5CoreJS: es5Manifest["core.js"],
|
||||
es5CustomPanelJS: es5Manifest["custom-panel.js"],
|
||||
@@ -206,6 +210,7 @@ gulp.task("gen-index-demo-dev", (done) => {
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: "/frontend_latest/main.js",
|
||||
|
||||
es5Compatibility: "/frontend_es5/compatibility.js",
|
||||
es5DemoJS: "/frontend_es5/main.js",
|
||||
});
|
||||
|
||||
@@ -228,6 +233,7 @@ gulp.task("gen-index-demo-prod", (done) => {
|
||||
const content = renderDemoTemplate("index", {
|
||||
latestDemoJS: latestManifest["main.js"],
|
||||
|
||||
es5Compatibility: es5Manifest["compatibility.js"],
|
||||
es5DemoJS: es5Manifest["main.js"],
|
||||
});
|
||||
const minified = minifyHtml(content);
|
||||
|
@@ -1,10 +1,7 @@
|
||||
// Run demo develop mode
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./clean.js");
|
||||
require("./translations.js");
|
||||
@@ -15,31 +12,6 @@ require("./service-worker.js");
|
||||
require("./entry-html.js");
|
||||
require("./rollup.js");
|
||||
|
||||
gulp.task("gather-gallery-demos", async function gatherDemos() {
|
||||
const files = await fs.promises.readdir(
|
||||
path.resolve(paths.gallery_dir, "src/demos")
|
||||
);
|
||||
|
||||
let content = "export const DEMOS = {\n";
|
||||
|
||||
for (const file of files) {
|
||||
const demoId = path.basename(file, ".ts");
|
||||
const demoPath = "../src/demos/" + demoId;
|
||||
content += ` "${demoId}": () => import("${demoPath}"),\n`;
|
||||
}
|
||||
|
||||
content += "};";
|
||||
|
||||
const galleryBuild = path.resolve(paths.gallery_dir, "build");
|
||||
|
||||
fs.mkdirSync(galleryBuild, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(galleryBuild, "import-demos.ts"),
|
||||
content,
|
||||
"utf-8"
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task(
|
||||
"develop-gallery",
|
||||
gulp.series(
|
||||
@@ -48,11 +20,7 @@ gulp.task(
|
||||
},
|
||||
"clean-gallery",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-json",
|
||||
"build-translations",
|
||||
"gather-gallery-demos"
|
||||
),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-gallery",
|
||||
"gen-index-gallery-dev",
|
||||
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
|
||||
@@ -67,11 +35,7 @@ gulp.task(
|
||||
},
|
||||
"clean-gallery",
|
||||
"translations-enable-merge-backend",
|
||||
gulp.parallel(
|
||||
"gen-icons-json",
|
||||
"build-translations",
|
||||
"gather-gallery-demos"
|
||||
),
|
||||
gulp.parallel("gen-icons-json", "build-translations"),
|
||||
"copy-static-gallery",
|
||||
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
|
||||
"gen-index-gallery-prod"
|
||||
|
@@ -36,13 +36,11 @@ function copyMdiIcons(staticDir) {
|
||||
function copyPolyfills(staticDir) {
|
||||
const staticPath = genStaticPath(staticDir);
|
||||
|
||||
// For custom panels using ES5 builds that don't use Babel 7+
|
||||
// Web Component polyfills and adapters
|
||||
copyFileDir(
|
||||
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
|
||||
staticPath("polyfills/")
|
||||
);
|
||||
|
||||
// Web Component polyfills and adapters
|
||||
copyFileDir(
|
||||
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
|
||||
staticPath("polyfills/")
|
||||
|
@@ -1,9 +1,6 @@
|
||||
const gulp = require("gulp");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const env = require("../env");
|
||||
const paths = require("../paths");
|
||||
|
||||
require("./clean.js");
|
||||
require("./gen-icons-json.js");
|
||||
@@ -11,24 +8,6 @@ require("./webpack.js");
|
||||
require("./compress.js");
|
||||
require("./rollup.js");
|
||||
|
||||
async function writeEntrypointJS() {
|
||||
// We ship two builds and we need to do feature detection on what version to load.
|
||||
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.resolve(paths.hassio_output_root, "entrypoint.js"),
|
||||
`
|
||||
try {
|
||||
new Function("import('${paths.hassio_publicPath}/frontend_latest/entrypoint.js')")();
|
||||
} catch (err) {
|
||||
var el = document.createElement('script');
|
||||
el.src = '${paths.hassio_publicPath}/frontend_es5/entrypoint.js';
|
||||
document.body.appendChild(el);
|
||||
}
|
||||
`,
|
||||
{ encoding: "utf-8" }
|
||||
);
|
||||
}
|
||||
|
||||
gulp.task(
|
||||
"develop-hassio",
|
||||
gulp.series(
|
||||
@@ -37,7 +16,6 @@ gulp.task(
|
||||
},
|
||||
"clean-hassio",
|
||||
"gen-icons-json",
|
||||
writeEntrypointJS,
|
||||
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
|
||||
)
|
||||
);
|
||||
@@ -51,7 +29,6 @@ gulp.task(
|
||||
"clean-hassio",
|
||||
"gen-icons-json",
|
||||
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
|
||||
writeEntrypointJS,
|
||||
...// Don't compress running tests
|
||||
(env.isTest() ? [] : ["compress-hassio"])
|
||||
)
|
||||
|
@@ -92,7 +92,11 @@ gulp.task("rollup-watch-app", () => {
|
||||
});
|
||||
|
||||
gulp.task("rollup-watch-hassio", () => {
|
||||
watchRollup(rollupConfig.createHassioConfig, ["hassio/src/**"]);
|
||||
watchRollup(
|
||||
// Force latestBuild = false for hassio config.
|
||||
(conf) => rollupConfig.createHassioConfig({ ...conf, latestBuild: false }),
|
||||
["hassio/src/**"]
|
||||
);
|
||||
});
|
||||
|
||||
gulp.task("rollup-dev-server-demo", () => {
|
||||
@@ -133,7 +137,12 @@ gulp.task(
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-hassio", () =>
|
||||
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true })
|
||||
buildRollup(
|
||||
rollupConfig.createHassioConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
gulp.task("rollup-prod-gallery", () =>
|
||||
|
@@ -129,7 +129,7 @@ gulp.task("webpack-watch-hassio", () => {
|
||||
webpack(
|
||||
createHassioConfig({
|
||||
isProdBuild: false,
|
||||
latestBuild: true,
|
||||
latestBuild: false,
|
||||
})
|
||||
).watch({}, handler());
|
||||
});
|
||||
@@ -139,8 +139,9 @@ gulp.task(
|
||||
() =>
|
||||
new Promise((resolve) =>
|
||||
webpack(
|
||||
bothBuilds(createHassioConfig, {
|
||||
createHassioConfig({
|
||||
isProdBuild: true,
|
||||
latestBuild: false,
|
||||
}),
|
||||
handler(resolve)
|
||||
)
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
|
||||
hassio_dir: path.resolve(__dirname, "../hassio"),
|
||||
hassio_output_root: path.resolve(__dirname, "../hassio/build"),
|
||||
hassio_publicPath: "/api/hassio/app",
|
||||
hassio_publicPath: "/api/hassio/app/",
|
||||
|
||||
translations_src: path.resolve(__dirname, "../src/translations"),
|
||||
};
|
||||
|
@@ -14,6 +14,32 @@ module.exports = function (userOptions = {}) {
|
||||
|
||||
return {
|
||||
name: "ignore",
|
||||
resolveId(importee, importer) {
|
||||
// Only use ignore to intercept imports that we don't control
|
||||
// inside node_module dependencies.
|
||||
if (
|
||||
importee.endsWith("commonjsHelpers.js") ||
|
||||
importee.endsWith("rollupPluginBabelHelpers.js") ||
|
||||
importee.endsWith("?commonjs-proxy") ||
|
||||
!importer ||
|
||||
!importer.includes("/node_modules/")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
let fullPath;
|
||||
try {
|
||||
fullPath = importee.startsWith(".")
|
||||
? path.resolve(importee, importer)
|
||||
: require.resolve(importee);
|
||||
} catch (err) {
|
||||
console.error("Error in ignore plugin", { importee, importer }, err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return files.some((toIgnorePath) => fullPath.startsWith(toIgnorePath))
|
||||
? fullPath
|
||||
: null;
|
||||
},
|
||||
|
||||
load(id) {
|
||||
return files.some((toIgnorePath) => id.startsWith(toIgnorePath))
|
||||
|
@@ -70,9 +70,7 @@ const createWebpackConfig = ({
|
||||
if (
|
||||
!context.includes("/node_modules/") ||
|
||||
// calling define.amd will call require("!!webpack amd options")
|
||||
resource.startsWith("!!webpack") ||
|
||||
// loaded by webpack dev server but doesn't exist.
|
||||
resource === "webpack/hot"
|
||||
resource.startsWith("!!webpack")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -82,11 +80,7 @@ const createWebpackConfig = ({
|
||||
? path.resolve(context, resource)
|
||||
: require.resolve(resource);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"Error in Home Assistant ignore plugin",
|
||||
resource,
|
||||
context
|
||||
);
|
||||
console.error("Error in ignore plugin", resource, context);
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
@@ -37,21 +37,24 @@
|
||||
<body>
|
||||
<%= renderTemplate('_js_base') %>
|
||||
|
||||
<script>
|
||||
import("<%= latestLauncherJS %>");
|
||||
window.latestJS = true;
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestLauncherJS %>";
|
||||
</script>
|
||||
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
<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");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<hc-layout subtitle="FAQ">
|
||||
@@ -251,7 +254,7 @@ http:
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
|
||||
(function (d, t) {
|
||||
(function(d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
|
@@ -28,21 +28,24 @@
|
||||
|
||||
<hc-connect></hc-connect>
|
||||
|
||||
<script>
|
||||
import("<%= latestLauncherJS %>");
|
||||
window.latestJS = true;
|
||||
<script type="module" crossorigin="use-credentials">
|
||||
import "<%= latestLauncherJS %>";
|
||||
</script>
|
||||
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
<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");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5LauncherJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5LauncherJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
|
@@ -192,8 +192,6 @@ export class HcMain extends HassElement {
|
||||
this._handleNewLovelaceConfig(lovelaceConfig)
|
||||
);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching Lovelace configuration", err, msg);
|
||||
// Generate a Lovelace config.
|
||||
this._unsubLovelace = () => undefined;
|
||||
await this._generateLovelaceConfig();
|
||||
|
@@ -1,8 +1,11 @@
|
||||
const { createCastConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
const { isProdBuild } = require("../build-scripts/env.js");
|
||||
|
||||
// File just used for stats builds
|
||||
|
||||
const latestBuild = true;
|
||||
|
||||
module.exports = createCastConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
latestBuild,
|
||||
});
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
} from "lit-element";
|
||||
import { until } from "lit-html/directives/until";
|
||||
import "../../../src/components/ha-card";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import { LovelaceCardConfig } from "../../../src/data/lovelace";
|
||||
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
|
||||
import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types";
|
||||
@@ -49,7 +49,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html` <paper-spinner-lite active></paper-spinner-lite> `
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import "../../src/resources/compatibility";
|
||||
import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
|
||||
import { navigate } from "../../src/common/navigate";
|
||||
import {
|
||||
|
@@ -86,26 +86,30 @@
|
||||
<%= renderTemplate('_js_base') %>
|
||||
<%= renderTemplate('_preload_roboto') %>
|
||||
|
||||
<script>
|
||||
import("<%= latestDemoJS %>");
|
||||
window.latestJS = true;
|
||||
</script>
|
||||
<script type="module" src="<%= latestDemoJS %>"></script>
|
||||
|
||||
<script>
|
||||
if (!window.latestJS) {
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5DemoJS %>");
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5DemoJS %>");
|
||||
<% } %>
|
||||
}
|
||||
<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");
|
||||
<% if (useRollup) { %>
|
||||
_ls("/static/js/s.min.js").onload = function() {
|
||||
System.import("<%= es5Compatibility %>").then(function() {
|
||||
System.import("<%= es5DemoJS %>");
|
||||
});
|
||||
};
|
||||
<% } else { %>
|
||||
_ls("<%= es5Compatibility %>");
|
||||
_ls("<%= es5DemoJS %>");
|
||||
<% } %>
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
|
||||
(function (d, t) {
|
||||
(function(d, t) {
|
||||
var g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src =
|
||||
|
@@ -3,7 +3,6 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../../src/components/ha-switch";
|
||||
import "../../../src/components/ha-formfield";
|
||||
import "./demo-card";
|
||||
|
||||
class DemoCards extends PolymerElement {
|
||||
@@ -27,10 +26,9 @@ class DemoCards extends PolymerElement {
|
||||
</style>
|
||||
<app-toolbar>
|
||||
<div class="filters">
|
||||
<ha-formfield label="Show config">
|
||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
|
||||
Show config
|
||||
</ha-switch>
|
||||
</div>
|
||||
</app-toolbar>
|
||||
<div class="cards">
|
||||
|
@@ -11,7 +11,9 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "../../src/components/ha-card";
|
||||
import "../../src/managers/notification-manager";
|
||||
import "../../src/styles/polymer-ha-style";
|
||||
import { DEMOS } from "../build/import-demos";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const DEMOS = require.context("./demos", true, /^(.*\.(ts$))[^.]*$/im);
|
||||
|
||||
const fixPath = (path) => path.substr(2, path.length - 5);
|
||||
|
||||
@@ -161,7 +163,7 @@ class HaGallery extends PolymerElement {
|
||||
},
|
||||
_demos: {
|
||||
type: Array,
|
||||
value: Object.keys(DEMOS),
|
||||
value: DEMOS.keys().map(fixPath),
|
||||
},
|
||||
_lovelaceDemos: {
|
||||
type: Array,
|
||||
@@ -208,7 +210,7 @@ class HaGallery extends PolymerElement {
|
||||
while (root.lastChild) root.removeChild(root.lastChild);
|
||||
|
||||
if (demo) {
|
||||
DEMOS[demo]();
|
||||
DEMOS(`./${demo}.ts`);
|
||||
const el = document.createElement(demo);
|
||||
root.appendChild(el);
|
||||
}
|
||||
|
@@ -1,8 +1,5 @@
|
||||
const { createGalleryConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
|
||||
module.exports = createGalleryConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
});
|
||||
|
@@ -22,7 +22,7 @@ import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/layouts/loading-screen";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import "./hassio-addon-repository";
|
||||
|
||||
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -11,7 +12,6 @@ import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "./hassio-addon-audio";
|
||||
import "./hassio-addon-config";
|
||||
import "./hassio-addon-network";
|
||||
@@ -24,7 +24,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
HassioAddonDetails,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/layouts/loading-screen";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -35,7 +35,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
mdiInformationVariant,
|
||||
mdiMathLog,
|
||||
} from "@mdi/js";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -19,7 +20,6 @@ import {
|
||||
HassioAddonDetails,
|
||||
} from "../../../src/data/hassio/addon";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import "../../../src/components/ha-circular-progress";
|
||||
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
@@ -56,7 +56,7 @@ class HassioAddonDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
|
||||
const addonTabs: PageNavigation[] = [
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -24,7 +24,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -8,7 +9,6 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
import { hassioStyle } from "../../resources/hassio-style";
|
||||
@@ -22,7 +22,7 @@ class HassioAddonLogDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <ha-circular-progress active></ha-circular-progress> `;
|
||||
return html` <paper-spinner-lite active></paper-spinner-lite> `;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -15,7 +15,7 @@ import {
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import "./hassio-addons";
|
||||
import "./hassio-update";
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -108,7 +108,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._prosessing
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<paper-spinner active></paper-spinner>`
|
||||
: "Add"}
|
||||
</mwc-button>
|
||||
</div>
|
||||
|
@@ -1,6 +1,9 @@
|
||||
import "../../src/resources/compatibility";
|
||||
import "../../src/resources/roboto";
|
||||
import "./hassio-main";
|
||||
window.loadES5Adapter().then(() => {
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
|
||||
// eslint-disable-next-line
|
||||
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
|
||||
});
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.innerHTML = `
|
||||
|
@@ -14,9 +14,7 @@ import {
|
||||
createHassioSession,
|
||||
fetchHassioHomeAssistantInfo,
|
||||
fetchHassioSupervisorInfo,
|
||||
fetchHassioInfo,
|
||||
HassioHomeAssistantInfo,
|
||||
HassioInfo,
|
||||
HassioPanelInfo,
|
||||
HassioSupervisorInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
@@ -77,8 +75,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
|
||||
@property() private _hostInfo: HassioHostInfo;
|
||||
|
||||
@property() private _hassioInfo?: HassioInfo;
|
||||
|
||||
@property() private _hassOsInfo?: HassioHassOSInfo;
|
||||
|
||||
@property() private _hassInfo: HassioHomeAssistantInfo;
|
||||
@@ -151,7 +147,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
hass: this.hass,
|
||||
narrow: this.narrow,
|
||||
supervisorInfo: this._supervisorInfo,
|
||||
hassioInfo: this._hassioInfo,
|
||||
hostInfo: this._hostInfo,
|
||||
hassInfo: this._hassInfo,
|
||||
hassOsInfo: this._hassOsInfo,
|
||||
@@ -161,7 +156,6 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
el.hass = this.hass;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this._supervisorInfo;
|
||||
el.hassioInfo = this._hassioInfo;
|
||||
el.hostInfo = this._hostInfo;
|
||||
el.hassInfo = this._hassInfo;
|
||||
el.hassOsInfo = this._hassOsInfo;
|
||||
@@ -175,14 +169,12 @@ class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = await Promise.all([
|
||||
const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
|
||||
fetchHassioSupervisorInfo(this.hass),
|
||||
fetchHassioHostInfo(this.hass),
|
||||
fetchHassioHomeAssistantInfo(this.hass),
|
||||
fetchHassioInfo(this.hass),
|
||||
]);
|
||||
this._supervisorInfo = supervisorInfo;
|
||||
this._hassioInfo = hassioInfo;
|
||||
this._hostInfo = hostInfo;
|
||||
this._hassInfo = hassInfo;
|
||||
|
||||
|
@@ -3,7 +3,6 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import {
|
||||
HassRouterPage,
|
||||
@@ -27,8 +26,6 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
|
||||
@@ -57,7 +54,6 @@ class HassioPanelRouter extends HassRouterPage {
|
||||
el.route = this.route;
|
||||
el.narrow = this.narrow;
|
||||
el.supervisorInfo = this.supervisorInfo;
|
||||
el.hassioInfo = this.hassioInfo;
|
||||
el.hostInfo = this.hostInfo;
|
||||
el.hassInfo = this.hassInfo;
|
||||
el.hassOsInfo = this.hassOsInfo;
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
@@ -9,11 +10,34 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
|
||||
import {
|
||||
HassioHomeAssistantInfo,
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../src/data/hassio/supervisor";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
import { HomeAssistant, Route } from "../../src/types";
|
||||
import "./hassio-panel-router";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
path: `/hassio/store`,
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("hassio-panel")
|
||||
class HassioPanel extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -24,8 +48,6 @@ class HassioPanel extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property({ attribute: false }) public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
|
||||
@@ -39,7 +61,6 @@ class HassioPanel extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.supervisorInfo=${this.supervisorInfo}
|
||||
.hassioInfo=${this.hassioInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassInfo=${this.hassInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
|
@@ -1,25 +0,0 @@
|
||||
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
|
||||
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
|
||||
|
||||
export const supervisorTabs: PageNavigation[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
path: `/hassio/dashboard`,
|
||||
iconPath: mdiViewDashboard,
|
||||
},
|
||||
{
|
||||
name: "Add-on store",
|
||||
path: `/hassio/store`,
|
||||
iconPath: mdiStore,
|
||||
},
|
||||
{
|
||||
name: "Snapshots",
|
||||
path: `/hassio/snapshots`,
|
||||
iconPath: mdiBackupRestore,
|
||||
},
|
||||
{
|
||||
name: "System",
|
||||
path: `/hassio/system`,
|
||||
iconPath: mdiCogs,
|
||||
},
|
||||
];
|
@@ -37,7 +37,7 @@ import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import "../components/hassio-card-content";
|
||||
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
||||
interface CheckboxItem {
|
||||
|
@@ -19,7 +19,6 @@ import {
|
||||
shutdownHost,
|
||||
updateOS,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import { HassioInfo } from "../../../src/data/hassio/supervisor";
|
||||
import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
@@ -36,8 +35,6 @@ class HassioHostInfo extends LitElement {
|
||||
|
||||
@property() public hostInfo!: HassioHostInfoType;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
@property() private _errors?: string;
|
||||
@@ -57,12 +54,6 @@ class HassioHostInfo extends LitElement {
|
||||
<td>System</td>
|
||||
<td>${this.hostInfo.operating_system}</td>
|
||||
</tr>
|
||||
${!this.hostInfo.features.includes("hassos")
|
||||
? html`<tr>
|
||||
<td>Docker version</td>
|
||||
<td>${this.hassioInfo.docker}</td>
|
||||
</tr>`
|
||||
: ""}
|
||||
${this.hostInfo.deployment
|
||||
? html`
|
||||
<tr>
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-menu-button/paper-menu-button";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -11,14 +12,11 @@ import {
|
||||
HassioHassOSInfo,
|
||||
HassioHostInfo,
|
||||
} from "../../../src/data/hassio/host";
|
||||
import {
|
||||
HassioSupervisorInfo,
|
||||
HassioInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
|
||||
import "../../../src/layouts/hass-tabs-subpage";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../src/types";
|
||||
import { supervisorTabs } from "../hassio-tabs";
|
||||
import { supervisorTabs } from "../hassio-panel";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
import "./hassio-host-info";
|
||||
import "./hassio-supervisor-info";
|
||||
@@ -34,11 +32,9 @@ class HassioSystem extends LitElement {
|
||||
|
||||
@property() public supervisorInfo!: HassioSupervisorInfo;
|
||||
|
||||
@property({ attribute: false }) public hassioInfo!: HassioInfo;
|
||||
|
||||
@property() public hostInfo!: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
|
||||
@property() public hassOsInfo!: HassioHassOSInfo;
|
||||
|
||||
public render(): TemplateResult | void {
|
||||
return html`
|
||||
@@ -60,7 +56,6 @@ class HassioSystem extends LitElement {
|
||||
></hassio-supervisor-info>
|
||||
<hassio-host-info
|
||||
.hass=${this.hass}
|
||||
.hassioInfo=${this.hassioInfo}
|
||||
.hostInfo=${this.hostInfo}
|
||||
.hassOsInfo=${this.hassOsInfo}
|
||||
></hassio-host-info>
|
||||
|
@@ -1,8 +1,11 @@
|
||||
const { createHassioConfig } = require("../build-scripts/webpack.js");
|
||||
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
|
||||
const { isProdBuild } = require("../build-scripts/env.js");
|
||||
|
||||
// File just used for stats builds
|
||||
|
||||
const latestBuild = false;
|
||||
|
||||
module.exports = createHassioConfig({
|
||||
isProdBuild: isProdBuild(),
|
||||
isStatsBuild: isStatsBuild(),
|
||||
latestBuild: true,
|
||||
latestBuild,
|
||||
});
|
||||
|
14
package.json
14
package.json
@@ -17,7 +17,9 @@
|
||||
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||
"format": "npm run format:eslint && npm run format:prettier",
|
||||
"mocha": "node_modules/.bin/ts-mocha -p test-mocha/tsconfig.test.json --opts test-mocha/mocha.opts",
|
||||
"test": "npm run lint && npm run mocha"
|
||||
"test": "npm run lint && npm run mocha",
|
||||
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
|
||||
"bash": "sh ./script/docker_run.sh bash $npm_package_version"
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
@@ -26,7 +28,6 @@
|
||||
"@fullcalendar/core": "^5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "^5.0.0-beta.2",
|
||||
"@material/chips": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/circular-progress": "7.0.0-canary.d92d8c93e.0",
|
||||
"@material/mwc-button": "^0.15.0",
|
||||
"@material/mwc-checkbox": "^0.15.0",
|
||||
"@material/mwc-dialog": "^0.15.0",
|
||||
@@ -48,6 +49,7 @@
|
||||
"@polymer/iron-image": "^3.0.1",
|
||||
"@polymer/iron-input": "^3.0.1",
|
||||
"@polymer/iron-label": "^3.0.1",
|
||||
"@polymer/iron-media-query": "^3.0.1",
|
||||
"@polymer/iron-overlay-behavior": "^3.0.2",
|
||||
"@polymer/iron-resizable-behavior": "^3.0.1",
|
||||
"@polymer/paper-card": "^3.0.1",
|
||||
@@ -65,6 +67,7 @@
|
||||
"@polymer/paper-radio-group": "^3.0.1",
|
||||
"@polymer/paper-ripple": "^3.0.1",
|
||||
"@polymer/paper-slider": "^3.0.1",
|
||||
"@polymer/paper-spinner": "^3.0.2",
|
||||
"@polymer/paper-styles": "^3.0.1",
|
||||
"@polymer/paper-tabs": "^3.0.1",
|
||||
"@polymer/paper-toast": "^3.0.1",
|
||||
@@ -73,7 +76,6 @@
|
||||
"@thomasloven/round-slider": "0.5.0",
|
||||
"@vaadin/vaadin-combo-box": "^5.0.10",
|
||||
"@vaadin/vaadin-date-picker": "^4.0.7",
|
||||
"@vue/web-component-wrapper": "^1.2.0",
|
||||
"@webcomponents/webcomponentsjs": "^2.2.7",
|
||||
"chart.js": "~2.8.0",
|
||||
"chartjs-chart-timeline": "^0.3.0",
|
||||
@@ -87,7 +89,7 @@
|
||||
"fuse.js": "^6.0.0",
|
||||
"google-timezones-json": "^1.0.2",
|
||||
"hls.js": "^0.12.4",
|
||||
"home-assistant-js-websocket": "^5.3.0",
|
||||
"home-assistant-js-websocket": "^5.2.1",
|
||||
"idb-keyval": "^3.2.0",
|
||||
"intl-messageformat": "^8.3.9",
|
||||
"js-yaml": "^3.13.1",
|
||||
@@ -106,8 +108,6 @@
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"superstruct": "^0.6.1",
|
||||
"unfetch": "^4.1.0",
|
||||
"vue": "^2.6.11",
|
||||
"vue2-daterange-picker": "^0.5.1",
|
||||
"web-animations-js": "^2.3.2",
|
||||
"workbox-core": "^5.1.3",
|
||||
"workbox-precaching": "^5.1.3",
|
||||
@@ -187,7 +187,7 @@
|
||||
"sinon": "^7.3.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"systemjs": "^6.3.2",
|
||||
"terser-webpack-plugin": "^3.0.6",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"ts-lit-plugin": "^1.1.10",
|
||||
"ts-mocha": "^6.0.0",
|
||||
"typescript": "^3.8.3",
|
||||
|
14
script/docker_entrypoint.sh
Normal file
14
script/docker_entrypoint.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Docker entry point inspired by travis build and script/build_frontend
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
# Build the frontend but not used the npm run build
|
||||
/bin/bash script/build_frontend
|
||||
|
||||
# TEST
|
||||
npm run test
|
||||
|
||||
#
|
||||
#xvfb-run wct
|
103
script/docker_run.sh
Executable file
103
script/docker_run.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# Basic Docker Management scripts
|
||||
# With this script you can build software, or enter an agnostic development environment and run commands interactively.
|
||||
|
||||
|
||||
|
||||
check_mandatory_tools(){
|
||||
if [ "x$(which docker)" == "x" ]; then
|
||||
echo "UNKNOWN - Missing docker binary! Are you sure it is installed and reachable?"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
docker info > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "UNKNOWN - Unable to talk to the docker daemon! Maybe the docker daemon is not running"
|
||||
exit 3
|
||||
fi
|
||||
}
|
||||
|
||||
check_dev_image(){
|
||||
if [[ "$(docker images -q ${IMAGE_NAME}:$IMAGE_TAG 2> /dev/null)" == "" ]]; then
|
||||
echo "UNKNOWN - Can't find the development docker image ${IMAGE_NAME}:$IMAGE_TAG"
|
||||
while true; do
|
||||
read -p "Do you want to create it now?" yn
|
||||
case $yn in
|
||||
[Yy]* ) create_image; break;;
|
||||
[Nn]* ) exit 3;;
|
||||
* ) echo "Please answer y or n";;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Building the basic image for compiling the production frontend
|
||||
create_image(){
|
||||
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
|
||||
}
|
||||
|
||||
#
|
||||
# Execute interactive bash on basic image
|
||||
#
|
||||
run_bash_on_docker(){
|
||||
|
||||
check_dev_image
|
||||
|
||||
docker run -it \
|
||||
-v $PWD/:/frontend/ \
|
||||
-v /frontend/node_modules \
|
||||
-v /frontend/bower_components \
|
||||
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash
|
||||
}
|
||||
|
||||
#
|
||||
# Execute the basic image for compiling the production frontend
|
||||
#
|
||||
build_all(){
|
||||
|
||||
check_dev_image
|
||||
|
||||
docker run -it \
|
||||
-v $PWD/:/frontend/ \
|
||||
-v /frontend/node_modules \
|
||||
-v /frontend/bower_components \
|
||||
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash script/build_frontend
|
||||
|
||||
}
|
||||
|
||||
# Init Global Variable
|
||||
IMAGE_NAME=home_assistant_fe_image
|
||||
IMAGE_TAG=${2:-latest}
|
||||
|
||||
check_mandatory_tools
|
||||
|
||||
case "$1" in
|
||||
setup_env)
|
||||
create_image
|
||||
;;
|
||||
bash)
|
||||
run_bash_on_docker
|
||||
;;
|
||||
build)
|
||||
build_all
|
||||
;;
|
||||
*)
|
||||
echo "NAME"
|
||||
echo " Docker Management."
|
||||
echo ""
|
||||
echo "SYNOPSIS"
|
||||
echo " ${0} command [version]"
|
||||
echo ""
|
||||
echo "DESCRIPTION"
|
||||
echo " With this script you can build software, or enter an agnostic development environment and run commands interactively."
|
||||
echo ""
|
||||
echo " The command are:"
|
||||
echo " setup_env Create develop images"
|
||||
echo " bash Run bash on develop enviroments"
|
||||
echo " build Run silent build"
|
||||
echo ""
|
||||
echo " The version is optional, if not inserted it assumes \"latest\". "
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
2
setup.py
2
setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200623.1",
|
||||
version="20200603.1",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@@ -6,18 +6,19 @@ import {
|
||||
property,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import {
|
||||
AuthProvider,
|
||||
fetchAuthProviders,
|
||||
AuthUrlSearchParams,
|
||||
} from "../data/auth";
|
||||
import { AuthProvider, fetchAuthProviders } from "../data/auth";
|
||||
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
|
||||
import { registerServiceWorker } from "../util/register-service-worker";
|
||||
import "./ha-auth-flow";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
|
||||
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
|
||||
|
||||
interface QueryParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
@property() public clientId?: string;
|
||||
|
||||
@@ -32,7 +33,14 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
constructor() {
|
||||
super();
|
||||
this.translationFragment = "page-authorize";
|
||||
const query = extractSearchParamsObject() as AuthUrlSearchParams;
|
||||
const query: QueryParams = {};
|
||||
const values = location.search.substr(1).split("&");
|
||||
for (const item of values) {
|
||||
const value = item.split("=");
|
||||
if (value.length > 1) {
|
||||
query[decodeURIComponent(value[0])] = decodeURIComponent(value[1]);
|
||||
}
|
||||
}
|
||||
if (query.client_id) {
|
||||
this.clientId = query.client_id;
|
||||
}
|
||||
@@ -137,7 +145,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
response.status === 400 &&
|
||||
authProviders.code === "onboarding_required"
|
||||
) {
|
||||
location.href = `/onboarding.html${location.search}`;
|
||||
location.href = "/?";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,6 @@ export const DOMAINS_WITH_MORE_INFO = [
|
||||
"fan",
|
||||
"group",
|
||||
"history_graph",
|
||||
"humidifier",
|
||||
"input_datetime",
|
||||
"light",
|
||||
"lock",
|
||||
@@ -80,7 +79,6 @@ export const DOMAINS_TOGGLE = new Set([
|
||||
"switch",
|
||||
"group",
|
||||
"automation",
|
||||
"humidifier",
|
||||
]);
|
||||
|
||||
/** Temperature units. */
|
||||
|
@@ -55,12 +55,6 @@ export const computeStateDisplay = (
|
||||
return formatDateTime(date, language);
|
||||
}
|
||||
|
||||
if (domain === "humidifier") {
|
||||
if (stateObj.state === "on" && stateObj.attributes.humidity) {
|
||||
return `${stateObj.attributes.humidity}%`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// Return device class translation
|
||||
(stateObj.attributes.device_class &&
|
||||
|
@@ -22,7 +22,6 @@ const fixedIcons = {
|
||||
history_graph: "hass:chart-line",
|
||||
homeassistant: "hass:home-assistant",
|
||||
homekit: "hass:home-automation",
|
||||
humidifier: "hass:air-humidifier",
|
||||
image_processing: "hass:image-filter-frames",
|
||||
input_boolean: "hass:toggle-switch-outline",
|
||||
input_datetime: "hass:calendar-clock",
|
||||
|
@@ -6,7 +6,7 @@ export const isValidEntityId = (entityId: string) =>
|
||||
export const createValidEntityId = (input: string) =>
|
||||
input
|
||||
.toLowerCase()
|
||||
.replace(/\s|'|\./g, "_") // replace spaces, points and quotes with underscore
|
||||
.replace(/\s|'/g, "_") // replace spaces and quotes with underscore
|
||||
.replace(/\W/g, "") // remove not allowed chars
|
||||
.replace(/_{2,}/g, "_") // replace multiple underscores with 1
|
||||
.replace(/_$/, ""); // remove underscores at the end
|
||||
|
@@ -8,7 +8,6 @@ export const iconColorCSS = css`
|
||||
ha-icon[data-domain="camera"][data-state="streaming"],
|
||||
ha-icon[data-domain="cover"][data-state="open"],
|
||||
ha-icon[data-domain="fan"][data-state="on"],
|
||||
ha-icon[data-domain="humidifier"][data-state="on"],
|
||||
ha-icon[data-domain="light"][data-state="on"],
|
||||
ha-icon[data-domain="input_boolean"][data-state="on"],
|
||||
ha-icon[data-domain="lock"][data-state="unlocked"],
|
||||
@@ -58,11 +57,8 @@ export const iconColorCSS = css`
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,8 +0,0 @@
|
||||
export const extractSearchParamsObject = (): { [key: string]: string } => {
|
||||
const query = {};
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
for (const [key, value] of searchParams.entries()) {
|
||||
query[key] = value;
|
||||
}
|
||||
return query;
|
||||
};
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "../ha-circular-progress";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@@ -8,9 +8,6 @@ class HaProgressButton extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
outline: none;
|
||||
}
|
||||
.container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -51,9 +48,7 @@ class HaProgressButton extends PolymerElement {
|
||||
<slot></slot>
|
||||
</mwc-button>
|
||||
<template is="dom-if" if="[[progress]]">
|
||||
<div class="progress">
|
||||
<ha-circular-progress active size="small"></ha-circular-progress>
|
||||
</div>
|
||||
<div class="progress"><paper-spinner active=""></paper-spinner></div>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
|
@@ -619,11 +619,6 @@ export class HaDataTable extends LitElement {
|
||||
text-transform: inherit;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--numeric {
|
||||
text-align: right;
|
||||
}
|
||||
|
@@ -1,228 +0,0 @@
|
||||
import Vue from "vue";
|
||||
import wrap from "@vue/web-component-wrapper";
|
||||
import DateRangePicker from "vue2-daterange-picker";
|
||||
// @ts-ignore
|
||||
import dateRangePickerStyles from "vue2-daterange-picker/dist/vue2-daterange-picker.css";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { Constructor } from "../types";
|
||||
import { customElement } from "lit-element/lib/decorators";
|
||||
|
||||
const Component = Vue.extend({
|
||||
props: {
|
||||
twentyfourHours: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
ranges: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
startDate: {
|
||||
type: [String, Date],
|
||||
default() {
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
endDate: {
|
||||
type: [String, Date],
|
||||
default() {
|
||||
return new Date();
|
||||
},
|
||||
},
|
||||
},
|
||||
render(createElement) {
|
||||
// @ts-ignore
|
||||
return createElement(DateRangePicker, {
|
||||
props: {
|
||||
"time-picker": true,
|
||||
"auto-apply": false,
|
||||
opens: "right",
|
||||
"show-dropdowns": false,
|
||||
"time-picker24-hour": this.twentyfourHours,
|
||||
disabled: this.disabled,
|
||||
ranges: this.ranges ? {} : false,
|
||||
},
|
||||
model: {
|
||||
value: {
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
},
|
||||
callback: (value) => {
|
||||
// @ts-ignore
|
||||
fireEvent(this.$el as HTMLElement, "change", value);
|
||||
},
|
||||
expression: "dateRange",
|
||||
},
|
||||
scopedSlots: {
|
||||
input() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "input" },
|
||||
});
|
||||
},
|
||||
header() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "header" },
|
||||
});
|
||||
},
|
||||
ranges() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "ranges" },
|
||||
});
|
||||
},
|
||||
footer() {
|
||||
return createElement("slot", {
|
||||
domProps: { name: "footer" },
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const WrappedElement: Constructor<HTMLElement> = wrap(Vue, Component);
|
||||
|
||||
@customElement("date-range-picker")
|
||||
class DateRangePickerElement extends WrappedElement {
|
||||
constructor() {
|
||||
super();
|
||||
const style = document.createElement("style");
|
||||
style.innerHTML = `
|
||||
${dateRangePickerStyles}
|
||||
.calendars {
|
||||
display: flex;
|
||||
}
|
||||
.daterangepicker {
|
||||
left: 0px !important;
|
||||
top: auto;
|
||||
background-color: var(--card-background-color);
|
||||
border: none;
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
min-width: initial !important;
|
||||
}
|
||||
.daterangepicker:after {
|
||||
border-bottom: 6px solid var(--card-background-color);
|
||||
}
|
||||
.daterangepicker .calendar-table {
|
||||
background-color: var(--card-background-color);
|
||||
border: none;
|
||||
}
|
||||
.daterangepicker .calendar-table td,
|
||||
.daterangepicker .calendar-table th {
|
||||
background-color: transparent;
|
||||
color: var(--secondary-text-color);
|
||||
border-radius: 0;
|
||||
outline: none;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.daterangepicker td.off,
|
||||
.daterangepicker td.off.end-date,
|
||||
.daterangepicker td.off.in-range,
|
||||
.daterangepicker td.off.start-date {
|
||||
background-color: var(--secondary-background-color);
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.daterangepicker td.in-range {
|
||||
background-color: var(--light-primary-color);
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.daterangepicker td.active,
|
||||
.daterangepicker td.active:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.daterangepicker td.start-date.end-date {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.daterangepicker td.start-date {
|
||||
border-radius: 50% 0 0 50%;
|
||||
}
|
||||
.daterangepicker td.end-date {
|
||||
border-radius: 0 50% 50% 0;
|
||||
}
|
||||
.reportrange-text {
|
||||
background: none !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.daterangepicker .calendar-table .next span,
|
||||
.daterangepicker .calendar-table .prev span {
|
||||
border: solid var(--primary-text-color);
|
||||
border-width: 0 2px 2px 0;
|
||||
}
|
||||
.daterangepicker .ranges li {
|
||||
outline: none;
|
||||
}
|
||||
.daterangepicker .ranges li:hover {
|
||||
background-color: var(--secondary-background-color);
|
||||
}
|
||||
.daterangepicker .ranges li.active {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-primary-color);
|
||||
}
|
||||
.daterangepicker select.ampmselect,
|
||||
.daterangepicker select.hourselect,
|
||||
.daterangepicker select.minuteselect,
|
||||
.daterangepicker select.secondselect {
|
||||
background: transparent;
|
||||
border: 1px solid var(--divider-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
.daterangepicker .drp-buttons .btn {
|
||||
border: 1px solid var(--primary-color);
|
||||
background-color: transparent;
|
||||
color: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.calendars-container {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.drp-calendar.col.right .calendar-table {
|
||||
display: none;
|
||||
}
|
||||
.daterangepicker.show-ranges .drp-calendar.left {
|
||||
border-left: 0px;
|
||||
}
|
||||
.daterangepicker .drp-calendar.left {
|
||||
padding: 8px;
|
||||
}
|
||||
.daterangepicker.show-calendar .ranges {
|
||||
margin-top: 0;
|
||||
padding-top: 8px;
|
||||
border-right: 1px solid var(--divider-color);
|
||||
}
|
||||
@media only screen and (max-width: 800px) {
|
||||
.calendars {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
.calendar-table {
|
||||
padding: 0 !important;
|
||||
}
|
||||
`;
|
||||
const shadowRoot = this.shadowRoot!;
|
||||
shadowRoot.appendChild(style);
|
||||
// Stop click events from reaching the document, otherwise it will close the picker immediately.
|
||||
shadowRoot.addEventListener("click", (ev) => ev.stopPropagation());
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"date-range-picker": DateRangePickerElement;
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ const isOn = (stateObj?: HassEntity) =>
|
||||
!STATES_OFF.includes(stateObj.state) &&
|
||||
!UNAVAILABLE_STATES.includes(stateObj.state);
|
||||
|
||||
export class HaEntityToggle extends LitElement {
|
||||
class HaEntityToggle extends LitElement {
|
||||
// hass is not a property so that we only re-render on stateObj changes
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
|
@@ -40,7 +40,7 @@ export class HaButtonMenu extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
`;
|
||||
|
@@ -176,9 +176,7 @@ class HaCameraStream extends LitElement {
|
||||
Hls: HLSModule,
|
||||
url: string
|
||||
) {
|
||||
const hls = new Hls({
|
||||
liveBackBufferLength: 60,
|
||||
});
|
||||
const hls = new Hls();
|
||||
this._hlsPolyfillInstance = hls;
|
||||
hls.attachMedia(videoEl);
|
||||
hls.on(Hls.Events.MEDIA_ATTACHED, () => {
|
||||
|
@@ -12,8 +12,6 @@ import {
|
||||
class HaCard extends LitElement {
|
||||
@property() public header?: string;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public outlined = false;
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
@@ -21,12 +19,12 @@ class HaCard extends LitElement {
|
||||
--ha-card-background,
|
||||
var(--paper-card-background-color, white)
|
||||
);
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
border-radius: var(--ha-card-border-radius, 2px);
|
||||
box-shadow: var(
|
||||
--ha-card-box-shadow,
|
||||
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
|
||||
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
0px 1px 3px 0px rgba(0, 0, 0, 0.12)
|
||||
0 2px 2px 0 rgba(0, 0, 0, 0.14),
|
||||
0 1px 5px 0 rgba(0, 0, 0, 0.12),
|
||||
0 3px 1px -2px rgba(0, 0, 0, 0.2)
|
||||
);
|
||||
color: var(--primary-text-color);
|
||||
display: block;
|
||||
@@ -34,16 +32,6 @@ class HaCard extends LitElement {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
:host([outlined]) {
|
||||
box-shadow: none;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: var(
|
||||
--ha-card-border-color,
|
||||
var(--divider-color, #e0e0e0)
|
||||
);
|
||||
}
|
||||
|
||||
.card-header,
|
||||
:host ::slotted(.card-header) {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
|
@@ -1,93 +0,0 @@
|
||||
import {
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
svg,
|
||||
html,
|
||||
customElement,
|
||||
unsafeCSS,
|
||||
SVGTemplateResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
// @ts-ignore
|
||||
import progressStyles from "@material/circular-progress/dist/mdc.circular-progress.min.css";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
export class HaCircularProgress extends LitElement {
|
||||
@property({ type: Boolean })
|
||||
public active = false;
|
||||
|
||||
@property()
|
||||
public alt = "Loading";
|
||||
|
||||
@property()
|
||||
public size: "small" | "medium" | "large" = "medium";
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
let indeterminatePart: SVGTemplateResult;
|
||||
|
||||
if (this.size === "small") {
|
||||
indeterminatePart = svg`
|
||||
<svg class="mdc-circular-progress__indeterminate-circle-graphic" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="12" cy="12" r="8.75" stroke-dasharray="54.978" stroke-dashoffset="27.489"/>
|
||||
</svg>`;
|
||||
} else if (this.size === "large") {
|
||||
indeterminatePart = svg`
|
||||
<svg class="mdc-circular-progress__indeterminate-circle-graphic" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="24" cy="24" r="18" stroke-dasharray="113.097" stroke-dashoffset="56.549"/>
|
||||
</svg>`;
|
||||
} else {
|
||||
// medium
|
||||
indeterminatePart = svg`
|
||||
<svg class="mdc-circular-progress__indeterminate-circle-graphic" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="16" cy="16" r="12.5" stroke-dasharray="78.54" stroke-dashoffset="39.27"/>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// ignoring prettier as it will introduce unwanted whitespace
|
||||
// We have not implemented the determinate support of mdc circular progress.
|
||||
// prettier-ignore
|
||||
return html`
|
||||
<div
|
||||
class="mdc-circular-progress ${classMap({
|
||||
"mdc-circular-progress--indeterminate": this.active,
|
||||
[`mdc-circular-progress--${this.size}`]: true,
|
||||
})}"
|
||||
role="progressbar"
|
||||
aria-label=${this.alt}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="1"
|
||||
>
|
||||
<div class="mdc-circular-progress__indeterminate-container">
|
||||
<div class="mdc-circular-progress__spinner-layer">
|
||||
<div class="mdc-circular-progress__circle-clipper mdc-circular-progress__circle-left">
|
||||
${indeterminatePart}
|
||||
</div><div class="mdc-circular-progress__gap-patch">
|
||||
${indeterminatePart}
|
||||
</div><div class="mdc-circular-progress__circle-clipper mdc-circular-progress__circle-right">
|
||||
${indeterminatePart}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [
|
||||
unsafeCSS(progressStyles),
|
||||
css`
|
||||
:host {
|
||||
text-align: initial;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-circular-progress": HaCircularProgress;
|
||||
}
|
||||
}
|
@@ -1,195 +0,0 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { mdiCalendar } from "@mdi/js";
|
||||
import { formatDateTime } from "../common/datetime/format_date_time";
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import "./ha-svg-icon";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "@material/mwc-list/mwc-list";
|
||||
import "./date-range-picker";
|
||||
|
||||
export interface DateRangePickerRanges {
|
||||
[key: string]: [Date, Date];
|
||||
}
|
||||
|
||||
@customElement("ha-date-range-picker")
|
||||
export class HaDateRangePicker extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public startDate!: Date;
|
||||
|
||||
@property() public endDate!: Date;
|
||||
|
||||
@property() public ranges?: DateRangePickerRanges;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property({ type: Boolean }) private _hour24format = false;
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || oldHass.language !== this.hass.language) {
|
||||
this._hour24format = this._compute24hourFormat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<date-range-picker
|
||||
?disabled=${this.disabled}
|
||||
twentyfour-hours=${this._hour24format}
|
||||
start-date=${this.startDate}
|
||||
end-date=${this.endDate}
|
||||
?ranges=${this.ranges !== undefined}
|
||||
>
|
||||
<div slot="input" class="date-range-inputs">
|
||||
<ha-svg-icon path=${mdiCalendar}></ha-svg-icon>
|
||||
<paper-input
|
||||
.value=${formatDateTime(this.startDate, this.hass.language)}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.date-range-picker.start_date"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleInputClick}
|
||||
readonly
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${formatDateTime(this.endDate, this.hass.language)}
|
||||
label=${this.hass.localize(
|
||||
"ui.components.date-range-picker.end_date"
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
@click=${this._handleInputClick}
|
||||
readonly
|
||||
></paper-input>
|
||||
</div>
|
||||
${this.ranges
|
||||
? html`<div slot="ranges" class="date-range-ranges">
|
||||
<mwc-list @click=${this._setDateRange}>
|
||||
${Object.entries(this.ranges).map(
|
||||
([name, dates]) => html`<mwc-list-item
|
||||
.activated=${this.startDate.getTime() ===
|
||||
dates[0].getTime() &&
|
||||
this.endDate.getTime() === dates[1].getTime()}
|
||||
.startDate=${dates[0]}
|
||||
.endDate=${dates[1]}
|
||||
>
|
||||
${name}
|
||||
</mwc-list-item>`
|
||||
)}
|
||||
</mwc-list>
|
||||
</div>`
|
||||
: ""}
|
||||
<div slot="footer" class="date-range-footer">
|
||||
<mwc-button @click=${this._cancelDateRange}
|
||||
>${this.hass.localize("ui.common.cancel")}</mwc-button
|
||||
>
|
||||
<mwc-button @click=${this._applyDateRange}
|
||||
>${this.hass.localize(
|
||||
"ui.components.date-range-picker.select"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
</date-range-picker>
|
||||
`;
|
||||
}
|
||||
|
||||
private _compute24hourFormat() {
|
||||
return (
|
||||
new Intl.DateTimeFormat(this.hass.language, {
|
||||
hour: "numeric",
|
||||
})
|
||||
.formatToParts(new Date(2020, 0, 1, 13))
|
||||
.find((part) => part.type === "hour")!.value.length === 2
|
||||
);
|
||||
}
|
||||
|
||||
private _setDateRange(ev: Event) {
|
||||
const target = ev.target as any;
|
||||
const startDate = target.startDate;
|
||||
const endDate = target.endDate;
|
||||
const dateRangePicker = this._dateRangePicker;
|
||||
dateRangePicker.clickRange([startDate, endDate]);
|
||||
dateRangePicker.clickedApply();
|
||||
}
|
||||
|
||||
private _cancelDateRange() {
|
||||
this._dateRangePicker.clickCancel();
|
||||
}
|
||||
|
||||
private _applyDateRange() {
|
||||
this._dateRangePicker.clickedApply();
|
||||
}
|
||||
|
||||
private get _dateRangePicker() {
|
||||
const dateRangePicker = this.shadowRoot!.querySelector(
|
||||
"date-range-picker"
|
||||
) as any;
|
||||
return dateRangePicker.vueComponent.$children[0];
|
||||
}
|
||||
|
||||
private _handleInputClick() {
|
||||
// close the date picker, so it will open again on the click event
|
||||
if (this._dateRangePicker.open) {
|
||||
this._dateRangePicker.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-svg-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.date-range-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.date-range-ranges {
|
||||
border-right: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 800px) {
|
||||
.date-range-ranges {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
.date-range-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
paper-input {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
paper-input:last-child {
|
||||
margin-left: 8px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-date-range-picker": HaDateRangePicker;
|
||||
}
|
||||
}
|
24
src/components/ha-demo-badge.js
Normal file
24
src/components/ha-demo-badge.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import "./ha-label-badge";
|
||||
|
||||
class HaDemoBadge extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
--ha-label-badge-color: #dac90d;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ha-label-badge
|
||||
icon="hass:emoticon"
|
||||
label="Demo"
|
||||
description=""
|
||||
></ha-label-badge>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-demo-badge", HaDemoBadge);
|
@@ -13,7 +13,7 @@ export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
<mwc-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
dialogAction="close"
|
||||
class="header_button"
|
||||
class="close_button"
|
||||
>
|
||||
<ha-svg-icon path=${mdiClose}></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
@@ -25,9 +25,6 @@ export class HaDialog extends MwcDialog {
|
||||
return [
|
||||
style,
|
||||
css`
|
||||
.mdc-dialog {
|
||||
z-index: var(--dialog-z-index, 7);
|
||||
}
|
||||
.mdc-dialog__actions {
|
||||
justify-content: var(--justify-action-buttons, flex-end);
|
||||
}
|
||||
@@ -38,15 +35,10 @@ export class HaDialog extends MwcDialog {
|
||||
display: block;
|
||||
height: 20px;
|
||||
}
|
||||
.mdc-dialog__content {
|
||||
padding: var(--dialog-content-padding, 20px 24px);
|
||||
}
|
||||
.header_button {
|
||||
.close_button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 12px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,33 +0,0 @@
|
||||
import "@material/mwc-formfield";
|
||||
import type { Formfield } from "@material/mwc-formfield";
|
||||
import { style } from "@material/mwc-formfield/mwc-formfield-css";
|
||||
import { css, CSSResult, customElement } from "lit-element";
|
||||
import { Constructor } from "../types";
|
||||
|
||||
const MwcFormfield = customElements.get("mwc-formfield") as Constructor<
|
||||
Formfield
|
||||
>;
|
||||
|
||||
@customElement("ha-formfield")
|
||||
export class HaFormfield extends MwcFormfield {
|
||||
protected static get styles(): CSSResult[] {
|
||||
return [
|
||||
style,
|
||||
css`
|
||||
::slotted(ha-switch) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
[dir="rtl"] ::slotted(ha-switch),
|
||||
::slotted(ha-switch)[dir="rtl"] {
|
||||
margin-left: 10px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-formfield": HaFormfield;
|
||||
}
|
||||
}
|
@@ -1,37 +1,17 @@
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-button-arrow-next")
|
||||
export class HaIconButtonArrowNext extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() private _icon = mdiArrowRight;
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonArrowNext extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiArrowRight
|
||||
: mdiArrowLeft;
|
||||
? "hass:arrow-right"
|
||||
: "hass:arrow-left";
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||
</mwc-icon-button> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -39,3 +19,5 @@ declare global {
|
||||
"ha-icon-button-arrow-next": HaIconButtonArrowNext;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-arrow-next", HaIconButtonArrowNext);
|
||||
|
@@ -1,15 +1,8 @@
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { LitElement, property, TemplateResult, html } from "lit-element";
|
||||
import { mdiArrowLeft, mdiArrowRight } from "@mdi/js";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-button-arrow-prev")
|
||||
export class HaIconButtonArrowPrev extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@@ -39,3 +32,5 @@ declare global {
|
||||
"ha-icon-button-arrow-prev": HaIconButtonArrowPrev;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-arrow-prev", HaIconButtonArrowPrev);
|
||||
|
@@ -1,37 +1,17 @@
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
||||
import "@material/mwc-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-button-next")
|
||||
export class HaIconButtonNext extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() private _icon = mdiChevronRight;
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonNext extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiChevronRight
|
||||
: mdiChevronLeft;
|
||||
? "hass:chevron-right"
|
||||
: "hass:chevron-left";
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||
</mwc-icon-button> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -39,3 +19,5 @@ declare global {
|
||||
"ha-icon-button-next": HaIconButtonNext;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-next", HaIconButtonNext);
|
||||
|
@@ -1,37 +1,17 @@
|
||||
import {
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
html,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
import { mdiChevronRight, mdiChevronLeft } from "@mdi/js";
|
||||
import "@material/mwc-icon-button/mwc-icon-button";
|
||||
import "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-button-prev")
|
||||
export class HaIconButtonPrev extends LitElement {
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@property() private _icon = mdiChevronLeft;
|
||||
import { HaIconButton } from "./ha-icon-button";
|
||||
|
||||
export class HaIconButtonPrev extends HaIconButton {
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
this.icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight;
|
||||
? "hass:chevron-left"
|
||||
: "hass:chevron-right";
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<mwc-icon-button .disabled=${this.disabled}>
|
||||
<ha-svg-icon .path=${this._icon}></ha-svg-icon>
|
||||
</mwc-icon-button> `;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
@@ -39,3 +19,5 @@ declare global {
|
||||
"ha-icon-button-prev": HaIconButtonPrev;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-icon-button-prev", HaIconButtonPrev);
|
||||
|
@@ -24,21 +24,28 @@ export class HaIconButton extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<mwc-icon-button .label=${this.label} .disabled=${this.disabled}>
|
||||
<mwc-icon-button
|
||||
.label=${this.label}
|
||||
?disabled=${this.disabled}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
<ha-icon .icon=${this.icon}></ha-icon>
|
||||
</mwc-icon-button>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(ev) {
|
||||
if (this.disabled) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-block;
|
||||
outline: none;
|
||||
}
|
||||
:host([disabled]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
mwc-icon-button {
|
||||
--mdc-theme-on-primary: currentColor;
|
||||
--mdc-theme-text-disabled-on-light: var(--disabled-text-color);
|
||||
|
@@ -13,7 +13,7 @@ class HaMarkdownElement extends UpdatingElement {
|
||||
protected update(changedProps) {
|
||||
super.update(changedProps);
|
||||
if (this.content !== undefined) {
|
||||
this._render();
|
||||
this._render();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -54,7 +54,7 @@ class HaMarkdown extends LitElement {
|
||||
}
|
||||
ha-markdown-element code,
|
||||
pre {
|
||||
background-color: var(--markdown-code-background-color, none);
|
||||
background-color: var(--markdown-code-background-color, #f6f8fa);
|
||||
border-radius: 3px;
|
||||
}
|
||||
ha-markdown-element code {
|
||||
|
@@ -1,5 +1,3 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
css,
|
||||
@@ -14,7 +12,8 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { subscribeNotifications } from "../data/persistent_notification";
|
||||
import { HomeAssistant } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-icon-button";
|
||||
import { mdiMenu } from "@mdi/js";
|
||||
|
||||
@customElement("ha-menu-button")
|
||||
class HaMenuButton extends LitElement {
|
||||
|
@@ -1,11 +1,6 @@
|
||||
import "@material/mwc-icon-button";
|
||||
import {
|
||||
mdiBell,
|
||||
mdiCellphoneSettingsVariant,
|
||||
mdiMenuOpen,
|
||||
mdiMenu,
|
||||
mdiViewDashboard,
|
||||
} from "@mdi/js";
|
||||
import { mdiBell, mdiCellphoneSettingsVariant } from "@mdi/js";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "./ha-icon-button";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import type { PaperIconItemElement } from "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
@@ -15,7 +10,6 @@ import {
|
||||
CSSResult,
|
||||
eventOptions,
|
||||
html,
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
@@ -35,9 +29,9 @@ import {
|
||||
getExternalConfig,
|
||||
} from "../external_app/external_config";
|
||||
import type { HomeAssistant, PanelInfo } from "../types";
|
||||
import "./ha-svg-icon";
|
||||
import "./ha-icon";
|
||||
import "./ha-menu-button";
|
||||
import "./ha-svg-icon";
|
||||
import "./user/ha-user-badge";
|
||||
|
||||
const SHOW_AFTER_SPACER = ["config", "developer-tools", "hassio"];
|
||||
@@ -109,7 +103,9 @@ const computePanels = (hass: HomeAssistant): [PanelInfo[], PanelInfo[]] => {
|
||||
return [beforeSpacer, afterSpacer];
|
||||
};
|
||||
|
||||
@customElement("ha-sidebar")
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaSidebar extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@@ -157,16 +153,13 @@ class HaSidebar extends LitElement {
|
||||
<div class="menu">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<mwc-icon-button
|
||||
.label=${hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
<ha-icon-button
|
||||
aria-label=${hass.localize("ui.sidebar.sidebar_toggle")}
|
||||
.icon=${hass.dockedSidebar === "docked"
|
||||
? "hass:menu-open"
|
||||
: "hass:menu"}
|
||||
@click=${this._toggleSidebar}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${hass.dockedSidebar === "docked"
|
||||
? mdiMenuOpen
|
||||
: mdiMenu}
|
||||
></ha-svg-icon>
|
||||
</mwc-icon-button>
|
||||
></ha-icon-button>
|
||||
`
|
||||
: ""}
|
||||
<span class="title">Home Assistant</span>
|
||||
@@ -181,16 +174,14 @@ class HaSidebar extends LitElement {
|
||||
>
|
||||
${this._renderPanel(
|
||||
defaultPanel.url_path,
|
||||
defaultPanel.title || hass.localize("panel.states"),
|
||||
defaultPanel.icon,
|
||||
!defaultPanel.icon ? mdiViewDashboard : undefined
|
||||
defaultPanel.icon || "hass:view-dashboard",
|
||||
defaultPanel.title || hass.localize("panel.states")
|
||||
)}
|
||||
${beforeSpacer.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
undefined
|
||||
hass.localize(`panel.${panel.title}`) || panel.title
|
||||
)
|
||||
)}
|
||||
<div class="spacer" disabled></div>
|
||||
@@ -198,9 +189,8 @@ class HaSidebar extends LitElement {
|
||||
${afterSpacer.map((panel) =>
|
||||
this._renderPanel(
|
||||
panel.url_path,
|
||||
hass.localize(`panel.${panel.title}`) || panel.title,
|
||||
panel.icon,
|
||||
undefined
|
||||
hass.localize(`panel.${panel.title}`) || panel.title
|
||||
)
|
||||
)}
|
||||
${this._externalConfig && this._externalConfig.hasSettingsScreen
|
||||
@@ -453,12 +443,7 @@ class HaSidebar extends LitElement {
|
||||
fireEvent(this, "hass-toggle-menu");
|
||||
}
|
||||
|
||||
private _renderPanel(
|
||||
urlPath: string,
|
||||
title: string | null,
|
||||
icon?: string | null,
|
||||
iconPath?: string | null
|
||||
) {
|
||||
private _renderPanel(urlPath, icon, title) {
|
||||
return html`
|
||||
<a
|
||||
aria-role="option"
|
||||
@@ -469,12 +454,7 @@ class HaSidebar extends LitElement {
|
||||
@mouseleave=${this._itemMouseLeave}
|
||||
>
|
||||
<paper-icon-item>
|
||||
${iconPath
|
||||
? html`<ha-svg-icon
|
||||
slot="item-icon"
|
||||
.path=${iconPath}
|
||||
></ha-svg-icon>`
|
||||
: html`<ha-icon slot="item-icon" .icon=${icon}></ha-icon>`}
|
||||
<ha-icon slot="item-icon" .icon="${icon}"></ha-icon>
|
||||
<span class="item-text">${title}</span>
|
||||
</paper-icon-item>
|
||||
</a>
|
||||
@@ -516,13 +496,13 @@ class HaSidebar extends LitElement {
|
||||
width: 256px;
|
||||
}
|
||||
|
||||
.menu mwc-icon-button {
|
||||
.menu ha-icon-button {
|
||||
color: var(--sidebar-icon-color);
|
||||
}
|
||||
:host([expanded]) .menu mwc-icon-button {
|
||||
:host([expanded]) .menu ha-icon-button {
|
||||
margin-right: 23px;
|
||||
}
|
||||
:host([expanded][_rtl]) .menu mwc-icon-button {
|
||||
:host([expanded][_rtl]) .menu ha-icon-button {
|
||||
margin-right: 0px;
|
||||
margin-left: 23px;
|
||||
}
|
||||
@@ -734,7 +714,7 @@ class HaSidebar extends LitElement {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:host([_rtl]) .menu mwc-icon-button {
|
||||
:host([_rtl]) .menu ha-icon-button {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
@@ -747,3 +727,5 @@ declare global {
|
||||
"ha-sidebar": HaSidebar;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-sidebar", HaSidebar);
|
||||
|
@@ -1,7 +1,15 @@
|
||||
import { ripple } from "@material/mwc-ripple/ripple-directive";
|
||||
import "@material/mwc-switch";
|
||||
import type { Switch } from "@material/mwc-switch";
|
||||
import { style } from "@material/mwc-switch/mwc-switch-css";
|
||||
import { css, CSSResult, customElement, property } from "lit-element";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
property,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import { forwardHaptic } from "../data/haptics";
|
||||
import { Constructor } from "../types";
|
||||
|
||||
@@ -14,12 +22,18 @@ export class HaSwitch extends MwcSwitch {
|
||||
// Do not add haptic when a user is required to press save.
|
||||
@property({ type: Boolean }) public haptic = false;
|
||||
|
||||
@query("slot") private _slot!: HTMLSlotElement;
|
||||
|
||||
protected firstUpdated() {
|
||||
super.firstUpdated();
|
||||
this.style.setProperty(
|
||||
"--mdc-theme-secondary",
|
||||
"var(--switch-checked-color)"
|
||||
);
|
||||
this.classList.toggle(
|
||||
"slotted",
|
||||
Boolean(this._slot.assignedNodes().length)
|
||||
);
|
||||
this.addEventListener("change", () => {
|
||||
if (this.haptic) {
|
||||
forwardHaptic("light");
|
||||
@@ -27,10 +41,40 @@ export class HaSwitch extends MwcSwitch {
|
||||
});
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<div class="mdc-switch">
|
||||
<div class="mdc-switch__track"></div>
|
||||
<div
|
||||
class="mdc-switch__thumb-underlay"
|
||||
.ripple="${ripple({
|
||||
interactionNode: this,
|
||||
})}"
|
||||
>
|
||||
<div class="mdc-switch__thumb">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="basic-switch"
|
||||
class="mdc-switch__native-control"
|
||||
role="switch"
|
||||
@change="${this._haChangeHandler}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label for="basic-switch"><slot></slot></label>
|
||||
`;
|
||||
}
|
||||
|
||||
protected static get styles(): CSSResult[] {
|
||||
return [
|
||||
style,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.mdc-switch.mdc-switch--checked .mdc-switch__thumb {
|
||||
background-color: var(--switch-checked-button-color);
|
||||
border-color: var(--switch-checked-button-color);
|
||||
@@ -47,9 +91,18 @@ export class HaSwitch extends MwcSwitch {
|
||||
background-color: var(--switch-unchecked-track-color);
|
||||
border-color: var(--switch-unchecked-track-color);
|
||||
}
|
||||
:host(.slotted) .mdc-switch {
|
||||
margin-right: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
private _haChangeHandler(e: Event) {
|
||||
this.mdcFoundation.handleChange(e);
|
||||
// catch "click" event and sync properties
|
||||
this.checked = this.formElement.checked;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
46
src/components/ha-textarea.js
Normal file
46
src/components/ha-textarea.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
Wrapper for paper-textarea.
|
||||
|
||||
paper-textarea crashes on iOS when created programmatically. This only impacts
|
||||
our automation and script editors as they are using Preact. Polymer is using
|
||||
template elements and does not have this issue.
|
||||
|
||||
paper-textarea issue: https://github.com/PolymerElements/paper-input/issues/556
|
||||
WebKit issue: https://bugs.webkit.org/show_bug.cgi?id=174629
|
||||
*/
|
||||
|
||||
import "@polymer/paper-input/paper-textarea";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
||||
class HaTextarea extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<paper-textarea
|
||||
label="[[label]]"
|
||||
placeholder="[[placeholder]]"
|
||||
value="{{value}}"
|
||||
></paper-textarea>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
name: String,
|
||||
label: String,
|
||||
placeholder: String,
|
||||
value: {
|
||||
type: String,
|
||||
notify: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-textarea", HaTextarea);
|
@@ -262,28 +262,6 @@ class StateHistoryChartLine extends LocalizeMixin(PolymerElement) {
|
||||
pushData(new Date(state.last_changed), series);
|
||||
}
|
||||
});
|
||||
} else if (domain === "humidifier") {
|
||||
addColumn(
|
||||
`${this.hass.localize(
|
||||
"ui.card.humidifier.target_humidity_entity",
|
||||
"name",
|
||||
name
|
||||
)}`,
|
||||
true
|
||||
);
|
||||
addColumn(
|
||||
`${this.hass.localize("ui.card.humidifier.on_entity", "name", name)}`,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
states.states.forEach((state) => {
|
||||
if (!state.attributes) return;
|
||||
const target = safeParseFloat(state.attributes.humidity);
|
||||
const series = [target];
|
||||
series.push(state.state === "on" ? target : null);
|
||||
pushData(new Date(state.last_changed), series);
|
||||
});
|
||||
} else {
|
||||
// Only disable interpolation for sensors
|
||||
const isStep = domain === "sensor";
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import "./ha-circular-progress";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
|
@@ -1,11 +1,5 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface AuthUrlSearchParams {
|
||||
client_id?: string;
|
||||
redirect_uri?: string;
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export interface AuthProvider {
|
||||
name: string;
|
||||
id: string;
|
||||
|
@@ -5,7 +5,7 @@ import { HomeAssistant } from "../types";
|
||||
import { DataEntryFlowProgress, DataEntryFlowStep } from "./data_entry_flow";
|
||||
import { domainToName } from "./integration";
|
||||
|
||||
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf", "discovery"];
|
||||
export const DISCOVERY_SOURCES = ["unignore", "homekit", "ssdp", "zeroconf"];
|
||||
|
||||
export const createConfigFlow = (hass: HomeAssistant, handler: string) =>
|
||||
hass.callApi<DataEntryFlowStep>("POST", "config/config_entries/flow", {
|
||||
|
@@ -12,11 +12,6 @@ export interface ConfigUpdateValues {
|
||||
internal_url?: string | null;
|
||||
}
|
||||
|
||||
export interface CheckConfigResult {
|
||||
result: "valid" | "invalid";
|
||||
errors: string | null;
|
||||
}
|
||||
|
||||
export const saveCoreConfig = (
|
||||
hass: HomeAssistant,
|
||||
values: Partial<ConfigUpdateValues>
|
||||
@@ -30,6 +25,3 @@ export const detectCoreConfig = (hass: HomeAssistant) =>
|
||||
hass.callWS<Partial<ConfigUpdateValues>>({
|
||||
type: "config/core/detect",
|
||||
});
|
||||
|
||||
export const checkCoreConfig = (hass: HomeAssistant) =>
|
||||
hass.callApi<CheckConfigResult>("POST", "config/core/check_config");
|
||||
|
@@ -4,20 +4,6 @@ import { hassioApiResultExtractor, HassioResponse } from "./common";
|
||||
export type HassioHomeAssistantInfo = any;
|
||||
export type HassioSupervisorInfo = any;
|
||||
|
||||
export type HassioInfo = {
|
||||
arch: string;
|
||||
channel: string;
|
||||
docker: string;
|
||||
hassos?: string;
|
||||
homeassistant: string;
|
||||
hostname: string;
|
||||
logging: string;
|
||||
maching: string;
|
||||
supervisor: string;
|
||||
supported_arch: string[];
|
||||
timezone: string;
|
||||
};
|
||||
|
||||
export type HassioPanelInfo = PanelInfo<
|
||||
| undefined
|
||||
| {
|
||||
@@ -52,12 +38,6 @@ export const fetchHassioSupervisorInfo = async (hass: HomeAssistant) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioInfo = async (hass: HomeAssistant) => {
|
||||
return hassioApiResultExtractor(
|
||||
await hass.callApi<HassioResponse<HassioInfo>>("GET", "hassio/info")
|
||||
);
|
||||
};
|
||||
|
||||
export const fetchHassioLogs = async (
|
||||
hass: HomeAssistant,
|
||||
provider: string
|
||||
|
@@ -5,15 +5,13 @@ import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
const DOMAINS_USE_LAST_UPDATED = ["climate", "humidifier", "water_heater"];
|
||||
const DOMAINS_USE_LAST_UPDATED = ["climate", "water_heater"];
|
||||
const LINE_ATTRIBUTES_TO_KEEP = [
|
||||
"temperature",
|
||||
"current_temperature",
|
||||
"target_temp_low",
|
||||
"target_temp_high",
|
||||
"hvac_action",
|
||||
"humidity",
|
||||
"mode",
|
||||
];
|
||||
|
||||
export interface LineChartState {
|
||||
@@ -226,8 +224,6 @@ export const computeHistory = (
|
||||
unit = hass.config.unit_system.temperature;
|
||||
} else if (computeStateDomain(stateInfo[0]) === "water_heater") {
|
||||
unit = hass.config.unit_system.temperature;
|
||||
} else if (computeStateDomain(stateInfo[0]) === "humidifier") {
|
||||
unit = "%";
|
||||
}
|
||||
|
||||
if (!unit) {
|
||||
|
@@ -1,19 +0,0 @@
|
||||
import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
|
||||
export type HumidifierEntity = HassEntityBase & {
|
||||
attributes: HassEntityAttributeBase & {
|
||||
humidity?: number;
|
||||
min_humidity?: number;
|
||||
max_humidity?: number;
|
||||
mode?: string;
|
||||
available_modes?: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export const HUMIDIFIER_SUPPORT_MODES = 1;
|
||||
|
||||
export const HUMIDIFIER_DEVICE_CLASS_HUMIDIFIER = "humidifier";
|
||||
export const HUMIDIFIER_DEVICE_CLASS_DEHUMIDIFIER = "dehumidifier";
|
@@ -1,67 +1,7 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface LogbookEntry {
|
||||
when: string;
|
||||
name: string;
|
||||
message: string;
|
||||
entity_id?: string;
|
||||
domain: string;
|
||||
context_user_id?: string;
|
||||
}
|
||||
|
||||
const DATA_CACHE: {
|
||||
[cacheKey: string]: { [entityId: string]: Promise<LogbookEntry[]> };
|
||||
} = {};
|
||||
|
||||
export const getLogbookData = (
|
||||
hass: HomeAssistant,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityId?: string
|
||||
) => {
|
||||
const ALL_ENTITIES = "*";
|
||||
|
||||
if (!entityId) {
|
||||
entityId = ALL_ENTITIES;
|
||||
}
|
||||
|
||||
const cacheKey = `${startDate}${endDate}`;
|
||||
|
||||
if (!DATA_CACHE[cacheKey]) {
|
||||
DATA_CACHE[cacheKey] = {};
|
||||
}
|
||||
|
||||
if (DATA_CACHE[cacheKey][entityId]) {
|
||||
return DATA_CACHE[cacheKey][entityId];
|
||||
}
|
||||
|
||||
if (entityId !== ALL_ENTITIES && DATA_CACHE[cacheKey][ALL_ENTITIES]) {
|
||||
return DATA_CACHE[cacheKey][ALL_ENTITIES].then((entities) =>
|
||||
entities.filter((entity) => entity.entity_id === entityId)
|
||||
);
|
||||
}
|
||||
|
||||
DATA_CACHE[cacheKey][entityId] = getLogbookDataFromServer(
|
||||
hass,
|
||||
startDate,
|
||||
endDate,
|
||||
entityId !== ALL_ENTITIES ? entityId : undefined
|
||||
).then((entries) => entries.reverse());
|
||||
return DATA_CACHE[cacheKey][entityId];
|
||||
};
|
||||
|
||||
const getLogbookDataFromServer = async (
|
||||
hass: HomeAssistant,
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
entityId?: string
|
||||
) => {
|
||||
const url = `logbook/${startDate}?end_time=${endDate}${
|
||||
entityId ? `&entity=${entityId}` : ""
|
||||
}`;
|
||||
return hass.callApi<LogbookEntry[]>("GET", url);
|
||||
};
|
||||
|
||||
export const clearLogbookCache = (startDate, endDate) => {
|
||||
DATA_CACHE[`${startDate}${endDate}`] = {};
|
||||
};
|
||||
|
@@ -51,7 +51,7 @@ export const onboardCoreConfigStep = (hass: HomeAssistant) =>
|
||||
|
||||
export const onboardIntegrationStep = (
|
||||
hass: HomeAssistant,
|
||||
params: { client_id: string; redirect_uri: string }
|
||||
params: { client_id: string }
|
||||
) =>
|
||||
hass.callApi<OnboardingIntegrationStepResponse>(
|
||||
"POST",
|
||||
|
@@ -10,9 +10,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "../../components/ha-switch";
|
||||
import "../../components/ha-formfield";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import {
|
||||
getConfigEntrySystemOptions,
|
||||
@@ -76,7 +74,7 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
${this._loading
|
||||
? html`
|
||||
<div class="init-spinner">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
@@ -84,8 +82,13 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
? html` <div class="error">${this._error}</div> `
|
||||
: ""}
|
||||
<div class="form">
|
||||
<ha-formfield
|
||||
.label=${html`<p>
|
||||
<ha-switch
|
||||
.checked=${!this._disableNewEntities}
|
||||
@change=${this._disableNewEntitiesChanged}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.enable_new_entities_label"
|
||||
)}
|
||||
@@ -98,15 +101,9 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
`component.${this._params.entry.domain}.title`
|
||||
) || this._params.entry.domain
|
||||
)}
|
||||
</p>`}
|
||||
>
|
||||
<ha-switch
|
||||
.checked=${!this._disableNewEntities}
|
||||
@change=${this._disableNewEntitiesChanged}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
</ha-switch>
|
||||
</ha-formfield>
|
||||
</p>
|
||||
</div>
|
||||
</ha-switch>
|
||||
</div>
|
||||
`}
|
||||
</paper-dialog-scrollable>
|
||||
@@ -175,6 +172,9 @@ class DialogConfigEntrySystemOptions extends LitElement {
|
||||
padding-bottom: 24px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import "@material/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import {
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import type { HaPaperDialog } from "../../components/dialog/ha-paper-dialog";
|
||||
import "../../components/ha-form/ha-form";
|
||||
import "../../components/ha-markdown";
|
||||
import {
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
DeviceRegistryEntry,
|
||||
subscribeDeviceRegistry,
|
||||
} from "../../data/device_registry";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { DataEntryFlowDialogParams } from "./show-dialog-data-entry-flow";
|
||||
@@ -88,6 +90,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
// We only load the handlers once
|
||||
if (this._handlers === undefined) {
|
||||
this._loading = true;
|
||||
this.updateComplete.then(() => this._scheduleCenterDialog());
|
||||
try {
|
||||
this._handlers = await params.flowConfig.getFlowHandlers(this.hass);
|
||||
} finally {
|
||||
@@ -95,6 +98,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
await this.updateComplete;
|
||||
this._scheduleCenterDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,6 +115,9 @@ class DataEntryFlowDialog extends LitElement {
|
||||
|
||||
this._processStep(step);
|
||||
this._loading = false;
|
||||
// When the flow changes, center the dialog.
|
||||
// Don't do it on each step or else the dialog keeps bouncing.
|
||||
this._scheduleCenterDialog();
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
@@ -119,84 +126,80 @@ class DataEntryFlowDialog extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closing=${this._close}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
hideActions
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
modal
|
||||
@opened-changed=${this._openedChanged}
|
||||
>
|
||||
<div>
|
||||
${this._loading ||
|
||||
(this._step === null && this._handlers === undefined)
|
||||
? html`
|
||||
<step-flow-loading
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.loading_first_time"
|
||||
)}
|
||||
></step-flow-loading>
|
||||
`
|
||||
: this._step === undefined
|
||||
? // When we are going to next step, we render 1 round of empty
|
||||
// to reset the element.
|
||||
""
|
||||
: html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.dismiss"
|
||||
)}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
></ha-icon-button>
|
||||
${this._step === null
|
||||
? // Show handler picker
|
||||
html`
|
||||
<step-flow-pick-handler
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.hass=${this.hass}
|
||||
.handlers=${this._handlers}
|
||||
.showAdvanced=${this._params.showAdvanced}
|
||||
></step-flow-pick-handler>
|
||||
`
|
||||
: this._step.type === "form"
|
||||
? html`
|
||||
<step-flow-form
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
></step-flow-form>
|
||||
`
|
||||
: this._step.type === "external"
|
||||
? html`
|
||||
<step-flow-external
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
></step-flow-external>
|
||||
`
|
||||
: this._step.type === "abort"
|
||||
? html`
|
||||
<step-flow-abort
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
></step-flow-abort>
|
||||
`
|
||||
: this._devices === undefined || this._areas === undefined
|
||||
? // When it's a create entry result, we will fetch device & area registry
|
||||
html` <step-flow-loading></step-flow-loading> `
|
||||
: html`
|
||||
<step-flow-create-entry
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.devices=${this._devices}
|
||||
.areas=${this._areas}
|
||||
></step-flow-create-entry>
|
||||
`}
|
||||
`}
|
||||
</div>
|
||||
</ha-dialog>
|
||||
${this._loading || (this._step === null && this._handlers === undefined)
|
||||
? html`
|
||||
<step-flow-loading
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.loading_first_time"
|
||||
)}
|
||||
></step-flow-loading>
|
||||
`
|
||||
: this._step === undefined
|
||||
? // When we are going to next step, we render 1 round of empty
|
||||
// to reset the element.
|
||||
""
|
||||
: html`
|
||||
<ha-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.dismiss"
|
||||
)}
|
||||
icon="hass:close"
|
||||
dialog-dismiss
|
||||
></ha-icon-button>
|
||||
${this._step === null
|
||||
? // Show handler picker
|
||||
html`
|
||||
<step-flow-pick-handler
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.hass=${this.hass}
|
||||
.handlers=${this._handlers}
|
||||
.showAdvanced=${this._params.showAdvanced}
|
||||
></step-flow-pick-handler>
|
||||
`
|
||||
: this._step.type === "form"
|
||||
? html`
|
||||
<step-flow-form
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
></step-flow-form>
|
||||
`
|
||||
: this._step.type === "external"
|
||||
? html`
|
||||
<step-flow-external
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
></step-flow-external>
|
||||
`
|
||||
: this._step.type === "abort"
|
||||
? html`
|
||||
<step-flow-abort
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
></step-flow-abort>
|
||||
`
|
||||
: this._devices === undefined || this._areas === undefined
|
||||
? // When it's a create entry result, we will fetch device & area registry
|
||||
html` <step-flow-loading></step-flow-loading> `
|
||||
: html`
|
||||
<step-flow-create-entry
|
||||
.flowConfig=${this._params.flowConfig}
|
||||
.step=${this._step}
|
||||
.hass=${this.hass}
|
||||
.devices=${this._devices}
|
||||
.areas=${this._areas}
|
||||
></step-flow-create-entry>
|
||||
`}
|
||||
`}
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -222,6 +225,18 @@ class DataEntryFlowDialog extends LitElement {
|
||||
this._areas = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProps.has("_devices") && this._dialog) {
|
||||
this._scheduleCenterDialog();
|
||||
}
|
||||
}
|
||||
|
||||
private _scheduleCenterDialog() {
|
||||
setTimeout(() => this._dialog.center(), 0);
|
||||
}
|
||||
|
||||
private get _dialog(): HaPaperDialog {
|
||||
return this.shadowRoot!.querySelector("ha-paper-dialog")!;
|
||||
}
|
||||
|
||||
private async _fetchDevices(configEntryId) {
|
||||
@@ -295,13 +310,16 @@ class DataEntryFlowDialog extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
if (this._step) {
|
||||
this._flowDone();
|
||||
} else if (this._step === null) {
|
||||
// Flow aborted during picking flow
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
// Closed dialog by clicking on the overlay
|
||||
if (!ev.detail.value) {
|
||||
if (this._step) {
|
||||
this._flowDone();
|
||||
} else if (this._step === null) {
|
||||
// Flow aborted during picking flow
|
||||
this._step = undefined;
|
||||
this._params = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,14 +327,18 @@ class DataEntryFlowDialog extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--dialog-content-padding: 0;
|
||||
ha-paper-dialog {
|
||||
max-width: 600px;
|
||||
}
|
||||
ha-paper-dialog > * {
|
||||
margin: 0;
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
ha-icon-button {
|
||||
padding: 16px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
float: right;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import "../../components/ha-circular-progress";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
css,
|
||||
@@ -76,7 +76,7 @@ class StepFlowForm extends LitElement {
|
||||
${this._loading
|
||||
? html`
|
||||
<div class="submit-spinner">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<paper-spinner active></paper-spinner>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../components/ha-circular-progress";
|
||||
|
||||
@customElement("step-flow-loading")
|
||||
class StepFlowLoading extends LitElement {
|
||||
@@ -17,7 +17,7 @@ class StepFlowLoading extends LitElement {
|
||||
return html`
|
||||
<div class="init-spinner">
|
||||
${this.label ? html` <div>${this.label}</div> ` : ""}
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class StepFlowLoading extends LitElement {
|
||||
padding: 50px 100px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-circular-progress {
|
||||
paper-spinner-lite {
|
||||
margin-top: 16px;
|
||||
}
|
||||
`;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-item/paper-item-body";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite";
|
||||
import Fuse from "fuse.js";
|
||||
import {
|
||||
css,
|
||||
@@ -41,8 +42,6 @@ class StepFlowPickHandler extends LitElement {
|
||||
|
||||
private _width?: number;
|
||||
|
||||
private _height?: number;
|
||||
|
||||
private _getHandlers = memoizeOne(
|
||||
(h: string[], filter?: string, _localize?: LocalizeFunc) => {
|
||||
const handlers: HandlerObj[] = h.map((handler) => {
|
||||
@@ -83,10 +82,7 @@ class StepFlowPickHandler extends LitElement {
|
||||
@value-changed=${this._filterChanged}
|
||||
></search-input>
|
||||
<div
|
||||
style=${styleMap({
|
||||
width: `${this._width}px`,
|
||||
height: `${this._height}px`,
|
||||
})}
|
||||
style=${styleMap({ width: `${this._width}px` })}
|
||||
class=${classMap({ advanced: Boolean(this.showAdvanced) })}
|
||||
>
|
||||
${handlers.map(
|
||||
@@ -143,20 +139,13 @@ class StepFlowPickHandler extends LitElement {
|
||||
|
||||
protected updated(changedProps) {
|
||||
super.updated(changedProps);
|
||||
// Store the width and height so that when we search, box doesn't jump
|
||||
const div = this.shadowRoot!.querySelector("div")!;
|
||||
// Store the width so that when we search, box doesn't jump
|
||||
if (!this._width) {
|
||||
const width = div.clientWidth;
|
||||
const width = this.shadowRoot!.querySelector("div")!.clientWidth;
|
||||
if (width) {
|
||||
this._width = width;
|
||||
}
|
||||
}
|
||||
if (!this._height) {
|
||||
const height = div.clientHeight;
|
||||
if (height) {
|
||||
this._height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _filterChanged(e) {
|
||||
@@ -177,8 +166,8 @@ class StepFlowPickHandler extends LitElement {
|
||||
configFlowContentStyles,
|
||||
css`
|
||||
img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
max-width: 40px;
|
||||
max-height: 40px;
|
||||
}
|
||||
search-input {
|
||||
display: block;
|
||||
@@ -191,12 +180,12 @@ class StepFlowPickHandler extends LitElement {
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
@media all and (max-height: 900px) {
|
||||
@media all and (max-height: 1px) {
|
||||
div {
|
||||
max-height: calc(100vh - 134px);
|
||||
max-height: calc(100vh - 205px);
|
||||
}
|
||||
div.advanced {
|
||||
max-height: calc(100vh - 250px);
|
||||
max-height: calc(100vh - 300px);
|
||||
}
|
||||
}
|
||||
paper-icon-item {
|
||||
|
@@ -2,21 +2,8 @@ import { css } from "lit-element";
|
||||
|
||||
export const configFlowContentStyles = css`
|
||||
h2 {
|
||||
margin: 24px 0 0;
|
||||
margin-top: 24px;
|
||||
padding: 0 24px;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: var(
|
||||
--mdc-typography-headline6-font-family,
|
||||
var(--mdc-typography-font-family, Roboto, sans-serif)
|
||||
);
|
||||
font-size: var(--mdc-typography-headline6-font-size, 1.25rem);
|
||||
line-height: var(--mdc-typography-headline6-line-height, 2rem);
|
||||
font-weight: var(--mdc-typography-headline6-font-weight, 500);
|
||||
letter-spacing: var(--mdc-typography-headline6-letter-spacing, 0.0125em);
|
||||
text-decoration: var(--mdc-typography-headline6-text-decoration, inherit);
|
||||
text-transform: var(--mdc-typography-headline6-text-transform, inherit);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
@@ -66,7 +66,7 @@ class DialogDeviceRegistryDetail extends LitElement {
|
||||
<paper-input
|
||||
.value=${this._nameByUser}
|
||||
@value-changed=${this._nameChanged}
|
||||
.label=${this.hass.localize("ui.panel.config.devices.name")}
|
||||
.label=${this.hass.localize("ui.dialogs.devices.name")}
|
||||
.placeholder=${device.name || ""}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
css,
|
||||
@@ -10,7 +11,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/dialog/ha-paper-dialog";
|
||||
import "../../components/ha-switch";
|
||||
import { PolymerChangedEvent } from "../../polymer-types";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
@@ -40,17 +41,21 @@ class DialogBox extends LitElement {
|
||||
const confirmPrompt = this._params.confirmation || this._params.prompt;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
@close=${this._close}
|
||||
.heading=${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
this.hass.localize("ui.dialogs.generic.default_confirmation_title")}
|
||||
<ha-paper-dialog
|
||||
with-backdrop
|
||||
opened
|
||||
modal
|
||||
@opened-changed="${this._openedChanged}"
|
||||
>
|
||||
<div>
|
||||
<h2>
|
||||
${this._params.title
|
||||
? this._params.title
|
||||
: this._params.confirmation &&
|
||||
this.hass.localize(
|
||||
"ui.dialogs.generic.default_confirmation_title"
|
||||
)}
|
||||
</h2>
|
||||
<paper-dialog-scrollable>
|
||||
${this._params.text
|
||||
? html`
|
||||
<p
|
||||
@@ -78,21 +83,23 @@ class DialogBox extends LitElement {
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
${confirmPrompt &&
|
||||
html`
|
||||
<mwc-button @click=${this._dismiss} slot="secondaryAction">
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</paper-dialog-scrollable>
|
||||
<div class="paper-dialog-buttons">
|
||||
${confirmPrompt &&
|
||||
html`
|
||||
<mwc-button @click="${this._dismiss}">
|
||||
${this._params.dismissText
|
||||
? this._params.dismissText
|
||||
: this.hass.localize("ui.dialogs.generic.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button @click="${this._confirm}">
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
</mwc-button>
|
||||
`}
|
||||
<mwc-button @click=${this._confirm} slot="primaryAction">
|
||||
${this._params.confirmText
|
||||
? this._params.confirmText
|
||||
: this.hass.localize("ui.dialogs.generic.ok")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
</div>
|
||||
</ha-paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -120,8 +127,10 @@ class DialogBox extends LitElement {
|
||||
this._dismiss();
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
if (!(ev.detail as any).value) {
|
||||
this._params = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
@@ -132,6 +141,15 @@ class DialogBox extends LitElement {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
ha-paper-dialog {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
}
|
||||
@media (max-width: 400px) {
|
||||
ha-paper-dialog {
|
||||
min-width: initial;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
@@ -147,10 +165,6 @@ class DialogBox extends LitElement {
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-dialog {
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import "@material/mwc-button";
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/iron-input/iron-input";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../components/ha-circular-progress";
|
||||
import "@polymer/paper-spinner/paper-spinner";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
@@ -38,7 +38,7 @@ class MoreInfoConfigurator extends PolymerElement {
|
||||
height: 41px;
|
||||
}
|
||||
|
||||
ha-circular-progress {
|
||||
paper-spinner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 20px;
|
||||
@@ -75,11 +75,11 @@ class MoreInfoConfigurator extends PolymerElement {
|
||||
disabled="[[isConfiguring]]"
|
||||
on-click="submitClicked"
|
||||
>
|
||||
<ha-circular-progress
|
||||
<paper-spinner
|
||||
active="[[isConfiguring]]"
|
||||
hidden="[[!isConfiguring]]"
|
||||
alt="Configuring"
|
||||
></ha-circular-progress>
|
||||
></paper-spinner>
|
||||
[[stateObj.attributes.submit_caption]]
|
||||
</mwc-button>
|
||||
</p>
|
||||
|
@@ -14,7 +14,6 @@ import "./more-info-default";
|
||||
import "./more-info-fan";
|
||||
import "./more-info-group";
|
||||
import "./more-info-history_graph";
|
||||
import "./more-info-humidifier";
|
||||
import "./more-info-input_datetime";
|
||||
import "./more-info-light";
|
||||
import "./more-info-lock";
|
||||
|
@@ -1,218 +0,0 @@
|
||||
import "@polymer/iron-flex-layout/iron-flex-layout-classes";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { computeRTLDirection } from "../../../common/util/compute_rtl";
|
||||
import "../../../components/ha-paper-dropdown-menu";
|
||||
import "../../../components/ha-paper-slider";
|
||||
import "../../../components/ha-switch";
|
||||
import {
|
||||
HumidifierEntity,
|
||||
HUMIDIFIER_SUPPORT_MODES,
|
||||
} from "../../../data/humidifier";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
class MoreInfoHumidifier extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property() public stateObj?: HumidifierEntity;
|
||||
|
||||
private _resizeDebounce?: number;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.stateObj) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const hass = this.hass;
|
||||
const stateObj = this.stateObj;
|
||||
|
||||
const supportModes = supportsFeature(stateObj, HUMIDIFIER_SUPPORT_MODES);
|
||||
|
||||
const rtlDirection = computeRTLDirection(hass);
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${classMap({
|
||||
"has-modes": supportModes,
|
||||
})}
|
||||
>
|
||||
<div class="container-humidity">
|
||||
<div>${hass.localize("ui.card.humidifier.humidity")}</div>
|
||||
<div class="single-row">
|
||||
<div class="target-humidity">
|
||||
${stateObj.attributes.humidity} %
|
||||
</div>
|
||||
<ha-paper-slider
|
||||
class="humidity"
|
||||
step="1"
|
||||
pin
|
||||
ignore-bar-touch
|
||||
dir=${rtlDirection}
|
||||
.min=${stateObj.attributes.min_humidity}
|
||||
.max=${stateObj.attributes.max_humidity}
|
||||
.secondaryProgress=${stateObj.attributes.max_humidity}
|
||||
.value=${stateObj.attributes.humidity}
|
||||
@change=${this._targetHumiditySliderChanged}
|
||||
>
|
||||
</ha-paper-slider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${supportModes
|
||||
? html`
|
||||
<div class="container-modes">
|
||||
<ha-paper-dropdown-menu
|
||||
label-float
|
||||
dynamic-align
|
||||
.label=${hass.localize("ui.card.humidifier.mode")}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-name"
|
||||
.selected=${stateObj.attributes.mode}
|
||||
@selected-changed=${this._handleModeChanged}
|
||||
>
|
||||
${stateObj.attributes.available_modes!.map(
|
||||
(mode) => html`
|
||||
<paper-item item-name=${mode}>
|
||||
${hass.localize(
|
||||
`state_attributes.humidifier.mode.${mode}`
|
||||
) || mode}
|
||||
</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!changedProps.has("stateObj") || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._resizeDebounce) {
|
||||
clearTimeout(this._resizeDebounce);
|
||||
}
|
||||
this._resizeDebounce = window.setTimeout(() => {
|
||||
fireEvent(this, "iron-resize");
|
||||
this._resizeDebounce = undefined;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _targetHumiditySliderChanged(ev) {
|
||||
const newVal = ev.target.value;
|
||||
this._callServiceHelper(
|
||||
this.stateObj!.attributes.humidity,
|
||||
newVal,
|
||||
"set_humidity",
|
||||
{ humidity: newVal }
|
||||
);
|
||||
}
|
||||
|
||||
private _handleModeChanged(ev) {
|
||||
const newVal = ev.detail.value || null;
|
||||
this._callServiceHelper(
|
||||
this.stateObj!.attributes.mode,
|
||||
newVal,
|
||||
"set_mode",
|
||||
{ mode: newVal }
|
||||
);
|
||||
}
|
||||
|
||||
private async _callServiceHelper(
|
||||
oldVal: unknown,
|
||||
newVal: unknown,
|
||||
service: string,
|
||||
data: {
|
||||
entity_id?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
) {
|
||||
if (oldVal === newVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.entity_id = this.stateObj!.entity_id;
|
||||
const curState = this.stateObj;
|
||||
|
||||
await this.hass.callService("humidifier", service, data);
|
||||
|
||||
// We reset stateObj to re-sync the inputs with the state. It will be out
|
||||
// of sync if our service call did not result in the entity to be turned
|
||||
// on. Since the state is not changing, the resync is not called automatic.
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
// No need to resync if we received a new state.
|
||||
if (this.stateObj !== curState) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.stateObj = undefined;
|
||||
await this.updateComplete;
|
||||
// Only restore if not set yet by a state change
|
||||
if (this.stateObj === undefined) {
|
||||
this.stateObj = curState;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
ha-paper-dropdown-menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ha-paper-slider {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container-humidity .single-row {
|
||||
display: flex;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.target-humidity {
|
||||
width: 90px;
|
||||
font-size: 200%;
|
||||
margin: auto;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.humidity {
|
||||
--paper-slider-active-color: var(--paper-blue-400);
|
||||
--paper-slider-secondary-color: var(--paper-blue-400);
|
||||
}
|
||||
|
||||
.single-row {
|
||||
padding: 8px 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("more-info-humidifier", MoreInfoHumidifier);
|
@@ -86,7 +86,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="main-title" main-title="" on-click="enlarge">
|
||||
[[_computeStateName(stateObj)]]
|
||||
</div>
|
||||
<template is="dom-if" if="[[hass.user.is_admin]]">
|
||||
<template is="dom-if" if="[[_computeConfig(hass)]]">
|
||||
<ha-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
@@ -219,6 +219,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return stateObj ? computeStateName(stateObj) : "";
|
||||
}
|
||||
|
||||
_computeConfig(hass) {
|
||||
return hass.user.is_admin && isComponentLoaded(hass, "config");
|
||||
}
|
||||
|
||||
_computeEdit(hass, stateObj) {
|
||||
const domain = this._computeDomain(stateObj);
|
||||
return (
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user