Compare commits

..

1 Commits

Author SHA1 Message Date
Paulus Schoutsen
3272c32f87 Move compatibility to own entrypoint again 2020-06-03 16:24:23 -07:00
780 changed files with 19072 additions and 47903 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
hass_frontend
hass_frontend_es5
.git

View File

@@ -1,7 +1,7 @@
{ {
"extends": [ "extends": [
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"airbnb-typescript/base",
"plugin:wc/recommended", "plugin:wc/recommended",
"plugin:lit/recommended", "plugin:lit/recommended",
"prettier", "prettier",
@@ -45,16 +45,16 @@
"func-names": 0, "func-names": 0,
"prefer-arrow-callback": 0, "prefer-arrow-callback": 0,
"no-underscore-dangle": 0, "no-underscore-dangle": 0,
"no-var": 0,
"strict": 0, "strict": 0,
"prefer-spread": 0, "prefer-spread": 0,
"no-plusplus": 0, "no-plusplus": 0,
"no-bitwise": 2, "no-bitwise": 0,
"comma-dangle": 0, "comma-dangle": 0,
"vars-on-top": 0, "vars-on-top": 0,
"no-continue": 0, "no-continue": 0,
"no-param-reassign": 0, "no-param-reassign": 0,
"no-multi-assign": 0, "no-multi-assign": 0,
"no-console": 2,
"radix": 0, "radix": 0,
"no-alert": 0, "no-alert": 0,
"no-return-await": 0, "no-return-await": 0,

View File

@@ -51,7 +51,7 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
<!-- <!--
Provide details about the versions you are using, which helps us reproducing Provide details about the versions you are using, which helps us reproducing
and finding the issue quicker. Version information is found in the and finding the issue quicker. Version information is found in the
Home Assistant frontend: Configuration -> Info. Home Assistant frontend: Developer tools -> Info.
Browser version and operating system is important! Please try to replicate Browser version and operating system is important! Please try to replicate
your issue in a different browser and be sure to include your findings. your issue in a different browser and be sure to include your findings.

View File

@@ -34,8 +34,10 @@ jobs:
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Build resources - name: Build icons
run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos run: ./node_modules/.bin/gulp gen-icons-json
- name: Build translations
run: ./node_modules/.bin/gulp build-translations
- name: Run eslint - name: Run eslint
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
- name: Run tsc - name: Run tsc

View File

@@ -1,60 +0,0 @@
name: "CodeQL"
on:
push:
branches: [dev, master]
pull_request:
# The branches below must be a subset of the branches above
branches: [dev]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

31
Dockerfile Normal file
View 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" ]

View File

@@ -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. 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 ## License
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects. Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.

View File

@@ -20,13 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
// Loads stuff from a CDN // Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"), require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"), require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
// Compatibility not needed for latest builds // Polyfill only needed for ES5 workers so filter out in latestBuild
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.
latestBuild && require.resolve("proxy-polyfill/src/index.js"), latestBuild && require.resolve("proxy-polyfill/src/index.js"),
].filter(Boolean); ].filter(Boolean);
@@ -57,7 +51,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
].filter(Boolean), ].filter(Boolean),
plugins: [ plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2}) // Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [ [
"@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true }, { loose: true, useBuiltIns: true },
], ],
@@ -73,7 +67,7 @@ module.exports.babelOptions = ({ latestBuild }) => ({
require("@babel/plugin-proposal-class-properties").default, require("@babel/plugin-proposal-class-properties").default,
{ loose: true }, { loose: true },
], ],
].filter(Boolean), ],
}); });
// Are already ES5, cause warnings when babelified. // Are already ES5, cause warnings when babelified.
@@ -85,8 +79,8 @@ module.exports.babelExclude = () => [
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5"); path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");
const publicPath = (latestBuild, root = "") => const publicPath = (latestBuild) =>
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`; latestBuild ? "/frontend_latest/" : "/frontend_es5/";
/* /*
BundleConfig { BundleConfig {
@@ -118,6 +112,7 @@ module.exports.config = {
authorize: "./src/entrypoints/authorize.ts", authorize: "./src/entrypoints/authorize.ts",
onboarding: "./src/entrypoints/onboarding.ts", onboarding: "./src/entrypoints/onboarding.ts",
core: "./src/entrypoints/core.ts", core: "./src/entrypoints/core.ts",
compatibility: "./src/entrypoints/compatibility.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts", "custom-panel": "./src/entrypoints/custom-panel.ts",
}, },
outputPath: outputPath(paths.app_output_root, latestBuild), outputPath: outputPath(paths.app_output_root, latestBuild),
@@ -132,6 +127,10 @@ module.exports.config = {
return { return {
entry: { entry: {
main: path.resolve(paths.demo_dir, "src/entrypoint.ts"), 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), outputPath: outputPath(paths.demo_output_root, latestBuild),
publicPath: publicPath(latestBuild), publicPath: publicPath(latestBuild),
@@ -170,14 +169,18 @@ module.exports.config = {
}, },
hassio({ isProdBuild, latestBuild }) { hassio({ isProdBuild, latestBuild }) {
if (latestBuild) {
throw new Error("Hass.io does not support latest build!");
}
return { return {
entry: { entry: {
entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"), entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.ts"),
}, },
outputPath: outputPath(paths.hassio_output_root, latestBuild), outputPath: paths.hassio_output_root,
publicPath: publicPath(latestBuild, paths.hassio_publicPath), publicPath: paths.hassio_publicPath,
isProdBuild, isProdBuild,
latestBuild, latestBuild,
dontHash: new Set(["entrypoint"]),
}; };
}, },

View File

@@ -20,7 +20,6 @@ gulp.task(
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast", "copy-static-cast",
"gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
) )
); );

View File

@@ -6,32 +6,30 @@ const merge = require("merge-stream");
const path = require("path"); const path = require("path");
const paths = require("../paths"); const paths = require("../paths");
const zopfliOptions = { threshold: 150 };
gulp.task("compress-app", function compressApp() { gulp.task("compress-app", function compressApp() {
const jsLatest = gulp const jsLatest = gulp
.src(path.resolve(paths.app_output_latest, "**/*.js")) .src(path.resolve(paths.app_output_latest, "**/*.js"))
.pipe(zopfli(zopfliOptions)) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.app_output_latest)); .pipe(gulp.dest(paths.app_output_latest));
const jsEs5 = gulp const jsEs5 = gulp
.src(path.resolve(paths.app_output_es5, "**/*.js")) .src(path.resolve(paths.app_output_es5, "**/*.js"))
.pipe(zopfli(zopfliOptions)) .pipe(zopfli({ threshold: 150 }))
.pipe(gulp.dest(paths.app_output_es5)); .pipe(gulp.dest(paths.app_output_es5));
const polyfills = gulp const polyfills = gulp
.src(path.resolve(paths.app_output_static, "polyfills/*.js")) .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"))); .pipe(gulp.dest(path.resolve(paths.app_output_static, "polyfills")));
const translations = gulp const translations = gulp
.src(path.resolve(paths.app_output_static, "translations/**/*.json")) .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"))); .pipe(gulp.dest(path.resolve(paths.app_output_static, "translations")));
const icons = gulp const icons = gulp
.src(path.resolve(paths.app_output_static, "mdi/*.json")) .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"))); .pipe(gulp.dest(path.resolve(paths.app_output_static, "mdi")));
return merge(jsLatest, jsEs5, polyfills, translations, icons); return merge(jsLatest, jsEs5, polyfills, translations, icons);
@@ -40,6 +38,6 @@ gulp.task("compress-app", function compressApp() {
gulp.task("compress-hassio", function compressApp() { gulp.task("compress-hassio", function compressApp() {
return gulp return gulp
.src(path.resolve(paths.hassio_output_root, "**/*.js")) .src(path.resolve(paths.hassio_output_root, "**/*.js"))
.pipe(zopfli(zopfliOptions)) .pipe(zopfli())
.pipe(gulp.dest(paths.hassio_output_root)); .pipe(gulp.dest(paths.hassio_output_root));
}); });

View File

@@ -53,6 +53,7 @@ gulp.task("gen-pages-dev", (done) => {
const content = renderTemplate(page, { const content = renderTemplate(page, {
latestPageJS: `/frontend_latest/${page}.js`, latestPageJS: `/frontend_latest/${page}.js`,
es5Compatibility: "/frontend_es5/compatibility.js",
es5PageJS: `/frontend_es5/${page}.js`, es5PageJS: `/frontend_es5/${page}.js`,
}); });
@@ -78,6 +79,7 @@ gulp.task("gen-pages-prod", (done) => {
const content = renderTemplate(page, { const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`], latestPageJS: latestManifest[`${page}.js`],
es5Compatibility: es5Manifest["compatibility.js"],
es5PageJS: es5Manifest[`${page}.js`], es5PageJS: es5Manifest[`${page}.js`],
}); });
@@ -90,11 +92,14 @@ gulp.task("gen-pages-prod", (done) => {
}); });
gulp.task("gen-index-app-dev", (done) => { gulp.task("gen-index-app-dev", (done) => {
// In dev mode we don't mangle names, so we hardcode urls. That way we can
// run webpack as last in watch mode, which blocks output.
const content = renderTemplate("index", { const content = renderTemplate("index", {
latestAppJS: "/frontend_latest/app.js", latestAppJS: "/frontend_latest/app.js",
latestCoreJS: "/frontend_latest/core.js", latestCoreJS: "/frontend_latest/core.js",
latestCustomPanelJS: "/frontend_latest/custom-panel.js", latestCustomPanelJS: "/frontend_latest/custom-panel.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5AppJS: "/frontend_es5/app.js", es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js", es5CoreJS: "/frontend_es5/core.js",
es5CustomPanelJS: "/frontend_es5/custom-panel.js", es5CustomPanelJS: "/frontend_es5/custom-panel.js",
@@ -118,6 +123,7 @@ gulp.task("gen-index-app-prod", (done) => {
latestCoreJS: latestManifest["core.js"], latestCoreJS: latestManifest["core.js"],
latestCustomPanelJS: latestManifest["custom-panel.js"], latestCustomPanelJS: latestManifest["custom-panel.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5AppJS: es5Manifest["app.js"], es5AppJS: es5Manifest["app.js"],
es5CoreJS: es5Manifest["core.js"], es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"], es5CustomPanelJS: es5Manifest["custom-panel.js"],
@@ -199,9 +205,12 @@ gulp.task("gen-index-cast-prod", (done) => {
}); });
gulp.task("gen-index-demo-dev", (done) => { gulp.task("gen-index-demo-dev", (done) => {
// In dev mode we don't mangle names, so we hardcode urls. That way we can
// run webpack as last in watch mode, which blocks output.
const content = renderDemoTemplate("index", { const content = renderDemoTemplate("index", {
latestDemoJS: "/frontend_latest/main.js", latestDemoJS: "/frontend_latest/main.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5DemoJS: "/frontend_es5/main.js", es5DemoJS: "/frontend_es5/main.js",
}); });
@@ -224,6 +233,7 @@ gulp.task("gen-index-demo-prod", (done) => {
const content = renderDemoTemplate("index", { const content = renderDemoTemplate("index", {
latestDemoJS: latestManifest["main.js"], latestDemoJS: latestManifest["main.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5DemoJS: es5Manifest["main.js"], es5DemoJS: es5Manifest["main.js"],
}); });
const minified = minifyHtml(content); const minified = minifyHtml(content);
@@ -236,6 +246,8 @@ gulp.task("gen-index-demo-prod", (done) => {
}); });
gulp.task("gen-index-gallery-dev", (done) => { gulp.task("gen-index-gallery-dev", (done) => {
// In dev mode we don't mangle names, so we hardcode urls. That way we can
// run webpack as last in watch mode, which blocks output.
const content = renderGalleryTemplate("index", { const content = renderGalleryTemplate("index", {
latestGalleryJS: "./frontend_latest/entrypoint.js", latestGalleryJS: "./frontend_latest/entrypoint.js",
}); });
@@ -263,42 +275,3 @@ gulp.task("gen-index-gallery-prod", (done) => {
); );
done(); done();
}); });
gulp.task("gen-index-hassio-dev", async () => {
writeHassioEntrypoint(
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
);
});
gulp.task("gen-index-hassio-prod", async () => {
const latestManifest = require(path.resolve(
paths.hassio_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.hassio_output_es5,
"manifest.json"
));
writeHassioEntrypoint(
latestManifest["entrypoint.js"],
es5Manifest["entrypoint.js"]
);
});
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
`,
{ encoding: "utf-8" }
);
}

View File

@@ -1,10 +1,7 @@
// Run demo develop mode // Run demo develop mode
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env"); const env = require("../env");
const paths = require("../paths");
require("./clean.js"); require("./clean.js");
require("./translations.js"); require("./translations.js");
@@ -15,31 +12,6 @@ require("./service-worker.js");
require("./entry-html.js"); require("./entry-html.js");
require("./rollup.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( gulp.task(
"develop-gallery", "develop-gallery",
gulp.series( gulp.series(
@@ -48,11 +20,7 @@ gulp.task(
}, },
"clean-gallery", "clean-gallery",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( gulp.parallel("gen-icons-json", "build-translations"),
"gen-icons-json",
"build-translations",
"gather-gallery-demos"
),
"copy-static-gallery", "copy-static-gallery",
"gen-index-gallery-dev", "gen-index-gallery-dev",
env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery" env.useRollup() ? "rollup-dev-server-gallery" : "webpack-dev-server-gallery"
@@ -67,11 +35,7 @@ gulp.task(
}, },
"clean-gallery", "clean-gallery",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( gulp.parallel("gen-icons-json", "build-translations"),
"gen-icons-json",
"build-translations",
"gather-gallery-demos"
),
"copy-static-gallery", "copy-static-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
"gen-index-gallery-prod" "gen-index-gallery-prod"

View File

@@ -36,13 +36,11 @@ function copyMdiIcons(staticDir) {
function copyPolyfills(staticDir) { function copyPolyfills(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
// For custom panels using ES5 builds that don't use Babel 7+ // Web Component polyfills and adapters
copyFileDir( copyFileDir(
npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"), npmPath("@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"),
staticPath("polyfills/") staticPath("polyfills/")
); );
// Web Component polyfills and adapters
copyFileDir( copyFileDir(
npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"), npmPath("@webcomponents/webcomponentsjs/webcomponents-bundle.js"),
staticPath("polyfills/") staticPath("polyfills/")

View File

@@ -11,7 +11,6 @@ const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json"); const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg"); const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi"); const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json");
const encoding = "utf8"; const encoding = "utf8";
@@ -26,13 +25,6 @@ const getMeta = () => {
}); });
}; };
const addRemovedMeta = (meta) => {
const file = fs.readFileSync(REMOVED_ICONS_PATH, { encoding });
const removed = JSON.parse(file);
const combinedMeta = [...meta, ...removed];
return combinedMeta.sort((a, b) => a.name.localeCompare(b.name));
};
const splitBySize = (meta) => { const splitBySize = (meta) => {
const chunks = []; const chunks = [];
const CHUNK_SIZE = 50000; const CHUNK_SIZE = 50000;
@@ -77,7 +69,7 @@ const findDifferentiator = (curString, prevString) => {
}; };
gulp.task("gen-icons-json", (done) => { gulp.task("gen-icons-json", (done) => {
const meta = addRemovedMeta(getMeta()); const meta = getMeta();
const split = splitBySize(meta); const split = splitBySize(meta);
if (!fs.existsSync(OUTPUT_DIR)) { if (!fs.existsSync(OUTPUT_DIR)) {

View File

@@ -1,9 +1,6 @@
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env"); const env = require("../env");
const paths = require("../paths");
require("./clean.js"); require("./clean.js");
require("./gen-icons-json.js"); require("./gen-icons-json.js");
@@ -19,7 +16,6 @@ gulp.task(
}, },
"clean-hassio", "clean-hassio",
"gen-icons-json", "gen-icons-json",
"gen-index-hassio-dev",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
) )
); );
@@ -33,7 +29,6 @@ gulp.task(
"clean-hassio", "clean-hassio",
"gen-icons-json", "gen-icons-json",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod",
...// Don't compress running tests ...// Don't compress running tests
(env.isTest() ? [] : ["compress-hassio"]) (env.isTest() ? [] : ["compress-hassio"])
) )

View File

@@ -92,7 +92,11 @@ gulp.task("rollup-watch-app", () => {
}); });
gulp.task("rollup-watch-hassio", () => { 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", () => { gulp.task("rollup-dev-server-demo", () => {
@@ -133,7 +137,12 @@ gulp.task(
); );
gulp.task("rollup-prod-hassio", () => gulp.task("rollup-prod-hassio", () =>
bothBuilds(rollupConfig.createHassioConfig, { isProdBuild: true }) buildRollup(
rollupConfig.createHassioConfig({
isProdBuild: true,
latestBuild: false,
})
)
); );
gulp.task("rollup-prod-gallery", () => gulp.task("rollup-prod-gallery", () =>

View File

@@ -129,7 +129,7 @@ gulp.task("webpack-watch-hassio", () => {
webpack( webpack(
createHassioConfig({ createHassioConfig({
isProdBuild: false, isProdBuild: false,
latestBuild: true, latestBuild: false,
}) })
).watch({}, handler()); ).watch({}, handler());
}); });
@@ -139,8 +139,9 @@ gulp.task(
() => () =>
new Promise((resolve) => new Promise((resolve) =>
webpack( webpack(
bothBuilds(createHassioConfig, { createHassioConfig({
isProdBuild: true, isProdBuild: true,
latestBuild: false,
}), }),
handler(resolve) handler(resolve)
) )

View File

@@ -34,12 +34,7 @@ module.exports = {
hassio_dir: path.resolve(__dirname, "../hassio"), hassio_dir: path.resolve(__dirname, "../hassio"),
hassio_output_root: path.resolve(__dirname, "../hassio/build"), hassio_output_root: path.resolve(__dirname, "../hassio/build"),
hassio_output_latest: path.resolve( hassio_publicPath: "/api/hassio/app/",
__dirname,
"../hassio/build/frontend_latest"
),
hassio_output_es5: path.resolve(__dirname, "../hassio/build/frontend_es5"),
hassio_publicPath: "/api/hassio/app",
translations_src: path.resolve(__dirname, "../src/translations"), translations_src: path.resolve(__dirname, "../src/translations"),
}; };

View File

@@ -1,267 +0,0 @@
[
{
"path": "M17.5,15.61C17.33,15.37 9.53,5.4 9.27,5.08C9,4.75 9.08,4.65 9.13,4.59C9.22,4.5 9.36,4.5 9.93,4.5C10.26,4.5 13.59,4.5 13.94,4.47C14.66,4.47 14.78,4.53 14.85,4.56C14.93,4.58 15.13,4.75 15.26,4.92C15.33,5 22.32,13.36 22.39,13.45C22.46,13.54 22.59,13.69 22.67,13.84C22.76,14 22.77,14.18 22.64,14.25C22.56,14.3 18.7,15.89 18.59,15.93C18.5,16 18.27,16.06 18.11,16.04C18,16 17.77,15.92 17.5,15.61M21.47,15.42L21.75,15.47C21.75,15.47 22.68,15.65 22.77,15.67C22.87,15.69 22.96,15.76 22.95,15.79C22.94,15.87 22.9,15.91 22.83,15.95C22.77,16 18.58,18.58 18.5,18.62C18.43,18.66 18.33,18.72 18.11,18.75C17.7,18.83 16.91,18.61 16.66,18.56C16.41,18.5 6.15,16.23 6.06,16.2C5.97,16.17 5.91,16.16 5.9,16.08C5.89,15.94 6.11,15.88 6.28,15.81C6.46,15.75 11.28,14 11.45,13.93C11.62,13.86 11.84,13.84 11.95,13.83C12.06,13.82 12.73,13.93 13.03,13.97C13.34,14 14.2,14.15 14.2,14.15L16.16,16.7C16.5,17.09 16.72,17.25 17,17.28C17.15,17.29 17.31,17.25 17.42,17.2C17.5,17.16 21.47,15.42 21.47,15.42M10.25,9.18L11.96,11.37L12,11.45V11.5C11.96,11.54 8.93,14.32 8.91,14.35L5.72,15.5C5.72,15.5 5.63,15.55 5.58,15.58C5.53,15.61 5.47,15.67 5.5,15.82C5.5,15.87 5.5,16.59 5.5,16.79L1.56,18.04C1.37,18.1 1,18.23 0.95,18.19C0.88,18.14 0.97,18.03 1,17.97C1.06,17.91 9.08,10 9.39,9.7C9.84,9.24 10.25,9.18 10.25,9.18",
"name": "accusoft"
},
{
"path": "M4.94,11.12C5.23,11.12 5.5,11.16 5.76,11.23C5.77,9.09 7.5,7.35 9.65,7.35C11.27,7.35 12.67,8.35 13.24,9.77C13.83,9 14.74,8.53 15.76,8.53C17.5,8.53 18.94,9.95 18.94,11.71C18.94,11.95 18.91,12.2 18.86,12.43C19.1,12.34 19.37,12.29 19.65,12.29C20.95,12.29 22,13.35 22,14.65C22,15.95 20.95,17 19.65,17C18.35,17 6.36,17 4.94,17C3.32,17 2,15.68 2,14.06C2,12.43 3.32,11.12 4.94,11.12Z",
"name": "amazon-drive"
},
{
"path": "M8,11.5A1.25,1.25 0 0,0 6.75,12.75A1.25,1.25 0 0,0 8,14A1.25,1.25 0 0,0 9.25,12.75A1.25,1.25 0 0,0 8,11.5M16,11.5A1.25,1.25 0 0,0 14.75,12.75A1.25,1.25 0 0,0 16,14A1.25,1.25 0 0,0 17.25,12.75A1.25,1.25 0 0,0 16,11.5M12,7C13.5,7 14.9,7.33 16.18,7.91L18.34,5.75C18.73,5.36 19.36,5.36 19.75,5.75C20.14,6.14 20.14,6.77 19.75,7.16L17.95,8.96C20.41,10.79 22,13.71 22,17H2C2,13.71 3.59,10.79 6.05,8.96L4.25,7.16C3.86,6.77 3.86,6.14 4.25,5.75C4.64,5.36 5.27,5.36 5.66,5.75L7.82,7.91C9.1,7.33 10.5,7 12,7Z",
"name": "android-head"
},
{
"path": "M2,16.25C2,16.25 4,3.75 12,3.75C20,3.75 22,16.25 22,16.25C22,16.25 20,20.25 12,20.25C4,20.25 2,16.25 2,16.25M3.35,15.65C3.35,15.65 4.3,19 12,19C17,19 20,17.8 20.65,15.85C21.3,13.9 15.65,7.6 14.65,7.6C13.65,7.6 11.2,12 10.45,12C8.45,12 8.9,10 7.15,10C5.4,10 3.35,15.65 3.35,15.65Z",
"name": "basecamp"
},
{
"path": "M7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7C10.87,7 9.84,7.37 9,8V2.46C9.95,2.16 10.95,2 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12C2,8.3 4,5.07 7,3.34V12M12,9A3,3 0 0,1 15,12A3,3 0 0,1 12,15A3,3 0 0,1 9,12A3,3 0 0,1 12,9Z",
"name": "beats"
},
{
"path": "M19.58,12.27C19.54,11.65 19.33,11.18 18.96,10.86C18.59,10.54 18.13,10.38 17.58,10.38C17,10.38 16.5,10.55 16.19,10.89C15.86,11.23 15.65,11.69 15.57,12.27M21.92,12.04C22,12.45 22,13.04 22,13.81H15.5C15.55,14.71 15.85,15.33 16.44,15.69C16.79,15.92 17.22,16.03 17.73,16.03C18.26,16.03 18.69,15.89 19,15.62C19.2,15.47 19.36,15.27 19.5,15H21.88C21.82,15.54 21.53,16.07 21,16.62C20.22,17.5 19.1,17.92 17.66,17.92C16.47,17.92 15.43,17.55 14.5,16.82C13.62,16.09 13.16,14.9 13.16,13.25C13.16,11.7 13.57,10.5 14.39,9.7C15.21,8.87 16.27,8.46 17.58,8.46C18.35,8.46 19.05,8.6 19.67,8.88C20.29,9.16 20.81,9.59 21.21,10.2C21.58,10.73 21.81,11.34 21.92,12.04M9.58,14.07C9.58,13.42 9.31,12.97 8.79,12.73C8.5,12.6 8.08,12.53 7.54,12.5H4.87V15.84H7.5C8.04,15.84 8.46,15.77 8.76,15.62C9.31,15.35 9.58,14.83 9.58,14.07M4.87,10.46H7.5C8.04,10.46 8.5,10.36 8.82,10.15C9.16,9.95 9.32,9.58 9.32,9.06C9.32,8.5 9.1,8.1 8.66,7.91C8.27,7.78 7.78,7.72 7.19,7.72H4.87M11.72,12.42C12.04,12.92 12.2,13.53 12.2,14.24C12.2,15 12,15.64 11.65,16.23C11.41,16.62 11.12,16.94 10.77,17.21C10.37,17.5 9.9,17.72 9.36,17.83C8.82,17.94 8.24,18 7.61,18H2V5.55H8C9.53,5.58 10.6,6 11.23,6.88C11.61,7.41 11.8,8.04 11.8,8.78C11.8,9.54 11.61,10.15 11.23,10.61C11,10.87 10.7,11.11 10.28,11.32C10.91,11.55 11.39,11.92 11.72,12.42M20.06,7.32H15.05V6.07H20.06V7.32Z",
"name": "behance"
},
{
"path": "M5.45,10.28C6.4,10.28 7.5,11.05 7.5,12C7.5,12.95 6.4,13.72 5.45,13.72H2L2.69,10.28H5.45M6.14,4.76C7.09,4.76 8.21,5.53 8.21,6.5C8.21,7.43 7.09,8.21 6.14,8.21H2.69L3.38,4.76H6.14M13.03,4.76C14,4.76 15.1,5.53 15.1,6.5C15.1,7.43 14,8.21 13.03,8.21H9.41L10.1,4.76H13.03M12.34,10.28C13.3,10.28 14.41,11.05 14.41,12C14.41,12.95 13.3,13.72 12.34,13.72H8.72L9.41,10.28H12.34M10.97,15.79C11.92,15.79 13.03,16.57 13.03,17.5C13.03,18.47 11.92,19.24 10.97,19.24H7.5L8.21,15.79H10.97M18.55,13.72C19.5,13.72 20.62,14.5 20.62,15.45C20.62,16.4 19.5,17.17 18.55,17.17H15.1L15.79,13.72H18.55M19.93,8.21C20.88,8.21 22,9 22,9.93C22,10.88 20.88,11.66 19.93,11.66H16.5L17.17,8.21H19.93Z",
"name": "blackberry"
},
{
"path": "M12,3A9,9 0 0,1 21,12A9,9 0 0,1 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3M5.94,8.5C4,11.85 5.15,16.13 8.5,18.06C11.85,20 18.85,7.87 15.5,5.94C12.15,4 7.87,5.15 5.94,8.5Z",
"name": "cisco-webex"
},
{
"path": "M11.9,14.5H10.8V9.5H11.9C13.5,9.5 14.6,10.4 14.6,12C14.6,13.6 13.5,14.5 11.9,14.5M11.9,7H8.1V17H11.8C15.3,17 17.4,14.9 17.4,12V12C17.4,9.1 15.4,7 11.9,7M12,20C10.1,20 8.3,19.3 6.9,18.1L6.2,17.5L4.5,17.7L5.2,16.1L4.9,15.3C4.4,14.2 4.2,13.1 4.2,11.9C4.2,7.5 7.8,3.9 12.1,3.9C16.4,3.9 19.9,7.6 19.9,12C19.9,16.4 16.3,20 12,20M12,2C6.5,2 2.1,6.5 2.1,12C2.1,13.5 2.4,14.9 3,16.2L1.4,20.3L5.7,19.7C7.4,21.2 9.7,22.1 12.1,22.1C17.6,22.1 22,17.6 22,12.1C22,6.6 17.5,2 12,2Z",
"name": "disqus-outline"
},
{
"path": "M16.42,18.42C16,16.5 15.5,14.73 15,13.17C15.5,13.1 16,13.06 16.58,13.06H16.6V13.06H16.6C17.53,13.06 18.55,13.18 19.66,13.43C19.28,15.5 18.08,17.27 16.42,18.42M12,19.8C10.26,19.8 8.66,19.23 7.36,18.26C7.64,17.81 8.23,16.94 9.18,16.04C10.14,15.11 11.5,14.15 13.23,13.58C13.82,15.25 14.36,17.15 14.77,19.29C13.91,19.62 13,19.8 12,19.8M4.2,12C4.2,11.96 4.2,11.93 4.2,11.89C4.42,11.9 4.71,11.9 5.05,11.9H5.06C6.62,11.89 9.36,11.76 12.14,10.89C12.29,11.22 12.44,11.56 12.59,11.92C10.73,12.54 9.27,13.53 8.19,14.5C7.16,15.46 6.45,16.39 6.04,17C4.9,15.66 4.2,13.91 4.2,12M8.55,5C9.1,5.65 10.18,7.06 11.34,9.25C9,9.96 6.61,10.12 5.18,10.12C5.14,10.12 5.1,10.12 5.06,10.12H5.05C4.81,10.12 4.6,10.12 4.43,10.11C5,7.87 6.5,6 8.55,5M12,4.2C13.84,4.2 15.53,4.84 16.86,5.91C15.84,7.14 14.5,8 13.03,8.65C12,6.67 11,5.25 10.34,4.38C10.88,4.27 11.43,4.2 12,4.2M18.13,7.18C19.1,8.42 19.71,9.96 19.79,11.63C18.66,11.39 17.6,11.28 16.6,11.28V11.28H16.59C15.79,11.28 15.04,11.35 14.33,11.47C14.16,11.05 14,10.65 13.81,10.26C15.39,9.57 16.9,8.58 18.13,7.18M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z",
"name": "dribbble"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M15.09,16.5C14.81,15.14 14.47,13.91 14.08,12.82L15.2,12.74H15.22V12.74C15.87,12.74 16.59,12.82 17.36,13C17.09,14.44 16.26,15.69 15.09,16.5M12,17.46C10.79,17.46 9.66,17.06 8.76,16.39C8.95,16.07 9.36,15.46 10,14.83C10.7,14.18 11.64,13.5 12.86,13.11C13.28,14.27 13.65,15.6 13.94,17.1C13.33,17.33 12.68,17.46 12,17.46M6.54,12V11.92L7.14,11.93V11.93C8.24,11.93 10.15,11.83 12.1,11.22L12.41,11.94C11.11,12.38 10.09,13.07 9.34,13.76C8.61,14.42 8.12,15.08 7.83,15.5C7.03,14.56 6.54,13.34 6.54,12M9.59,7.11C9.97,7.56 10.73,8.54 11.54,10.08C9.89,10.57 8.23,10.68 7.22,10.68H7.14V10.68H6.7C7.09,9.11 8.17,7.81 9.59,7.11M12,6.54C13.29,6.54 14.47,7 15.41,7.74C14.69,8.6 13.74,9.22 12.72,9.66C12,8.27 11.31,7.28 10.84,6.67C11.21,6.59 11.6,6.54 12,6.54M16.29,8.63C16.97,9.5 17.4,10.57 17.45,11.74C16.66,11.58 15.92,11.5 15.22,11.5V11.5C14.66,11.5 14.13,11.54 13.63,11.63L13.27,10.78C14.37,10.3 15.43,9.61 16.29,8.63M12,5A7,7 0 0,0 5,12A7,7 0 0,0 12,19A7,7 0 0,0 19,12A7,7 0 0,0 12,5Z",
"name": "dribbble-box"
},
{
"path": "M6.72,20.78C8.23,20.71 10.07,20.78 11.87,20.78C13.72,20.78 15.62,20.66 17.12,20.78C17.72,20.83 18.28,21.19 18.77,20.87C19.16,20.38 18.87,19.71 18.96,19.05C19.12,17.78 20.28,16.27 18.59,15.95C17.87,16.61 18.35,17.23 17.95,18.05C17.45,19.03 15.68,19.37 14,19.5C12.54,19.62 10,19.76 9.5,18.77C9.04,17.94 9.29,16.65 9.29,15.58C9.29,14.38 9.16,13.22 9.5,12.3C11.32,12.43 13.7,11.69 15,12.5C15.87,13 15.37,14.06 16.38,14.4C17.07,14.21 16.7,13.32 16.66,12.5C16.63,11.94 16.63,11.19 16.66,10.57C16.69,9.73 17,8.76 16.1,8.74C15.39,9.3 15.93,10.23 15.18,10.75C14.95,10.92 14.43,11 14.08,11C12.7,11.17 10.54,11.05 9.38,10.84C9.23,9.16 9.24,6.87 9.38,5.19C10,4.57 11.45,4.54 12.42,4.55C14.13,4.55 16.79,4.7 17.3,5.55C17.58,6 17.36,7 17.85,7.1C18.85,7.33 18.36,5.55 18.41,4.73C18.44,4.11 18.71,3.72 18.59,3.27C18.27,2.83 17.79,3.05 17.5,3.09C14.35,3.5 9.6,3.27 6.26,3.27C5.86,3.27 5.16,3.07 4.88,3.54C4.68,4.6 6.12,4.16 6.62,4.73C6.79,4.91 7.03,5.73 7.08,6.28C7.23,7.74 7.08,9.97 7.08,12.12C7.08,14.38 7.26,16.67 7.08,18.05C7,18.53 6.73,19.3 6.62,19.41C6,20.04 4.34,19.35 4.5,20.69C5.09,21.08 5.93,20.82 6.72,20.78Z",
"name": "etsy"
},
{
"path": "M12,17.5C10.15,17.5 8.42,16.56 7.41,15L17.41,12.75L22.08,11.75C22.05,10.32 21.71,8.92 21.08,7.64C18.66,2.61 12.62,0.5 7.58,2.92C2.55,5.34 0.44,11.38 2.86,16.41C5.29,21.44 11.33,23.56 16.36,21.14C18.5,20.09 20.25,18.31 21.22,16.11L16.61,15C15.6,16.57 13.86,17.5 12,17.5M12,6.5C13.76,6.5 15.41,7.34 16.44,8.77L6.57,11.19C6.96,8.5 9.28,6.5 12,6.5Z",
"name": "eventbrite"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M18,5H15.5A3.5,3.5 0 0,0 12,8.5V11H10V14H12V21H15V14H18V11H15V9A1,1 0 0,1 16,8H18V5Z",
"name": "facebook-box"
},
{
"path": "M21,12A9,9 0 0,1 12,21H4.5L9.74,15.76L11.16,17.17L9.33,19H12A7,7 0 0,0 19,12V7L21,5V12M3,12A9,9 0 0,1 12,3H19.5L14.26,8.24L12.84,6.83L14.67,5H12A7,7 0 0,0 5,12V17L3,19V12Z",
"name": "flattr"
},
{
"path": "M11,12C11,14.5 9,16.5 6.5,16.5C4,16.5 2,14.5 2,12C2,9.5 4,7.5 6.5,7.5C9,7.5 11,9.5 11,12M17.5,7.5C15,7.5 13,9.5 13,12C13,14.5 15,16.5 17.5,16.5C20,16.5 22,14.5 22,12C22,9.5 20,7.5 17.5,7.5Z",
"name": "flickr"
},
{
"path": "M17,5L16.57,7.5C16.5,7.73 16.2,8 15.91,8C15.61,8 12,8 12,8C11.53,8 10.95,8.32 10.95,8.79V9.2C10.95,9.67 11.53,10 12,10C12,10 14.95,10 15.28,10C15.61,10 15.93,10.36 15.86,10.71C15.79,11.07 14.94,13.28 14.9,13.5C14.86,13.67 14.64,14 14.25,14C13.92,14 11.37,14 11.37,14C10.85,14 10.69,14.07 10.34,14.5C10,14.94 7.27,18.1 7.27,18.1C7.24,18.13 7,18.04 7,18V5C7,4.7 7.61,4 8,4C8,4 16.17,4 16.5,4C16.82,4 17.08,4.61 17,5M17,14.45C17.11,13.97 18.78,6.72 19.22,4.55M17.58,2C17.58,2 8.38,2 6.91,2C5.43,2 5,3.11 5,3.8C5,4.5 5,20.76 5,20.76C5,21.54 5.42,21.84 5.66,21.93C5.9,22.03 6.55,22.11 6.94,21.66C6.94,21.66 11.65,16.22 11.74,16.13C11.87,16 11.87,16 12,16C12.26,16 14.2,16 15.26,16C16.63,16 16.85,15 17,14.45C17.11,13.97 18.78,6.72 19.22,4.55C19.56,2.89 19.14,2 17.58,2Z",
"name": "foursquare"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H14.56C14.24,20.93 14.23,20.32 14.23,20.11L14.24,17.64C14.24,16.8 13.95,16.25 13.63,15.97C15.64,15.75 17.74,15 17.74,11.53C17.74,10.55 17.39,9.74 16.82,9.11C16.91,8.89 17.22,7.97 16.73,6.73C16.73,6.73 15.97,6.5 14.25,7.66C13.53,7.46 12.77,7.36 12,7.35C11.24,7.36 10.46,7.46 9.75,7.66C8.03,6.5 7.27,6.73 7.27,6.73C6.78,7.97 7.09,8.89 7.18,9.11C6.61,9.74 6.26,10.55 6.26,11.53C6.26,15 8.36,15.75 10.36,16C10.1,16.2 9.87,16.6 9.79,17.18C9.27,17.41 7.97,17.81 7.17,16.43C7.17,16.43 6.69,15.57 5.79,15.5C5.79,15.5 4.91,15.5 5.73,16.05C5.73,16.05 6.32,16.33 6.73,17.37C6.73,17.37 7.25,19.12 9.76,18.58L9.77,20.11C9.77,20.32 9.75,20.93 9.43,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z",
"name": "github-box"
},
{
"path": "M20.38,8.53C20.54,8.13 21.06,6.54 20.21,4.39C20.21,4.39 18.9,4 15.91,6C14.66,5.67 13.33,5.62 12,5.62C10.68,5.62 9.34,5.67 8.09,6C5.1,3.97 3.79,4.39 3.79,4.39C2.94,6.54 3.46,8.13 3.63,8.53C2.61,9.62 2,11 2,12.72C2,19.16 6.16,20.61 12,20.61C17.79,20.61 22,19.16 22,12.72C22,11 21.39,9.62 20.38,8.53M12,19.38C7.88,19.38 4.53,19.19 4.53,15.19C4.53,14.24 5,13.34 5.8,12.61C7.14,11.38 9.43,12.03 12,12.03C14.59,12.03 16.85,11.38 18.2,12.61C19,13.34 19.5,14.23 19.5,15.19C19.5,19.18 16.13,19.38 12,19.38M8.86,13.12C8.04,13.12 7.36,14.12 7.36,15.34C7.36,16.57 8.04,17.58 8.86,17.58C9.69,17.58 10.36,16.58 10.36,15.34C10.36,14.11 9.69,13.12 8.86,13.12M15.14,13.12C14.31,13.12 13.64,14.11 13.64,15.34C13.64,16.58 14.31,17.58 15.14,17.58C15.96,17.58 16.64,16.58 16.64,15.34C16.64,14.11 16,13.12 15.14,13.12Z",
"name": "github-face"
},
{
"path": "M8,2A3,3 0 0,0 5,5V16.5H8V5H19A3,3 0 0,0 16,2H8M16,7.5V19H5A3,3 0 0,0 8,22H16A3,3 0 0,0 19,19V7.5H16Z",
"name": "glassdoor"
},
{
"path": "M2,22L8.5,2H15.4L9.2,20C9.2,20 8.6,22 7,22C5.9,22 2,22 2,22M16.4,5L13,15L15,20.7C15,20.7 15.4,22 17,22C18.3,22 22,22 22,22L16.4,5Z",
"name": "google-adwords"
},
{
"path": "M19,3H13V8L17,7L16,11H21V5C21,3.89 20.1,3 19,3M17,17L13,16V21H19A2,2 0 0,0 21,19V13H16M8,13H3V19A2,2 0 0,0 5,21H11V16L7,17M3,5V11H8L7,7L11,8V3H5C3.89,3 3,3.89 3,5Z",
"name": "google-pages"
},
{
"path": "M12,1.5A9,9 0 0,1 21,10.5C21,13.11 19.89,15.47 18.11,17.11L17.05,16.05C18.55,14.68 19.5,12.7 19.5,10.5A7.5,7.5 0 0,0 12,3A7.5,7.5 0 0,0 4.5,10.5C4.5,12.7 5.45,14.68 6.95,16.05L5.89,17.11C4.11,15.47 3,13.11 3,10.5A9,9 0 0,1 12,1.5M12,4.5A6,6 0 0,1 18,10.5C18,12.28 17.22,13.89 16,15L14.92,13.92C15.89,13.1 16.5,11.87 16.5,10.5C16.5,8 14.5,6 12,6C9.5,6 7.5,8 7.5,10.5C7.5,11.87 8.11,13.1 9.08,13.92L8,15C6.78,13.89 6,12.28 6,10.5A6,6 0 0,1 12,4.5M8.11,17.65L11.29,14.46C11.68,14.07 12.32,14.07 12.71,14.46L15.89,17.65C16.28,18.04 16.28,18.67 15.89,19.06L12.71,22.24C12.32,22.63 11.68,22.63 11.29,22.24L8.11,19.06C7.72,18.67 7.72,18.04 8.11,17.65Z",
"name": "google-physical-web"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M19.5,12H18V10.5H17V12H15.5V13H17V14.5H18V13H19.5V12M9.65,11.36V12.9H12.22C12.09,13.54 11.45,14.83 9.65,14.83C8.11,14.83 6.89,13.54 6.89,12C6.89,10.46 8.11,9.17 9.65,9.17C10.55,9.17 11.13,9.56 11.45,9.88L12.67,8.72C11.9,7.95 10.87,7.5 9.65,7.5C7.14,7.5 5.15,9.5 5.15,12C5.15,14.5 7.14,16.5 9.65,16.5C12.22,16.5 13.96,14.7 13.96,12.13C13.96,11.81 13.96,11.61 13.89,11.36H9.65Z",
"name": "google-plus-box"
},
{
"path": "M14,20.95H20V10.78L8,7.34V3.05H4V20.95H10V15.31H14V20.95Z",
"name": "houzz"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M13.5,18.71H18V11.09L9,8.5V5.29H6V18.71H10.5V14.5H13.5V18.71Z",
"name": "houzz-box"
},
{
"path": "M10,5A1,1 0 0,0 9,4H8V2H16V4H15A1,1 0 0,0 14,5V19A1,1 0 0,0 15,20H16V22H8V20H9A1,1 0 0,0 10,19V5Z",
"name": "instapaper"
},
{
"path": "M7.85,17.07C7.03,17.17 3.5,17.67 4.06,20.26C4.69,23.3 9.87,22.59 9.83,19C9.81,16.57 9.83,9.2 9.83,9.2C9.83,9.2 9.76,8.53 10.43,8.39L18.19,6.79C18.19,6.79 18.83,6.65 18.83,7.29C18.83,7.89 18.83,14.2 18.83,14.2C18.83,14.2 18.9,14.83 18.12,15C17.34,15.12 13.91,15.4 14.19,18C14.5,21.07 20,20.65 20,17.07V2.61C20,2.61 20.04,1.62 18.9,1.87L9.5,3.78C9.5,3.78 8.66,3.96 8.66,4.77C8.66,5.5 8.66,16.11 8.66,16.11C8.66,16.11 8.66,16.96 7.85,17.07Z",
"name": "itunes"
},
{
"path": "M2,5.69C8.92,1.07 11.1,7 11.28,10.27C11.46,13.53 8.29,17.64 4.31,14.92V20.3L2,18.77V5.69M4.22,7.4V12.78C7.84,14.95 9.08,13.17 9.08,10.09C9.08,5.74 6.57,5.59 4.22,7.4M15.08,4.15C15.08,4.15 14.9,7.64 15.08,11.07C15.44,14.5 19.69,11.84 19.69,11.84V4.92L22,5.2V14.44C22,20.6 15.85,20.3 15.85,20.3L15.08,18C20.46,18 19.78,14.43 19.78,14.43C13.27,16.97 12.77,12.61 12.77,12.61V5.69L15.08,4.15Z",
"name": "language-python-text"
},
{
"path": "M18,17.93C15.92,17.92 14.81,16.9 14.04,15.09L13.82,14.6L11.92,10.23C11.29,8.69 9.72,7.64 7.96,7.64C5.57,7.64 3.63,9.59 3.63,12C3.63,14.41 5.57,16.36 7.96,16.36C9.62,16.36 11.08,15.41 11.8,14L12.57,15.81C11.5,17.15 9.82,18 7.96,18C4.67,18 2,15.32 2,12C2,8.69 4.67,6 7.96,6C10.44,6 12.45,7.34 13.47,9.7C13.54,9.89 14.54,12.24 15.42,14.24C15.96,15.5 16.42,16.31 17.91,16.36C19.38,16.41 20.39,15.5 20.39,14.37C20.39,13.26 19.62,13 18.32,12.56C16,11.79 14.79,11 14.79,9.15C14.79,7.33 16,6.12 18,6.12C19.31,6.12 20.24,6.7 20.89,7.86L19.62,8.5C19.14,7.84 18.61,7.57 17.94,7.57C17,7.57 16.33,8.23 16.33,9.1C16.33,10.34 17.43,10.53 18.97,11.03C21.04,11.71 22,12.5 22,14.42C22,16.45 20.27,17.93 18,17.93Z",
"name": "lastfm"
},
{
"path": "M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M18.5,18.5V13.2A3.26,3.26 0 0,0 15.24,9.94C14.39,9.94 13.4,10.46 12.92,11.24V10.13H10.13V18.5H12.92V13.57C12.92,12.8 13.54,12.17 14.31,12.17A1.4,1.4 0 0,1 15.71,13.57V18.5H18.5M6.88,8.56A1.68,1.68 0 0,0 8.56,6.88C8.56,5.95 7.81,5.19 6.88,5.19A1.69,1.69 0 0,0 5.19,6.88C5.19,7.81 5.95,8.56 6.88,8.56M8.27,18.5V10.13H5.5V18.5H8.27Z",
"name": "linkedin-box"
},
{
"path": "M9.56,12.5C9.56,12.6 9.5,12.72 9.4,12.79C9.2,12.97 8.89,12.94 8.71,12.74C8.63,12.65 8.59,12.53 8.59,12.41V8.5H5.66V13.39A2.44,2.44 0 0,0 8.1,15.83C8.68,15.83 9.24,15.62 9.68,15.24C9.64,15.6 9.43,15.93 9.11,16.11C8.75,16.31 8.35,16.42 7.94,16.41C7.46,16.41 7,16.3 6.56,16.09L6.39,16V18.6C7.04,18.86 7.74,19 8.44,19C9.47,19 10.46,18.66 11.25,18C12.08,17.25 12.54,16.18 12.5,15.06V8.5H9.56V12.5M4.93,13.39V5.59H2V12.9C1.84,14.35 2.88,15.65 4.33,15.81C4.41,15.82 4.5,15.83 4.56,15.83V15.83C4.93,15.83 5.29,15.74 5.63,15.59L5.75,15.5L5.65,15.41C5.17,14.85 4.91,14.13 4.93,13.39M22,11.39V8.5H21C20.59,6.38 18.53,5 16.41,5.41C16.17,5.45 15.94,5.5 15.71,5.61C14.28,6.24 13.33,7.62 13.26,9.18V15.83H13.39C14.95,15.76 16.19,14.47 16.19,12.9H17.41V10H16.15V9.17C16.15,8.86 16.32,8.57 16.59,8.41C17.06,8.13 17.68,8.28 17.96,8.76C18.05,8.91 18.09,9.07 18.1,9.24V11.93C18.07,14.05 19.75,15.79 21.87,15.83H22V12.9H22A1,1 0 0,1 21,11.9V11.41L22,11.39Z",
"name": "lyft"
},
{
"path": "M15.45,11.91C15.34,9.7 13.7,8.37 11.72,8.37H11.64C9.35,8.37 8.09,10.17 8.09,12.21C8.09,14.5 9.62,15.95 11.63,15.95C13.88,15.95 15.35,14.3 15.46,12.36M11.65,6.39C13.18,6.39 14.62,7.07 15.67,8.13V8.13C15.67,7.62 16,7.24 16.5,7.24H16.61C17.35,7.24 17.5,7.94 17.5,8.16V16.06C17.46,16.58 18.04,16.84 18.37,16.5C19.64,15.21 21.15,9.81 17.58,6.69C14.25,3.77 9.78,4.25 7.4,5.89C4.88,7.63 3.26,11.5 4.83,15.11C6.54,19.06 11.44,20.24 14.35,19.06C15.83,18.47 16.5,20.46 15,21.11C12.66,22.1 6.23,22 3.22,16.79C1.19,13.27 1.29,7.08 6.68,3.87C10.81,1.42 16.24,2.1 19.5,5.5C22.95,9.1 22.75,15.8 19.4,18.41C17.89,19.59 15.64,18.44 15.66,16.71L15.64,16.15C14.59,17.2 13.18,17.81 11.65,17.81C8.63,17.81 6,15.15 6,12.13C6,9.08 8.63,6.39 11.65,6.39Z",
"name": "mail-ru"
},
{
"path": "M20.93,14C20.66,15.4 18.5,16.95 15.97,17.25C14.66,17.4 13.38,17.55 12,17.5C9.76,17.38 8,16.95 8,16.95L8.03,17.57C8.32,19.78 10.22,19.92 12.03,20C13.85,20.04 15.47,19.53 15.47,19.53L15.55,21.17C15.55,21.17 14.27,21.86 12,22C10.75,22.05 9.2,21.95 7.39,21.47C3.47,20.43 2.79,16.25 2.69,12L2.68,8.57C2.68,4.23 5.5,2.96 5.5,2.96C6.95,2.3 9.41,2 11.97,2H12.03C14.59,2 17.05,2.3 18.5,2.96C18.5,2.96 21.33,4.23 21.33,8.57C21.33,8.57 21.36,11.77 20.93,14M8.33,10.32C8.33,9.54 7.7,8.91 6.93,8.91C6.15,8.91 5.5,9.54 5.5,10.32C5.5,11.09 6.15,11.72 6.93,11.72A1.4,1.4 0 0,0 8.33,10.32M13.41,10.32A1.41,1.41 0 0,0 12,8.91A1.41,1.41 0 0,0 10.59,10.32C10.59,11.09 11.22,11.72 12,11.72C12.78,11.72 13.41,11.09 13.41,10.32M18.5,10.32C18.5,9.54 17.85,8.91 17.07,8.91C16.3,8.91 15.67,9.54 15.67,10.32A1.4,1.4 0 0,0 17.07,11.72C17.85,11.72 18.5,11.09 18.5,10.32Z",
"name": "mastodon-variant"
},
{
"path": "M4.37,7.3C4.4,7.05 4.3,6.81 4.12,6.65L2.25,4.4V4.06H8.05L12.53,13.89L16.47,4.06H22V4.4L20.4,5.93C20.27,6.03 20.2,6.21 20.23,6.38V17.62C20.2,17.79 20.27,17.97 20.4,18.07L21.96,19.6V19.94H14.12V19.6L15.73,18.03C15.89,17.88 15.89,17.83 15.89,17.59V8.5L11.4,19.9H10.8L5.57,8.5V16.14C5.5,16.46 5.63,16.78 5.86,17L7.96,19.57V19.9H2V19.57L4.1,17C4.33,16.78 4.43,16.46 4.37,16.14V7.3Z",
"name": "medium"
},
{
"path": "M19.61,14.86C19.61,16.68 18.3,18.25 16.5,18.55C16.29,18.59 16.07,18.62 15.84,18.61C15.76,18.61 15.73,18.64 15.71,18.71C15.35,19.74 14.64,20.35 13.57,20.5C12.86,20.6 12.22,20.41 11.65,19.97C11.57,19.9 11.5,19.9 11.44,19.96C10.78,20.43 10.04,20.64 9.23,20.59C7.66,20.5 6.33,19.29 6.08,17.74C6.06,17.63 6.04,17.5 6.04,17.41C6.04,17.32 6,17.29 5.92,17.27C5.44,17.18 5,17 4.63,16.68C3.92,16.13 3.5,15.41 3.4,14.5C3.29,13.5 3.61,12.62 4.32,11.89C4.38,11.83 4.38,11.79 4.34,11.72C4.07,11.24 3.94,10.72 3.96,10.17C4,8.79 4.97,7.65 6.31,7.37C6.46,7.33 6.54,7.27 6.61,7.13C7.27,5.71 8.37,4.85 9.91,4.56C11,4.36 12,4.58 12.94,5.13C13,5.18 13.08,5.18 13.17,5.16C14.67,4.72 16,5.04 17.12,6.11C17.78,6.74 18.15,7.54 18.26,8.46C18.28,8.66 18.29,8.86 18.28,9.06C18.27,9.14 18.29,9.17 18.37,9.19C19.04,9.44 19.5,9.91 19.71,10.6C19.96,11.45 19.75,12.21 19.11,12.83C19.05,12.89 19.07,12.92 19.1,12.97C19.44,13.56 19.61,14.18 19.61,14.86M12.93,14.57C12.93,15.34 13.43,16 14.14,16.26C14.5,16.37 14.85,16.43 15.22,16.45C15.5,16.46 15.75,16.44 16,16.32C16.19,16.22 16.28,16.06 16.27,15.85C16.26,15.64 16.16,15.5 15.96,15.4C15.89,15.37 15.82,15.34 15.74,15.33C15.5,15.29 15.3,15.26 15.07,15.21C14.71,15.14 14.55,14.95 14.55,14.57C14.54,14.24 14.63,13.93 14.73,13.63C14.92,13.07 15.17,12.53 15.41,12C15.64,11.47 15.88,10.95 16.04,10.4C16.13,10.1 16.18,9.8 16.09,9.5C15.97,9 15.69,8.7 15.2,8.61C14.75,8.5 14.3,8.5 13.9,8.78C13.76,8.87 13.63,8.85 13.5,8.74C13.43,8.67 13.34,8.58 13.26,8.5C12.84,8.12 12.3,8.1 11.85,8.45C11.67,8.59 11.5,8.76 11.33,8.89C11.16,9 11,9.03 10.79,8.92C10.6,8.83 10.42,8.74 10.23,8.65C10.03,8.57 9.85,8.46 9.63,8.44C8.95,8.38 8.24,8.79 7.94,9.41C7.8,9.68 7.69,9.96 7.59,10.25C7.11,11.57 6.72,12.91 6.32,14.26C6.14,14.86 6.35,15.45 6.86,15.77C7.26,16 7.69,16.09 8.14,15.95C8.5,15.84 8.71,15.55 8.85,15.22C9.31,14.13 9.73,13 10.17,11.91C10.29,11.61 10.41,11.3 10.54,11C10.67,10.7 11.04,10.6 11.26,10.8C11.4,10.92 11.44,11.09 11.42,11.26C11.41,11.45 11.34,11.62 11.27,11.79C11,12.5 10.69,13.24 10.4,13.97C10.34,14.11 10.28,14.26 10.25,14.42C10.21,14.69 10.31,14.93 10.54,15C10.76,15.12 11,15.14 11.23,15.05C11.5,14.95 11.67,14.74 11.79,14.5C12.22,13.65 12.65,12.8 13.08,11.95C13.28,11.56 13.5,11.17 13.68,10.78C13.76,10.64 13.85,10.5 14,10.41C14.12,10.33 14.25,10.33 14.38,10.4C14.5,10.47 14.5,10.6 14.5,10.73C14.5,10.8 14.5,10.87 14.47,10.93C14.41,11.07 14.36,11.2 14.3,11.33C13.94,12.09 13.57,12.84 13.22,13.59C13.07,13.91 12.91,14.23 12.93,14.57M17.96,20.12C17.96,19.62 17.54,19.2 17.04,19.2C16.5,19.2 16.1,19.61 16.1,20.12A0.93,0.93 0 0,0 17.03,21.05A0.93,0.93 0 0,0 17.96,20.12M2.38,12.46C2.86,12.46 3.27,12.05 3.27,11.57C3.27,11.09 2.87,10.69 2.39,10.69C1.89,10.69 1.5,11.08 1.5,11.57C1.5,12.06 1.89,12.46 2.38,12.46M13.26,2.55C12.77,2.55 12.37,2.94 12.37,3.42C12.37,3.91 12.77,4.3 13.25,4.3C13.74,4.3 14.13,3.92 14.13,3.43C14.13,2.95 13.74,2.55 13.26,2.55M20.45,8.03C20.45,7.63 20.11,7.29 19.71,7.29C19.3,7.28 18.95,7.63 18.95,8.04C18.95,8.45 19.28,8.78 19.7,8.78C20.12,8.78 20.46,8.45 20.45,8.03M5.04,5.89C5.04,6.27 5.34,6.56 5.71,6.56C6.09,6.56 6.39,6.26 6.38,5.88C6.38,5.5 6.09,5.22 5.72,5.22C5.33,5.22 5.04,5.5 5.04,5.89M12.06,21.44C12.06,21.12 11.81,20.86 11.5,20.86C11.16,20.86 10.91,21.11 10.91,21.44C10.91,21.75 11.16,22 11.5,22C11.8,22 12.06,21.75 12.06,21.44M21,12.5C20.71,12.5 20.45,12.78 20.45,13.08A0.55,0.55 0 0,0 21,13.63C21.33,13.63 21.57,13.4 21.57,13.08C21.57,12.77 21.33,12.5 21,12.5M7.62,2C7.35,2 7.14,2.2 7.14,2.47C7.14,2.73 7.35,2.94 7.62,2.94A0.47,0.47 0 0,0 8.09,2.47C8.09,2.2 7.89,2 7.62,2M22.08,10C21.86,10 21.67,10.17 21.66,10.4C21.66,10.63 21.85,10.82 22.08,10.82C22.32,10.82 22.5,10.64 22.5,10.41C22.5,10.17 22.32,10 22.08,10M5.5,18.26C5.5,18.04 5.29,17.85 5.06,17.84C4.84,17.84 4.65,18.03 4.65,18.27C4.65,18.5 4.84,18.68 5.07,18.68C5.3,18.68 5.5,18.5 5.5,18.26Z",
"name": "meetup"
},
{
"path": "M21.11,18.5C20.97,18.5 20.83,18.44 20.71,18.36C20.37,18.13 20.28,17.68 20.5,17.34C21.18,16.34 21.54,15.16 21.54,13.93C21.54,12.71 21.18,11.53 20.5,10.5C20.28,10.18 20.37,9.73 20.71,9.5C21.04,9.28 21.5,9.37 21.72,9.7C22.56,10.95 23,12.41 23,13.93C23,15.45 22.56,16.91 21.72,18.16C21.58,18.37 21.35,18.5 21.11,18.5M19,17.29C18.88,17.29 18.74,17.25 18.61,17.17C18.28,16.94 18.19,16.5 18.42,16.15C18.86,15.5 19.1,14.73 19.1,13.93C19.1,13.14 18.86,12.37 18.42,11.71C18.19,11.37 18.28,10.92 18.61,10.69C18.95,10.47 19.4,10.55 19.63,10.89C20.24,11.79 20.56,12.84 20.56,13.93C20.56,15 20.24,16.07 19.63,16.97C19.5,17.18 19.25,17.29 19,17.29M14.9,15.73C15.89,15.73 16.7,14.92 16.7,13.93C16.7,13.17 16.22,12.5 15.55,12.25C15.5,12.55 15.43,12.85 15.34,13.14C15.23,13.44 14.95,13.64 14.64,13.64C14.57,13.64 14.5,13.62 14.41,13.6C14.03,13.47 13.82,13.06 13.95,12.67C14.09,12.24 14.17,11.78 14.17,11.32C14.17,8.93 12.22,7 9.82,7C8.1,7 6.56,8 5.87,9.5C6.54,9.7 7.16,10.04 7.66,10.54C7.95,10.83 7.95,11.29 7.66,11.58C7.38,11.86 6.91,11.86 6.63,11.58C6.17,11.12 5.56,10.86 4.9,10.86C3.56,10.86 2.46,11.96 2.46,13.3C2.46,14.64 3.56,15.73 4.9,15.73H14.9M15.6,10.75C17.06,11.07 18.17,12.37 18.17,13.93C18.17,15.73 16.7,17.19 14.9,17.19H4.9C2.75,17.19 1,15.45 1,13.3C1,11.34 2.45,9.73 4.33,9.45C5.12,7.12 7.33,5.5 9.82,5.5C12.83,5.5 15.31,7.82 15.6,10.75Z",
"name": "mixcloud"
},
{
"path": "M5.68,3.96L11.41,11.65C11.55,11.84 11.55,12.1 11.41,12.29L5.65,20L5.5,20.18C4.76,21 3.47,21.07 2.64,20.31C1.85,19.59 1.79,18.37 2.43,17.5L6.56,11.97L2.46,6.47C1.83,5.62 1.88,4.39 2.67,3.67L2.82,3.54C3.73,2.87 5,3.05 5.68,3.96M18.32,3.96C19,3.05 20.27,2.87 21.18,3.54L21.33,3.67C22.12,4.39 22.17,5.61 21.54,6.47L17.44,11.97L21.57,17.5C22.21,18.36 22.15,19.59 21.36,20.31C20.53,21.07 19.24,21 18.5,20.18L18.35,20L12.59,12.29C12.45,12.1 12.45,11.84 12.59,11.65L18.32,3.96Z",
"name": "mixer"
},
{
"path": "M3.25,4.03L19.95,20.73L18.7,22L14.86,18.13C14.77,18.12 14.68,18.09 14.59,18.05C14.26,17.89 14.14,17.62 14.11,17.38L12.18,15.45C12.14,15.53 12.09,15.6 12.05,15.66C11.62,16.26 11.19,16.26 10.86,16.04C10.54,15.83 5.5,12 5.23,11.87C4.95,11.76 4.85,12.03 5.12,13.5C5.39,15 4.95,15.39 4.57,15.45C4.2,15.5 3.06,15.18 3,12.14C2.95,9.11 3.76,8.62 4.14,8.62C4.6,8.62 7.08,10.69 8.84,12.12L2,5.28L3.25,4.03M18.38,16.56C18.75,15.4 19.12,13.8 19.1,12.03V12C19.14,8.5 17.66,5.58 17.66,5.58C17.66,5.58 17.42,4.72 18.12,4.39C18.83,4.06 19.3,4.61 19.3,4.61C21.12,8.22 21,11.64 21,12C21,12.27 21.09,14.96 19.88,18.05L18.38,16.56M15.14,13.31C15.19,12.92 15.22,12.5 15.24,12.03V12C15.14,8.5 14.13,7.21 14.13,7.21C14.13,7.21 13.89,6.34 14.59,6C15.3,5.69 15.77,6.23 15.77,6.23C17.26,8.94 17.16,11.64 17.14,12C17.15,12.2 17.2,13.38 16.82,15L15.14,13.31M10.2,8.38C10.23,7.77 10.59,7.64 10.59,7.64C10.59,7.64 11.19,7.37 11.57,7.8C11.91,8.19 12.72,9.57 12.89,11.07L10.2,8.38Z",
"name": "nfc-off"
},
{ "path": "M20,4H4V20H12V8H16V20H20V4", "name": "npm-variant" },
{
"path": "M3,3V21H21V3H3M6,6H18V18H15V9H12V18H6V6Z",
"name": "npm-variant-outline"
},
{
"path": "M8.32,21.97C8.21,21.92 8.08,21.76 8.06,21.65C8.03,21.5 8,21.76 8.66,17.56C9.26,13.76 9.25,13.82 9.33,13.71C9.46,13.54 9.44,13.54 10.94,13.53C12.26,13.5 12.54,13.5 13.13,13.41C16.38,12.96 18.39,11.05 19.09,7.75C19.13,7.53 19.17,7.34 19.18,7.34C19.18,7.33 19.25,7.38 19.33,7.44C20.36,8.22 20.71,9.66 20.32,11.58C19.86,13.87 18.64,15.39 16.74,16.04C15.93,16.32 15.25,16.43 14.05,16.46C13.25,16.5 13.23,16.5 13,16.65C12.83,16.82 12.84,16.79 12.45,19.2C12.18,20.9 12.08,21.45 12.04,21.55C11.97,21.71 11.83,21.85 11.67,21.93L11.56,22H10C8.71,22 8.38,22 8.32,21.97V21.97M3.82,19.74C3.63,19.64 3.5,19.47 3.5,19.27C3.5,19 6.11,2.68 6.18,2.5C6.27,2.32 6.5,2.13 6.68,2.06L6.83,2H10.36C14.27,2 14.12,2 15,2.2C17.62,2.75 18.82,4.5 18.37,7.13C17.87,10.06 16.39,11.8 13.87,12.43C13,12.64 12.39,12.7 10.73,12.7C9.42,12.7 9.32,12.71 9.06,12.85C8.8,13 8.59,13.27 8.5,13.6C8.46,13.67 8.23,15.07 7.97,16.7C7.71,18.33 7.5,19.69 7.5,19.72L7.47,19.78H5.69C4.11,19.78 3.89,19.78 3.82,19.74V19.74Z",
"name": "paypal"
},
{
"path": "M12,7A2,2 0 0,1 10,9A2,2 0 0,1 8,7C7.37,7.84 7,8.87 7,10A5,5 0 0,0 12,15A5,5 0 0,0 17,10A5,5 0 0,0 12,5C11.57,5 11.16,5.05 10.77,5.15C11.5,5.45 12,6.17 12,7M12,2A8,8 0 0,1 20,10C20,11.05 19.8,12.04 19.43,12.96C17.89,17.38 13.63,22 12,22C10.37,22 6.11,17.38 4.57,12.96C4.2,12.04 4,11.05 4,10A8,8 0 0,1 12,2Z",
"name": "periscope"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H9.29C9.69,20.33 10.19,19.38 10.39,18.64L11.05,16.34C11.36,16.95 12.28,17.45 13.22,17.45C16.17,17.45 18.22,14.78 18.22,11.45C18.22,8.28 15.64,5.89 12.3,5.89C8.14,5.89 5.97,8.67 5.97,11.72C5.97,13.14 6.69,14.89 7.91,15.45C8.08,15.56 8.19,15.5 8.19,15.34L8.47,14.28C8.5,14.14 8.5,14.06 8.41,14C7.97,13.45 7.69,12.61 7.69,11.78C7.69,9.64 9.3,7.61 12.03,7.61C14.42,7.61 16.08,9.19 16.08,11.5C16.08,14.11 14.75,15.95 13.03,15.95C12.05,15.95 11.39,15.11 11.55,14.17C11.83,13.03 12.39,11.83 12.39,11C12.39,10.22 12,9.61 11.16,9.61C10.22,9.61 9.39,10.61 9.39,11.95C9.39,12.83 9.66,13.39 9.66,13.39L8.55,18.17C8.39,19 8.47,20.25 8.55,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z",
"name": "pinterest-box"
},
{
"path": "M21.9,4.26C21.64,3.55 20.96,3.07 20.2,3.07H20.19L18.46,3.07H3.81C3.07,3.07 2.39,3.54 2.12,4.24C2.04,4.45 2,4.66 2,4.88V10.92L2.07,12.12C2.36,14.85 3.78,17.23 5.97,18.9C6,18.93 6.05,18.96 6.09,19H6.11C7.29,19.86 8.6,20.44 10,20.73C10.68,20.86 11.35,20.93 12,20.93C12.63,20.93 13.25,20.87 13.85,20.76C13.93,20.75 14,20.73 14.07,20.72C14.09,20.71 14.11,20.7 14.14,20.69C15.5,20.4 16.76,19.83 17.89,19H17.91C17.95,18.96 18,18.93 18.03,18.9C20.22,17.23 21.64,14.85 21.93,12.12L22,10.92V4.88C22,4.68 21.97,4.47 21.9,4.26M17.67,10.55L12.96,15.06C12.7,15.32 12.35,15.44 12,15.44C11.67,15.44 11.33,15.32 11.06,15.06L6.36,10.55C5.81,10.03 5.79,9.16 6.32,8.61C6.84,8.06 7.71,8.05 8.26,8.57L12,12.17L15.77,8.57C16.31,8.05 17.18,8.07 17.71,8.61C18.23,9.16 18.21,10.03 17.67,10.55Z",
"name": "pocket"
},
{
"path": "M12,3A9,9 0 0,1 21,12C21,13.76 20.5,15.4 19.62,16.79L21,18.17V20A1,1 0 0,1 20,21H18.18L16.79,19.62C15.41,20.5 13.76,21 12,21A9,9 0 0,1 3,12A9,9 0 0,1 12,3M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17C12.65,17 13.26,16.88 13.83,16.65L10.95,13.77C10.17,13 10.17,11.72 10.95,10.94C11.73,10.16 13,10.16 13.78,10.94L16.66,13.82C16.88,13.26 17,12.64 17,12A5,5 0 0,0 12,7Z",
"name": "quicktime"
},
{
"path": "M18.61,5.89C18.6,5.79 18.5,5.73 18.44,5.73C18.37,5.72 16.83,5.61 16.83,5.61C16.83,5.61 15.76,4.55 15.65,4.43C15.53,4.31 15.3,4.35 15.21,4.37C15.2,4.37 15,4.44 14.61,4.55C14.25,3.5 13.62,2.58 12.43,2.58C12.11,2.18 11.72,2 11.38,2C8.8,2 7.57,5.22 7.18,6.86C6.18,7.17 5.47,7.39 5.37,7.42C4.82,7.6 4.8,7.62 4.73,8.14C4.67,8.54 3.21,19.86 3.21,19.86L14.61,22L20.79,20.66C20.79,20.66 18.62,6 18.61,5.89M14,4.76C13.69,4.85 13.37,4.95 13,5.06C13,5 13,4.93 13,4.85C13,4.21 12.93,3.7 12.79,3.29C13.35,3.36 13.73,4 14,4.76M12.08,3.42C12.24,3.82 12.34,4.39 12.34,5.16C12.34,5.2 12.34,5.24 12.34,5.27C11.71,5.46 11.03,5.68 10.35,5.89C10.73,4.4 11.45,3.69 12.08,3.42M11.31,2.69C11.42,2.69 11.53,2.73 11.64,2.8C10.81,3.19 9.93,4.17 9.55,6.12C9,6.3 8.47,6.46 8,6.62C8.42,5.12 9.46,2.69 11.31,2.69M12.5,9.15L11.76,11.42C11.76,11.42 11.09,11.06 10.27,11.06C9.07,11.06 9,11.81 9,12C9,13.04 11.71,13.43 11.71,15.86C11.71,17.77 10.5,19 8.87,19C6.91,19 5.91,17.78 5.91,17.78L6.43,16.05C6.43,16.05 7.46,16.93 8.33,16.93C8.9,16.93 9.13,16.5 9.13,16.16C9.13,14.81 6.92,14.75 6.92,12.53C6.92,10.66 8.26,8.85 10.97,8.85C12,8.85 12.5,9.15 12.5,9.15M15.43,5.29L16.75,6.6L17.71,6.68C18.05,9 19.19,16.73 19.66,19.88L14.66,20.97L15.43,5.29Z",
"name": "shopify"
},
{
"path": "M7.47,17.19C7.37,17.95 7.08,18.21 6.19,18.21C5.27,18.21 4.87,17.79 4.81,16.87L4.61,13.65C4.61,12.91 4.87,12.55 5.86,12.55C7.21,12.55 7.04,13.54 7.64,14.5C8.33,15.62 10,16.35 12.31,16.35C15.17,16.35 17,15.14 17,13.57C17,12.23 15.89,11.39 13.85,11.16L11.5,10.89C7.21,10.42 5.1,9.19 5.1,6.62C5.1,4.07 8.06,2 12.21,2C13.5,2 14.81,2.29 16.29,2.76C16.29,2.26 16.58,2.1 17.3,2.1C18.46,2.1 18.55,2.39 18.62,3.08L18.85,5.88C18.85,6.5 18.39,6.83 17.63,6.83C16.35,6.83 16.55,5.88 15.86,5.07C15.17,4.26 13.79,3.73 12.08,3.73C9.44,3.73 7.7,4.89 7.7,6.5C7.7,7.8 8.92,8.56 11.38,8.82L13.95,9.08C17.7,9.5 19.61,10.92 19.61,13.33C19.61,16.17 16.71,18.08 12.21,18.08C10.56,18.08 9.08,17.77 7.47,17.19M1,16H2V21H23V22H1V16Z",
"name": "slackware"
},
{
"path": "M6,3H18A3,3 0 0,1 21,6V18A3,3 0 0,1 18,21H6A3,3 0 0,1 3,18V6A3,3 0 0,1 6,3M7,6A1,1 0 0,0 6,7V17A1,1 0 0,0 7,18H17A1,1 0 0,0 18,17V7A1,1 0 0,0 17,6H7M9.5,9H14.5A0.5,0.5 0 0,1 15,9.5V14.5A0.5,0.5 0 0,1 14.5,15H9.5A0.5,0.5 0 0,1 9,14.5V9.5A0.5,0.5 0 0,1 9.5,9Z",
"name": "square-inc"
},
{
"path": "M5.5,0H18.5A5.5,5.5 0 0,1 24,5.5V18.5A5.5,5.5 0 0,1 18.5,24H5.5A5.5,5.5 0 0,1 0,18.5V5.5A5.5,5.5 0 0,1 5.5,0M15.39,15.18C15.39,16.76 14.5,17.81 12.85,17.95V12.61C14.55,13.13 15.39,13.66 15.39,15.18M11.65,6V10.88C10.34,10.5 9.03,9.93 9.03,8.43C9.03,6.94 10.18,6.12 11.65,6M15.5,7.6L16.5,6.8C15.62,5.66 14.4,4.92 12.85,4.77V3.8H11.65V3.8L11.65,4.75C9.5,4.89 7.68,6.17 7.68,8.5C7.68,11 9.74,11.78 11.65,12.29V17.96C10.54,17.84 9.29,17.31 8.43,16.03L7.3,16.78C8.2,18.12 9.76,19 11.65,19.14V20.2H12.07L12.85,20.2V19.16C15.35,19 16.7,17.34 16.7,15.14C16.7,12.58 14.81,11.76 12.85,11.19V6.05C14,6.22 14.85,6.76 15.5,7.6Z",
"name": "square-inc-cash"
},
{
"path": "M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V15L6.19,16.31C6.45,17.6 7.6,18.58 8.97,18.58C10.53,18.58 11.8,17.31 11.8,15.75V15.62L15.2,13.19H15.28C17.36,13.19 19.05,11.5 19.05,9.42C19.05,7.34 17.36,5.65 15.28,5.65C13.2,5.65 11.5,7.34 11.5,9.42V9.47L9.13,12.93L8.97,12.92C8.38,12.92 7.83,13.1 7.38,13.41L3,11.6V5A2,2 0 0,1 5,3H19M8.28,17.17C9.08,17.5 10,17.13 10.33,16.33C10.66,15.53 10.28,14.62 9.5,14.29L8.22,13.76C8.71,13.58 9.26,13.57 9.78,13.79C10.31,14 10.72,14.41 10.93,14.94C11.15,15.46 11.15,16.04 10.93,16.56C10.5,17.64 9.23,18.16 8.15,17.71C7.65,17.5 7.27,17.12 7.06,16.67L8.28,17.17M17.8,9.42C17.8,10.81 16.67,11.94 15.28,11.94C13.9,11.94 12.77,10.81 12.77,9.42A2.51,2.51 0 0,1 15.28,6.91C16.67,6.91 17.8,8.04 17.8,9.42M13.4,9.42C13.4,10.46 14.24,11.31 15.29,11.31C16.33,11.31 17.17,10.46 17.17,9.42C17.17,8.38 16.33,7.53 15.29,7.53C14.24,7.53 13.4,8.38 13.4,9.42Z",
"name": "steam-box"
},
{
"path": "M14.92,17.16L16.75,13.53H19.45L14.94,22.5L10.37,13.53H13.07L14.92,17.16M10.63,8.66L8.18,13.55H4.55L10.61,1.5L16.74,13.55H13.11L10.63,8.66Z",
"name": "strava"
},
{
"path": "M12,14C11,14 9,15 9,16C9,18 12,18 12,18V17A1,1 0 0,1 11,16A1,1 0 0,1 12,15V14M12,19C12,19 8,18.5 8,16.5C8,13.5 11,12.75 12,12.75V11.5C11,11.5 7,13 7,16C7,20 12,20 12,20V19M10.07,7.03L11.26,7.56C11.69,5.12 12.84,3.5 12.84,3.5C12.41,4.53 12.13,5.38 11.95,6.05C13.16,3.55 15.61,2 15.61,2C14.43,3.18 13.56,4.46 12.97,5.53C14.55,3.85 16.74,2.75 16.74,2.75C14.05,4.47 12.84,7.2 12.54,7.96L13.09,8.04C13.09,8.56 13.09,9.04 13.34,9.42C14.1,11.31 18,11.47 18,16C18,20.53 13.97,22 11.83,22C9.69,22 5,21.03 5,16C5,10.97 9.95,10.93 10.83,8.92C10.95,8.54 10.07,7.03 10.07,7.03Z",
"name": "tor"
},
{
"path": "M17,11H13V15.5C13,16.44 13.28,17 14.5,17H17V21C17,21 15.54,21.05 14.17,21.05C10.8,21.05 9.5,19 9.5,16.75V11H7V7C10.07,6.74 10.27,4.5 10.5,3H13V7H17",
"name": "tumblr"
},
{
"path": "M16,11H13V14.9C13,15.63 13.14,16 14.1,16H16V19C16,19 14.97,19.1 13.9,19.1C11.25,19.1 10,17.5 10,15.7V11H8V8.2C10.41,8 10.62,6.16 10.8,5H13V8H16M20,2H4C2.89,2 2,2.89 2,4V20A2,2 0 0,0 4,22H20A2,2 0 0,0 22,20V4C22,2.89 21.1,2 20,2Z",
"name": "tumblr-box"
},
{
"path": "M3.75,17L8,12.75V16H18V11.5L20,9.5V16A2,2 0 0,1 18,18H8V21.25L3.75,17M20.25,7L16,11.25V8H6V12.5L4,14.5V8A2,2 0 0,1 6,6H16V2.75L20.25,7Z",
"name": "tumblr-reblog"
},
{
"path": "M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3M17.71,9.33C18.19,8.93 18.75,8.45 19,7.92C18.59,8.13 18.1,8.26 17.56,8.33C18.06,7.97 18.47,7.5 18.68,6.86C18.16,7.14 17.63,7.38 16.97,7.5C15.42,5.63 11.71,7.15 12.37,9.95C9.76,9.79 8.17,8.61 6.85,7.16C6.1,8.38 6.75,10.23 7.64,10.74C7.18,10.71 6.83,10.57 6.5,10.41C6.54,11.95 7.39,12.69 8.58,13.09C8.22,13.16 7.82,13.18 7.44,13.12C7.81,14.19 8.58,14.86 9.9,15C9,15.76 7.34,16.29 6,16.08C7.15,16.81 8.46,17.39 10.28,17.31C14.69,17.11 17.64,13.95 17.71,9.33Z",
"name": "twitter-box"
},
{
"path": "M17.71,9.33C18.19,8.93 18.75,8.45 19,7.92C18.59,8.13 18.1,8.26 17.56,8.33C18.06,7.97 18.47,7.5 18.68,6.86C18.16,7.14 17.63,7.38 16.97,7.5C15.42,5.63 11.71,7.15 12.37,9.95C9.76,9.79 8.17,8.61 6.85,7.16C6.1,8.38 6.75,10.23 7.64,10.74C7.18,10.71 6.83,10.57 6.5,10.41C6.54,11.95 7.39,12.69 8.58,13.09C8.22,13.16 7.82,13.18 7.44,13.12C7.81,14.19 8.58,14.86 9.9,15C9,15.76 7.34,16.29 6,16.08C7.15,16.81 8.46,17.39 10.28,17.31C14.69,17.11 17.64,13.95 17.71,9.33M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2Z",
"name": "twitter-circle"
},
{
"path": "M6.38,13.24V13.24C6.38,11.84 6.38,10.44 6.38,9.04H7.4V15.84H6.39C6.39,15.63 6.39,15.42 6.39,15.21C5.93,15.68 5.29,15.96 4.58,15.96C3.12,15.96 2,14.9 2,13.3V9.04H3V13.24C3,14.33 3.74,15.04 4.7,15.04C5.64,15.04 6.38,14.31 6.38,13.24M9.14,9.04V11.5C9.37,11.29 9.65,11.1 9.95,10.97C10.25,10.85 10.58,10.78 10.91,10.78C12.37,10.78 13.5,11.94 13.5,13.37C13.5,14.8 12.37,15.96 10.91,15.96C10.58,15.96 10.25,15.89 9.95,15.77C9.64,15.64 9.37,15.45 9.13,15.22C9.13,15.43 9.13,15.63 9.13,15.84C8.81,15.84 8.5,15.84 8.16,15.84V9.04H9.14M12.55,13.37V13.37C12.55,12.41 11.77,11.65 10.84,11.65C9.89,11.65 9.13,12.41 9.13,13.37C9.13,14.32 9.88,15.09 10.84,15.09C11.77,15.09 12.55,14.32 12.55,13.37M16.46,10.79C17.9,10.79 18.95,11.89 18.95,13.36V13.69H14.91C15.04,14.5 15.71,15.09 16.55,15.09C17.13,15.09 17.61,14.86 18,14.36L18.7,14.89C18.2,15.55 17.46,15.95 16.55,15.95C15.06,15.95 13.91,14.84 13.91,13.36C13.91,11.97 15,10.79 16.46,10.79M14.92,12.91H17.95C17.79,12.15 17.18,11.65 16.44,11.65C15.71,11.65 15.1,12.15 14.92,12.91M20.5,13V15.84H19.5V10.89C19.82,10.89 20.14,10.89 20.47,10.89V11.5C20.71,11.1 21.11,10.85 21.66,10.85H22V11.76H21.59C20.95,11.76 20.5,12.26 20.5,13",
"name": "uber"
},
{
"path": "M19.5,3C20.14,4.08 20.44,5.19 20.44,6.6C20.44,11.08 16.61,16.91 13.5,21H6.41L3.56,4L9.77,3.39L11.28,15.5C12.69,13.21 14.42,9.61 14.42,7.16C14.42,5.81 14.19,4.9 13.83,4.15L19.5,3Z",
"name": "venmo"
},
{
"path": "M5,3A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3H5M5.5,8.5H7C7.36,8.5 7.5,8.66 7.64,9.07C8.36,11.17 9.57,13 10.07,13C10.26,13 10.35,12.92 10.35,12.45V10.28C10.29,9.28 9.76,9.19 9.76,8.84C9.76,8.67 9.9,8.5 10.14,8.5H12.45C12.77,8.5 12.87,8.67 12.87,9.04V11.96C12.87,12.27 13,12.38 13.1,12.38C13.29,12.38 13.45,12.27 13.79,11.93C14.85,10.74 15.6,8.92 15.6,8.92C15.7,8.7 15.87,8.5 16.24,8.5H17.71C18.16,8.5 18.26,8.73 18.16,9.04C17.97,9.9 16.18,12.43 16.18,12.43C16,12.68 15.96,12.8 16.18,13.09C16.33,13.3 16.85,13.74 17.19,14.15C17.83,14.86 18.3,15.46 18.44,15.87C18.56,16.29 18.35,16.5 17.93,16.5H16.45C15.89,16.5 15.73,16.05 14.73,15.05C13.85,14.21 13.5,14.1 13.26,14.1C12.96,14.1 12.87,14.18 12.87,14.61V15.93C12.87,16.29 12.76,16.5 11.82,16.5C10.26,16.5 8.54,15.55 7.33,13.8C5.5,11.24 5,9.31 5,8.92C5,8.7 5.08,8.5 5.5,8.5Z",
"name": "vk-box"
},
{
"path": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M5.5,8.5H7C7.36,8.5 7.5,8.66 7.64,9.07C8.36,11.17 9.57,13 10.07,13C10.26,13 10.35,12.92 10.35,12.45V10.28C10.29,9.28 9.76,9.19 9.76,8.84C9.76,8.67 9.9,8.5 10.14,8.5H12.45C12.77,8.5 12.87,8.67 12.87,9.04V11.96C12.87,12.27 13,12.38 13.1,12.38C13.29,12.38 13.45,12.27 13.79,11.93C14.85,10.74 15.6,8.92 15.6,8.92C15.7,8.7 15.87,8.5 16.24,8.5H17.71C18.16,8.5 18.26,8.73 18.16,9.04C17.97,9.9 16.18,12.43 16.18,12.43C16,12.68 15.96,12.8 16.18,13.09C16.33,13.3 16.85,13.74 17.19,14.15C17.83,14.86 18.3,15.46 18.44,15.87C18.56,16.29 18.35,16.5 17.93,16.5H16.45C15.89,16.5 15.73,16.05 14.73,15.05C13.85,14.21 13.5,14.1 13.26,14.1C12.96,14.1 12.87,14.18 12.87,14.61V15.93C12.87,16.29 12.76,16.5 11.82,16.5C10.26,16.5 8.54,15.55 7.33,13.8C5.5,11.24 5,9.31 5,8.92C5,8.7 5.08,8.5 5.5,8.5Z",
"name": "vk-circle"
},
{
"path": "M17,17.5L12,15L7,17.5V5H5V19H19V5H17V17.5M12,12.42L14.25,13.77L13.65,11.22L15.64,9.5L13,9.27L12,6.86L11,9.27L8.36,9.5L10.35,11.22L9.75,13.77L12,12.42M5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3Z",
"name": "wunderlist"
},
{
"path": "M0 16.7L3.2 12.9L0 9.1L1.5 7.8L4.5 11.3L7.5 7.8L9 9.1L5.8 12.9L9 16.7L7.5 18L4.5 14.4L1.5 18L0 16.7M24 16.9C24 17.4 23.6 17.9 23 17.9H20C18.9 17.9 18 17 18 15.9V13.9C18 12.8 18.9 11.9 20 11.9H22V9.9H18V8H23C23.5 8 24 8.4 24 9M22 14H20V16H22V14M16 16.9C16 17.4 15.6 17.9 15 17.9H12C10.9 17.9 10 17 10 15.9V9.9C10 8.8 10.9 7.9 12 7.9H14V5H16V16.9M14 15.9V9.9H12V15.9H14Z",
"name": "xda"
},
{
"path": "M4.8,3C3.8,3 3,3.8 3,4.8V19.2C3,20.2 3.8,21 4.8,21H19.2C20.2,21 21,20.2 21,19.2V4.8C21,3.8 20.2,3 19.2,3M16.07,5H18.11C18.23,5 18.33,5.04 18.37,5.13C18.43,5.22 18.43,5.33 18.37,5.44L13.9,13.36L16.75,18.56C16.81,18.67 16.81,18.78 16.75,18.87C16.7,18.95 16.61,19 16.5,19H14.47C14.16,19 14,18.79 13.91,18.61L11.04,13.35C11.18,13.1 15.53,5.39 15.53,5.39C15.64,5.19 15.77,5 16.07,5M7.09,7.76H9.1C9.41,7.76 9.57,7.96 9.67,8.15L11.06,10.57C10.97,10.71 8.88,14.42 8.88,14.42C8.77,14.61 8.63,14.81 8.32,14.81H6.3C6.18,14.81 6.09,14.76 6.04,14.67C6,14.59 6,14.47 6.04,14.36L8.18,10.57L6.82,8.2C6.77,8.09 6.75,8 6.81,7.89C6.86,7.81 6.96,7.76 7.09,7.76Z",
"name": "xing-box"
},
{
"path": "M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M15.85,6H17.74C17.86,6 17.94,6.04 18,6.12C18.04,6.2 18.04,6.3 18,6.41L13.84,13.76L16.5,18.59C16.53,18.69 16.53,18.8 16.5,18.88C16.43,18.96 16.35,19 16.24,19H14.36C14.07,19 13.93,18.81 13.84,18.64L11.17,13.76C11.31,13.5 15.35,6.36 15.35,6.36C15.45,6.18 15.57,6 15.85,6M7.5,8.57H9.39C9.67,8.57 9.81,8.75 9.9,8.92L11.19,11.17C11.12,11.3 9.17,14.75 9.17,14.75C9.07,14.92 8.94,15.11 8.66,15.11H6.78C6.67,15.11 6.59,15.06 6.54,15C6.5,14.9 6.5,14.8 6.54,14.69L8.53,11.17L7.27,9C7.21,8.87 7.2,8.77 7.25,8.69C7.3,8.61 7.39,8.57 7.5,8.57Z",
"name": "xing-circle"
},
{
"path": "M10.59,2C11.23,2 11.5,2.27 11.58,2.97L11.79,6.14L12.03,10.29C12.05,10.64 12,11 11.86,11.32C11.64,11.77 11.14,11.89 10.73,11.58C10.5,11.39 10.31,11.14 10.15,10.87L6.42,4.55C6.06,3.94 6.17,3.54 6.77,3.16C7.5,2.68 9.73,2 10.59,2M14.83,14.85L15.09,14.91L18.95,16.31C19.61,16.55 19.79,16.92 19.5,17.57C19.06,18.7 18.34,19.66 17.42,20.45C16.96,20.85 16.5,20.78 16.21,20.28L13.94,16.32C13.55,15.61 14.03,14.8 14.83,14.85M4.5,14C4.5,13.26 4.5,12.55 4.75,11.87C4.97,11.2 5.33,11 6,11.27L9.63,12.81C10.09,13 10.35,13.32 10.33,13.84C10.3,14.36 9.97,14.58 9.53,14.73L5.85,15.94C5.15,16.17 4.79,15.96 4.64,15.25C4.55,14.83 4.47,14.4 4.5,14M11.97,21C11.95,21.81 11.6,22.12 10.81,22C9.77,21.8 8.81,21.4 7.96,20.76C7.54,20.44 7.45,19.95 7.76,19.53L10.47,15.97C10.7,15.67 11.03,15.6 11.39,15.74C11.77,15.88 11.97,16.18 11.97,16.59V21M14.45,13.32C13.73,13.33 13.23,12.5 13.64,11.91C14.47,10.67 15.35,9.46 16.23,8.26C16.5,7.85 16.94,7.82 17.31,8.16C18.24,9 18.91,10 19.29,11.22C19.43,11.67 19.25,12.08 18.83,12.2L15.09,13.17L14.45,13.32Z",
"name": "yelp"
}
]

View File

@@ -14,6 +14,32 @@ module.exports = function (userOptions = {}) {
return { return {
name: "ignore", 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) { load(id) {
return files.some((toIgnorePath) => id.startsWith(toIgnorePath)) return files.some((toIgnorePath) => id.startsWith(toIgnorePath))

View File

@@ -70,9 +70,7 @@ const createWebpackConfig = ({
if ( if (
!context.includes("/node_modules/") || !context.includes("/node_modules/") ||
// calling define.amd will call require("!!webpack amd options") // calling define.amd will call require("!!webpack amd options")
resource.startsWith("!!webpack") || resource.startsWith("!!webpack")
// loaded by webpack dev server but doesn't exist.
resource === "webpack/hot"
) { ) {
return false; return false;
} }
@@ -82,11 +80,7 @@ const createWebpackConfig = ({
? path.resolve(context, resource) ? path.resolve(context, resource)
: require.resolve(resource); : require.resolve(resource);
} catch (err) { } catch (err) {
console.error( console.error("Error in ignore plugin", resource, context);
"Error in Home Assistant ignore plugin",
resource,
context
);
throw err; throw err;
} }

View File

@@ -37,13 +37,15 @@
<body> <body>
<%= renderTemplate('_js_base') %> <%= renderTemplate('_js_base') %>
<script> <script type="module" crossorigin="use-credentials">
import("<%= latestLauncherJS %>"); import "<%= latestLauncherJS %>";
window.latestJS = true;
</script> </script>
<script> <script nomodule>
if (!window.latestJS) { (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) { %> <% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() { _ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>"); System.import("<%= es5LauncherJS %>");
@@ -52,6 +54,7 @@
_ls("<%= es5LauncherJS %>"); _ls("<%= es5LauncherJS %>");
<% } %> <% } %>
} }
})();
</script> </script>
<hc-layout subtitle="FAQ"> <hc-layout subtitle="FAQ">
@@ -251,7 +254,7 @@ http:
<script> <script>
var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]]; var _gaq = [["_setAccount", "UA-57927901-9"], ["_trackPageview"]];
(function (d, t) { (function(d, t) {
var g = d.createElement(t), var g = d.createElement(t),
s = d.getElementsByTagName(t)[0]; s = d.getElementsByTagName(t)[0];
g.src = g.src =

View File

@@ -28,13 +28,15 @@
<hc-connect></hc-connect> <hc-connect></hc-connect>
<script> <script type="module" crossorigin="use-credentials">
import("<%= latestLauncherJS %>"); import "<%= latestLauncherJS %>";
window.latestJS = true;
</script> </script>
<script> <script nomodule>
if (!window.latestJS) { (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) { %> <% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() { _ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>"); System.import("<%= es5LauncherJS %>");
@@ -43,6 +45,7 @@
_ls("<%= es5LauncherJS %>"); _ls("<%= es5LauncherJS %>");
<% } %> <% } %>
} }
})();
</script> </script>
<script> <script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){

View File

@@ -8,7 +8,6 @@ import {
html, html,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager"; import { CastManager } from "../../../../src/cast/cast_manager";
@@ -29,7 +28,7 @@ import {
getLovelaceCollection, getLovelaceCollection,
LovelaceConfig, LovelaceConfig,
} from "../../../../src/data/lovelace"; } from "../../../../src/data/lovelace";
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config"; import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import "./hc-layout"; import "./hc-layout";
import "@material/mwc-button/mwc-button"; import "@material/mwc-button/mwc-button";
@@ -42,13 +41,13 @@ class HcCast extends LitElement {
@property() public castManager!: CastManager; @property() public castManager!: CastManager;
@internalProperty() private askWrite = false; @property() private askWrite = false;
@internalProperty() private lovelaceConfig?: LovelaceConfig | null; @property() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult { protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) { if (this.lovelaceConfig === undefined) {
return html` <hass-loading-screen no-toolbar></hass-loading-screen>> `; return html` <loading-screen></loading-screen>> `;
} }
const error = const error =

View File

@@ -17,8 +17,8 @@ import {
customElement, customElement,
html, html,
LitElement, LitElement,
property,
TemplateResult, TemplateResult,
internalProperty,
} from "lit-element"; } from "lit-element";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager"; import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages"; import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
@@ -27,7 +27,7 @@ import {
saveTokens, saveTokens,
} from "../../../../src/common/auth/token_storage"; } from "../../../../src/common/auth/token_storage";
import "../../../../src/components/ha-icon"; import "../../../../src/components/ha-icon";
import "../../../../src/layouts/hass-loading-screen"; import "../../../../src/layouts/loading-screen";
import { registerServiceWorker } from "../../../../src/util/register-service-worker"; import { registerServiceWorker } from "../../../../src/util/register-service-worker";
import "./hc-layout"; import "./hc-layout";
@@ -60,19 +60,19 @@ const INTRO = html`
@customElement("hc-connect") @customElement("hc-connect")
export class HcConnect extends LitElement { export class HcConnect extends LitElement {
@internalProperty() private loading = false; @property() private loading = false;
// If we had stored credentials but we cannot connect, // If we had stored credentials but we cannot connect,
// show a screen asking retry or logout. // show a screen asking retry or logout.
@internalProperty() private cannotConnect = false; @property() private cannotConnect = false;
@internalProperty() private error?: string | TemplateResult; @property() private error?: string | TemplateResult;
@internalProperty() private auth?: Auth; @property() private auth?: Auth;
@internalProperty() private connection?: Connection; @property() private connection?: Connection;
@internalProperty() private castManager?: CastManager | null; @property() private castManager?: CastManager | null;
private openDemo = false; private openDemo = false;
@@ -98,7 +98,7 @@ export class HcConnect extends LitElement {
} }
if (this.castManager === undefined || this.loading) { if (this.castManager === undefined || this.loading) {
return html` <hass-loading-screen no-toolbar></hass-loading-screen> `; return html` <loading-screen></loading-screen> `;
} }
if (this.castManager === null) { if (this.castManager === null) {

View File

@@ -1,10 +1,4 @@
import { import { customElement, html, property, TemplateResult } from "lit-element";
customElement,
html,
property,
internalProperty,
TemplateResult,
} from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history"; import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace"; import { LovelaceConfig } from "../../../../src/data/lovelace";
import { import {
@@ -19,9 +13,9 @@ import "./hc-lovelace";
@customElement("hc-demo") @customElement("hc-demo")
class HcDemo extends HassElement { class HcDemo extends HassElement {
@property({ attribute: false }) public lovelacePath!: string; @property() public lovelacePath!: string;
@internalProperty() private _lovelaceConfig?: LovelaceConfig; @property() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._lovelaceConfig) { if (!this._lovelaceConfig) {

View File

@@ -11,7 +11,7 @@ import { HomeAssistant } from "../../../../src/types";
@customElement("hc-launch-screen") @customElement("hc-launch-screen")
class HcLaunchScreen extends LitElement { class HcLaunchScreen extends LitElement {
@property({ attribute: false }) public hass?: HomeAssistant; @property() public hass?: HomeAssistant;
@property() public error?: string; @property() public error?: string;

View File

@@ -16,14 +16,12 @@ import "./hc-launch-screen";
@customElement("hc-lovelace") @customElement("hc-lovelace")
class HcLovelace extends LitElement { class HcLovelace extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property({ attribute: false }) public lovelaceConfig!: LovelaceConfig; @property() public lovelaceConfig!: LovelaceConfig;
@property() public viewPath?: string | number; @property() public viewPath?: string | number;
public urlPath?: string | null;
protected render(): TemplateResult { protected render(): TemplateResult {
const index = this._viewIndex; const index = this._viewIndex;
if (index === undefined) { if (index === undefined) {
@@ -37,7 +35,6 @@ class HcLovelace extends LitElement {
const lovelace: Lovelace = { const lovelace: Lovelace = {
config: this.lovelaceConfig, config: this.lovelaceConfig,
editMode: false, editMode: false,
urlPath: this.urlPath!,
enableFullEditMode: () => undefined, enableFullEditMode: () => undefined,
mode: "storage", mode: "storage",
language: "en", language: "en",

View File

@@ -3,12 +3,7 @@ import {
getAuth, getAuth,
UnsubscribeFunc, UnsubscribeFunc,
} from "home-assistant-js-websocket"; } from "home-assistant-js-websocket";
import { import { customElement, html, property, TemplateResult } from "lit-element";
customElement,
html,
internalProperty,
TemplateResult,
} from "lit-element";
import { CAST_NS } from "../../../../src/cast/const"; import { CAST_NS } from "../../../../src/cast/const";
import { import {
ConnectMessage, ConnectMessage,
@@ -36,13 +31,13 @@ let resourcesLoaded = false;
@customElement("hc-main") @customElement("hc-main")
export class HcMain extends HassElement { export class HcMain extends HassElement {
@internalProperty() private _showDemo = false; @property() private _showDemo = false;
@internalProperty() private _lovelaceConfig?: LovelaceConfig; @property() private _lovelaceConfig?: LovelaceConfig;
@internalProperty() private _lovelacePath: string | number | null = null; @property() private _lovelacePath: string | number | null = null;
@internalProperty() private _error?: string; @property() private _error?: string;
private _unsubLovelace?: UnsubscribeFunc; private _unsubLovelace?: UnsubscribeFunc;
@@ -87,7 +82,6 @@ export class HcMain extends HassElement {
.hass=${this.hass} .hass=${this.hass}
.lovelaceConfig=${this._lovelaceConfig} .lovelaceConfig=${this._lovelaceConfig}
.viewPath=${this._lovelacePath} .viewPath=${this._lovelacePath}
.urlPath=${this._urlPath}
@config-refresh=${this._generateLovelaceConfig} @config-refresh=${this._generateLovelaceConfig}
></hc-lovelace> ></hc-lovelace>
`; `;
@@ -198,8 +192,6 @@ export class HcMain extends HassElement {
this._handleNewLovelaceConfig(lovelaceConfig) this._handleNewLovelaceConfig(lovelaceConfig)
); );
} catch (err) { } catch (err) {
// eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg);
// Generate a Lovelace config. // Generate a Lovelace config.
this._unsubLovelace = () => undefined; this._unsubLovelace = () => undefined;
await this._generateLovelaceConfig(); await this._generateLovelaceConfig();

View File

@@ -1,8 +1,11 @@
const { createCastConfig } = require("../build-scripts/webpack.js"); 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({ module.exports = createCastConfig({
isProdBuild: isProdBuild(), isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(), latestBuild,
latestBuild: true,
}); });

View File

@@ -26,9 +26,9 @@ export const demoThemeJimpower = () => ({
"switch-checked-color": "var(--accent-color)", "switch-checked-color": "var(--accent-color)",
"paper-dialog-background-color": "#434954", "paper-dialog-background-color": "#434954",
"secondary-text-color": "#5294E2", "secondary-text-color": "#5294E2",
"error-color": "#E45E65", "google-red-500": "#E45E65",
"divider-color": "rgba(0, 0, 0, .12)", "divider-color": "rgba(0, 0, 0, .12)",
"success-color": "#39E949", "google-green-500": "#39E949",
"switch-unchecked-button-color": "var(--disabled-text-color)", "switch-unchecked-button-color": "var(--disabled-text-color)",
"label-badge-border-color": "green", "label-badge-border-color": "green",
"paper-listbox-color": "var(--primary-color)", "paper-listbox-color": "var(--primary-color)",

View File

@@ -27,9 +27,9 @@ export const demoThemeKernehed = () => ({
"switch-checked-color": "var(--accent-color)", "switch-checked-color": "var(--accent-color)",
"paper-dialog-background-color": "#292929", "paper-dialog-background-color": "#292929",
"secondary-text-color": "#b58e31", "secondary-text-color": "#b58e31",
"error-color": "#b58e31", "google-red-500": "#b58e31",
"divider-color": "rgba(0, 0, 0, .12)", "divider-color": "rgba(0, 0, 0, .12)",
"success-color": "#2980b9", "google-green-500": "#2980b9",
"switch-unchecked-button-color": "var(--disabled-text-color)", "switch-unchecked-button-color": "var(--disabled-text-color)",
"label-badge-border-color": "green", "label-badge-border-color": "green",
"paper-listbox-color": "#777777", "paper-listbox-color": "#777777",

View File

@@ -4,7 +4,7 @@ import {
customElement, customElement,
html, html,
LitElement, LitElement,
internalProperty, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { CastManager } from "../../../src/cast/cast_manager"; import { CastManager } from "../../../src/cast/cast_manager";
@@ -20,7 +20,7 @@ import { HomeAssistant } from "../../../src/types";
class CastDemoRow extends LitElement implements LovelaceRow { class CastDemoRow extends LitElement implements LovelaceRow {
public hass!: HomeAssistant; public hass!: HomeAssistant;
@internalProperty() private _castManager?: CastManager | null; @property() private _castManager?: CastManager | null;
public setConfig(_config: CastConfig): void { public setConfig(_config: CastConfig): void {
// No config possible. // No config possible.
@@ -52,6 +52,7 @@ class CastDemoRow extends LitElement implements LovelaceRow {
}); });
mgr.castContext.addEventListener( mgr.castContext.addEventListener(
// eslint-disable-next-line no-undef // eslint-disable-next-line no-undef
// @ts-ignore
cast.framework.CastContextEventType.SESSION_STATE_CHANGED, cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
(ev) => { (ev) => {
// On Android, opening a new session always results in SESSION_RESUMED. // On Android, opening a new session always results in SESSION_RESUMED.

View File

@@ -1,16 +1,15 @@
import "@material/mwc-button"; import "@material/mwc-button";
import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
html, html,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-circular-progress";
import { LovelaceCardConfig } from "../../../src/data/lovelace"; import { LovelaceCardConfig } from "../../../src/data/lovelace";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types"; import { Lovelace, LovelaceCard } from "../../../src/panels/lovelace/types";
@@ -22,11 +21,11 @@ import {
} from "../configs/demo-configs"; } from "../configs/demo-configs";
export class HADemoCard extends LitElement implements LovelaceCard { export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public lovelace?: Lovelace; @property() public lovelace?: Lovelace;
@property({ attribute: false }) public hass!: MockHomeAssistant; @property() public hass!: MockHomeAssistant;
@internalProperty() private _switching?: boolean; @property() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card; private _hidden = localStorage.hide_demo_card;
@@ -50,7 +49,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
<div class="picker"> <div class="picker">
<div class="label"> <div class="label">
${this._switching ${this._switching
? html`<ha-circular-progress active></ha-circular-progress>` ? html` <paper-spinner-lite active></paper-spinner-lite> `
: until( : until(
selectedDemoConfig.then( selectedDemoConfig.then(
(conf) => html` (conf) => html`

View File

@@ -1,4 +1,3 @@
import "../../src/resources/compatibility";
import { isNavigationClick } from "../../src/common/dom/is-navigation-click"; import { isNavigationClick } from "../../src/common/dom/is-navigation-click";
import { navigate } from "../../src/common/navigate"; import { navigate } from "../../src/common/navigate";
import { import {

View File

@@ -86,26 +86,30 @@
<%= renderTemplate('_js_base') %> <%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %> <%= renderTemplate('_preload_roboto') %>
<script> <script type="module" src="<%= latestDemoJS %>"></script>
import("<%= latestDemoJS %>");
window.latestJS = true;
</script>
<script> <script nomodule>
if (!window.latestJS) { (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) { %> <% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() { _ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5Compatibility %>").then(function() {
System.import("<%= es5DemoJS %>"); System.import("<%= es5DemoJS %>");
});
}; };
<% } else { %> <% } else { %>
_ls("<%= es5Compatibility %>");
_ls("<%= es5DemoJS %>"); _ls("<%= es5DemoJS %>");
<% } %> <% } %>
} }
})();
</script> </script>
<script> <script>
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]]; var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
(function (d, t) { (function(d, t) {
var g = d.createElement(t), var g = d.createElement(t),
s = d.getElementsByTagName(t)[0]; s = d.getElementsByTagName(t)[0];
g.src = g.src =

View File

@@ -3,7 +3,6 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
import "../../../src/components/ha-formfield";
import "./demo-card"; import "./demo-card";
class DemoCards extends PolymerElement { class DemoCards extends PolymerElement {
@@ -27,10 +26,9 @@ class DemoCards extends PolymerElement {
</style> </style>
<app-toolbar> <app-toolbar>
<div class="filters"> <div class="filters">
<ha-formfield label="Show config">
<ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled"> <ha-switch checked="[[_showConfig]]" on-change="_showConfigToggled">
Show config
</ha-switch> </ha-switch>
</ha-formfield>
</div> </div>
</app-toolbar> </app-toolbar>
<div class="cards"> <div class="cards">

View File

@@ -2,8 +2,8 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */ /* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/dialogs/more-info/controls/more-info-content";
import "../../../src/state-summary/state-card-content"; import "../../../src/state-summary/state-card-content";
import "./more-info-content";
class DemoMoreInfo extends PolymerElement { class DemoMoreInfo extends PolymerElement {
static get template() { static get template() {

View File

@@ -1,73 +0,0 @@
import { HassEntity } from "home-assistant-js-websocket";
import { property, PropertyValues, UpdatingElement } from "lit-element";
import dynamicContentUpdater from "../../../src/common/dom/dynamic_content_updater";
import { stateMoreInfoType } from "../../../src/common/entity/state_more_info_type";
import "../../../src/dialogs/more-info/controls/more-info-alarm_control_panel";
import "../../../src/dialogs/more-info/controls/more-info-automation";
import "../../../src/dialogs/more-info/controls/more-info-camera";
import "../../../src/dialogs/more-info/controls/more-info-climate";
import "../../../src/dialogs/more-info/controls/more-info-configurator";
import "../../../src/dialogs/more-info/controls/more-info-counter";
import "../../../src/dialogs/more-info/controls/more-info-cover";
import "../../../src/dialogs/more-info/controls/more-info-default";
import "../../../src/dialogs/more-info/controls/more-info-fan";
import "../../../src/dialogs/more-info/controls/more-info-group";
import "../../../src/dialogs/more-info/controls/more-info-humidifier";
import "../../../src/dialogs/more-info/controls/more-info-input_datetime";
import "../../../src/dialogs/more-info/controls/more-info-light";
import "../../../src/dialogs/more-info/controls/more-info-lock";
import "../../../src/dialogs/more-info/controls/more-info-media_player";
import "../../../src/dialogs/more-info/controls/more-info-person";
import "../../../src/dialogs/more-info/controls/more-info-script";
import "../../../src/dialogs/more-info/controls/more-info-sun";
import "../../../src/dialogs/more-info/controls/more-info-timer";
import "../../../src/dialogs/more-info/controls/more-info-vacuum";
import "../../../src/dialogs/more-info/controls/more-info-water_heater";
import "../../../src/dialogs/more-info/controls/more-info-weather";
import { HomeAssistant } from "../../../src/types";
class MoreInfoContent extends UpdatingElement {
@property({ attribute: false }) public hass?: HomeAssistant;
@property() public stateObj?: HassEntity;
private _detachedChild?: ChildNode;
protected firstUpdated(): void {
this.style.position = "relative";
this.style.display = "block";
}
// This is not a lit element, but an updating element, so we implement update
protected update(changedProps: PropertyValues): void {
super.update(changedProps);
const stateObj = this.stateObj;
const hass = this.hass;
if (!stateObj || !hass) {
if (this.lastChild) {
this._detachedChild = this.lastChild;
// Detach child to prevent it from doing work.
this.removeChild(this.lastChild);
}
return;
}
if (this._detachedChild) {
this.appendChild(this._detachedChild);
this._detachedChild = undefined;
}
const moreInfoType =
stateObj.attributes && "custom_ui_more_info" in stateObj.attributes
? stateObj.attributes.custom_ui_more_info
: "more-info-" + stateMoreInfoType(stateObj);
dynamicContentUpdater(this, moreInfoType.toUpperCase(), {
hass,
stateObj,
});
}
}
customElements.define("more-info-content", MoreInfoContent);

View File

@@ -420,6 +420,15 @@ export default {
last_changed: "2018-07-19T10:44:46.105940+00:00", last_changed: "2018-07-19T10:44:46.105940+00:00",
last_updated: "2018-07-19T10:44:46.105940+00:00", last_updated: "2018-07-19T10:44:46.105940+00:00",
}, },
"weblink.router": {
entity_id: "weblink.router",
state: "http://192.168.1.1",
attributes: {
friendly_name: "Router",
},
last_changed: "2018-07-19T10:44:46.107286+00:00",
last_updated: "2018-07-19T10:44:46.107286+00:00",
},
"group.all_plants": { "group.all_plants": {
entity_id: "group.all_plants", entity_id: "group.all_plants",
state: "ok", state: "ok",
@@ -1081,6 +1090,18 @@ export default {
last_changed: "2018-07-19T10:44:46.510448+00:00", last_changed: "2018-07-19T10:44:46.510448+00:00",
last_updated: "2018-07-19T10:44:46.510448+00:00", last_updated: "2018-07-19T10:44:46.510448+00:00",
}, },
"history_graph.recent_switches": {
entity_id: "history_graph.recent_switches",
state: "unknown",
attributes: {
hours_to_show: 1,
refresh: 60,
entity_id: ["switch.ac", "switch.decorative_lights"],
friendly_name: "Recent Switches",
},
last_changed: "2018-07-19T10:44:46.512351+00:00",
last_updated: "2018-07-19T10:44:46.512351+00:00",
},
"scene.switch_on_and_off": { "scene.switch_on_and_off": {
entity_id: "scene.switch_on_and_off", entity_id: "scene.switch_on_and_off",
state: "scening", state: "scening",

View File

@@ -3,10 +3,10 @@ import { html } from "@polymer/polymer/lib/utils/html-tag";
import { PolymerElement } from "@polymer/polymer/polymer-element"; import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { SUPPORT_BRIGHTNESS } from "../../../src/data/light"; import { SUPPORT_BRIGHTNESS } from "../../../src/data/light";
import "../../../src/dialogs/more-info/controls/more-info-content";
import { getEntity } from "../../../src/fake_data/entity"; import { getEntity } from "../../../src/fake_data/entity";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import "../components/demo-more-infos"; import "../components/demo-more-infos";
import "../components/more-info-content";
const ENTITIES = [ const ENTITIES = [
getEntity("light", "bed_light", "on", { getEntity("light", "bed_light", "on", {

View File

@@ -11,7 +11,9 @@ import { PolymerElement } from "@polymer/polymer/polymer-element";
import "../../src/components/ha-card"; import "../../src/components/ha-card";
import "../../src/managers/notification-manager"; import "../../src/managers/notification-manager";
import "../../src/styles/polymer-ha-style"; 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); const fixPath = (path) => path.substr(2, path.length - 5);
@@ -161,7 +163,7 @@ class HaGallery extends PolymerElement {
}, },
_demos: { _demos: {
type: Array, type: Array,
value: Object.keys(DEMOS), value: DEMOS.keys().map(fixPath),
}, },
_lovelaceDemos: { _lovelaceDemos: {
type: Array, type: Array,
@@ -208,7 +210,7 @@ class HaGallery extends PolymerElement {
while (root.lastChild) root.removeChild(root.lastChild); while (root.lastChild) root.removeChild(root.lastChild);
if (demo) { if (demo) {
DEMOS[demo](); DEMOS(`./${demo}.ts`);
const el = document.createElement(demo); const el = document.createElement(demo);
root.appendChild(el); root.appendChild(el);
} }

View File

@@ -1,8 +1,5 @@
const { createGalleryConfig } = require("../build-scripts/webpack.js"); const { createGalleryConfig } = require("../build-scripts/webpack.js");
const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js");
module.exports = createGalleryConfig({ module.exports = createGalleryConfig({
isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(),
latestBuild: true, latestBuild: true,
}); });

View File

@@ -21,7 +21,7 @@ import { filterAndSort } from "../components/hassio-filter-addons";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
class HassioAddonRepositoryEl extends LitElement { class HassioAddonRepositoryEl extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public repo!: HassioAddonRepository; @property() public repo!: HassioAddonRepository;

View File

@@ -1,11 +1,9 @@
import "@material/mwc-icon-button/mwc-icon-button"; import "@material/mwc-icon-button/mwc-icon-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { import {
css, css,
CSSResult, CSSResult,
internalProperty,
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
@@ -20,12 +18,11 @@ import {
HassioAddonRepository, HassioAddonRepository,
reloadHassioAddons, reloadHassioAddons,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/layouts/loading-screen";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories"; import { showRepositoriesDialog } from "../dialogs/repositories/show-dialog-repositories";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-panel";
import "./hassio-addon-repository"; import "./hassio-addon-repository";
const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => { const sortRepos = (a: HassioAddonRepository, b: HassioAddonRepository) => {
@@ -55,7 +52,7 @@ class HassioAddonStore extends LitElement {
@property({ attribute: false }) private _repos?: HassioAddonRepository[]; @property({ attribute: false }) private _repos?: HassioAddonRepository[];
@internalProperty() private _filter?: string; @property() private _filter?: string;
public async refreshData() { public async refreshData() {
this._repos = undefined; this._repos = undefined;
@@ -99,23 +96,19 @@ class HassioAddonStore extends LitElement {
.tabs=${supervisorTabs} .tabs=${supervisorTabs}
> >
<span slot="header">Add-on store</span> <span slot="header">Add-on store</span>
<ha-button-menu <ha-button-menu corner="BOTTOM_START" slot="toolbar-icon">
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<mwc-icon-button slot="trigger" alt="menu"> <mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item> <mwc-list-item @tap=${this._manageRepositories}>
Repositories Repositories
</mwc-list-item> </mwc-list-item>
<mwc-list-item> <mwc-list-item @tap=${this.refreshData}>
Reload Reload
</mwc-list-item> </mwc-list-item>
</ha-button-menu> </ha-button-menu>
${repos.length === 0 ${repos.length === 0
? html`<hass-loading-screen no-toolbar></hass-loading-screen>` ? html`<loading-screen></loading-screen>`
: html` : html`
<div class="search"> <div class="search">
<search-input <search-input
@@ -149,17 +142,6 @@ class HassioAddonStore extends LitElement {
this._loadData(); this._loadData();
} }
private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._manageRepositories();
break;
case 1:
this.refreshData();
break;
}
}
private apiCalled(ev) { private apiCalled(ev) {
if (ev.detail.success) { if (ev.detail.success) {
this._loadData(); this._loadData();
@@ -180,7 +162,7 @@ class HassioAddonStore extends LitElement {
this._repos.sort(sortRepos); this._repos.sort(sortRepos);
this._addons = addonsInfo.addons; this._addons = addonsInfo.addons;
} catch (err) { } catch (err) {
alert(extractApiErrorMessage(err)); alert("Failed to fetch add-on info");
} }
} }

View File

@@ -9,7 +9,6 @@ import {
html, html,
LitElement, LitElement,
property, property,
internalProperty,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
@@ -28,7 +27,6 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/buttons/ha-progress-button";
@customElement("hassio-addon-audio") @customElement("hassio-addon-audio")
class HassioAddonAudio extends LitElement { class HassioAddonAudio extends LitElement {
@@ -36,15 +34,15 @@ class HassioAddonAudio extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @property() private _error?: string;
@internalProperty() private _inputDevices?: HassioHardwareAudioDevice[]; @property() private _inputDevices?: HassioHardwareAudioDevice[];
@internalProperty() private _outputDevices?: HassioHardwareAudioDevice[]; @property() private _outputDevices?: HassioHardwareAudioDevice[];
@internalProperty() private _selectedInput!: null | string; @property() private _selectedInput!: null | string;
@internalProperty() private _selectedOutput!: null | string; @property() private _selectedOutput!: null | string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
@@ -92,9 +90,7 @@ class HassioAddonAudio extends LitElement {
</paper-dropdown-menu> </paper-dropdown-menu>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button @click=${this._saveSettings}> <mwc-button @click=${this._saveSettings}>Save</mwc-button>
Save
</ha-progress-button>
</div> </div>
</ha-card> </ha-card>
`; `;
@@ -111,7 +107,7 @@ class HassioAddonAudio extends LitElement {
display: block; display: block;
} }
.errors { .errors {
color: var(--error-color); color: var(--google-red-500);
margin-bottom: 16px; margin-bottom: 16px;
} }
paper-item { paper-item {
@@ -175,10 +171,7 @@ class HassioAddonAudio extends LitElement {
} }
} }
private async _saveSettings(ev: CustomEvent): Promise<void> { private async _saveSettings(): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
this._error = undefined; this._error = undefined;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
audio_input: audio_input:
@@ -188,14 +181,12 @@ class HassioAddonAudio extends LitElement {
}; };
try { try {
await setHassioAddonOption(this.hass, this.addon.slug, data); await setHassioAddonOption(this.hass, this.addon.slug, data);
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon);
}
} catch { } catch {
this._error = "Failed to set addon audio device"; this._error = "Failed to set addon audio device";
} }
if (!this._error && this.addon?.state === "started") {
button.progress = false; await suggestAddonRestart(this, this.hass, this.addon);
}
} }
} }

View File

@@ -1,3 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
@@ -11,7 +12,6 @@ import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
import "../../../../src/components/ha-circular-progress";
import "./hassio-addon-audio"; import "./hassio-addon-audio";
import "./hassio-addon-config"; import "./hassio-addon-config";
import "./hassio-addon-network"; import "./hassio-addon-network";
@@ -24,7 +24,7 @@ class HassioAddonConfigDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.addon) { if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`; return html` <paper-spinner-lite active></paper-spinner-lite> `;
} }
return html` return html`
<div class="content"> <div class="content">

View File

@@ -5,7 +5,6 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
@@ -13,7 +12,6 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/ha-yaml-editor"; import "../../../../src/components/ha-yaml-editor";
import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor"; import type { HaYamlEditor } from "../../../../src/components/ha-yaml-editor";
@@ -22,7 +20,6 @@ import {
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
setHassioAddonOption, setHassioAddonOption,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
@@ -35,7 +32,7 @@ class HassioAddonConfig extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @property() private _error?: string;
@property({ type: Boolean }) private _configHasChanged = false; @property({ type: Boolean }) private _configHasChanged = false;
@@ -57,20 +54,50 @@ class HassioAddonConfig extends LitElement {
${valid ? "" : html` <div class="errors">Invalid YAML</div> `} ${valid ? "" : html` <div class="errors">Invalid YAML</div> `}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}> <mwc-button class="warning" @click=${this._resetTapped}>
Reset to defaults Reset to defaults
</ha-progress-button> </mwc-button>
<ha-progress-button <mwc-button
@click=${this._saveTapped} @click=${this._saveTapped}
.disabled=${!this._configHasChanged || !valid} .disabled=${!this._configHasChanged || !valid}
> >
Save Save
</ha-progress-button> </mwc-button>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
}
.card-actions {
display: flex;
justify-content: space-between;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: monospace;
}
.syntaxerror {
color: var(--google-red-500);
}
`,
];
}
protected updated(changedProperties: PropertyValues): void { protected updated(changedProperties: PropertyValues): void {
super.updated(changedProperties); super.updated(changedProperties);
if (changedProperties.has("addon")) { if (changedProperties.has("addon")) {
@@ -83,10 +110,7 @@ class HassioAddonConfig extends LitElement {
this.requestUpdate(); this.requestUpdate();
} }
private async _resetTapped(ev: CustomEvent): Promise<void> { private async _resetTapped(): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: this.addon.name, title: this.addon.name,
text: "Are you sure you want to reset all your options?", text: "Are you sure you want to reset all your options?",
@@ -95,7 +119,6 @@ class HassioAddonConfig extends LitElement {
}); });
if (!confirmed) { if (!confirmed) {
button.progress = false;
return; return;
} }
@@ -113,17 +136,13 @@ class HassioAddonConfig extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
this._error = `Failed to reset addon configuration, ${extractApiErrorMessage( this._error = `Failed to reset addon configuration, ${
err err.body?.message || err
)}`; }`;
} }
button.progress = false;
} }
private async _saveTapped(ev: CustomEvent): Promise<void> { private async _saveTapped(): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
let data: HassioAddonSetOptionParams; let data: HassioAddonSetOptionParams;
this._error = undefined; this._error = undefined;
try { try {
@@ -143,45 +162,14 @@ class HassioAddonConfig extends LitElement {
path: "options", path: "options",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { } catch (err) {
this._error = `Failed to save addon configuration, ${
err.body?.message || err
}`;
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon); await suggestAddonRestart(this, this.hass, this.addon);
} }
} catch (err) {
this._error = `Failed to save addon configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
}
.card-actions {
display: flex;
justify-content: space-between;
}
.errors {
color: var(--error-color);
margin-top: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: monospace;
}
.syntaxerror {
color: var(--error-color);
}
`,
];
} }
} }

View File

@@ -4,21 +4,18 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import { import {
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
setHassioAddonOption, setHassioAddonOption,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart"; import { suggestAddonRestart } from "../../dialogs/suggestAddonRestart";
@@ -40,9 +37,9 @@ class HassioAddonNetwork extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @property() private _error?: string;
@internalProperty() private _config?: NetworkItem[]; @property() private _config?: NetworkItem[];
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
@@ -74,7 +71,7 @@ class HassioAddonNetwork extends LitElement {
<paper-input <paper-input
@value-changed=${this._configChanged} @value-changed=${this._configChanged}
placeholder="disabled" placeholder="disabled"
.value=${item.host ? String(item.host) : ""} .value=${String(item.host)}
.container=${item.container} .container=${item.container}
no-label-float no-label-float
></paper-input> ></paper-input>
@@ -87,17 +84,38 @@ class HassioAddonNetwork extends LitElement {
</table> </table>
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button class="warning" @click=${this._resetTapped}> <mwc-button class="warning" @click=${this._resetTapped}>
Reset to defaults Reset to defaults
</ha-progress-button> </mwc-button>
<ha-progress-button @click=${this._saveTapped}> <mwc-button @click=${this._saveTapped}>Save</mwc-button>
Save
</ha-progress-button>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
}
`,
];
}
protected update(changedProperties: PropertyValues): void { protected update(changedProperties: PropertyValues): void {
super.update(changedProperties); super.update(changedProperties);
if (changedProperties.has("addon")) { if (changedProperties.has("addon")) {
@@ -130,10 +148,7 @@ class HassioAddonNetwork extends LitElement {
}); });
} }
private async _resetTapped(ev: CustomEvent): Promise<void> { private async _resetTapped(): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const data: HassioAddonSetOptionParams = { const data: HassioAddonSetOptionParams = {
network: null, network: null,
}; };
@@ -146,22 +161,17 @@ class HassioAddonNetwork extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { } catch (err) {
this._error = `Failed to set addon network configuration, ${
err.body?.message || err
}`;
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon); await suggestAddonRestart(this, this.hass, this.addon);
} }
} catch (err) {
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
err
)}`;
} }
button.progress = false; private async _saveTapped(): Promise<void> {
}
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
this._error = undefined; this._error = undefined;
const networkconfiguration = {}; const networkconfiguration = {};
this._config!.forEach((item) => { this._config!.forEach((item) => {
@@ -180,38 +190,14 @@ class HassioAddonNetwork extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
if (this.addon?.state === "started") { } catch (err) {
this._error = `Failed to set addon network configuration, ${
err.body?.message || err
}`;
}
if (!this._error && this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.addon); await suggestAddonRestart(this, this.hass, this.addon);
} }
} catch (err) {
this._error = `Failed to set addon network configuration, ${extractApiErrorMessage(
err
)}`;
}
button.progress = false;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;
}
`,
];
} }
} }

View File

@@ -1,21 +1,19 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import { import {
fetchHassioAddonDocumentation, fetchHassioAddonDocumentation,
HassioAddonDetails, HassioAddonDetails,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import "../../../../src/layouts/loading-screen";
import "../../../../src/layouts/hass-loading-screen";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
@@ -26,9 +24,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
@property({ attribute: false }) public addon?: HassioAddonDetails; @property({ attribute: false }) public addon?: HassioAddonDetails;
@internalProperty() private _error?: string; @property() private _error?: string;
@internalProperty() private _content?: string; @property() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
@@ -37,7 +35,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.addon) { if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`; return html` <paper-spinner-lite active></paper-spinner-lite> `;
} }
return html` return html`
<div class="content"> <div class="content">
@@ -46,7 +44,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
<div class="card-content"> <div class="card-content">
${this._content ${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>` ? html`<ha-markdown .content=${this._content}></ha-markdown>`
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`} : html`<loading-screen></loading-screen>`}
</div> </div>
</ha-card> </ha-card>
</div> </div>
@@ -81,9 +79,9 @@ class HassioAddonDocumentationDashboard extends LitElement {
this.addon!.slug this.addon!.slug
); );
} catch (err) { } catch (err) {
this._error = `Failed to get addon documentation, ${extractApiErrorMessage( this._error = `Failed to get addon documentation, ${
err err.body?.message || err
)}`; }`;
} }
} }
} }

View File

@@ -4,6 +4,7 @@ import {
mdiInformationVariant, mdiInformationVariant,
mdiMathLog, mdiMathLog,
} from "@mdi/js"; } from "@mdi/js";
import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
@@ -19,7 +20,6 @@ import {
HassioAddonDetails, HassioAddonDetails,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import "../../../src/components/ha-circular-progress";
import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage"; import type { PageNavigation } from "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
@@ -56,7 +56,7 @@ class HassioAddonDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.addon) { if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`; return html` <paper-spinner-lite active></paper-spinner-lite> `;
} }
const addonTabs: PageNavigation[] = [ const addonTabs: PageNavigation[] = [

View File

@@ -1,3 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
@@ -8,7 +9,6 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-circular-progress";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
@@ -24,7 +24,7 @@ class HassioAddonInfoDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.addon) { if (!this.addon) {
return html`<ha-circular-progress active></ha-circular-progress>`; return html` <paper-spinner-lite active></paper-spinner-lite> `;
} }
return html` return html`

View File

@@ -9,17 +9,18 @@ import {
mdiExclamationThick, mdiExclamationThick,
mdiFlask, mdiFlask,
mdiHomeAssistant, mdiHomeAssistant,
mdiInformation,
mdiKey, mdiKey,
mdiNetwork, mdiNetwork,
mdiPound, mdiPound,
mdiShield, mdiShield,
} from "@mdi/js"; } from "@mdi/js";
import "@polymer/paper-tooltip/paper-tooltip";
import { import {
css, css,
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
@@ -33,27 +34,19 @@ import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge"; import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch"; import "../../../../src/components/ha-switch";
import { import {
fetchHassioAddonChangelog, fetchHassioAddonChangelog,
fetchHassioAddonInfo,
HassioAddonDetails, HassioAddonDetails,
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
HassioAddonSetSecurityParams, HassioAddonSetSecurityParams,
installHassioAddon, installHassioAddon,
setHassioAddonOption, setHassioAddonOption,
setHassioAddonSecurity, setHassioAddonSecurity,
startHassioAddon,
uninstallHassioAddon, uninstallHassioAddon,
validateHassioAddonOption,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-card-content"; import "../../components/hassio-card-content";
@@ -131,7 +124,9 @@ class HassioAddonInfo extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @property() private _error?: string;
@property({ type: Boolean }) private _installing = false;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
@@ -246,9 +241,9 @@ class HassioAddonInfo extends LitElement {
` `
: ""} : ""}
<div class="security"> <div class="security">
${this.addon.stage !== "stable" <ha-label-badge
? html` <ha-label-badge
class=${classMap({ class=${classMap({
green: this.addon.stage === "stable",
yellow: this.addon.stage === "experimental", yellow: this.addon.stage === "experimental",
red: this.addon.stage === "deprecated", red: this.addon.stage === "deprecated",
})} })}
@@ -257,12 +252,8 @@ class HassioAddonInfo extends LitElement {
label="stage" label="stage"
description="" description=""
> >
<ha-svg-icon <ha-svg-icon .path=${STAGE_ICON[this.addon.stage]}></ha-svg-icon>
.path=${STAGE_ICON[this.addon.stage]} </ha-label-badge>
></ha-svg-icon>
</ha-label-badge>`
: ""}
<ha-label-badge <ha-label-badge
class=${classMap({ class=${classMap({
green: [5, 6].includes(Number(this.addon.rating)), green: [5, 6].includes(Number(this.addon.rating)),
@@ -390,94 +381,67 @@ class HassioAddonInfo extends LitElement {
${this.addon.version ${this.addon.version
? html` ? html`
<div class="addon-options"> <div class="state">
<ha-settings-row ?three-line=${this.narrow}> <div>Start on boot</div>
<span slot="heading">
Start on boot
</span>
<span slot="description">
Make the add-on start during a system boot
</span>
<ha-switch <ha-switch
@change=${this._startOnBootToggled} @change=${this._startOnBootToggled}
.checked=${this.addon.boot === "auto"} .checked=${this.addon.boot === "auto"}
haptic haptic
></ha-switch> ></ha-switch>
</ha-settings-row> </div>
${this.addon.startup !== "once"
? html`
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
Watchdog
</span>
<span slot="description">
This will start the add-on if it crashes
</span>
<ha-switch
@change=${this._watchdogToggled}
.checked=${this.addon.watchdog}
haptic
></ha-switch>
</ha-settings-row>
`
: ""}
${this.addon.auto_update || this.hass.userData?.showAdvanced ${this.addon.auto_update || this.hass.userData?.showAdvanced
? html` ? html`
<ha-settings-row ?three-line=${this.narrow}> <div class="state">
<span slot="heading"> <div>Auto update</div>
Auto update
</span>
<span slot="description">
Auto update the add-on when there is a new version
available
</span>
<ha-switch <ha-switch
@change=${this._autoUpdateToggled} @change=${this._autoUpdateToggled}
.checked=${this.addon.auto_update} .checked=${this.addon.auto_update}
haptic haptic
></ha-switch> ></ha-switch>
</ha-settings-row> </div>
` `
: ""} : ""}
${this.addon.ingress ${this.addon.ingress
? html` ? html`
<ha-settings-row ?three-line=${this.narrow}> <div class="state">
<span slot="heading"> <div>Show in sidebar</div>
Show in sidebar
</span>
<span slot="description">
${this._computeCannotIngressSidebar
? "This option requires Home Assistant 0.92 or later."
: "Add this add-on to your sidebar"}
</span>
<ha-switch <ha-switch
@change=${this._panelToggled} @change=${this._panelToggled}
.checked=${this.addon.ingress_panel} .checked=${this.addon.ingress_panel}
.disabled=${this._computeCannotIngressSidebar} .disabled=${this._computeCannotIngressSidebar}
haptic haptic
></ha-switch> ></ha-switch>
</ha-settings-row> ${this._computeCannotIngressSidebar
? html`
<span>
This option requires Home Assistant 0.92 or
later.
</span>
`
: ""}
</div>
` `
: ""} : ""}
${this._computeUsesProtectedOptions ${this._computeUsesProtectedOptions
? html` ? html`
<ha-settings-row ?three-line=${this.narrow}> <div class="state">
<span slot="heading"> <div>
Protection mode Protection mode
<span>
<ha-svg-icon path=${mdiInformation}></ha-svg-icon>
<paper-tooltip>
Grant the add-on elevated system access.
</paper-tooltip>
</span> </span>
<span slot="description"> </div>
Blocks elevated system access from the add-on
</span>
<ha-switch <ha-switch
@change=${this._protectionToggled} @change=${this._protectionToggled}
.checked=${this.addon.protected} .checked=${this.addon.protected}
haptic haptic
></ha-switch> ></ha-switch>
</ha-settings-row> </div>
` `
: ""} : ""}
</div>
` `
: ""} : ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -503,9 +467,12 @@ class HassioAddonInfo extends LitElement {
</ha-call-api-button> </ha-call-api-button>
` `
: html` : html`
<ha-progress-button @click=${this._startClicked}> <ha-call-api-button
.hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/start"
>
Start Start
</ha-progress-button> </ha-call-api-button>
`} `}
${this._computeShowWebUI ${this._computeShowWebUI
? html` ? html`
@@ -529,12 +496,12 @@ class HassioAddonInfo extends LitElement {
</mwc-button> </mwc-button>
` `
: ""} : ""}
<ha-progress-button <mwc-button
class=" right warning" class=" right warning"
@click=${this._uninstallClicked} @click=${this._uninstallClicked}
> >
Uninstall Uninstall
</ha-progress-button> </mwc-button>
${this.addon.build ${this.addon.build
? html` ? html`
<ha-call-api-button <ha-call-api-button
@@ -556,7 +523,8 @@ class HassioAddonInfo extends LitElement {
` `
: ""} : ""}
<ha-progress-button <ha-progress-button
.disabled=${!this.addon.available} .disabled=${!this.addon.available || this._installing}
.progress=${this._installing}
@click=${this._installClicked} @click=${this._installClicked}
> >
Install Install
@@ -579,6 +547,137 @@ class HassioAddonInfo extends LitElement {
`; `;
} }
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
margin-bottom: 16px;
}
ha-card.warning {
background-color: var(--google-red-500);
color: white;
}
ha-card.warning .card-header {
color: white;
}
ha-card.warning .card-content {
color: white;
}
ha-card.warning mwc-button {
--mdc-theme-primary: white !important;
}
.warning {
color: var(--google-red-500);
--mdc-theme-primary: var(--google-red-500);
}
.light-color {
color: var(--secondary-text-color);
}
.addon-header {
padding-left: 8px;
font-size: 24px;
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.errors {
color: var(--google-red-500);
margin-bottom: 16px;
}
.description {
margin-bottom: 16px;
}
img.logo {
max-height: 60px;
margin: 16px 0;
display: block;
}
.state {
display: flex;
margin: 33px 0;
}
.state div {
width: 180px;
display: inline-block;
}
.state ha-svg-icon {
width: 16px;
height: 16px;
color: var(--secondary-text-color);
}
ha-switch {
display: flex;
}
ha-svg-icon.running {
color: var(--paper-green-400);
}
ha-svg-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a {
color: var(--primary-color);
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.card-actions {
display: flow-root;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
.changelog-link {
color: var(--primary-color);
text-decoration: underline;
cursor: pointer;
}
ha-markdown {
padding: 16px;
}
`,
];
}
private get _computeHassioApi(): boolean { private get _computeHassioApi(): boolean {
return ( return (
this.addon.hassio_api && this.addon.hassio_api &&
@@ -663,29 +762,7 @@ class HassioAddonInfo extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
this._error = `Failed to set addon option, ${extractApiErrorMessage( this._error = `Failed to set addon option, ${err.body?.message || err}`;
err
)}`;
}
}
private async _watchdogToggled(): Promise<void> {
this._error = undefined;
const data: HassioAddonSetOptionParams = {
watchdog: !this.addon.watchdog,
};
try {
await setHassioAddonOption(this.hass, this.addon.slug, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) {
this._error = `Failed to set addon option, ${extractApiErrorMessage(
err
)}`;
} }
} }
@@ -703,9 +780,7 @@ class HassioAddonInfo extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
this._error = `Failed to set addon option, ${extractApiErrorMessage( this._error = `Failed to set addon option, ${err.body?.message || err}`;
err
)}`;
} }
} }
@@ -723,9 +798,9 @@ class HassioAddonInfo extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
this._error = `Failed to set addon security option, ${extractApiErrorMessage( this._error = `Failed to set addon security option, ${
err err.body?.message || err
)}`; }`;
} }
} }
@@ -743,13 +818,12 @@ class HassioAddonInfo extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
this._error = `Failed to set addon option, ${extractApiErrorMessage( this._error = `Failed to set addon option, ${err.body?.message || err}`;
err
)}`;
} }
} }
private async _openChangelog(): Promise<void> { private async _openChangelog(): Promise<void> {
this._error = undefined;
try { try {
const content = await fetchHassioAddonChangelog( const content = await fetchHassioAddonChangelog(
this.hass, this.hass,
@@ -760,17 +834,15 @@ class HassioAddonInfo extends LitElement {
content, content,
}); });
} catch (err) { } catch (err) {
showAlertDialog(this, { this._error = `Failed to get addon changelog, ${
title: "Failed to get addon changelog", err.body?.message || err
text: extractApiErrorMessage(err), }`;
});
} }
} }
private async _installClicked(ev: CustomEvent): Promise<void> { private async _installClicked(): Promise<void> {
const button = ev.currentTarget as any; this._error = undefined;
button.progress = true; this._installing = true;
try { try {
await installHassioAddon(this.hass, this.addon.slug); await installHassioAddon(this.hass, this.addon.slug);
const eventdata = { const eventdata = {
@@ -780,62 +852,12 @@ class HassioAddonInfo extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
showAlertDialog(this, { this._error = `Failed to install addon, ${err.body?.message || err}`;
title: "Failed to install addon",
text: extractApiErrorMessage(err),
});
} }
button.progress = false; this._installing = false;
} }
private async _startClicked(ev: CustomEvent): Promise<void> { private async _uninstallClicked(): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
const validate = await validateHassioAddonOption(
this.hass,
this.addon.slug
);
if (!validate.data.valid) {
await showConfirmationDialog(this, {
title: "Failed to start addon - configruation validation faled!",
text: validate.data.message.split(" Got ")[0],
confirm: () => this._openConfiguration(),
confirmText: "Go to configruation",
dismissText: "Cancel",
});
button.progress = false;
return;
}
} catch (err) {
showAlertDialog(this, {
title: "Failed to validate addon configuration",
text: extractApiErrorMessage(err),
});
button.progress = false;
return;
}
try {
await startHassioAddon(this.hass, this.addon.slug);
this.addon = await fetchHassioAddonInfo(this.hass, this.addon.slug);
} catch (err) {
showAlertDialog(this, {
title: "Failed to start addon",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private _openConfiguration(): void {
navigate(this, `/hassio/addon/${this.addon.slug}/config`);
}
private async _uninstallClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: this.addon.name, title: this.addon.name,
text: "Are you sure you want to uninstall this add-on?", text: "Are you sure you want to uninstall this add-on?",
@@ -844,7 +866,6 @@ class HassioAddonInfo extends LitElement {
}); });
if (!confirmed) { if (!confirmed) {
button.progress = false;
return; return;
} }
@@ -858,152 +879,8 @@ class HassioAddonInfo extends LitElement {
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
showAlertDialog(this, { this._error = `Failed to uninstall addon, ${err.body?.message || err}`;
title: "Failed to uninstall addon",
text: extractApiErrorMessage(err),
});
} }
button.progress = false;
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
:host {
display: block;
}
ha-card {
display: block;
margin-bottom: 16px;
}
ha-card.warning {
background-color: var(--error-color);
color: white;
}
ha-card.warning .card-header {
color: white;
}
ha-card.warning .card-content {
color: white;
}
ha-card.warning mwc-button {
--mdc-theme-primary: white !important;
}
.warning {
color: var(--error-color);
--mdc-theme-primary: var(--error-color);
}
.light-color {
color: var(--secondary-text-color);
}
.addon-header {
padding-left: 8px;
font-size: 24px;
color: var(--ha-card-header-color, --primary-text-color);
}
.addon-version {
float: right;
font-size: 15px;
vertical-align: middle;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.description {
margin-bottom: 16px;
}
img.logo {
max-height: 60px;
margin: 16px 0;
display: block;
}
ha-switch {
display: flex;
}
ha-svg-icon.running {
color: var(--paper-green-400);
}
ha-svg-icon.stopped {
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.right {
float: right;
}
protection-enable mwc-button {
--mdc-theme-primary: white;
}
.description a {
color: var(--primary-color);
}
.red {
--ha-label-badge-color: var(--label-badge-red, #df4c1e);
}
.blue {
--ha-label-badge-color: var(--label-badge-blue, #039be5);
}
.green {
--ha-label-badge-color: var(--label-badge-green, #0da035);
}
.yellow {
--ha-label-badge-color: var(--label-badge-yellow, #f4b400);
}
.security {
margin-bottom: 16px;
}
.card-actions {
display: flow-root;
}
.security h3 {
margin-bottom: 8px;
font-weight: normal;
}
.security ha-label-badge {
cursor: pointer;
margin-right: 4px;
--ha-label-badge-padding: 8px 0 0 0;
}
.changelog {
display: contents;
}
.changelog-link {
color: var(--primary-color);
text-decoration: underline;
cursor: pointer;
}
ha-markdown {
padding: 16px;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row > span[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
ha-settings-row[three-line] {
height: 74px;
}
.addon-options {
max-width: 50%;
}
@media (max-width: 720px) {
.addon-options {
max-width: 100%;
}
}
`,
];
} }
} }
declare global { declare global {

View File

@@ -1,3 +1,4 @@
import "@polymer/paper-spinner/paper-spinner-lite";
import { import {
css, css,
CSSResult, CSSResult,
@@ -8,7 +9,6 @@ import {
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { HassioAddonDetails } from "../../../../src/data/hassio/addon"; import { HassioAddonDetails } from "../../../../src/data/hassio/addon";
import "../../../../src/components/ha-circular-progress";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style"; import { hassioStyle } from "../../resources/hassio-style";
@@ -22,7 +22,7 @@ class HassioAddonLogDashboard extends LitElement {
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.addon) { if (!this.addon) {
return html` <ha-circular-progress active></ha-circular-progress> `; return html` <paper-spinner-lite active></paper-spinner-lite> `;
} }
return html` return html`
<div class="content"> <div class="content">

View File

@@ -4,7 +4,6 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
@@ -14,7 +13,6 @@ import {
fetchHassioAddonLogs, fetchHassioAddonLogs,
HassioAddonDetails, HassioAddonDetails,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { haStyle } from "../../../../src/resources/styles"; import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-ansi-to-html"; import "../../components/hassio-ansi-to-html";
@@ -26,9 +24,9 @@ class HassioAddonLogs extends LitElement {
@property({ attribute: false }) public addon!: HassioAddonDetails; @property({ attribute: false }) public addon!: HassioAddonDetails;
@internalProperty() private _error?: string; @property() private _error?: string;
@internalProperty() private _content?: string; @property() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
@@ -64,7 +62,7 @@ class HassioAddonLogs extends LitElement {
display: block; display: block;
} }
.errors { .errors {
color: var(--error-color); color: var(--google-red-500);
margin-bottom: 16px; margin-bottom: 16px;
} }
`, `,
@@ -76,7 +74,7 @@ class HassioAddonLogs extends LitElement {
try { try {
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug); this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
} catch (err) { } catch (err) {
this._error = `Failed to get addon logs, ${extractApiErrorMessage(err)}`; this._error = `Failed to get addon logs, ${err.body?.message || err}`;
} }
} }

View File

@@ -21,7 +21,7 @@ interface State {
class HassioAnsiToHtml extends LitElement { class HassioAnsiToHtml extends LitElement {
@property() public content!: string; @property() public content!: string;
protected render(): TemplateResult | void { public render(): TemplateResult | void {
return html`${this._parseTextToColoredPre(this.content)}`; return html`${this._parseTextToColoredPre(this.content)}`;
} }

View File

@@ -14,7 +14,7 @@ import { HomeAssistant } from "../../../src/types";
@customElement("hassio-card-content") @customElement("hassio-card-content")
class HassioCardContent extends LitElement { class HassioCardContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public title!: string; @property() public title!: string;
@@ -98,7 +98,7 @@ class HassioCardContent extends LitElement {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }
ha-svg-icon.not_available { ha-svg-icon.not_available {
color: var(--error-color); color: var(--google-red-500);
} }
.title { .title {
color: var(--primary-text-color); color: var(--primary-text-color);

View File

@@ -19,7 +19,7 @@ import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-addons") @customElement("hassio-addons")
class HassioAddons extends LitElement { class HassioAddons extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public addons?: HassioAddonInfo[]; @property() public addons?: HassioAddonInfo[];

View File

@@ -15,7 +15,7 @@ import {
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-panel";
import "./hassio-addons"; import "./hassio-addons";
import "./hassio-update"; import "./hassio-update";

View File

@@ -5,43 +5,33 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import {
extractApiErrorMessage,
HassioResponse,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { HassioHassOSInfo } from "../../../src/data/hassio/host"; import { HassioHassOSInfo } from "../../../src/data/hassio/host";
import { import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-update") @customElement("hassio-update")
export class HassioUpdate extends LitElement { export class HassioUpdate extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo; @property() public hassInfo: HassioHomeAssistantInfo;
@property({ attribute: false }) public hassOsInfo?: HassioHassOSInfo; @property() public hassOsInfo?: HassioHassOSInfo;
@property() public supervisorInfo: HassioSupervisorInfo; @property() public supervisorInfo: HassioSupervisorInfo;
@internalProperty() private _error?: string; @property() private _error?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
const updatesAvailable: number = [ const updatesAvailable: number = [
@@ -135,47 +125,32 @@ export class HassioUpdate extends LitElement {
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer"> <a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
<mwc-button>Release notes</mwc-button> <mwc-button>Release notes</mwc-button>
</a> </a>
<ha-progress-button <ha-call-api-button
.apiPath=${apiPath} .hass=${this.hass}
.name=${name} .path=${apiPath}
.version=${lastVersion} @hass-api-called=${this._apiCalled}
@click=${this._confirmUpdate}
> >
Update Update
</ha-progress-button> </ha-call-api-button>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
private async _confirmUpdate(ev): Promise<void> { private _apiCalled(ev): void {
const item = ev.currentTarget; if (ev.detail.success) {
item.progress = true; this._error = "";
const confirmed = await showConfirmationDialog(this, {
title: `Update ${item.name}`,
text: `Are you sure you want to upgrade ${item.name} to version ${item.version}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
item.progress = false;
return; return;
} }
try {
await this.hass.callApi<HassioResponse<void>>("POST", item.apiPath); const response = ev.detail.response;
} catch (err) {
// Only show an error if the status code was not expected (user behind proxy) if (typeof response.body === "object") {
// or no status at all(connection terminated) this._error = response.body.message || "Unknown error";
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) { } else {
showAlertDialog(this, { this._error = response.body;
title: "Update failed",
text: extractApiErrorMessage(err),
});
} }
} }
item.progress = false;
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
@@ -205,7 +180,7 @@ export class HassioUpdate extends LitElement {
text-align: right; text-align: right;
} }
.errors { .errors {
color: var(--error-color); color: var(--google-red-500);
padding: 16px; padding: 16px;
} }
a { a {

View File

@@ -5,7 +5,6 @@ import {
html, html,
LitElement, LitElement,
property, property,
internalProperty,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
@@ -17,13 +16,13 @@ import { HassioMarkdownDialogParams } from "./show-dialog-hassio-markdown";
@customElement("dialog-hassio-markdown") @customElement("dialog-hassio-markdown")
class HassioMarkdownDialog extends LitElement { class HassioMarkdownDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public title!: string; @property() public title!: string;
@property() public content!: string; @property() public content!: string;
@internalProperty() private _opened = false; @property() private _opened = false;
public showDialog(params: HassioMarkdownDialogParams) { public showDialog(params: HassioMarkdownDialogParams) {
this.title = params.title; this.title = params.title;
@@ -31,10 +30,6 @@ class HassioMarkdownDialog extends LitElement {
this._opened = true; this._opened = true;
} }
public closeDialog() {
this._opened = false;
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._opened) { if (!this._opened) {
return html``; return html``;
@@ -42,7 +37,7 @@ class HassioMarkdownDialog extends LitElement {
return html` return html`
<ha-dialog <ha-dialog
open open
@closed=${this.closeDialog} @closing=${this._closeDialog}
.heading=${createCloseHeading(this.hass, this.title)} .heading=${createCloseHeading(this.hass, this.title)}
> >
<ha-markdown .content=${this.content || ""}></ha-markdown> <ha-markdown .content=${this.content || ""}></ha-markdown>
@@ -50,6 +45,10 @@ class HassioMarkdownDialog extends LitElement {
`; `;
} }
private _closeDialog(): void {
this._opened = false;
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
haStyleDialog, haStyleDialog,

View File

@@ -1,333 +0,0 @@
import "@material/mwc-button/mwc-button";
import "@material/mwc-icon-button";
import "@material/mwc-tab";
import "@material/mwc-tab-bar";
import { mdiClose } from "@mdi/js";
import { PaperInputElement } from "@polymer/paper-input/paper-input";
import {
css,
CSSResult,
customElement,
html,
internalProperty,
LitElement,
property,
TemplateResult,
} from "lit-element";
import { cache } from "lit-html/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-formfield";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-radio";
import type { HaRadio } from "../../../../src/components/ha-radio";
import "../../../../src/components/ha-related-items";
import "../../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
NetworkInterface,
updateNetworkInterface,
} from "../../../../src/data/hassio/network";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { HassioNetworkDialogParams } from "./show-dialog-network";
@customElement("dialog-hassio-network")
export class DialogHassioNetwork extends LitElement implements HassDialog {
@property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _prosessing = false;
@internalProperty() private _params?: HassioNetworkDialogParams;
@internalProperty() private _network!: {
interface: string;
data: NetworkInterface;
}[];
@internalProperty() private _curTabIndex = 0;
@internalProperty() private _device?: {
interface: string;
data: NetworkInterface;
};
@internalProperty() private _dirty = false;
public async showDialog(params: HassioNetworkDialogParams): Promise<void> {
this._params = params;
this._dirty = false;
this._curTabIndex = 0;
this._network = Object.keys(params.network?.interfaces)
.map((device) => ({
interface: device,
data: params.network.interfaces[device],
}))
.sort((a, b) => {
return a.data.primary > b.data.primary ? -1 : 1;
});
this._device = this._network[this._curTabIndex];
this._device.data.nameservers = String(this._device.data.nameservers);
await this.updateComplete;
}
public closeDialog(): void {
this._params = undefined;
this._prosessing = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._params || !this._network) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
.heading=${true}
hideActions
@closed=${this.closeDialog}
>
<div slot="heading">
<ha-header-bar>
<span slot="title">
Network settings
</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
${this._network.length > 1
? html` <mwc-tab-bar
.activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated}
>${this._network.map(
(device) =>
html`<mwc-tab
.id=${device.interface}
.label=${device.interface}
>
</mwc-tab>`
)}
</mwc-tab-bar>`
: ""}
</div>
${cache(this._renderTab())}
</ha-dialog>
`;
}
private _renderTab() {
return html` <div class="form container">
<ha-formfield label="DHCP">
<ha-radio
@change=${this._handleRadioValueChanged}
value="dhcp"
name="method"
?checked=${this._device!.data.method === "dhcp"}
>
</ha-radio>
</ha-formfield>
<ha-formfield label="Static">
<ha-radio
@change=${this._handleRadioValueChanged}
value="static"
name="method"
?checked=${this._device!.data.method === "static"}
>
</ha-radio>
</ha-formfield>
${this._device!.data.method !== "dhcp"
? html` <paper-input
class="flex-auto"
id="ip_address"
label="IP address/Netmask"
.value="${this._device!.data.ip_address}"
@value-changed=${this._handleInputValueChanged}
></paper-input>
<paper-input
class="flex-auto"
id="gateway"
label="Gateway address"
.value="${this._device!.data.gateway}"
@value-changed=${this._handleInputValueChanged}
></paper-input>
<paper-input
class="flex-auto"
id="nameservers"
label="DNS servers"
.value="${this._device!.data.nameservers as string}"
@value-changed=${this._handleInputValueChanged}
></paper-input>
NB!: If you are changing IP or gateway addresses, you might lose
the connection.`
: ""}
</div>
<div class="buttons">
<mwc-button label="close" @click=${this.closeDialog}> </mwc-button>
<mwc-button @click=${this._updateNetwork} ?disabled=${!this._dirty}>
${this._prosessing
? html`<ha-circular-progress active></ha-circular-progress>`
: "Update"}
</mwc-button>
</div>`;
}
private async _updateNetwork() {
this._prosessing = true;
let options: Partial<NetworkInterface> = {
method: this._device!.data.method,
};
if (options.method !== "dhcp") {
options = {
...options,
address: this._device!.data.ip_address,
gateway: this._device!.data.gateway,
dns: String(this._device!.data.nameservers).split(","),
};
}
try {
await updateNetworkInterface(this.hass, this._device!.interface, options);
} catch (err) {
showAlertDialog(this, {
title: "Failed to change network settings",
text: extractApiErrorMessage(err),
});
this._prosessing = false;
return;
}
this._params?.loadData();
this.closeDialog();
}
private async _handleTabActivated(ev: CustomEvent): Promise<void> {
if (this._dirty) {
const confirm = await showConfirmationDialog(this, {
text:
"You have unsaved changes, these will get lost if you change tabs, do you want to continue?",
confirmText: "yes",
dismissText: "no",
});
if (!confirm) {
this.requestUpdate("_device");
return;
}
}
this._curTabIndex = ev.detail.index;
this._device = this._network[ev.detail.index];
this._device.data.nameservers = String(this._device.data.nameservers);
}
private _handleRadioValueChanged(ev: CustomEvent): void {
const value = (ev.target as HaRadio).value as "dhcp" | "static";
if (!value || !this._device || this._device!.data.method === value) {
return;
}
this._dirty = true;
this._device!.data.method = value;
this.requestUpdate("_device");
}
private _handleInputValueChanged(ev: CustomEvent): void {
const value: string | null | undefined = (ev.target as PaperInputElement)
.value;
const id = (ev.target as PaperInputElement).id;
if (!value || !this._device || this._device.data[id] === value) {
return;
}
this._dirty = true;
this._device.data[id] = value;
}
static get styles(): CSSResult[] {
return [
haStyleDialog,
css`
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
}
mwc-tab-bar {
border-bottom: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
}
ha-dialog {
--dialog-content-position: static;
--dialog-content-padding: 0;
--dialog-z-index: 6;
}
@media all and (min-width: 451px) and (min-height: 501px) {
.container {
width: 400px;
}
}
.content {
display: block;
padding: 20px 24px;
}
/* overrule the ha-style-dialog max-height on small screens */
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-header-bar {
--mdc-theme-primary: var(--app-header-background-color);
--mdc-theme-on-primary: var(--app-header-text-color, white);
}
}
mwc-button.warning {
--mdc-theme-primary: var(--error-color);
}
:host([rtl]) app-toolbar {
direction: rtl;
text-align: right;
}
.container {
padding: 20px 24px;
}
.form {
margin-bottom: 53px;
}
.buttons {
position: absolute;
bottom: 0;
width: 100%;
box-sizing: border-box;
border-top: 1px solid
var(--mdc-dialog-scroll-divider-color, rgba(0, 0, 0, 0.12));
display: flex;
justify-content: space-between;
padding: 8px;
padding-bottom: max(env(safe-area-inset-bottom), 8px);
background-color: var(--mdc-theme-surface, #fff);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-network": DialogHassioNetwork;
}
}

View File

@@ -1,22 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { NetworkInfo } from "../../../../src/data/hassio/network";
import "./dialog-hassio-network";
export interface HassioNetworkDialogParams {
network: NetworkInfo;
loadData: () => Promise<void>;
}
export const showNetworkDialog = (
element: HTMLElement,
dialogParams: HassioNetworkDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-network",
dialogImport: () =>
import(
/* webpackChunkName: "dialog-hassio-network" */ "./dialog-hassio-network"
),
dialogParams,
});
};

View File

@@ -5,26 +5,24 @@ import "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@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";
import "@polymer/paper-item/paper-item-body"; import "@polymer/paper-item/paper-item-body";
import "@polymer/paper-spinner/paper-spinner";
import { import {
css, css,
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
query, query,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { import {
fetchHassioAddonsInfo, fetchHassioAddonsInfo,
HassioAddonRepository, HassioAddonRepository,
} from "../../../../src/data/hassio/addon"; } from "../../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { setSupervisorOption } from "../../../../src/data/hassio/supervisor"; import { setSupervisorOption } from "../../../../src/data/hassio/supervisor";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
@@ -41,11 +39,11 @@ class HassioRepositoriesDialog extends LitElement {
@query("#repository_input") private _optionInput?: PaperInputElement; @query("#repository_input") private _optionInput?: PaperInputElement;
@internalProperty() private _opened = false; @property() private _opened = false;
@internalProperty() private _prosessing = false; @property() private _prosessing = false;
@internalProperty() private _error?: string; @property() private _error?: string;
public async showDialog(_dialogParams: any): Promise<void> { public async showDialog(_dialogParams: any): Promise<void> {
this._dialogParams = _dialogParams; this._dialogParams = _dialogParams;
@@ -110,7 +108,7 @@ class HassioRepositoriesDialog extends LitElement {
></paper-input> ></paper-input>
<mwc-button @click=${this._addRepository}> <mwc-button @click=${this._addRepository}>
${this._prosessing ${this._prosessing
? html`<ha-circular-progress active></ha-circular-progress>` ? html`<paper-spinner active></paper-spinner>`
: "Add"} : "Add"}
</mwc-button> </mwc-button>
</div> </div>
@@ -191,7 +189,7 @@ class HassioRepositoriesDialog extends LitElement {
input.value = ""; input.value = "";
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = err.message;
} }
this._prosessing = false; this._prosessing = false;
} }
@@ -223,7 +221,7 @@ class HassioRepositoriesDialog extends LitElement {
await this._dialogParams!.loadData(); await this._dialogParams!.loadData();
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = err.message;
} }
} }
} }

View File

@@ -7,7 +7,6 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
@@ -15,12 +14,10 @@ import {
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
fetchHassioSnapshotInfo, fetchHassioSnapshotInfo,
HassioSnapshotDetail, HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot"; } from "../../../../src/data/hassio/snapshot";
import { showConfirmationDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { PolymerChangedEvent } from "../../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../../src/polymer-types";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
@@ -71,21 +68,21 @@ interface FolderItem {
@customElement("dialog-hassio-snapshot") @customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog extends LitElement { class HassioSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@internalProperty() private _error?: string; @property() private _error?: string;
@internalProperty() private _snapshot?: HassioSnapshotDetail; @property() private _snapshot?: HassioSnapshotDetail;
@internalProperty() private _folders!: FolderItem[]; @property() private _folders!: FolderItem[];
@internalProperty() private _addons!: AddonItem[]; @property() private _addons!: AddonItem[];
@internalProperty() private _dialogParams?: HassioSnapshotDialogParams; @property() private _dialogParams?: HassioSnapshotDialogParams;
@internalProperty() private _snapshotPassword!: string; @property() private _snapshotPassword!: string;
@internalProperty() private _restoreHass: boolean | null | undefined = true; @property() private _restoreHass: boolean | null | undefined = true;
public async showDialog(params: HassioSnapshotDialogParams) { public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug); this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
@@ -227,7 +224,7 @@ class HassioSnapshotDialog extends LitElement {
} }
.warning, .warning,
.error { .error {
color: var(--error-color); color: var(--google-red-500);
} }
.buttons { .buttons {
display: flex; display: flex;
@@ -268,12 +265,8 @@ class HassioSnapshotDialog extends LitElement {
this._snapshotPassword = ev.detail.value; this._snapshotPassword = ev.detail.value;
} }
private async _partialRestoreClicked() { private _partialRestoreClicked() {
if ( if (!confirm("Are you sure you want to restore this snapshot?")) {
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?",
}))
) {
return; return;
} }
@@ -318,13 +311,8 @@ class HassioSnapshotDialog extends LitElement {
); );
} }
private async _fullRestoreClicked() { private _fullRestoreClicked() {
if ( if (!confirm("Are you sure you want to restore this snapshot?")) {
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this snapshot?",
}))
) {
return; return;
} }
@@ -349,12 +337,8 @@ class HassioSnapshotDialog extends LitElement {
); );
} }
private async _deleteClicked() { private _deleteClicked() {
if ( if (!confirm("Are you sure you want to delete this snapshot?")) {
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?",
}))
) {
return; return;
} }
@@ -380,7 +364,7 @@ class HassioSnapshotDialog extends LitElement {
`/api/hassio/snapshots/${this._snapshot!.slug}/download` `/api/hassio/snapshots/${this._snapshot!.slug}/download`
); );
} catch (err) { } catch (err) {
alert(`Error: ${extractApiErrorMessage(err)}`); alert(`Error: ${err.message}`);
return; return;
} }

View File

@@ -3,7 +3,6 @@ import {
HassioAddonDetails, HassioAddonDetails,
restartHassioAddon, restartHassioAddon,
} from "../../../src/data/hassio/addon"; } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -27,7 +26,7 @@ export const suggestAddonRestart = async (
} catch (err) { } catch (err) {
showAlertDialog(element, { showAlertDialog(element, {
title: "Failed to restart", title: "Failed to restart",
text: extractApiErrorMessage(err), text: err.body.message,
}); });
} }
} }

View File

@@ -1,6 +1,9 @@
import "../../src/resources/compatibility"; window.loadES5Adapter().then(() => {
import "../../src/resources/roboto"; // eslint-disable-next-line
import "./hassio-main"; import(/* webpackChunkName: "roboto" */ "../../src/resources/roboto");
// eslint-disable-next-line
import(/* webpackChunkName: "hassio-main" */ "./hassio-main");
});
const styleEl = document.createElement("style"); const styleEl = document.createElement("style");
styleEl.innerHTML = ` styleEl.innerHTML = `

View File

@@ -1,35 +1,107 @@
import { import { PolymerElement } from "@polymer/polymer";
html, import { customElement, property, PropertyValues } from "lit-element";
PropertyValues,
customElement,
LitElement,
property,
} from "lit-element";
import "./hassio-router";
import { urlSyncMixin } from "../../src/state/url-sync-mixin";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import { HomeAssistant, Route } from "../../src/types";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element"; import { applyThemesOnElement } from "../../src/common/dom/apply_themes_on_element";
import { fireEvent } from "../../src/common/dom/fire_event"; import { fireEvent } from "../../src/common/dom/fire_event";
import { navigate } from "../../src/common/navigate";
import { fetchHassioAddonInfo } from "../../src/data/hassio/addon";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
} from "../../src/data/hassio/host";
import {
createHassioSession,
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
HassioHomeAssistantInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import {
AlertDialogParams,
showAlertDialog,
} from "../../src/dialogs/generic/show-dialog-box";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import { atLeastVersion } from "../../src/common/config/version"; import {
HassRouterPage,
RouterOptions,
} from "../../src/layouts/hass-router-page";
import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin";
import "../../src/resources/ha-style";
import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel";
@customElement("hassio-main") @customElement("hassio-main")
export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) { class HassioMain extends ProvideHassLitMixin(HassRouterPage) {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public panel!: HassioPanelInfo; @property() public panel!: HassioPanelInfo;
@property() public narrow!: boolean; @property() public narrow!: boolean;
@property() public route?: Route; protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
initialLoad: () => this._fetchData(),
showLoading: true,
routes: {
dashboard: {
tag: "hassio-panel",
cache: true,
},
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {
tag: "hassio-addon-dashboard",
load: () =>
import(
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
),
},
ingress: {
tag: "hassio-ingress-view",
load: () =>
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
},
},
};
@property() private _supervisorInfo: HassioSupervisorInfo;
@property() private _hostInfo: HassioHostInfo;
@property() private _hassOsInfo?: HassioHassOSInfo;
@property() private _hassInfo: HassioHomeAssistantInfo;
protected firstUpdated(changedProps: PropertyValues) { protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps); super.firstUpdated(changedProps);
this._applyTheme(); applyThemesOnElement(
this.parentElement,
this.hass.themes,
this.hass.selectedTheme || this.hass.themes.default_theme
);
this.style.setProperty(
"--app-header-background-color",
"var(--sidebar-background-color)"
);
this.style.setProperty(
"--app-header-text-color",
"var(--sidebar-text-color)"
);
this.style.setProperty(
"--app-header-border-bottom",
"1px solid var(--divider-color)"
);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
// Paulus - March 17, 2019 // Paulus - March 17, 2019
// We went to a single hass-toggle-menu event in HA 0.90. However, the // We went to a single hass-toggle-menu event in HA 0.90. However, the
// supervisor UI can also run under older versions of Home Assistant. // supervisor UI can also run under older versions of Home Assistant.
@@ -62,61 +134,148 @@ export class HassioMain extends urlSyncMixin(ProvideHassLitMixin(LitElement)) {
}); });
}); });
makeDialogManager(this, this.shadowRoot!); makeDialogManager(this, document.body);
} }
protected updated(changedProps: PropertyValues) { protected updatePageEl(el) {
super.updated(changedProps); // the tabs page does its own routing so needs full route.
const oldHass = changedProps.get("hass") as HomeAssistant | undefined; const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
if (!oldHass) {
if ("setProperties" in el) {
// As long as we have Polymer pages
(el as PolymerElement).setProperties({
hass: this.hass,
narrow: this.narrow,
supervisorInfo: this._supervisorInfo,
hostInfo: this._hostInfo,
hassInfo: this._hassInfo,
hassOsInfo: this._hassOsInfo,
route,
});
} else {
el.hass = this.hass;
el.narrow = this.narrow;
el.supervisorInfo = this._supervisorInfo;
el.hostInfo = this._hostInfo;
el.hassInfo = this._hassInfo;
el.hassOsInfo = this._hassOsInfo;
el.route = route;
}
}
private async _fetchData() {
if (this.panel.config && this.panel.config.ingress) {
await this._redirectIngress(this.panel.config.ingress);
return; return;
} }
if (oldHass.themes !== this.hass.themes) {
this._applyTheme(); const [supervisorInfo, hostInfo, hassInfo] = await Promise.all([
fetchHassioSupervisorInfo(this.hass),
fetchHassioHostInfo(this.hass),
fetchHassioHomeAssistantInfo(this.hass),
]);
this._supervisorInfo = supervisorInfo;
this._hostInfo = hostInfo;
this._hassInfo = hassInfo;
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
} }
} }
protected render() { private async _redirectIngress(addonSlug: string) {
return html` // When we trigger a navigation, we sleep to make sure we don't
<hassio-router // show the hassio dashboard before navigating away.
.hass=${this.hass} const awaitAlert = async (
.route=${this.route} alertParams: AlertDialogParams,
.panel=${this.panel} action: () => void
.narrow=${this.narrow} ) => {
></hassio-router> await new Promise((resolve) => {
`; alertParams.confirm = resolve;
} showAlertDialog(this, alertParams);
});
private _applyTheme() { action();
let themeName: string; await new Promise((resolve) => setTimeout(resolve, 1000));
let options: Partial<HomeAssistant["selectedTheme"]> | undefined;
if (atLeastVersion(this.hass.config.version, 0, 114)) {
themeName =
this.hass.selectedTheme?.theme ||
(this.hass.themes.darkMode && this.hass.themes.default_dark_theme
? this.hass.themes.default_dark_theme!
: this.hass.themes.default_theme);
options = this.hass.selectedTheme;
if (themeName === "default" && options?.dark === undefined) {
options = {
...this.hass.selectedTheme,
dark: this.hass.themes.darkMode,
}; };
}
} else { const createSessionPromise = createHassioSession(this.hass).then(
themeName = () => true,
((this.hass.selectedTheme as unknown) as string) || () => false
this.hass.themes.default_theme; );
let addon;
try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err) {
await awaitAlert(
{
text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor",
},
() => history.back()
);
return;
} }
applyThemesOnElement( if (!addon.ingress_url) {
this.parentElement, await awaitAlert(
this.hass.themes, {
themeName, text: "Add-on does not support Ingress",
options title: addon.name,
},
() => history.back()
); );
return;
}
if (addon.state !== "started") {
await awaitAlert(
{
text: "Add-on is not running. Please start it first",
title: addon.name,
},
() => navigate(this, `/hassio/addon/${addon.slug}/info`, true)
);
return;
}
if (!(await createSessionPromise)) {
await awaitAlert(
{
text: "Unable to create an Ingress session",
title: addon.name,
},
() => history.back()
);
return;
}
location.assign(addon.ingress_url);
// await a promise that doesn't resolve, so we show the loading screen
// while we load the next page.
await new Promise(() => undefined);
}
private _apiCalled(ev) {
if (!ev.detail.success) {
return;
}
let tries = 1;
const tryUpdate = () => {
this._fetchData().catch(() => {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
} }
} }

View File

@@ -3,7 +3,6 @@ import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import { import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor"; } from "../../src/data/hassio/supervisor";
import { import {
HassRouterPage, HassRouterPage,
@@ -27,8 +26,6 @@ class HassioPanelRouter extends HassRouterPage {
@property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo; @property({ attribute: false }) public supervisorInfo: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo: HassioHostInfo; @property({ attribute: false }) public hostInfo: HassioHostInfo;
@property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo; @property({ attribute: false }) public hassInfo: HassioHomeAssistantInfo;
@@ -57,7 +54,6 @@ class HassioPanelRouter extends HassRouterPage {
el.route = this.route; el.route = this.route;
el.narrow = this.narrow; el.narrow = this.narrow;
el.supervisorInfo = this.supervisorInfo; el.supervisorInfo = this.supervisorInfo;
el.hassioInfo = this.hassioInfo;
el.hostInfo = this.hostInfo; el.hostInfo = this.hostInfo;
el.hassInfo = this.hassInfo; el.hassInfo = this.hassInfo;
el.hassOsInfo = this.hassOsInfo; el.hassOsInfo = this.hassOsInfo;

View File

@@ -1,21 +1,43 @@
import { mdiBackupRestore, mdiCogs, mdiStore, mdiViewDashboard } from "@mdi/js";
import { import {
customElement, customElement,
html, html,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
css,
CSSResult,
} from "lit-element"; } from "lit-element";
import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host"; import { HassioHassOSInfo, HassioHostInfo } from "../../src/data/hassio/host";
import { import {
HassioHomeAssistantInfo, HassioHomeAssistantInfo,
HassioSupervisorInfo, HassioSupervisorInfo,
HassioInfo,
} from "../../src/data/hassio/supervisor"; } from "../../src/data/hassio/supervisor";
import type { PageNavigation } from "../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router"; 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") @customElement("hassio-panel")
class HassioPanel extends LitElement { class HassioPanel extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@@ -26,8 +48,6 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo; @property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property({ attribute: false }) public hostInfo!: HassioHostInfo; @property({ attribute: false }) public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo; @property({ attribute: false }) public hassInfo!: HassioHomeAssistantInfo;
@@ -35,32 +55,18 @@ class HassioPanel extends LitElement {
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo; @property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this.supervisorInfo) {
return html``;
}
return html` return html`
<hassio-panel-router <hassio-panel-router
.route=${this.route} .route=${this.route}
.hass=${this.hass} .hass=${this.hass}
.narrow=${this.narrow} .narrow=${this.narrow}
.supervisorInfo=${this.supervisorInfo} .supervisorInfo=${this.supervisorInfo}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo} .hostInfo=${this.hostInfo}
.hassInfo=${this.hassInfo} .hassInfo=${this.hassInfo}
.hassOsInfo=${this.hassOsInfo} .hassOsInfo=${this.hassOsInfo}
></hassio-panel-router> ></hassio-panel-router>
`; `;
} }
static get styles(): CSSResult {
return css`
:host {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
--app-header-border-bottom: 1px solid var(--divider-color);
}
`;
}
} }
declare global { declare global {

View File

@@ -1,150 +0,0 @@
import {
customElement,
property,
internalProperty,
PropertyValues,
} from "lit-element";
import {
fetchHassioHassOsInfo,
fetchHassioHostInfo,
HassioHassOSInfo,
HassioHostInfo,
} from "../../src/data/hassio/host";
import {
fetchHassioHomeAssistantInfo,
fetchHassioSupervisorInfo,
fetchHassioInfo,
HassioHomeAssistantInfo,
HassioInfo,
HassioPanelInfo,
HassioSupervisorInfo,
} from "../../src/data/hassio/supervisor";
import {
HassRouterPage,
RouterOptions,
} from "../../src/layouts/hass-router-page";
import "../../src/resources/ha-style";
import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel";
@customElement("hassio-router")
class HassioRouter extends HassRouterPage {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public panel!: HassioPanelInfo;
@property() public narrow!: boolean;
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
initialLoad: () => this._fetchData(),
showLoading: true,
routes: {
dashboard: {
tag: "hassio-panel",
cache: true,
},
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {
tag: "hassio-addon-dashboard",
load: () =>
import(
/* webpackChunkName: "hassio-addon-dashboard" */ "./addon-view/hassio-addon-dashboard"
),
},
ingress: {
tag: "hassio-ingress-view",
load: () =>
import(
/* webpackChunkName: "hassio-ingress-view" */ "./ingress-view/hassio-ingress-view"
),
},
},
};
@internalProperty() private _supervisorInfo: HassioSupervisorInfo;
@internalProperty() private _hostInfo: HassioHostInfo;
@internalProperty() private _hassioInfo?: HassioInfo;
@internalProperty() private _hassOsInfo?: HassioHassOSInfo;
@internalProperty() private _hassInfo: HassioHomeAssistantInfo;
protected firstUpdated(changedProps: PropertyValues) {
super.firstUpdated(changedProps);
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
protected updatePageEl(el) {
// the tabs page does its own routing so needs full route.
const route = el.nodeName === "HASSIO-PANEL" ? this.route : this.routeTail;
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;
el.route = route;
if (el.localName === "hassio-ingress-view") {
el.ingressPanel = this.panel.config && this.panel.config.ingress;
}
}
private async _fetchData() {
if (this.panel.config && this.panel.config.ingress) {
this._redirectIngress(this.panel.config.ingress);
return;
}
const [supervisorInfo, hostInfo, hassInfo, hassioInfo] = 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;
if (this._hostInfo.features && this._hostInfo.features.includes("hassos")) {
this._hassOsInfo = await fetchHassioHassOsInfo(this.hass);
}
}
private _redirectIngress(addonSlug: string) {
this.route = { prefix: "/hassio", path: `/ingress/${addonSlug}` };
}
private _apiCalled(ev) {
if (!ev.detail.success) {
return;
}
let tries = 1;
const tryUpdate = () => {
this._fetchData().catch(() => {
tries += 1;
setTimeout(tryUpdate, Math.min(tries, 5) * 1000);
});
};
tryUpdate();
}
}
declare global {
interface HTMLElementTagNameMap {
"hassio-router": HassioRouter;
}
}

View File

@@ -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,
},
];

View File

@@ -5,7 +5,6 @@ import {
html, html,
LitElement, LitElement,
property, property,
internalProperty,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
@@ -17,56 +16,29 @@ import { createHassioSession } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { navigate } from "../../../src/common/navigate";
import { mdiMenu } from "@mdi/js";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("hassio-ingress-view") @customElement("hassio-ingress-view")
class HassioIngressView extends LitElement { class HassioIngressView extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public route!: Route; @property() public route!: Route;
@property() public ingressPanel = false; @property() private _addon?: HassioAddonDetails;
@internalProperty() private _addon?: HassioAddonDetails;
@property({ type: Boolean })
public narrow = false;
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._addon) { if (!this._addon) {
return html` <hass-loading-screen></hass-loading-screen> `; return html` <hass-loading-screen></hass-loading-screen> `;
} }
const iframe = html`<iframe src=${this._addon.ingress_url!}></iframe>`; return html`
<hass-subpage .header=${this._addon.name} hassio>
if (!this.ingressPanel) { <iframe src=${this._addon.ingress_url}></iframe>
return html`<hass-subpage </hass-subpage>
.header=${this._addon.name} `;
.narrow=${this.narrow}
>
${iframe}
</hass-subpage>`;
}
return html`${this.narrow || this.hass.dockedSidebar === "always_hidden"
? html`<div class="header">
<mwc-icon-button
aria-label=${this.hass.localize("ui.sidebar.sidebar_toggle")}
@click=${this._toggleMenu}
>
<ha-svg-icon path=${mdiMenu}></ha-svg-icon>
</mwc-icon-button>
<div class="main-title">${this._addon.name}</div>
</div>
${iframe}`
: iframe}`;
} }
protected updated(changedProps: PropertyValues) { protected updated(changedProps: PropertyValues) {
super.updated(changedProps); super.firstUpdated(changedProps);
if (!changedProps.has("route")) { if (!changedProps.has("route")) {
return; return;
@@ -83,56 +55,27 @@ class HassioIngressView extends LitElement {
} }
private async _fetchData(addonSlug: string) { private async _fetchData(addonSlug: string) {
const createSessionPromise = createHassioSession(this.hass).then(
() => true,
() => false
);
let addon;
try { try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug); const [addon] = await Promise.all([
} catch (err) { fetchHassioAddonInfo(this.hass, addonSlug).catch(() => {
await showAlertDialog(this, { throw new Error("Failed to fetch add-on info");
text: "Unable to fetch add-on info to start Ingress", }),
title: "Supervisor", createHassioSession(this.hass).catch(() => {
}); throw new Error("Failed to create an ingress session");
history.back(); }),
return; ]);
}
if (!addon.ingress_url) { if (!addon.ingress) {
await showAlertDialog(this, { throw new Error("This add-on does not support ingress");
text: "Add-on does not support Ingress",
title: addon.name,
});
history.back();
return;
}
if (addon.state !== "started") {
await showAlertDialog(this, {
text: "Add-on is not running. Please start it first",
title: addon.name,
});
navigate(this, `/hassio/addon/${addon.slug}/info`, true);
return;
}
if (!(await createSessionPromise)) {
await showAlertDialog(this, {
text: "Unable to create an Ingress session",
title: addon.name,
});
history.back();
return;
} }
this._addon = addon; this._addon = addon;
} catch (err) {
// eslint-disable-next-line
console.error(err);
alert(err.message || "Unknown error starting ingress.");
history.back();
} }
private _toggleMenu(): void {
fireEvent(this, "hass-toggle-menu");
} }
static get styles(): CSSResult { static get styles(): CSSResult {
@@ -143,41 +86,6 @@ class HassioIngressView extends LitElement {
height: 100%; height: 100%;
border: 0; border: 0;
} }
.header + iframe {
height: calc(100% - 40px);
}
.header {
display: flex;
align-items: center;
font-size: 16px;
height: 40px;
padding: 0 16px;
pointer-events: none;
background-color: var(--app-header-background-color);
font-weight: 400;
color: var(--app-header-text-color, white);
border-bottom: var(--app-header-border-bottom, none);
box-sizing: border-box;
--mdc-icon-size: 20px;
}
.main-title {
margin: 0 0 0 24px;
line-height: 20px;
flex-grow: 1;
}
mwc-icon-button {
pointer-events: auto;
}
hass-subpage {
--app-header-background-color: var(--sidebar-background-color);
--app-header-text-color: var(--sidebar-text-color);
--app-header-border-bottom: 1px solid var(--divider-color);
}
`; `;
} }
} }

View File

@@ -13,17 +13,14 @@ import {
CSSResultArray, CSSResultArray,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { import {
createHassioFullSnapshot, createHassioFullSnapshot,
createHassioPartialSnapshot, createHassioPartialSnapshot,
@@ -40,7 +37,7 @@ import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import "../components/hassio-card-content"; import "../components/hassio-card-content";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot"; 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"; import { hassioStyle } from "../resources/hassio-style";
interface CheckboxItem { interface CheckboxItem {
@@ -59,19 +56,19 @@ class HassioSnapshots extends LitElement {
@property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo; @property({ attribute: false }) public supervisorInfo!: HassioSupervisorInfo;
@internalProperty() private _snapshotName = ""; @property() private _snapshotName = "";
@internalProperty() private _snapshotPassword = ""; @property() private _snapshotPassword = "";
@internalProperty() private _snapshotHasPassword = false; @property() private _snapshotHasPassword = false;
@internalProperty() private _snapshotType: HassioSnapshot["type"] = "full"; @property() private _snapshotType: HassioSnapshot["type"] = "full";
@internalProperty() private _snapshots?: HassioSnapshot[] = []; @property() private _snapshots?: HassioSnapshot[] = [];
@internalProperty() private _addonList: CheckboxItem[] = []; @property() private _addonList: CheckboxItem[] = [];
@internalProperty() private _folderList: CheckboxItem[] = [ @property() private _folderList: CheckboxItem[] = [
{ {
slug: "homeassistant", slug: "homeassistant",
name: "Home Assistant configuration", name: "Home Assistant configuration",
@@ -82,7 +79,9 @@ class HassioSnapshots extends LitElement {
{ slug: "addons/local", name: "Local add-ons", checked: true }, { slug: "addons/local", name: "Local add-ons", checked: true },
]; ];
@internalProperty() private _error = ""; @property() private _creatingSnapshot = false;
@property() private _error = "";
public async refreshData() { public async refreshData() {
await reloadHassioSnapshots(this.hass); await reloadHassioSnapshots(this.hass);
@@ -192,9 +191,12 @@ class HassioSnapshots extends LitElement {
: undefined} : undefined}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button @click=${this._createSnapshot}> <mwc-button
.disabled=${this._creatingSnapshot}
@click=${this._createSnapshot}
>
Create Create
</ha-progress-button> </mwc-button>
</div> </div>
</ha-card> </ha-card>
</div> </div>
@@ -227,7 +229,7 @@ class HassioSnapshots extends LitElement {
.icon=${snapshot.type === "full" .icon=${snapshot.type === "full"
? mdiPackageVariantClosed ? mdiPackageVariantClosed
: mdiPackageVariant} : mdiPackageVariant}
icon-class="snapshot" .icon-class="snapshot"
></hassio-card-content> ></hassio-card-content>
</div> </div>
</ha-card> </ha-card>
@@ -290,20 +292,17 @@ class HassioSnapshots extends LitElement {
this._snapshots = await fetchHassioSnapshots(this.hass); this._snapshots = await fetchHassioSnapshots(this.hass);
this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1)); this._snapshots.sort((a, b) => (a.date < b.date ? 1 : -1));
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = err.message;
} }
} }
private async _createSnapshot(ev: CustomEvent): Promise<void> { private async _createSnapshot() {
const button = ev.currentTarget as any;
button.progress = true;
this._error = ""; this._error = "";
if (this._snapshotHasPassword && !this._snapshotPassword.length) { if (this._snapshotHasPassword && !this._snapshotPassword.length) {
this._error = "Please enter a password."; this._error = "Please enter a password.";
button.progress = false;
return; return;
} }
this._creatingSnapshot = true;
await this.updateComplete; await this.updateComplete;
const name = const name =
@@ -343,9 +342,10 @@ class HassioSnapshots extends LitElement {
this._updateSnapshots(); this._updateSnapshots();
fireEvent(this, "hass-api-called", { success: true, response: null }); fireEvent(this, "hass-api-called", { success: true, response: null });
} catch (err) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = err.message;
} finally {
this._creatingSnapshot = false;
} }
button.progress = false;
} }
private _computeDetails(snapshot: HassioSnapshot) { private _computeDetails(snapshot: HassioSnapshot) {

View File

@@ -1,32 +1,17 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { safeDump } from "js-yaml";
import { import {
css, css,
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import memoizeOne from "memoize-one"; import "../../../src/components/buttons/ha-call-api-button";
import { atLeastVersion } from "../../../src/common/config/version";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import {
extractApiErrorMessage,
ignoredStatusCodes,
} from "../../../src/data/hassio/common";
import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware"; import { fetchHassioHardwareInfo } from "../../../src/data/hassio/hardware";
import { import {
changeHostOptions, changeHostOptions,
configSyncOS,
fetchHassioHostInfo, fetchHassioHostInfo,
HassioHassOSInfo, HassioHassOSInfo,
HassioHostInfo as HassioHostInfoType, HassioHostInfo as HassioHostInfoType,
@@ -34,11 +19,6 @@ import {
shutdownHost, shutdownHost,
updateOS, updateOS,
} from "../../../src/data/hassio/host"; } from "../../../src/data/hassio/host";
import {
fetchNetworkInfo,
NetworkInfo,
} from "../../../src/data/hassio/network";
import { HassioInfo } from "../../../src/data/hassio/supervisor";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -47,192 +27,168 @@ import {
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown"; import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info") @customElement("hassio-host-info")
class HassioHostInfo extends LitElement { class HassioHostInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public hostInfo!: HassioHostInfoType; @property() public hostInfo!: HassioHostInfoType;
@property({ attribute: false }) public hassioInfo!: HassioInfo; @property() public hassOsInfo!: HassioHassOSInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo; @property() private _errors?: string;
@internalProperty() public _networkInfo?: NetworkInfo; public render(): TemplateResult | void {
protected render(): TemplateResult | void {
const primaryIpAddress = this.hostInfo.features.includes("network")
? this._primaryIpAddress(this._networkInfo!)
: "";
return html` return html`
<ha-card header="Host System"> <ha-card>
<div class="card-content"> <div class="card-content">
${this.hostInfo.features.includes("hostname") <h2>Host system</h2>
? html`<ha-settings-row> <table class="info">
<span slot="heading"> <tbody>
Hostname <tr>
</span> <td>Hostname</td>
<span slot="description"> <td>${this.hostInfo.hostname}</td>
${this.hostInfo.hostname} </tr>
</span> <tr>
<mwc-button <td>System</td>
title="Change the hostname" <td>${this.hostInfo.operating_system}</td>
label="Change" </tr>
@click=${this._changeHostnameClicked} ${this.hostInfo.deployment
>
</mwc-button>
</ha-settings-row>`
: ""}
${this.hostInfo.features.includes("network") &&
atLeastVersion(this.hass.config.version, 0, 115)
? html` <ha-settings-row>
<span slot="heading">
IP address
</span>
<span slot="description">
${primaryIpAddress}
</span>
<mwc-button
title="Change the network"
label="Change"
@click=${this._changeNetworkClicked}
>
</mwc-button>
</ha-settings-row>`
: ""}
<ha-settings-row>
<span slot="heading">
Operating system
</span>
<span slot="description">
${this.hostInfo.operating_system}
</span>
${this.hostInfo.version !== this.hostInfo.version_latest &&
this.hostInfo.features.includes("hassos")
? html` ? html`
<ha-progress-button <tr>
title="Update the host OS" <td>Deployment</td>
@click=${this._osUpdate} <td>${this.hostInfo.deployment}</td>
> </tr>
Update
</ha-progress-button>
` `
: ""} : ""}
</ha-settings-row> </tbody>
${!this.hostInfo.features.includes("hassos") </table>
? html`<ha-settings-row> <mwc-button raised @click=${this._showHardware} class="info">
<span slot="heading"> Hardware
Docker version </mwc-button>
</span> ${this.hostInfo.features.includes("hostname")
<span slot="description"> ? html`
${this.hassioInfo.docker} <mwc-button
</span> raised
</ha-settings-row>` @click=${this._changeHostnameClicked}
class="info"
>
Change hostname
</mwc-button>
`
: ""} : ""}
${this.hostInfo.deployment ${this._errors
? html`<ha-settings-row> ? html` <div class="errors">Error: ${this._errors}</div> `
<span slot="heading">
Deployment
</span>
<span slot="description">
${this.hostInfo.deployment}
</span>
</ha-settings-row>`
: ""} : ""}
</div> </div>
<div class="card-actions"> <div class="card-actions">
${this.hostInfo.features.includes("reboot") ${this.hostInfo.features.includes("reboot")
? html` ? html`
<ha-progress-button <mwc-button class="warning" @click=${this._rebootHost}
title="Reboot the host OS" >Reboot</mwc-button
class="warning"
@click=${this._hostReboot}
> >
Reboot
</ha-progress-button>
` `
: ""} : ""}
${this.hostInfo.features.includes("shutdown") ${this.hostInfo.features.includes("shutdown")
? html` ? html`
<ha-progress-button <mwc-button class="warning" @click=${this._shutdownHost}
title="Shutdown the host OS" >Shutdown</mwc-button
class="warning"
@click=${this._hostShutdown}
> >
Shutdown
</ha-progress-button>
` `
: ""} : ""}
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item title="Show a list of hardware">
Hardware
</mwc-list-item>
${this.hostInfo.features.includes("hassos") ${this.hostInfo.features.includes("hassos")
? html`<mwc-list-item ? html`
<ha-call-api-button
class="warning"
.hass=${this.hass}
path="hassio/os/config/sync"
title="Load HassOS configs or updates from USB" title="Load HassOS configs or updates from USB"
>Import from USB</ha-call-api-button
> >
Import from USB `
</mwc-list-item>` : ""}
${this.hostInfo.version !== this.hostInfo.version_latest
? html` <mwc-button @click=${this._updateOS}>Update</mwc-button> `
: ""} : ""}
</ha-button-menu>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
width: 100%;
}
.card-content {
color: var(--primary-text-color);
box-sizing: border-box;
height: calc(100% - 47px);
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
mwc-button.info {
max-width: calc(50% - 12px);
}
table.info {
margin-bottom: 10px;
}
.warning {
--mdc-theme-primary: var(--google-red-500);
}
`,
];
}
protected firstUpdated(): void { protected firstUpdated(): void {
this._loadData(); this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
} }
private _primaryIpAddress = memoizeOne((network_info: NetworkInfo) => { private _apiCalled(ev): void {
if (!network_info) { if (ev.detail.success) {
return ""; this._errors = undefined;
return;
} }
return Object.keys(network_info?.interfaces)
.map((device) => network_info.interfaces[device])
.find((device) => device.primary)?.ip_address;
});
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) { const response = ev.detail.response;
switch (ev.detail.index) {
case 0: this._errors =
await this._showHardware(); typeof response.body === "object"
break; ? response.body.message || "Unknown error"
case 1: : response.body;
await this._importFromUSB();
break;
}
} }
private async _showHardware(): Promise<void> { private async _showHardware(): Promise<void> {
try { try {
const content = await fetchHassioHardwareInfo(this.hass); const content = this._objectToMarkdown(
await fetchHassioHardwareInfo(this.hass)
);
showHassioMarkdownDialog(this, { showHassioMarkdownDialog(this, {
title: "Hardware", title: "Hardware",
content: `<pre>${safeDump(content, { indent: 2 })}</pre>`, content,
}); });
} catch (err) { } catch (err) {
showAlertDialog(this, { showHassioMarkdownDialog(this, {
title: "Failed to get Hardware list", title: "Hardware",
text: extractApiErrorMessage(err), content: "Error getting hardware info",
}); });
} }
} }
private async _hostReboot(ev: CustomEvent): Promise<void> { private async _rebootHost(): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: "Reboot", title: "Reboot",
text: "Are you sure you want to reboot the host?", text: "Are you sure you want to reboot the host?",
@@ -241,28 +197,20 @@ class HassioHostInfo extends LitElement {
}); });
if (!confirmed) { if (!confirmed) {
button.progress = false;
return; return;
} }
try { try {
await rebootHost(this.hass); await rebootHost(this.hass);
} catch (err) { } catch (err) {
// Ignore connection errors, these are all expected
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to reboot", title: "Failed to reboot",
text: extractApiErrorMessage(err), text: err.body.message,
}); });
} }
} }
button.progress = false;
}
private async _hostShutdown(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
private async _shutdownHost(): Promise<void> {
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: "Shutdown", title: "Shutdown",
text: "Are you sure you want to shutdown the host?", text: "Are you sure you want to shutdown the host?",
@@ -271,28 +219,20 @@ class HassioHostInfo extends LitElement {
}); });
if (!confirmed) { if (!confirmed) {
button.progress = false;
return; return;
} }
try { try {
await shutdownHost(this.hass); await shutdownHost(this.hass);
} catch (err) { } catch (err) {
// Ignore connection errors, these are all expected
if (err.status_code && !ignoredStatusCodes.has(err.status_code)) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to shutdown", title: "Failed to shutdown",
text: extractApiErrorMessage(err), text: err.body.message,
}); });
} }
} }
button.progress = false;
}
private async _osUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
private async _updateOS(): Promise<void> {
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: "Update", title: "Update",
text: "Are you sure you want to update the OS?", text: "Are you sure you want to update the OS?",
@@ -301,7 +241,6 @@ class HassioHostInfo extends LitElement {
}); });
if (!confirmed) { if (!confirmed) {
button.progress = false;
return; return;
} }
@@ -310,17 +249,30 @@ class HassioHostInfo extends LitElement {
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to update", title: "Failed to update",
text: extractApiErrorMessage(err), text: err.body.message,
}); });
} }
button.progress = false;
} }
private async _changeNetworkClicked(): Promise<void> { private _objectToMarkdown(obj, indent = ""): string {
showNetworkDialog(this, { let data = "";
network: this._networkInfo!, Object.keys(obj).forEach((key) => {
loadData: () => this._loadData(), if (typeof obj[key] !== "object") {
data += `${indent}- ${key}: ${obj[key]}\n`;
} else {
data += `${indent}- ${key}:\n`;
if (Array.isArray(obj[key])) {
if (obj[key].length) {
data +=
`${indent} - ` + obj[key].join(`\n${indent} - `) + "\n";
}
} else {
data += this._objectToMarkdown(obj[key], ` ${indent}`);
}
}
}); });
return data;
} }
private async _changeHostnameClicked(): Promise<void> { private async _changeHostnameClicked(): Promise<void> {
@@ -339,83 +291,11 @@ class HassioHostInfo extends LitElement {
} catch (err) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Setting hostname failed", title: "Setting hostname failed",
text: extractApiErrorMessage(err), text: err.body.message,
}); });
} }
} }
} }
private async _importFromUSB(): Promise<void> {
try {
await configSyncOS(this.hass);
this.hostInfo = await fetchHassioHostInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to import from USB",
text: extractApiErrorMessage(err),
});
}
}
private async _loadData(): Promise<void> {
this._networkInfo = await fetchNetworkInfo(this.hass);
}
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
.card-actions {
height: 48px;
border-top: none;
display: flex;
justify-content: space-between;
align-items: center;
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row[three-line] {
height: 74px;
}
ha-settings-row > span[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
.warning {
--mdc-theme-primary: var(--error-color);
}
ha-button-menu {
color: var(--secondary-text-color);
--mdc-menu-min-width: 200px;
}
@media (min-width: 563px) {
paper-listbox {
max-height: 150px;
overflow: auto;
}
}
paper-item {
cursor: pointer;
min-height: 35px;
}
mwc-list-item ha-svg-icon {
color: var(--secondary-text-color);
}
`,
];
}
} }
declare global { declare global {

View File

@@ -1,3 +1,4 @@
import "@material/mwc-button";
import { import {
css, css,
CSSResult, CSSResult,
@@ -7,148 +8,141 @@ import {
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../src/components/buttons/ha-progress-button"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-call-api-button";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch";
import { HassioHostInfo as HassioHostInfoType } from "../../../src/data/hassio/host";
import { import {
HassioSupervisorInfo as HassioSupervisorInfoType, HassioSupervisorInfo as HassioSupervisorInfoType,
reloadSupervisor,
setSupervisorOption, setSupervisorOption,
SupervisorOptions, SupervisorOptions,
updateSupervisor,
fetchHassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor"; } from "../../../src/data/hassio/supervisor";
import { import { showConfirmationDialog } from "../../../src/dialogs/generic/show-dialog-box";
showAlertDialog,
showConfirmationDialog,
} from "../../../src/dialogs/generic/show-dialog-box";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
@customElement("hassio-supervisor-info") @customElement("hassio-supervisor-info")
class HassioSupervisorInfo extends LitElement { class HassioSupervisorInfo extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property() public hass!: HomeAssistant;
@property() public supervisorInfo!: HassioSupervisorInfoType; @property() public supervisorInfo!: HassioSupervisorInfoType;
@property() public hostInfo!: HassioHostInfoType; @property() private _errors?: string;
protected render(): TemplateResult | void { public render(): TemplateResult | void {
return html` return html`
<ha-card header="Supervisor"> <ha-card>
<div class="card-content"> <div class="card-content">
<ha-settings-row> <h2>Supervisor</h2>
<span slot="heading"> <table class="info">
Version <tbody>
</span> <tr>
<span slot="description"> <td>Version</td>
${this.supervisorInfo.version} <td>${this.supervisorInfo.version}</td>
</span> </tr>
</ha-settings-row> <tr>
<ha-settings-row> <td>Latest version</td>
<span slot="heading"> <td>${this.supervisorInfo.version_latest}</td>
Newest version </tr>
</span> ${this.supervisorInfo.channel !== "stable"
<span slot="description">
${this.supervisorInfo.version_latest}
</span>
${this.supervisorInfo.version !== this.supervisorInfo.version_latest
? html` ? html`
<ha-progress-button <tr>
title="Update the supervisor" <td>Channel</td>
@click=${this._supervisorUpdate} <td>${this.supervisorInfo.channel}</td>
> </tr>
Update
</ha-progress-button>
` `
: ""} : ""}
</ha-settings-row> </tbody>
<ha-settings-row> </table>
<span slot="heading"> ${this._errors
Channel ? html` <div class="errors">Error: ${this._errors}</div> `
</span>
<span slot="description">
${this.supervisorInfo.channel}
</span>
${this.supervisorInfo.channel === "beta"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get stable updates for Home Assistant, supervisor and host"
>
Leave beta channel
</ha-progress-button>
`
: this.supervisorInfo.channel === "stable"
? html`
<ha-progress-button
@click=${this._toggleBeta}
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>
Join beta channel
</ha-progress-button>
`
: ""} : ""}
</ha-settings-row>
${this.supervisorInfo?.supported
? html` <ha-settings-row three-line>
<span slot="heading">
Share diagnostics
</span>
<div slot="description" class="diagnostics-description">
Share crash reports and diagnostic information.
<button
class="link"
title="Show more information about this"
@click=${this._diagnosticsInformationDialog}
>
Learn more
</button>
</div>
<ha-switch
haptic
.checked=${this.supervisorInfo.diagnostics}
@change=${this._toggleDiagnostics}
></ha-switch>
</ha-settings-row>`
: html`<div class="error">
You are running an unsupported installation.
<a
href="https://github.com/home-assistant/architecture/blob/master/adr/${this.hostInfo.features.includes(
"hassos"
)
? "0015-home-assistant-os.md"
: "0014-home-assistant-supervised.md"}"
target="_blank"
rel="noreferrer"
title="Learn more about how you can make your system compliant"
>
Learn More
</a>
</div>`}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button <ha-call-api-button .hass=${this.hass} path="hassio/supervisor/reload"
@click=${this._supervisorReload} >Reload</ha-call-api-button
title="Reload parts of the supervisor."
> >
Reload ${this.supervisorInfo.version !== this.supervisorInfo.version_latest
</ha-progress-button> ? html`
<ha-call-api-button
.hass=${this.hass}
path="hassio/supervisor/update"
>Update</ha-call-api-button
>
`
: ""}
${this.supervisorInfo.channel === "beta"
? html`
<ha-call-api-button
.hass=${this.hass}
path="hassio/supervisor/options"
.data=${{ channel: "stable" }}
>Leave beta channel</ha-call-api-button
>
`
: ""}
${this.supervisorInfo.channel === "stable"
? html`
<mwc-button
@click=${this._joinBeta}
class="warning"
title="Get beta updates for Home Assistant (RCs), supervisor and host"
>Join beta channel</mwc-button
>
`
: ""}
</div> </div>
</ha-card> </ha-card>
`; `;
} }
private async _toggleBeta(ev: CustomEvent): Promise<void> { static get styles(): CSSResult[] {
const button = ev.currentTarget as any; return [
button.progress = true; haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
width: 100%;
}
.card-content {
color: var(--primary-text-color);
box-sizing: border-box;
height: calc(100% - 47px);
}
.info {
width: 100%;
}
.info td:nth-child(2) {
text-align: right;
}
.errors {
color: var(--google-red-500);
margin-top: 16px;
}
`,
];
}
if (this.supervisorInfo.channel === "stable") { protected firstUpdated(): void {
this.addEventListener("hass-api-called", (ev) => this._apiCalled(ev));
}
private _apiCalled(ev): void {
if (ev.detail.success) {
this._errors = undefined;
return;
}
const response = ev.detail.response;
this._errors =
typeof response.body === "object"
? response.body.message || "Unknown error"
: response.body;
}
private async _joinBeta() {
const confirmed = await showConfirmationDialog(this, { const confirmed = await showConfirmationDialog(this, {
title: "WARNING", title: "WARNING",
text: html` Beta releases are for testers and early adopters and can text: html` Beta releases are for testers and early adopters and can
@@ -170,135 +164,22 @@ class HassioSupervisorInfo extends LitElement {
}); });
if (!confirmed) { if (!confirmed) {
button.progress = false;
return;
}
}
try {
const data: Partial<SupervisorOptions> = {
channel: this.supervisorInfo.channel === "stable" ? "beta" : "stable",
};
await setSupervisorOption(this.hass, data);
await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to set supervisor option",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _supervisorReload(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
await reloadSupervisor(this.hass);
this.supervisorInfo = await fetchHassioSupervisorInfo(this.hass);
} catch (err) {
showAlertDialog(this, {
title: "Failed to reload the supervisor",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _supervisorUpdate(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
const confirmed = await showConfirmationDialog(this, {
title: "Update supervisor",
text: `Are you sure you want to upgrade supervisor to version ${this.supervisorInfo.version_latest}?`,
confirmText: "update",
dismissText: "cancel",
});
if (!confirmed) {
button.progress = false;
return; return;
} }
try { try {
await updateSupervisor(this.hass); const data: SupervisorOptions = { channel: "beta" };
} catch (err) {
showAlertDialog(this, {
title: "Failed to update the supervisor",
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _diagnosticsInformationDialog(): Promise<void> {
await showAlertDialog(this, {
title: "Help Improve Home Assistant",
text: html`Would you want to automatically share crash reports and
diagnostic information when the supervisor encounters unexpected errors?
<br /><br />
This will allow us to fix the problems, the information is only
accessible to the Home Assistant Core team and will not be shared with
others.
<br /><br />
The data does not include any private/sensitive information and you can
disable this in settings at any time you want.`,
});
}
private async _toggleDiagnostics(): Promise<void> {
try {
const data: SupervisorOptions = {
diagnostics: !this.supervisorInfo?.diagnostics,
};
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
const eventdata = {
success: true,
response: undefined,
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err) { } catch (err) {
showAlertDialog(this, { this._errors = `Error joining beta channel, ${err.body?.message || err}`;
title: "Failed to set supervisor option",
text: extractApiErrorMessage(err),
});
} }
} }
static get styles(): CSSResult[] {
return [
haStyle,
hassioStyle,
css`
ha-card {
height: 100%;
justify-content: space-between;
flex-direction: column;
display: flex;
}
.card-actions {
height: 48px;
border-top: none;
display: flex;
justify-content: space-between;
align-items: center;
}
button.link {
color: var(--primary-color);
}
ha-settings-row {
padding: 0;
height: 54px;
width: 100%;
}
ha-settings-row[three-line] {
height: 74px;
}
ha-settings-row > div[slot="description"] {
white-space: normal;
color: var(--secondary-text-color);
}
`,
];
}
} }
declare global { declare global {

View File

@@ -7,16 +7,13 @@ import {
CSSResult, CSSResult,
customElement, customElement,
html, html,
internalProperty,
LitElement, LitElement,
property, property,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor"; import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-loading-screen"; import "../../../src/layouts/loading-screen";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import "../components/hassio-ansi-to-html"; import "../components/hassio-ansi-to-html";
@@ -58,18 +55,18 @@ const logProviders: LogProvider[] = [
class HassioSupervisorLog extends LitElement { class HassioSupervisorLog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@internalProperty() private _error?: string; @property() private _error?: string;
@internalProperty() private _selectedLogProvider = "supervisor"; @property() private _selectedLogProvider = "supervisor";
@internalProperty() private _content?: string; @property() private _content?: string;
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
super.connectedCallback(); super.connectedCallback();
await this._loadData(); await this._loadData();
} }
protected render(): TemplateResult | void { public render(): TemplateResult | void {
return html` return html`
<ha-card> <ha-card>
${this._error ? html` <div class="errors">${this._error}</div> ` : ""} ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
@@ -101,52 +98,21 @@ class HassioSupervisorLog extends LitElement {
? html`<hassio-ansi-to-html ? html`<hassio-ansi-to-html
.content=${this._content} .content=${this._content}
></hassio-ansi-to-html>` ></hassio-ansi-to-html>`
: html`<hass-loading-screen no-toolbar></hass-loading-screen>`} : html`<loading-screen></loading-screen>`}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<ha-progress-button @click=${this._refresh}> <mwc-button @click=${this._refresh}>Refresh</mwc-button>
Refresh
</ha-progress-button>
</div> </div>
</ha-card> </ha-card>
`; `;
} }
private async _setLogProvider(ev): Promise<void> {
const provider = ev.detail.item.getAttribute("provider");
this._selectedLogProvider = provider;
this._loadData();
}
private async _refresh(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
await this._loadData();
button.progress = false;
}
private async _loadData(): Promise<void> {
this._error = undefined;
try {
this._content = await fetchHassioLogs(
this.hass,
this._selectedLogProvider
);
} catch (err) {
this._error = `Failed to get supervisor logs, ${extractApiErrorMessage(
err
)}`;
}
}
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
haStyle, haStyle,
hassioStyle, hassioStyle,
css` css`
ha-card { ha-card {
margin-top: 8px;
width: 100%; width: 100%;
} }
pre { pre {
@@ -157,12 +123,41 @@ class HassioSupervisorLog extends LitElement {
width: 96%; width: 96%;
} }
.errors { .errors {
color: var(--error-color); color: var(--google-red-500);
margin-bottom: 16px; margin-bottom: 16px;
} }
.card-content {
padding-top: 0px;
}
`, `,
]; ];
} }
private async _setLogProvider(ev): Promise<void> {
const provider = ev.detail.item.getAttribute("provider");
this._selectedLogProvider = provider;
await this._loadData();
}
private async _loadData(): Promise<void> {
this._error = undefined;
this._content = undefined;
try {
this._content = await fetchHassioLogs(
this.hass,
this._selectedLogProvider
);
} catch (err) {
this._error = `Failed to get supervisor logs, ${
err.body?.message || err
}`;
}
}
private async _refresh(): Promise<void> {
await this._loadData();
}
} }
declare global { declare global {

View File

@@ -1,3 +1,4 @@
import "@polymer/paper-menu-button/paper-menu-button";
import { import {
css, css,
CSSResult, CSSResult,
@@ -11,14 +12,11 @@ import {
HassioHassOSInfo, HassioHassOSInfo,
HassioHostInfo, HassioHostInfo,
} from "../../../src/data/hassio/host"; } from "../../../src/data/hassio/host";
import { import { HassioSupervisorInfo } from "../../../src/data/hassio/supervisor";
HassioInfo,
HassioSupervisorInfo,
} from "../../../src/data/hassio/supervisor";
import "../../../src/layouts/hass-tabs-subpage"; import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-panel";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
import "./hassio-host-info"; import "./hassio-host-info";
import "./hassio-supervisor-info"; import "./hassio-supervisor-info";
@@ -34,13 +32,11 @@ class HassioSystem extends LitElement {
@property() public supervisorInfo!: HassioSupervisorInfo; @property() public supervisorInfo!: HassioSupervisorInfo;
@property({ attribute: false }) public hassioInfo!: HassioInfo;
@property() public hostInfo!: HassioHostInfo; @property() public hostInfo!: HassioHostInfo;
@property({ attribute: false }) public hassOsInfo!: HassioHassOSInfo; @property() public hassOsInfo!: HassioHassOSInfo;
protected render(): TemplateResult | void { public render(): TemplateResult | void {
return html` return html`
<hass-tabs-subpage <hass-tabs-subpage
.hass=${this.hass} .hass=${this.hass}
@@ -52,19 +48,19 @@ class HassioSystem extends LitElement {
> >
<span slot="header">System</span> <span slot="header">System</span>
<div class="content"> <div class="content">
<h1>Information</h1>
<div class="card-group"> <div class="card-group">
<hassio-supervisor-info <hassio-supervisor-info
.hass=${this.hass} .hass=${this.hass}
.hostInfo=${this.hostInfo}
.supervisorInfo=${this.supervisorInfo} .supervisorInfo=${this.supervisorInfo}
></hassio-supervisor-info> ></hassio-supervisor-info>
<hassio-host-info <hassio-host-info
.hass=${this.hass} .hass=${this.hass}
.hassioInfo=${this.hassioInfo}
.hostInfo=${this.hostInfo} .hostInfo=${this.hostInfo}
.hassOsInfo=${this.hassOsInfo} .hassOsInfo=${this.hassOsInfo}
></hassio-host-info> ></hassio-host-info>
</div> </div>
<h1>System log</h1>
<hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log> <hassio-supervisor-log .hass=${this.hass}></hassio-supervisor-log>
</div> </div>
</hass-tabs-subpage> </hass-tabs-subpage>

View File

@@ -1,8 +1,11 @@
const { createHassioConfig } = require("../build-scripts/webpack.js"); 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({ module.exports = createHassioConfig({
isProdBuild: isProdBuild(), isProdBuild: isProdBuild(),
isStatsBuild: isStatsBuild(), latestBuild,
latestBuild: true,
}); });

View File

@@ -13,39 +13,33 @@
"lint:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --check", "lint:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --check",
"format:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --write", "format:prettier": "prettier '**/src/**/*.{js,ts,json,css,md}' --write",
"lint:types": "tsc", "lint:types": "tsc",
"lint:lit": "lit-analyzer '**/src/**/*.ts' --format markdown --outFile result.md", "lint:lit": "lit-analyzer '**/src/**/*.ts'",
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types", "lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
"format": "npm run format:eslint && npm run format:prettier", "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", "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)", "author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@formatjs/intl-pluralrules": "^1.5.8", "@formatjs/intl-pluralrules": "^1.5.8",
"@fullcalendar/common": "5.1.0", "@fullcalendar/core": "^5.0.0-beta.2",
"@fullcalendar/core": "5.1.0", "@fullcalendar/daygrid": "^5.0.0-beta.2",
"@fullcalendar/daygrid": "5.1.0", "@material/chips": "7.0.0-canary.d92d8c93e.0",
"@fullcalendar/interaction": "5.1.0", "@material/mwc-button": "^0.15.0",
"@fullcalendar/list": "5.1.0", "@material/mwc-checkbox": "^0.15.0",
"@material/chips": "=8.0.0-canary.096a7a066.0", "@material/mwc-dialog": "^0.15.0",
"@material/circular-progress": "=8.0.0-canary.a78ceb112.0", "@material/mwc-fab": "^0.15.0",
"@material/mwc-button": "^0.18.0", "@material/mwc-formfield": "^0.15.0",
"@material/mwc-checkbox": "^0.18.0", "@material/mwc-icon-button": "^0.15.0",
"@material/mwc-dialog": "^0.18.0", "@material/mwc-list": "^0.15.0",
"@material/mwc-fab": "^0.18.0", "@material/mwc-menu": "^0.15.0",
"@material/mwc-formfield": "^0.18.0", "@material/mwc-ripple": "^0.15.0",
"@material/mwc-icon-button": "^0.18.0", "@material/mwc-switch": "^0.15.0",
"@material/mwc-list": "^0.18.0", "@mdi/js": "4.9.95",
"@material/mwc-menu": "^0.18.0", "@mdi/svg": "4.9.95",
"@material/mwc-radio": "^0.18.0",
"@material/mwc-ripple": "^0.18.0",
"@material/mwc-switch": "^0.18.0",
"@material/mwc-tab": "^0.18.0",
"@material/mwc-tab-bar": "^0.18.0",
"@material/top-app-bar": "=8.0.0-canary.096a7a066.0",
"@mdi/js": "5.5.55",
"@mdi/svg": "5.5.55",
"@polymer/app-layout": "^3.0.2", "@polymer/app-layout": "^3.0.2",
"@polymer/app-route": "^3.0.2", "@polymer/app-route": "^3.0.2",
"@polymer/app-storage": "^3.0.2", "@polymer/app-storage": "^3.0.2",
@@ -55,6 +49,7 @@
"@polymer/iron-image": "^3.0.1", "@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1", "@polymer/iron-input": "^3.0.1",
"@polymer/iron-label": "^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-overlay-behavior": "^3.0.2",
"@polymer/iron-resizable-behavior": "^3.0.1", "@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-card": "^3.0.1", "@polymer/paper-card": "^3.0.1",
@@ -72,32 +67,29 @@
"@polymer/paper-radio-group": "^3.0.1", "@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.1", "@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-slider": "^3.0.1", "@polymer/paper-slider": "^3.0.1",
"@polymer/paper-spinner": "^3.0.2",
"@polymer/paper-styles": "^3.0.1", "@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.0.1", "@polymer/paper-tabs": "^3.0.1",
"@polymer/paper-toast": "^3.0.1", "@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1", "@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.0", "@thomasloven/round-slider": "0.5.0",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/sortablejs": "^1.10.6",
"@vaadin/vaadin-combo-box": "^5.0.10", "@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7", "@vaadin/vaadin-date-picker": "^4.0.7",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.7", "@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "~2.8.0", "chart.js": "~2.8.0",
"chartjs-chart-timeline": "^0.3.0", "chartjs-chart-timeline": "^0.3.0",
"codemirror": "^5.49.0", "codemirror": "^5.49.0",
"comlink": "^4.3.0", "comlink": "^4.3.0",
"cpx": "^1.5.0", "cpx": "^1.5.0",
"cropperjs": "^1.5.7",
"deep-clone-simple": "^1.1.1", "deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1", "deep-freeze": "^0.0.1",
"es6-object-assign": "^1.1.0", "es6-object-assign": "^1.1.0",
"fecha": "^4.2.0", "fecha": "^4.2.0",
"fuse.js": "^6.0.0", "fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2", "google-timezones-json": "^1.0.2",
"hls.js": "^0.13.2", "hls.js": "^0.12.4",
"home-assistant-js-websocket": "^5.4.1", "home-assistant-js-websocket": "^5.2.1",
"idb-keyval": "^3.2.0", "idb-keyval": "^3.2.0",
"intl-messageformat": "^8.3.9", "intl-messageformat": "^8.3.9",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
@@ -106,20 +98,16 @@
"lit-element": "^2.3.1", "lit-element": "^2.3.1",
"lit-html": "^1.2.1", "lit-html": "^1.2.1",
"lit-virtualizer": "^0.4.2", "lit-virtualizer": "^0.4.2",
"marked": "^1.1.1", "marked": "^0.6.1",
"mdn-polyfills": "^5.16.0", "mdn-polyfills": "^5.16.0",
"memoize-one": "^5.0.2", "memoize-one": "^5.0.2",
"node-vibrant": "^3.1.5", "node-vibrant": "^3.1.5",
"proxy-polyfill": "^0.3.1", "proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1",
"regenerator-runtime": "^0.13.2", "regenerator-runtime": "^0.13.2",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"sortablejs": "^1.10.2", "superstruct": "^0.6.1",
"superstruct": "^0.10.12",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"vue": "^2.6.11",
"vue2-daterange-picker": "^0.5.1",
"web-animations-js": "^2.3.2", "web-animations-js": "^2.3.2",
"workbox-core": "^5.1.3", "workbox-core": "^5.1.3",
"workbox-precaching": "^5.1.3", "workbox-precaching": "^5.1.3",
@@ -144,14 +132,13 @@
"@rollup/plugin-replace": "^2.3.2", "@rollup/plugin-replace": "^2.3.2",
"@types/chai": "^4.1.7", "@types/chai": "^4.1.7",
"@types/chromecast-caf-receiver": "^3.0.12", "@types/chromecast-caf-receiver": "^3.0.12",
"@types/codemirror": "^0.0.97", "@types/codemirror": "^0.0.78",
"@types/hls.js": "^0.12.3", "@types/hls.js": "^0.12.3",
"@types/js-yaml": "^3.12.1", "@types/js-yaml": "^3.12.1",
"@types/leaflet": "^1.4.3", "@types/leaflet": "^1.4.3",
"@types/leaflet-draw": "^1.0.1", "@types/leaflet-draw": "^1.0.1",
"@types/marked": "^1.1.0",
"@types/memoize-one": "4.1.0", "@types/memoize-one": "4.1.0",
"@types/mocha": "^7.0.2", "@types/mocha": "^5.2.6",
"@types/resize-observer-browser": "^0.1.3", "@types/resize-observer-browser": "^0.1.3",
"@types/webspeechapi": "^0.0.29", "@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^2.28.0", "@typescript-eslint/eslint-plugin": "^2.28.0",
@@ -162,7 +149,7 @@
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-airbnb-typescript": "^7.2.1", "eslint-config-airbnb-typescript": "^7.2.1",
"eslint-config-prettier": "^6.10.1", "eslint-config-prettier": "^6.10.1",
"eslint-import-resolver-webpack": "^0.12.2", "eslint-import-resolver-webpack": "^0.12.1",
"eslint-plugin-disable": "^2.0.1", "eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-lit": "^1.2.0", "eslint-plugin-lit": "^1.2.0",
@@ -180,12 +167,12 @@
"html-minifier": "^4.0.0", "html-minifier": "^4.0.0",
"husky": "^1.3.1", "husky": "^1.3.1",
"lint-staged": "^8.1.5", "lint-staged": "^8.1.5",
"lit-analyzer": "^1.2.0", "lit-analyzer": "^1.1.10",
"lodash.template": "^4.5.0", "lodash.template": "^4.5.0",
"magic-string": "^0.25.7", "magic-string": "^0.25.7",
"map-stream": "^0.0.7", "map-stream": "^0.0.7",
"merge-stream": "^1.0.1", "merge-stream": "^1.0.1",
"mocha": "^7.2.0", "mocha": "^6.0.2",
"object-hash": "^2.0.3", "object-hash": "^2.0.3",
"open": "^7.0.4", "open": "^7.0.4",
"prettier": "^2.0.4", "prettier": "^2.0.4",
@@ -200,9 +187,9 @@
"sinon": "^7.3.1", "sinon": "^7.3.1",
"source-map-url": "^0.4.0", "source-map-url": "^0.4.0",
"systemjs": "^6.3.2", "systemjs": "^6.3.2",
"terser-webpack-plugin": "^3.0.6", "terser-webpack-plugin": "^1.2.3",
"ts-lit-plugin": "^1.2.0", "ts-lit-plugin": "^1.1.10",
"ts-mocha": "^7.0.0", "ts-mocha": "^6.0.0",
"typescript": "^3.8.3", "typescript": "^3.8.3",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0", "vinyl-source-stream": "^2.0.0",
@@ -220,10 +207,19 @@
"@polymer/polymer": "3.1.0", "@polymer/polymer": "3.1.0",
"lit-html": "1.2.1", "lit-html": "1.2.1",
"lit-element": "2.3.1", "lit-element": "2.3.1",
"@material/animation": "8.0.0-canary.096a7a066.0", "@material/animation": "7.0.0-canary.d92d8c93e.0",
"@material/base": "8.0.0-canary.096a7a066.0", "@material/base": "7.0.0-canary.d92d8c93e.0",
"@material/feature-targeting": "8.0.0-canary.096a7a066.0", "@material/checkbox": "7.0.0-canary.d92d8c93e.0",
"@material/theme": "8.0.0-canary.096a7a066.0" "@material/density": "7.0.0-canary.d92d8c93e.0",
"@material/dom": "7.0.0-canary.d92d8c93e.0",
"@material/elevation": "7.0.0-canary.d92d8c93e.0",
"@material/feature-targeting": "7.0.0-canary.d92d8c93e.0",
"@material/ripple": "7.0.0-canary.d92d8c93e.0",
"@material/rtl": "7.0.0-canary.d92d8c93e.0",
"@material/shape": "7.0.0-canary.d92d8c93e.0",
"@material/theme": "7.0.0-canary.d92d8c93e.0",
"@material/touch-target": "7.0.0-canary.d92d8c93e.0",
"@material/typography": "7.0.0-canary.d92d8c93e.0"
}, },
"main": "src/home-assistant.js", "main": "src/home-assistant.js",
"husky": { "husky": {

View 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
View 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

View File

@@ -16,31 +16,14 @@ function today() {
)}${String(now.getDate()).padStart(2, "0")}.0`; )}${String(now.getDate()).padStart(2, "0")}.0`;
} }
function auto(version) {
const todayVersion = today();
if (todayVersion !== version) {
return todayVersion;
}
return patch(version);
}
const methods = { const methods = {
patch, patch,
today, today,
auto,
}; };
async function main(args) { async function main(args) {
let method; const method = args.length > 0 && methods[args[0]];
let commit; const commit = args.length > 1 && args[1] == "--commit";
if (args.length === 0) {
method = methods.auto;
commit = true;
} else {
method = args.length > 0 && methods[args[0]];
commit = args.length > 1 && args[1] == "--commit";
}
if (!method) { if (!method) {
console.error( console.error(

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="home-assistant-frontend", name="home-assistant-frontend",
version="20200912.0", version="20200603.1",
description="The Home Assistant frontend", description="The Home Assistant frontend",
url="https://github.com/home-assistant/home-assistant-polymer", url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors", author="The Home Assistant Authors",

View File

@@ -5,7 +5,6 @@ import {
html, html,
LitElement, LitElement,
property, property,
internalProperty,
PropertyValues, PropertyValues,
TemplateResult, TemplateResult,
} from "lit-element"; } from "lit-element";
@@ -29,13 +28,13 @@ class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@property() public oauth2State?: string; @property() public oauth2State?: string;
@internalProperty() private _state: State = "loading"; @property() private _state: State = "loading";
@internalProperty() private _stepData: any = {}; @property() private _stepData: any = {};
@internalProperty() private _step?: DataEntryFlowStep; @property() private _step?: DataEntryFlowStep;
@internalProperty() private _errorMessage?: string; @property() private _errorMessage?: string;
protected render() { protected render() {
return html` return html`

View File

@@ -4,22 +4,21 @@ import {
html, html,
LitElement, LitElement,
property, property,
internalProperty,
PropertyValues, PropertyValues,
} from "lit-element"; } from "lit-element";
import { import { AuthProvider, fetchAuthProviders } from "../data/auth";
AuthProvider,
fetchAuthProviders,
AuthUrlSearchParams,
} from "../data/auth";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin"; import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import { registerServiceWorker } from "../util/register-service-worker"; import { registerServiceWorker } from "../util/register-service-worker";
import "./ha-auth-flow"; import "./ha-auth-flow";
import { extractSearchParamsObject } from "../common/url/search-params";
import punycode from "punycode";
import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider"); import(/* webpackChunkName: "pick-auth-provider" */ "./ha-pick-auth-provider");
interface QueryParams {
client_id?: string;
redirect_uri?: string;
state?: string;
}
class HaAuthorize extends litLocalizeLiteMixin(LitElement) { class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@property() public clientId?: string; @property() public clientId?: string;
@@ -27,14 +26,21 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
@property() public oauth2State?: string; @property() public oauth2State?: string;
@internalProperty() private _authProvider?: AuthProvider; @property() private _authProvider?: AuthProvider;
@internalProperty() private _authProviders?: AuthProvider[]; @property() private _authProviders?: AuthProvider[];
constructor() { constructor() {
super(); super();
this.translationFragment = "page-authorize"; 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) { if (query.client_id) {
this.clientId = query.client_id; this.clientId = query.client_id;
} }
@@ -76,7 +82,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
${this.localize( ${this.localize(
"ui.panel.page-authorize.authorizing_client", "ui.panel.page-authorize.authorizing_client",
"clientId", "clientId",
this.clientId ? punycode.toASCII(this.clientId) : this.clientId this.clientId
)} )}
</p> </p>
${loggingInWith} ${loggingInWith}
@@ -139,7 +145,7 @@ class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
response.status === 400 && response.status === 400 &&
authProviders.code === "onboarding_required" authProviders.code === "onboarding_required"
) { ) {
location.href = `/onboarding.html${location.search}`; location.href = "/?";
return; return;
} }

View File

@@ -0,0 +1,131 @@
import "@polymer/paper-card/paper-card";
import { html } from "@polymer/polymer/lib/utils/html-tag";
/* eslint-plugin-disable lit */
import { PolymerElement } from "@polymer/polymer/polymer-element";
import { computeStateName } from "../common/entity/compute_state_name";
import "../components/state-history-charts";
import "../data/ha-state-history-data";
import { EventsMixin } from "../mixins/events-mixin";
/*
* @appliesMixin EventsMixin
*/
class HaHistoryGraphCard extends EventsMixin(PolymerElement) {
static get template() {
return html`
<style>
paper-card:not([dialog]) .content {
padding: 0 16px 16px;
}
paper-card[dialog] {
padding-top: 16px;
background-color: transparent;
}
paper-card {
width: 100%;
/* prevent new stacking context, chart tooltip needs to overflow */
position: static;
}
.header {
@apply --paper-font-headline;
line-height: 40px;
color: var(--primary-text-color);
padding: 20px 16px 12px;
@apply --paper-font-common-nowrap;
}
paper-card[dialog] .header {
display: none;
}
</style>
<ha-state-history-data
hass="[[hass]]"
filter-type="recent-entity"
entity-id="[[computeHistoryEntities(stateObj)]]"
data="{{stateHistory}}"
is-loading="{{stateHistoryLoading}}"
cache-config="[[cacheConfig]]"
></ha-state-history-data>
<paper-card
dialog$="[[inDialog]]"
on-click="cardTapped"
elevation="[[computeElevation(inDialog)]]"
>
<div class="header">[[computeTitle(stateObj)]]</div>
<div class="content">
<state-history-charts
hass="[[hass]]"
history-data="[[stateHistory]]"
is-loading-data="[[stateHistoryLoading]]"
up-to-now
no-single
>
</state-history-charts>
</div>
</paper-card>
`;
}
static get properties() {
return {
hass: Object,
stateObj: {
type: Object,
observer: "stateObjObserver",
},
inDialog: {
type: Boolean,
value: false,
},
stateHistory: Object,
stateHistoryLoading: Boolean,
cacheConfig: {
type: Object,
value: {
refresh: 0,
cacheKey: null,
hoursToShow: 24,
},
},
};
}
stateObjObserver(stateObj) {
if (!stateObj) return;
if (
this.cacheConfig.cacheKey !== stateObj.entity_id ||
this.cacheConfig.refresh !== (stateObj.attributes.refresh || 0) ||
this.cacheConfig.hoursToShow !== (stateObj.attributes.hours_to_show || 24)
) {
this.cacheConfig = {
refresh: stateObj.attributes.refresh || 0,
cacheKey: stateObj.entity_id,
hoursToShow: stateObj.attributes.hours_to_show || 24,
};
}
}
computeTitle(stateObj) {
return computeStateName(stateObj);
}
computeContentClass(inDialog) {
return inDialog ? "" : "content";
}
computeHistoryEntities(stateObj) {
return stateObj.attributes.entity_id;
}
computeElevation(inDialog) {
return inDialog ? 0 : 1;
}
cardTapped(ev) {
const mq = window.matchMedia("(min-width: 610px) and (min-height: 550px)");
if (mq.matches) {
ev.stopPropagation();
this.fire("hass-more-info", { entityId: this.stateObj.entity_id });
}
}
}
customElements.define("ha-history_graph-card", HaHistoryGraphCard);

View File

@@ -1,8 +1,4 @@
/* eslint-disable no-undef, no-console */ /* eslint-disable no-undef, no-console */
import {
CastStateEventData,
SessionStateEventData,
} from "chromecast-caf-receiver/cast.framework";
import { Auth } from "home-assistant-js-websocket"; import { Auth } from "home-assistant-js-websocket";
import { castApiAvailable } from "./cast_framework"; import { castApiAvailable } from "./cast_framework";
import { CAST_APP_ID, CAST_DEV, CAST_NS } from "./const"; import { CAST_APP_ID, CAST_DEV, CAST_NS } from "./const";
@@ -44,13 +40,16 @@ export class CastManager {
const context = this.castContext; const context = this.castContext;
context.setOptions({ context.setOptions({
receiverApplicationId: CAST_APP_ID, receiverApplicationId: CAST_APP_ID,
// @ts-ignore
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
}); });
context.addEventListener( context.addEventListener(
// @ts-ignore
cast.framework.CastContextEventType.SESSION_STATE_CHANGED, cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
(ev) => this._sessionStateChanged(ev) (ev) => this._sessionStateChanged(ev)
); );
context.addEventListener( context.addEventListener(
// @ts-ignore
cast.framework.CastContextEventType.CAST_STATE_CHANGED, cast.framework.CastContextEventType.CAST_STATE_CHANGED,
(ev) => this._castStateChanged(ev) (ev) => this._castStateChanged(ev)
); );
@@ -119,7 +118,7 @@ export class CastManager {
} }
} }
private _sessionStateChanged(ev: SessionStateEventData) { private _sessionStateChanged(ev) {
if (__DEV__) { if (__DEV__) {
console.log("Cast session state changed", ev.sessionState); console.log("Cast session state changed", ev.sessionState);
} }
@@ -142,7 +141,7 @@ export class CastManager {
} }
} }
private _castStateChanged(ev: CastStateEventData) { private _castStateChanged(ev) {
if (__DEV__) { if (__DEV__) {
console.log("Cast state changed", ev.castState); console.log("Cast state changed", ev.castState);
} }

View File

@@ -1,113 +0,0 @@
const expand_hex = (hex: string): string => {
let result = "";
for (const val of hex) {
result += val + val;
}
return result;
};
const rgb_hex = (component: number): string => {
const hex = Math.round(Math.min(Math.max(component, 0), 255)).toString(16);
return hex.length === 1 ? `0${hex}` : hex;
};
// Conversion between HEX and RGB
export const hex2rgb = (hex: string): [number, number, number] => {
hex = hex.replace("#", "");
if (hex.length === 3 || hex.length === 4) {
hex = expand_hex(hex);
}
return [
parseInt(hex.substring(0, 2), 16),
parseInt(hex.substring(2, 4), 16),
parseInt(hex.substring(4, 6), 16),
];
};
export const rgb2hex = (rgb: [number, number, number]): string => {
return `#${rgb_hex(rgb[0])}${rgb_hex(rgb[1])}${rgb_hex(rgb[2])}`;
};
// Conversion between LAB, XYZ and RGB from https://github.com/gka/chroma.js
// Copyright (c) 2011-2019, Gregor Aisch
// Constants for XYZ and LAB conversion
const Xn = 0.95047;
const Yn = 1;
const Zn = 1.08883;
const t0 = 0.137931034; // 4 / 29
const t1 = 0.206896552; // 6 / 29
const t2 = 0.12841855; // 3 * t1 * t1
const t3 = 0.008856452; // t1 * t1 * t1
const rgb_xyz = (r: number) => {
r /= 255;
if (r <= 0.04045) {
return r / 12.92;
}
return ((r + 0.055) / 1.055) ** 2.4;
};
const xyz_lab = (t: number) => {
if (t > t3) {
return t ** (1 / 3);
}
return t / t2 + t0;
};
const xyz_rgb = (r: number) => {
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * r ** (1 / 2.4) - 0.055);
};
const lab_xyz = (t: number) => {
return t > t1 ? t * t * t : t2 * (t - t0);
};
// Conversions between RGB and LAB
const rgb2xyz = (rgb: [number, number, number]): [number, number, number] => {
let [r, g, b] = rgb;
r = rgb_xyz(r);
g = rgb_xyz(g);
b = rgb_xyz(b);
const x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / Xn);
const y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.072175 * b) / Yn);
const z = xyz_lab((0.0193339 * r + 0.119192 * g + 0.9503041 * b) / Zn);
return [x, y, z];
};
export const rgb2lab = (
rgb: [number, number, number]
): [number, number, number] => {
const [x, y, z] = rgb2xyz(rgb);
const l = 116 * y - 16;
return [l < 0 ? 0 : l, 500 * (x - y), 200 * (y - z)];
};
export const lab2rgb = (
lab: [number, number, number]
): [number, number, number] => {
const [l, a, b] = lab;
let y = (l + 16) / 116;
let x = isNaN(a) ? y : y + a / 500;
let z = isNaN(b) ? y : y - b / 200;
y = Yn * lab_xyz(y);
x = Xn * lab_xyz(x);
z = Zn * lab_xyz(z);
const r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z); // D65 -> sRGB
const g = xyz_rgb(-0.969266 * x + 1.8760108 * y + 0.041556 * z);
const b_ = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
return [r, g, b_];
};
export const lab2hex = (lab: [number, number, number]): string => {
const rgb = lab2rgb(lab);
return rgb2hex(rgb);
};

View File

@@ -1,16 +0,0 @@
// From https://github.com/gka/chroma.js
// Copyright (c) 2011-2019, Gregor Aisch
export const labDarken = (
lab: [number, number, number],
amount = 1
): [number, number, number] => {
return [lab[0] - 18 * amount, lab[1], lab[2]];
};
export const labBrighten = (
lab: [number, number, number],
amount = 1
): [number, number, number] => {
return labDarken(lab, -amount);
};

View File

@@ -1,24 +0,0 @@
const luminosity = (rgb: [number, number, number]): number => {
// http://www.w3.org/TR/WCAG20/#relativeluminancedef
const lum: [number, number, number] = [0, 0, 0];
for (let i = 0; i < rgb.length; i++) {
const chan = rgb[i] / 255;
lum[i] = chan <= 0.03928 ? chan / 12.92 : ((chan + 0.055) / 1.055) ** 2.4;
}
return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2];
};
export const rgbContrast = (
color1: [number, number, number],
color2: [number, number, number]
) => {
const lum1 = luminosity(color1);
const lum2 = luminosity(color2);
if (lum1 > lum2) {
return (lum1 + 0.05) / (lum2 + 0.05);
}
return (lum2 + 0.05) / (lum1 + 0.05);
};

View File

@@ -1,9 +0,0 @@
import { HomeAssistant } from "../../types";
/** Return an array of domains with the service. */
export const componentsWithService = (
hass: HomeAssistant,
service: string
): Array<string> =>
hass &&
Object.keys(hass.services).filter((key) => service in hass.services[key]);

View File

@@ -1,9 +0,0 @@
import { HomeAssistant } from "../../types";
/** Return if a service is loaded. */
export const isServiceLoaded = (
hass: HomeAssistant,
domain: string,
service: string
): boolean =>
hass && domain in hass.services && service in hass.services[domain];

View File

@@ -22,6 +22,7 @@ export const DOMAINS_WITH_CARD = [
"timer", "timer",
"vacuum", "vacuum",
"water_heater", "water_heater",
"weblink",
]; ];
/** Domains with separate more info dialog. */ /** Domains with separate more info dialog. */
@@ -35,7 +36,7 @@ export const DOMAINS_WITH_MORE_INFO = [
"cover", "cover",
"fan", "fan",
"group", "group",
"humidifier", "history_graph",
"input_datetime", "input_datetime",
"light", "light",
"lock", "lock",
@@ -44,6 +45,7 @@ export const DOMAINS_WITH_MORE_INFO = [
"script", "script",
"sun", "sun",
"timer", "timer",
"updater",
"vacuum", "vacuum",
"water_heater", "water_heater",
"weather", "weather",
@@ -55,10 +57,16 @@ export const DOMAINS_HIDE_MORE_INFO = [
"input_select", "input_select",
"input_text", "input_text",
"scene", "scene",
"weblink",
]; ];
/** Domains that should have the history hidden in the more info dialog. */ /** Domains that should have the history hidden in the more info dialog. */
export const DOMAINS_MORE_INFO_NO_HISTORY = ["camera", "configurator", "scene"]; export const DOMAINS_MORE_INFO_NO_HISTORY = [
"camera",
"configurator",
"history_graph",
"scene",
];
/** States that we consider "off". */ /** States that we consider "off". */
export const STATES_OFF = ["closed", "locked", "off"]; export const STATES_OFF = ["closed", "locked", "off"];
@@ -71,7 +79,6 @@ export const DOMAINS_TOGGLE = new Set([
"switch", "switch",
"group", "group",
"automation", "automation",
"humidifier",
]); ]);
/** Temperature units. */ /** Temperature units. */

View File

@@ -20,29 +20,31 @@ export default function relativeTime(
let delta = (compareTime.getTime() - dateObj.getTime()) / 1000; let delta = (compareTime.getTime() - dateObj.getTime()) / 1000;
const tense = delta >= 0 ? "past" : "future"; const tense = delta >= 0 ? "past" : "future";
delta = Math.abs(delta); delta = Math.abs(delta);
let roundedDelta = Math.round(delta);
if (roundedDelta === 0) { let timeDesc;
return localize("ui.components.relative_time.just_now");
}
let unit = "week";
for (let i = 0; i < tests.length; i++) { for (let i = 0; i < tests.length; i++) {
if (roundedDelta < tests[i]) { if (delta < tests[i]) {
unit = langKey[i]; delta = Math.floor(delta);
timeDesc = localize(
`ui.components.relative_time.duration.${langKey[i]}`,
"count",
delta
);
break; break;
} }
delta /= tests[i]; delta /= tests[i];
roundedDelta = Math.round(delta);
} }
const timeDesc = localize( if (timeDesc === undefined) {
`ui.components.relative_time.duration.${unit}`, delta = Math.floor(delta);
timeDesc = localize(
"ui.components.relative_time.duration.week",
"count", "count",
roundedDelta delta
); );
}
return options.includeTense === false return options.includeTense === false
? timeDesc ? timeDesc

View File

@@ -1,155 +0,0 @@
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { PropertyDeclaration, UpdatingElement } from "lit-element";
import type { ClassElement } from "../../types";
type Callback = (oldValue: any, newValue: any) => void;
class Storage {
constructor() {
window.addEventListener("storage", (ev: StorageEvent) => {
if (ev.key && this.hasKey(ev.key)) {
this._storage[ev.key] = ev.newValue
? JSON.parse(ev.newValue)
: ev.newValue;
if (this._listeners[ev.key]) {
this._listeners[ev.key].forEach((listener) =>
listener(
ev.oldValue ? JSON.parse(ev.oldValue) : ev.oldValue,
this._storage[ev.key!]
)
);
}
}
});
}
private _storage: { [storageKey: string]: any } = {};
private _listeners: {
[storageKey: string]: Callback[];
} = {};
public addFromStorage(storageKey: any): void {
if (!this._storage[storageKey]) {
const data = window.localStorage.getItem(storageKey);
if (data) {
this._storage[storageKey] = JSON.parse(data);
}
}
}
public subscribeChanges(
storageKey: string,
callback: Callback
): UnsubscribeFunc {
if (this._listeners[storageKey]) {
this._listeners[storageKey].push(callback);
} else {
this._listeners[storageKey] = [callback];
}
return () => {
this.unsubscribeChanges(storageKey, callback);
};
}
public unsubscribeChanges(storageKey: string, callback: Callback) {
if (!(storageKey in this._listeners)) {
return;
}
const index = this._listeners[storageKey].indexOf(callback);
if (index !== -1) {
this._listeners[storageKey].splice(index, 1);
}
}
public hasKey(storageKey: string): any {
return storageKey in this._storage;
}
public getValue(storageKey: string): any {
return this._storage[storageKey];
}
public setValue(storageKey: string, value: any): any {
this._storage[storageKey] = value;
try {
window.localStorage.setItem(storageKey, JSON.stringify(value));
} catch (err) {
// Safari in private mode doesn't allow localstorage
}
}
}
const storage = new Storage();
export const LocalStorage = (
storageKey?: string,
property?: boolean,
propertyOptions?: PropertyDeclaration
): any => {
return (clsElement: ClassElement) => {
const key = String(clsElement.key);
storageKey = storageKey || String(clsElement.key);
const initVal = clsElement.initializer
? clsElement.initializer()
: undefined;
storage.addFromStorage(storageKey);
const subscribe = (el: UpdatingElement): UnsubscribeFunc =>
storage.subscribeChanges(storageKey!, (oldValue) => {
el.requestUpdate(clsElement.key, oldValue);
});
const getValue = (): any => {
return storage.hasKey(storageKey!)
? storage.getValue(storageKey!)
: initVal;
};
const setValue = (el: UpdatingElement, value: any) => {
let oldValue: unknown | undefined;
if (property) {
oldValue = getValue();
}
storage.setValue(storageKey!, value);
if (property) {
el.requestUpdate(clsElement.key, oldValue);
}
};
return {
kind: "method",
placement: "prototype",
key: clsElement.key,
descriptor: {
set(this: UpdatingElement, value: unknown) {
setValue(this, value);
},
get() {
return getValue();
},
enumerable: true,
configurable: true,
},
finisher(cls: typeof UpdatingElement) {
if (property) {
const connectedCallback = cls.prototype.connectedCallback;
const disconnectedCallback = cls.prototype.disconnectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
this[`__unbsubLocalStorage${key}`] = subscribe(this);
};
cls.prototype.disconnectedCallback = function () {
disconnectedCallback.call(this);
this[`__unbsubLocalStorage${key}`]();
};
cls.createProperty(clsElement.key, {
noAccessor: true,
...propertyOptions,
});
}
},
};
};
};

View File

@@ -1,33 +0,0 @@
import type { LitElement } from "lit-element";
import type { ClassElement } from "../../types";
export const restoreScroll = (selector: string): any => {
return (element: ClassElement) => ({
kind: "method",
placement: "prototype",
key: element.key,
descriptor: {
set(this: LitElement, value: number) {
this[`__${String(element.key)}`] = value;
},
get(this: LitElement) {
return this[`__${String(element.key)}`];
},
enumerable: true,
configurable: true,
},
finisher(cls: typeof LitElement) {
const connectedCallback = cls.prototype.connectedCallback;
cls.prototype.connectedCallback = function () {
connectedCallback.call(this);
if (this[element.key]) {
const target = this.renderRoot.querySelector(selector);
if (!target) {
return;
}
target.scrollTop = this[element.key];
}
};
},
});
};

View File

@@ -1,20 +1,26 @@
import { derivedStyles, darkStyles } from "../../resources/styles"; import { derivedStyles } from "../../resources/styles";
import { HomeAssistant, Theme } from "../../types"; import { HomeAssistant, Theme } from "../../types";
import {
hex2rgb,
rgb2hex,
rgb2lab,
lab2rgb,
lab2hex,
} from "../color/convert-color";
import { rgbContrast } from "../color/rgb";
import { labDarken, labBrighten } from "../color/lab";
interface ProcessedTheme { interface ProcessedTheme {
keys: { [key: string]: "" }; keys: { [key: string]: "" };
styles: { [key: string]: string }; styles: { [key: string]: string };
} }
const hexToRgb = (hex: string): string | null => {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const checkHex = hex.replace(shorthandRegex, (_m, r, g, b) => {
return r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(checkHex);
return result
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(
result[3],
16
)}`
: null;
};
let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {}; let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
/** /**
@@ -27,56 +33,17 @@ let PROCESSED_THEMES: { [key: string]: ProcessedTheme } = {};
export const applyThemesOnElement = ( export const applyThemesOnElement = (
element, element,
themes: HomeAssistant["themes"], themes: HomeAssistant["themes"],
selectedTheme?: string, selectedTheme?: string
themeOptions?: Partial<HomeAssistant["selectedTheme"]>
) => { ) => {
let cacheKey = selectedTheme; const newTheme = selectedTheme
let themeRules: Partial<Theme> = {}; ? PROCESSED_THEMES[selectedTheme] || processTheme(selectedTheme, themes)
: undefined;
if (selectedTheme === "default" && themeOptions) { if (!element._themes && !newTheme) {
if (themeOptions.dark) {
cacheKey = `${cacheKey}__dark`;
themeRules = darkStyles;
}
if (themeOptions.primaryColor) {
cacheKey = `${cacheKey}__primary_${themeOptions.primaryColor}`;
const rgbPrimaryColor = hex2rgb(themeOptions.primaryColor);
const labPrimaryColor = rgb2lab(rgbPrimaryColor);
themeRules["primary-color"] = themeOptions.primaryColor;
const rgbLigthPrimaryColor = lab2rgb(labBrighten(labPrimaryColor));
themeRules["light-primary-color"] = rgb2hex(rgbLigthPrimaryColor);
themeRules["dark-primary-color"] = lab2hex(labDarken(labPrimaryColor));
themeRules["text-primary-color"] =
rgbContrast(rgbPrimaryColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
themeRules["text-light-primary-color"] =
rgbContrast(rgbLigthPrimaryColor, [33, 33, 33]) < 6
? "#fff"
: "#212121";
themeRules["state-icon-color"] = themeRules["dark-primary-color"];
}
if (themeOptions.accentColor) {
cacheKey = `${cacheKey}__accent_${themeOptions.accentColor}`;
themeRules["accent-color"] = themeOptions.accentColor;
const rgbAccentColor = hex2rgb(themeOptions.accentColor);
themeRules["text-accent-color"] =
rgbContrast(rgbAccentColor, [33, 33, 33]) < 6 ? "#fff" : "#212121";
}
}
if (selectedTheme && themes.themes[selectedTheme]) {
themeRules = themes.themes[selectedTheme];
}
if (!element._themes && !Object.keys(themeRules).length) {
// No styles to reset, and no styles to set // No styles to reset, and no styles to set
return; return;
} }
const newTheme =
themeRules && cacheKey
? PROCESSED_THEMES[cacheKey] || processTheme(cacheKey, themeRules)
: undefined;
// Add previous set keys to reset them, and new theme // Add previous set keys to reset them, and new theme
const styles = { ...element._themes, ...newTheme?.styles }; const styles = { ...element._themes, ...newTheme?.styles };
element._themes = newTheme?.keys; element._themes = newTheme?.keys;
@@ -91,45 +58,42 @@ export const applyThemesOnElement = (
}; };
const processTheme = ( const processTheme = (
cacheKey: string, themeName: string,
theme: Partial<Theme> themes: HomeAssistant["themes"]
): ProcessedTheme | undefined => { ): ProcessedTheme | undefined => {
if (!theme || !Object.keys(theme).length) { if (!themes.themes[themeName]) {
return undefined; return undefined;
} }
const combinedTheme: Partial<Theme> = { const theme: Theme = {
...derivedStyles, ...derivedStyles,
...theme, ...themes.themes[themeName],
}; };
const styles = {}; const styles = {};
const keys = {}; const keys = {};
for (const key of Object.keys(combinedTheme)) { for (const key of Object.keys(theme)) {
const prefixedKey = `--${key}`; const prefixedKey = `--${key}`;
const value = String(combinedTheme[key]!); const value = theme[key];
styles[prefixedKey] = value; styles[prefixedKey] = value;
keys[prefixedKey] = ""; keys[prefixedKey] = "";
// Try to create a rgb value for this key if it is not a var // Try to create a rgb value for this key if it is a hex color
if (value.startsWith("#")) { if (!value.startsWith("#")) {
// Can't convert non hex value // Not a hex color
continue; continue;
} }
const rgbKey = `rgb-${key}`; const rgbKey = `rgb-${key}`;
if (combinedTheme[rgbKey] !== undefined) { if (theme[rgbKey] !== undefined) {
// Theme has it's own rgb value // Theme has it's own rgb value
continue; continue;
} }
try { const rgbValue = hexToRgb(value);
const rgbValue = hex2rgb(value).join(","); if (rgbValue !== null) {
const prefixedRgbKey = `--${rgbKey}`; const prefixedRgbKey = `--${rgbKey}`;
styles[prefixedRgbKey] = rgbValue; styles[prefixedRgbKey] = rgbValue;
keys[prefixedRgbKey] = ""; keys[prefixedRgbKey] = "";
} catch (e) {
continue;
} }
} }
PROCESSED_THEMES[cacheKey] = { styles, keys }; PROCESSED_THEMES[themeName] = { styles, keys };
return { styles, keys }; return { styles, keys };
}; };

View File

@@ -1,8 +0,0 @@
export const deepActiveElement = (
root: DocumentOrShadowRoot = document
): Element | null => {
if (root.activeElement?.shadowRoot?.activeElement) {
return deepActiveElement(root.activeElement.shadowRoot);
}
return root.activeElement;
};

View File

@@ -1,4 +1,4 @@
import type { Map, TileLayer } from "leaflet"; import type { Map } from "leaflet";
// Sets up a Leaflet map on the provided DOM element // Sets up a Leaflet map on the provided DOM element
export type LeafletModuleType = typeof import("leaflet"); export type LeafletModuleType = typeof import("leaflet");
@@ -6,9 +6,9 @@ export type LeafletDrawModuleType = typeof import("leaflet-draw");
export const setupLeafletMap = async ( export const setupLeafletMap = async (
mapElement: HTMLElement, mapElement: HTMLElement,
darkMode?: boolean, darkMode = false,
draw = false draw = false
): Promise<[Map, LeafletModuleType, TileLayer]> => { ): Promise<[Map, LeafletModuleType]> => {
if (!mapElement.parentNode) { if (!mapElement.parentNode) {
throw new Error("Cannot setup Leaflet map on disconnected element"); throw new Error("Cannot setup Leaflet map on disconnected element");
} }
@@ -28,28 +28,15 @@ export const setupLeafletMap = async (
style.setAttribute("rel", "stylesheet"); style.setAttribute("rel", "stylesheet");
mapElement.parentNode.appendChild(style); mapElement.parentNode.appendChild(style);
map.setView([52.3731339, 4.8903147], 13); map.setView([52.3731339, 4.8903147], 13);
createTileLayer(Leaflet, darkMode).addTo(map);
const tileLayer = createTileLayer(Leaflet, Boolean(darkMode)).addTo(map); return [map, Leaflet];
return [map, Leaflet, tileLayer];
}; };
export const replaceTileLayer = ( export const createTileLayer = (
leaflet: LeafletModuleType,
map: Map,
tileLayer: TileLayer,
darkMode: boolean
): TileLayer => {
map.removeLayer(tileLayer);
tileLayer = createTileLayer(leaflet, darkMode);
tileLayer.addTo(map);
return tileLayer;
};
const createTileLayer = (
leaflet: LeafletModuleType, leaflet: LeafletModuleType,
darkMode: boolean darkMode: boolean
): TileLayer => { ) => {
return leaflet.tileLayer( return leaflet.tileLayer(
`https://{s}.basemaps.cartocdn.com/${ `https://{s}.basemaps.cartocdn.com/${
darkMode ? "dark_all" : "light_all" darkMode ? "dark_all" : "light_all"

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