Compare commits

..

2 Commits

Author SHA1 Message Date
Paulus Schoutsen
355440c0fa Hide default image if unavailable 2020-03-08 10:10:00 -07:00
Paulus Schoutsen
e7d89d0f9a Style unavailable/unknown cards 2020-03-08 10:02:40 -07:00
1057 changed files with 26951 additions and 50123 deletions

80
.eslintrc-hound.json Normal file
View File

@@ -0,0 +1,80 @@
{
"extends": ["airbnb-base", "prettier"],
"parserOptions": {
"ecmaVersion": "2020",
"ecmaFeatures": {
"jsx": true,
"modules": true
}
},
"settings": {
"react": {
"pragma": "h",
"version": "15.0"
},
"import/resolver": {
"webpack": {
"config": "webpack.config.js"
}
}
},
"globals": {
"__DEV__": false,
"__DEMO__": false,
"__BUILD__": false,
"__VERSION__": false,
"__STATIC_PATH__": false,
"Polymer": true,
"webkitSpeechRecognition": false,
"ResizeObserver": false
},
"env": {
"browser": true,
"mocha": true
},
"rules": {
"class-methods-use-this": 0,
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"no-var": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 0,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/extensions": [2, "ignorePackages"],
"object-curly-newline": 0,
"default-case": 0,
"react/jsx-no-bind": [2, { "ignoreRefs": true }],
"react/jsx-no-duplicate-props": 2,
"react/self-closing-comp": 2,
"react/prefer-es6-class": 2,
"react/no-string-refs": 2,
"react/require-render-return": 2,
"react/no-find-dom-node": 2,
"react/no-is-mounted": 2,
"react/jsx-no-comment-textnodes": 2,
"react/jsx-no-undef": 2,
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"no-restricted-syntax": [0, "ForOfStatement"],
"prettier/prettier": "error"
},
"plugins": ["react", "prettier"]
}

View File

@@ -1,88 +1,12 @@
{
"extends": [
"plugin:@typescript-eslint/recommended",
"airbnb-typescript/base",
"plugin:wc/recommended",
"plugin:lit/recommended",
"prettier",
"prettier/@typescript-eslint"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"ecmaFeatures": {
"modules": true
},
"sourceType": "module",
"project": "./tsconfig.json"
},
"settings": {
"import/resolver": {
"webpack": {
"config": "./webpack.config.js"
}
}
},
"globals": {
"__DEV__": false,
"__DEMO__": false,
"__BUILD__": false,
"__VERSION__": false,
"__STATIC_PATH__": false,
"Polymer": true,
"webkitSpeechRecognition": false,
"ResizeObserver": false
},
"extends": "./.eslintrc-hound.json",
"plugins": ["react"],
"env": {
"browser": true,
"es6": true
"browser": true
},
"rules": {
"class-methods-use-this": 0,
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"no-var": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 0,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": 0,
"import/order": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"import/extensions": [
2,
"ignorePackages",
{ "ts": "ignorePackages", "js": "ignorePackages" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": 0,
"default-case": 0,
"wc/no-self-class": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-ignore": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-function-return-type": 0
},
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable"
"import/no-unresolved": 2,
"linebreak-style": 0,
"implicit-arrow-linebreak": 0
}
}

View File

@@ -62,18 +62,6 @@ DO NOT DELETE ANY TEXT from this template! Otherwise, your issue may be closed w
- Browser and browser version:
- Operating system:
## State of relevant entities
<!--
If your issue is about how an entity is shown in the UI, please add the state
and attributes for all situations with a screenshot of the UI.
You can find this information at `/developer-tools/state`
-->
```yaml
```
## Problem-relevant configuration
<!--

View File

@@ -3,24 +3,23 @@ name: Request a feature for the UI, Frontend or Lovelace
about: Request an new feature for the Home Assistant frontend.
labels: feature request
---
<!--
DO NOT DELETE ANY TEXT from this template!
Otherwise, your request may be closed without comment.
-->
## The request
<!--
<!--
Describe to our maintainers, the feature you would like to be added.
Please be clear and concise and, if possible, provide a screenshot or mockup.
-->
## The alternatives
## The alternatives
<!--
Are you currently using, or have you considered alternatives?
If so, could you please describe those?
-->
## Additional information

View File

@@ -2,9 +2,7 @@
You are amazing! Thanks for contributing to our project!
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
-->
## Breaking change
<!--
If your PR contains a breaking change for existing users, it is important
to tell them what breaks, how to make it work again and why we did this.
@@ -13,20 +11,20 @@
Note: Remove this section if this PR is NOT a breaking change.
-->
## Proposed change
<!--
## Proposed change
<!--
Describe the big picture of your changes here to communicate to the
maintainers why we should accept this pull request. If it fixes a bug
or resolves a feature request, be sure to link to that issue in the
additional information section.
-->
## Type of change
## Type of change
<!--
What type of change does your PR introduce to the Home Assistant frontend?
NOTE: Please, check only 1! box!
NOTE: Please, check only 1! box!
If your PR requires multiple boxes to be checked, you'll most likely need to
split it into multiple PRs. This makes things easier and faster to code review.
-->
@@ -38,7 +36,6 @@
- [ ] Code quality improvements to existing code or addition of tests
## Example configuration
<!--
Supplying a configuration snippet, makes it easier for a maintainer to test
your PR.
@@ -49,18 +46,16 @@
```
## Additional information
<!--
Details are important, and help maintainers processing your PR.
Please be sure to fill out additional details, if applicable.
-->
- This PR fixes or closes issue: fixes #
- This PR is related to issue:
- Link to documentation pull request:
- This PR is related to issue:
- Link to documentation pull request:
## Checklist
<!--
Put an `x` in the boxes that apply. You can also fill these out after
creating the PR. If you're unsure about any of them, don't hesitate to ask.
@@ -79,5 +74,4 @@ If user exposed functionality or configuration variables are added/changed:
<!--
Thank you for contributing <3
-->
[docs-repository]: https://github.com/home-assistant/home-assistant.io

2
.github/lock.yml vendored
View File

@@ -24,4 +24,4 @@ only: pulls
# Optionally, specify configuration settings just for `issues` or `pulls`
issues:
daysUntilLock: 30
daysUntilLock: 30

View File

@@ -35,11 +35,13 @@ jobs:
env:
CI: true
- name: Build icons
run: ./node_modules/.bin/gulp gen-icons-json
run: ./node_modules/.bin/gulp gen-icons-hassio gen-icons-mdi gen-icons-app
- name: Build translations
run: ./node_modules/.bin/gulp build-translations
- name: Run eslint
run: ./node_modules/.bin/eslint '{**/src,src}/**/*.{js,ts,html}' --ignore-path .gitignore
run: ./node_modules/.bin/eslint src hassio/src gallery/src
- name: Run tslint
run: ./node_modules/.bin/tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts'
- name: Run tsc
run: ./node_modules/.bin/tsc
test:
@@ -94,7 +96,7 @@ jobs:
- name: Build Application
run: ./node_modules/.bin/gulp build-app
env:
IS_TEST: "true"
TRAVIS: "true"
supervisor:
runs-on: ubuntu-latest
needs: [lint, test]
@@ -122,4 +124,4 @@ jobs:
- name: Build Application
run: ./node_modules/.bin/gulp build-hassio
env:
IS_TEST: "true"
TRAVIS: "true"

4
.gitignore vendored
View File

@@ -5,6 +5,7 @@ npm-debug.log
.DS_Store
hass_frontend/*
.reify-cache
demo/hademo-icons.html
# Python stuff
*.py[cod]
@@ -30,6 +31,3 @@ src/cast/dev_const.ts
# Secrets
.lokalise_token
yarn-error.log
#asdf
.tool-versions

View File

@@ -1,10 +0,0 @@
build
build-translations/*
translations/*
node_modules/*
hass_frontend/*
pip-selfcheck.json
# vscode
.vscode/*
!.vscode/extensions.json

View File

@@ -1,6 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.vscode-typescript-tslint-plugin",
"esbenp.prettier-vscode",
"bierner.lit-html",
"runem.lit-plugin"

View File

@@ -8,7 +8,7 @@ schedules:
branches:
include:
- dev
always: true
always: false
variables:
- group: netlify
@@ -24,7 +24,4 @@ jobs:
# Demo
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_DEMO}
# Gallery
curl -X POST -d {} https://api.netlify.com/build_hooks/${NETLIFY_GALLERY}
displayName: 'Trigger netlify build preview'

View File

@@ -8,7 +8,7 @@ trigger:
pr: none
variables:
- name: versionWheels
value: '1.10.1-3.7-alpine3.11'
value: '1.3-3.7-alpine3.10'
- name: versionNode
value: '12.1'
- group: twine
@@ -47,13 +47,18 @@ stages:
script/release
displayName: "Build and release package"
- stage: "Wheels"
jobs:
- template: templates/azp-job-wheels.yaml@azure
parameters:
builderVersion: '$(versionWheels)'
wheelsRequirement: 'requirement.txt'
builderApk: 'build-base'
wheelsLocal: true
preBuild:
- task: NodeTool@0
displayName: "Use Node $(versionNode)"
inputs:
versionSpec: "$(versionNode)"
- script: |
sleep 240
echo "home-assistant-frontend==$(Build.SourceBranchName)" > requirement.txt
set -e
yarn install
script/build_frontend

View File

@@ -1,40 +1,50 @@
const options = ({ latestBuild }) => ({
presets: [
!latestBuild && [require("@babel/preset-env").default, { modules: false }],
require("@babel/preset-typescript").default,
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
],
});
module.exports.options = options;
module.exports.babelLoaderConfig = ({ latestBuild }) => {
if (latestBuild === undefined) {
throw Error("latestBuild not defined for babel loader config");
}
return {
test: /\.m?js$|\.tsx?$/,
exclude: [require.resolve("@mdi/js/mdi.js"), require.resolve("hls.js")],
use: {
loader: "babel-loader",
options: options({ latestBuild }),
options: {
presets: [
!latestBuild && [
require("@babel/preset-env").default,
{ modules: false },
],
[
require("@babel/preset-typescript").default,
{
jsxPragma: "h",
},
],
].filter(Boolean),
plugins: [
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
[
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/syntax-dynamic-import",
[
"@babel/transform-react-jsx",
{
pragma: "h",
},
],
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
[
require("@babel/plugin-proposal-decorators").default,
{ decoratorsBeforeExport: true },
],
[
require("@babel/plugin-proposal-class-properties").default,
{ loose: true },
],
],
},
},
};
};

View File

@@ -1,27 +1,6 @@
const fs = require("fs");
const path = require("path");
const paths = require("./paths.js");
module.exports = {
isProdBuild() {
return process.env.NODE_ENV === "production";
},
isStatsBuild() {
return process.env.STATS === "1";
},
isTest() {
return process.env.IS_TEST === "true";
},
isNetlify() {
return process.env.NETLIFY === "true";
},
version() {
const version = fs
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
.match(/\d{8}\.\d+/);
if (!version) {
throw Error("Version not found");
}
return version[0];
},
isProdBuild: process.env.NODE_ENV === "production",
isStatsBuild: process.env.STATS === "1",
isTravis: process.env.TRAVIS === "true",
isNetlify: process.env.NETLIFY === "true",
};

View File

@@ -5,7 +5,7 @@ const envVars = require("../env");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gen-icons.js");
require("./gather-static.js");
require("./compress.js");
require("./webpack.js");
@@ -21,7 +21,7 @@ gulp.task(
"clean",
gulp.parallel(
"gen-service-worker-dev",
"gen-icons-json",
gulp.parallel("gen-icons-app", "gen-icons-mdi"),
"gen-pages-dev",
"gen-index-app-dev",
"build-translations"
@@ -38,11 +38,11 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static",
"webpack-prod-app",
...// Don't compress running tests
(envVars.isTest() ? [] : ["compress-app"]),
(envVars.isTravis ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",

View File

@@ -2,6 +2,7 @@ const gulp = require("gulp");
require("./clean.js");
require("./translations.js");
require("./gen-icons.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
@@ -14,8 +15,12 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-cast",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel(
"gen-icons-app",
"gen-icons-mdi",
"gen-index-cast-dev",
"build-translations"
),
"copy-static-cast",
"webpack-dev-server-cast"
)
@@ -28,8 +33,7 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-cast",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static-cast",
"webpack-prod-cast",
"gen-index-cast-prod"

View File

@@ -3,7 +3,7 @@ const gulp = require("gulp");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gen-icons.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
@@ -16,8 +16,13 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-demo",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
gulp.parallel(
"gen-icons-app",
"gen-icons-mdi",
"gen-icons-demo",
"gen-index-demo-dev",
"build-translations"
),
"copy-static-demo",
"webpack-dev-server-demo"
)
@@ -30,9 +35,12 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-demo",
// Cast needs to be backwards compatible and older HA has no translations
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel(
"gen-icons-app",
"gen-icons-mdi",
"gen-icons-demo",
"build-translations"
),
"copy-static-demo",
"webpack-prod-demo",
"gen-index-demo-prod"

View File

@@ -2,7 +2,7 @@ const del = require("del");
const gulp = require("gulp");
const mapStream = require("map-stream");
const inDir = "translations/frontend";
const inDir = "translations";
const downloadDir = inDir + "/downloads";
const tasks = [];
@@ -12,7 +12,7 @@ function hasHtml(data) {
}
function recursiveCheckHasHtml(file, data, errors, recKey) {
Object.keys(data).forEach(function (key) {
Object.keys(data).forEach(function(key) {
if (typeof data[key] === "object") {
const nextRecKey = recKey ? `${recKey}.${key}` : key;
recursiveCheckHasHtml(file, data[key], errors, nextRecKey);
@@ -25,7 +25,7 @@ function recursiveCheckHasHtml(file, data, errors, recKey) {
function checkHtml() {
const errors = [];
return mapStream(function (file, cb) {
return mapStream(function(file, cb) {
const content = file.contents;
let error;
if (content) {
@@ -42,19 +42,19 @@ function checkHtml() {
}
let taskName = "clean-downloaded-translations";
gulp.task(taskName, function () {
gulp.task(taskName, function() {
return del([`${downloadDir}/**`]);
});
tasks.push(taskName);
taskName = "check-translations-html";
gulp.task(taskName, function () {
gulp.task(taskName, function() {
return gulp.src(`${downloadDir}/*.json`).pipe(checkHtml());
});
tasks.push(taskName);
taskName = "move-downloaded-translations";
gulp.task(taskName, function () {
gulp.task(taskName, function() {
return gulp.src(`${downloadDir}/*.json`).pipe(gulp.dest(inDir));
});
tasks.push(taskName);

View File

@@ -47,9 +47,11 @@ gulp.task("gen-pages-dev", (done) => {
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: `/frontend_latest/${page}.js`,
latestHassIconsJS: "/frontend_latest/hass-icons.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5PageJS: `/frontend_es5/${page}.js`,
es5HassIconsJS: "/frontend_es5/hass-icons.js",
});
fs.outputFileSync(path.resolve(config.root, `${page}.html`), content);
@@ -64,9 +66,11 @@ gulp.task("gen-pages-prod", (done) => {
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
latestHassIconsJS: latestManifest["hass-icons.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5PageJS: es5Manifest[`${page}.js`],
es5HassIconsJS: es5Manifest["hass-icons.js"],
});
fs.outputFileSync(
@@ -84,11 +88,13 @@ gulp.task("gen-index-app-dev", (done) => {
latestAppJS: "/frontend_latest/app.js",
latestCoreJS: "/frontend_latest/core.js",
latestCustomPanelJS: "/frontend_latest/custom-panel.js",
latestHassIconsJS: "/frontend_latest/hass-icons.js",
es5Compatibility: "/frontend_es5/compatibility.js",
es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js",
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
es5HassIconsJS: "/frontend_es5/hass-icons.js",
}).replace(/#THEMEC/g, "{{ theme_color }}");
fs.outputFileSync(path.resolve(config.root, "index.html"), content);
@@ -102,11 +108,13 @@ gulp.task("gen-index-app-prod", (done) => {
latestAppJS: latestManifest["app.js"],
latestCoreJS: latestManifest["core.js"],
latestCustomPanelJS: latestManifest["custom-panel.js"],
latestHassIconsJS: latestManifest["hass-icons.js"],
es5Compatibility: es5Manifest["compatibility.js"],
es5AppJS: es5Manifest["app.js"],
es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
es5HassIconsJS: es5Manifest["hass-icons.js"],
});
const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}");
@@ -214,7 +222,7 @@ 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", {
latestGalleryJS: "./frontend_latest/entrypoint.js",
latestGalleryJS: "./entrypoint.js",
});
fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content);

View File

@@ -3,7 +3,7 @@ const gulp = require("gulp");
require("./clean.js");
require("./translations.js");
require("./gen-icons-json.js");
require("./gen-icons.js");
require("./gather-static.js");
require("./webpack.js");
require("./service-worker.js");
@@ -16,8 +16,7 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-gallery",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static-gallery",
"gen-index-gallery-dev",
"webpack-dev-server-gallery"
@@ -31,8 +30,7 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-gallery",
"translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations"),
gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"),
"copy-static-gallery",
"webpack-prod-gallery",
"gen-index-gallery-prod"

View File

@@ -26,13 +26,6 @@ function copyTranslations(staticDir) {
);
}
function copyMdiIcons(staticDir) {
const staticPath = genStaticPath(staticDir);
// MDI icons output
fs.copySync(polyPath("build/mdi"), staticPath("mdi"));
}
function copyPolyfills(staticDir) {
const staticPath = genStaticPath(staticDir);
@@ -80,15 +73,19 @@ gulp.task("copy-translations", (done) => {
gulp.task("copy-static", (done) => {
const staticDir = paths.static;
const staticPath = genStaticPath(paths.static);
// Basic static files
fs.copySync(polyPath("public"), paths.root);
copyPolyfills(staticDir);
copyFonts(staticDir);
copyTranslations(staticDir);
copyMdiIcons(staticDir);
// Panel assets
copyFileDir(
npmPath("react-big-calendar/lib/css/react-big-calendar.css"),
staticPath("panels/calendar/")
);
copyMapPanel(staticDir);
done();
});
@@ -106,7 +103,6 @@ gulp.task("copy-static-demo", (done) => {
copyMapPanel(paths.demo_static);
copyFonts(paths.demo_static);
copyTranslations(paths.demo_static);
copyMdiIcons(paths.demo_static);
done();
});
@@ -119,7 +115,6 @@ gulp.task("copy-static-cast", (done) => {
copyMapPanel(paths.cast_static);
copyFonts(paths.cast_static);
copyTranslations(paths.cast_static);
copyMdiIcons(paths.cast_static);
done();
});
@@ -132,6 +127,5 @@ gulp.task("copy-static-gallery", (done) => {
copyMapPanel(paths.gallery_static);
copyFonts(paths.gallery_static);
copyTranslations(paths.gallery_static);
copyMdiIcons(paths.gallery_static);
done();
});

View File

@@ -1,112 +0,0 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs");
const hash = require("object-hash");
const ICON_PACKAGE_PATH = path.resolve(
__dirname,
"../../node_modules/@mdi/svg/"
);
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
const encoding = "utf8";
const getMeta = () => {
const file = fs.readFileSync(META_PATH, { encoding });
const meta = JSON.parse(file);
return meta.map((icon) => {
const svg = fs.readFileSync(`${ICON_PATH}/${icon.name}.svg`, {
encoding,
});
return { path: svg.match(/ d="([^"]+)"/)[1], name: icon.name };
});
};
const splitBySize = (meta) => {
const chunks = [];
const CHUNK_SIZE = 50000;
let curSize = 0;
let startKey;
let icons = [];
Object.values(meta).forEach((icon) => {
if (startKey === undefined) {
startKey = icon.name;
}
curSize += icon.path.length;
icons.push(icon);
if (curSize > CHUNK_SIZE) {
chunks.push({
startKey,
endKey: icon.name,
icons,
});
curSize = 0;
startKey = undefined;
icons = [];
}
});
chunks.push({
startKey,
icons,
});
return chunks;
};
const findDifferentiator = (curString, prevString) => {
for (let i = 0; i < curString.length; i++) {
if (curString[i] !== prevString[i]) {
return curString.substring(0, i + 1);
}
}
throw new Error("Cannot find differentiator", curString, prevString);
};
gulp.task("gen-icons-json", (done) => {
const meta = getMeta();
const split = splitBySize(meta);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
const parts = [];
let lastEnd;
split.forEach((chunk) => {
let startKey;
if (lastEnd === undefined) {
chunk.startKey = undefined;
startKey = undefined;
} else {
startKey = findDifferentiator(chunk.startKey, lastEnd);
}
lastEnd = chunk.endKey;
const output = {};
chunk.icons.forEach((icon) => {
output[icon.name] = icon.path;
});
const filename = hash(output);
parts.push({ start: startKey, file: filename });
fs.writeFileSync(
path.resolve(OUTPUT_DIR, `${filename}.json`),
JSON.stringify(output)
);
});
const file = fs.readFileSync(PACKAGE_PATH, { encoding });
const package = JSON.parse(file);
fs.writeFileSync(
path.resolve(OUTPUT_DIR, "iconMetadata.json"),
JSON.stringify({ version: package.version, parts })
);
done();
});

View File

@@ -0,0 +1,127 @@
const gulp = require("gulp");
const path = require("path");
const fs = require("fs");
const paths = require("../paths");
const { mapFiles } = require("../util");
const ICON_PACKAGE_PATH = path.resolve(
__dirname,
"../../node_modules/@mdi/svg/"
);
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
const OUTPUT_DIR = path.resolve(__dirname, "../../build");
const MDI_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "mdi.html");
const HASS_OUTPUT_PATH = path.resolve(OUTPUT_DIR, "hass-icons.html");
const BUILT_IN_PANEL_ICONS = [
"calendar", // Calendar
"settings", // Config
"home-assistant", // Hass.io
"poll-box", // History panel
"format-list-bulleted-type", // Logbook
"mailbox", // Mailbox
"tooltip-account", // Map
"cart", // Shopping List
"hammer", // developer-tools
];
// Given an icon name, load the SVG file
function loadIcon(name) {
const iconPath = path.resolve(ICON_PATH, `${name}.svg`);
try {
return fs.readFileSync(iconPath, "utf-8");
} catch (err) {
return null;
}
}
// Given an SVG file, convert it to an iron-iconset-svg definition
function transformXMLtoPolymer(name, xml) {
const start = xml.indexOf("><path") + 1;
const end = xml.length - start - 6;
const pth = xml.substr(start, end);
return `<g id="${name}">${pth}</g>`;
}
// Given an iconset name and icon names, generate a polymer iconset
function generateIconset(iconsetName, iconNames) {
const iconDefs = Array.from(iconNames)
.map((name) => {
const iconDef = loadIcon(name);
if (!iconDef) {
throw new Error(`Unknown icon referenced: ${name}`);
}
return transformXMLtoPolymer(name, iconDef);
})
.join("");
return `<ha-iconset-svg name="${iconsetName}" size="24"><svg><defs>${iconDefs}</defs></svg></ha-iconset-svg>`;
}
// Find all icons used by the project.
function findIcons(searchPath, iconsetName) {
const iconRegex = new RegExp(`${iconsetName}:[\\w-]+`, "g");
const icons = new Set();
function processFile(filename) {
const content = fs.readFileSync(filename);
let match;
// eslint-disable-next-line
while ((match = iconRegex.exec(content))) {
// strip off "hass:" and add to set
icons.add(match[0].substr(iconsetName.length + 1));
}
}
mapFiles(searchPath, ".js", processFile);
mapFiles(searchPath, ".ts", processFile);
return icons;
}
gulp.task("gen-icons-mdi", (done) => {
const meta = JSON.parse(
fs.readFileSync(path.resolve(ICON_PACKAGE_PATH, META_PATH), "UTF-8")
);
const iconNames = meta.map((iconInfo) => iconInfo.name);
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
fs.writeFileSync(MDI_OUTPUT_PATH, generateIconset("mdi", iconNames));
done();
});
gulp.task("gen-icons-app", (done) => {
const iconNames = findIcons("./src", "hass");
BUILT_IN_PANEL_ICONS.forEach((name) => iconNames.add(name));
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR);
}
fs.writeFileSync(HASS_OUTPUT_PATH, generateIconset("hass", iconNames));
done();
});
gulp.task("gen-icons-demo", (done) => {
const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo");
fs.writeFileSync(
path.resolve(paths.demo_dir, "hademo-icons.html"),
generateIconset("hademo", iconNames)
);
done();
});
gulp.task("gen-icons-hassio", (done) => {
const iconNames = findIcons(
path.resolve(paths.hassio_dir, "./src"),
"hassio"
);
// Find hassio icons inside HA main repo.
for (const item of findIcons(
path.resolve(paths.polymer_dir, "./src"),
"hassio"
)) {
iconNames.add(item);
}
fs.writeFileSync(
path.resolve(paths.hassio_dir, "hassio-icons.html"),
generateIconset("hassio", iconNames)
);
done();
});

View File

@@ -3,7 +3,7 @@ const gulp = require("gulp");
const envVars = require("../env");
require("./clean.js");
require("./gen-icons-json.js");
require("./gen-icons.js");
require("./webpack.js");
require("./compress.js");
@@ -14,7 +14,7 @@ gulp.task(
process.env.NODE_ENV = "development";
},
"clean-hassio",
"gen-icons-json",
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
"webpack-watch-hassio"
)
);
@@ -26,9 +26,9 @@ gulp.task(
process.env.NODE_ENV = "production";
},
"clean-hassio",
"gen-icons-json",
gulp.parallel("gen-icons-hassio", "gen-icons-mdi"),
"webpack-prod-hassio",
...// Don't compress running tests
(envVars.isTest() ? [] : ["compress-hassio"])
(envVars.isTravis ? [] : ["compress-hassio"])
)
);

View File

@@ -14,20 +14,13 @@ const { mapFiles } = require("../util");
const env = require("../env");
const paths = require("../paths");
const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend";
const inDir = "translations";
const workDir = "build-translations";
const fullDir = workDir + "/full";
const coreDir = workDir + "/core";
const outDir = workDir + "/output";
let mergeBackend = false;
gulp.task("translations-enable-merge-backend", (done) => {
mergeBackend = true;
done();
});
String.prototype.rsplit = function (sep, maxsplit) {
String.prototype.rsplit = function(sep, maxsplit) {
var split = this.split(sep);
return maxsplit
? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit))
@@ -52,7 +45,7 @@ const TRANSLATION_FRAGMENTS = [
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach(function (key) {
Object.keys(data).forEach(function(key) {
if (typeof data[key] === "object") {
output = {
...output,
@@ -114,12 +107,7 @@ function lokaliseTransform(data, original, file) {
output[key] = lokaliseTransform(value, original, file);
} else {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split("::").reduce((tr, k) => {
if (!tr) {
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
return tr[k];
}, original);
const replace = key.split("::").reduce((tr, k) => tr[k], original);
if (typeof replace !== "string") {
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
@@ -130,7 +118,7 @@ function lokaliseTransform(data, original, file) {
return output;
}
gulp.task("clean-translations", function () {
gulp.task("clean-translations", function() {
return del([workDir]);
});
@@ -141,7 +129,7 @@ gulp.task("ensure-translations-build-dir", (done) => {
done();
});
gulp.task("create-test-metadata", function (cb) {
gulp.task("create-test-metadata", function(cb) {
fs.writeFile(
workDir + "/testMetadata.json",
JSON.stringify({
@@ -159,7 +147,7 @@ gulp.task(
return gulp
.src(path.join(paths.translations_src, "en.json"))
.pipe(
transform(function (data, file) {
transform(function(data, file) {
return recursiveEmpty(data);
})
)
@@ -177,40 +165,28 @@ gulp.task(
* project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately.
*/
gulp.task("build-master-translation", function () {
const src = [path.join(paths.translations_src, "en.json")];
if (mergeBackend) {
src.push(path.join(inBackendDir, "en.json"));
}
gulp.task("build-master-translation", function() {
return gulp
.src(src)
.src(path.join(paths.translations_src, "en.json"))
.pipe(
transform(function (data, file) {
transform(function(data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
merge({
fileName: "translationMaster.json",
})
)
.pipe(rename("translationMaster.json"))
.pipe(gulp.dest(workDir));
});
gulp.task("build-merged-translations", function () {
gulp.task("build-merged-translations", function() {
return gulp
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true,
})
.src([inDir + "/*.json", workDir + "/test.json"], { allowEmpty: true })
.pipe(
transform(function (data, file) {
transform(function(data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
foreach(function (stream, file) {
foreach(function(stream, file) {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
@@ -226,10 +202,7 @@ gulp.task("build-merged-translations", function () {
if (lang === "test") {
src.push(workDir + "/test.json");
} else if (lang !== "en") {
src.push(inFrontendDir + "/" + lang + ".json");
if (mergeBackend) {
src.push(inBackendDir + "/" + lang + ".json");
}
src.push(inDir + "/" + lang + ".json");
}
}
return gulp
@@ -250,7 +223,7 @@ var taskName;
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, function () {
gulp.task(taskName, function() {
// Return only the translations for this fragment.
return gulp
.src(fullDir + "/*.json")
@@ -269,12 +242,12 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
});
taskName = "build-translation-core";
gulp.task(taskName, function () {
gulp.task(taskName, function() {
// Remove the fragment translations from the core translation.
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data, file) => {
transform((data) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
@@ -286,7 +259,7 @@ gulp.task(taskName, function () {
splitTasks.push(taskName);
gulp.task("build-flattened-translations", function () {
gulp.task("build-flattened-translations", function() {
// Flatten the split versions of our translations, and move them into outDir
return gulp
.src(
@@ -296,7 +269,7 @@ gulp.task("build-flattened-translations", function () {
{ base: workDir }
)
.pipe(
transform(function (data) {
transform(function(data) {
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
})
@@ -319,11 +292,10 @@ gulp.task(
function fingerprintTranslationFiles() {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
hash: env.isProdBuild
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
@@ -360,7 +332,7 @@ gulp.task(
gulp.series(
"clean-translations",
"ensure-translations-build-dir",
env.isProdBuild() ? (done) => done() : "create-test-translation",
env.isProdBuild ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
@@ -378,7 +350,7 @@ gulp.task(
)
.pipe(merge({}))
.pipe(
transform(function (data) {
transform(function(data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.

View File

@@ -28,7 +28,7 @@ const runDevServer = ({
open: true,
watchContentBase: true,
contentBase,
}).listen(port, listenHost, function (err) {
}).listen(port, listenHost, function(err) {
if (err) {
throw err;
}
@@ -150,8 +150,9 @@ gulp.task(
gulp.task("webpack-dev-server-gallery", () => {
runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
compiler: webpack(
createGalleryConfig({ latestBuild: true, isProdBuild: false })
),
contentBase: paths.gallery_root,
port: 8100,
});

View File

@@ -1,12 +1,20 @@
const webpack = require("webpack");
const fs = require("fs");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const ManifestPlugin = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const env = require("./env.js");
const { babelLoaderConfig } = require("./babel.js");
let version = fs
.readFileSync(path.resolve(paths.polymer_dir, "setup.py"), "utf8")
.match(/\d{8}\.\d+/);
if (!version) {
throw Error("Version not found");
}
version = version[0];
const createWebpackConfig = ({
entry,
outputRoot,
@@ -30,11 +38,17 @@ const createWebpackConfig = ({
test: /\.css$/,
use: "raw-loader",
},
{
test: /\.(html)$/,
use: {
loader: "html-loader",
options: {
exportAsEs6Default: true,
},
},
},
],
},
externals: {
esprima: "esprima",
},
optimization: {
minimizer: [
new TerserPlugin({
@@ -54,9 +68,8 @@ const createWebpackConfig = ({
new webpack.DefinePlugin({
__DEV__: !isProdBuild,
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()),
__VERSION__: JSON.stringify(version),
__DEMO__: false,
__BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify(
isProdBuild ? "production" : "development"
@@ -83,6 +96,14 @@ const createWebpackConfig = ({
].filter(Boolean),
resolve: {
extensions: [".ts", ".js", ".json"],
alias: {
react: "preact-compat",
"react-dom": "preact-compat",
// Not necessary unless you consume a module using `createClass`
"create-react-class": "preact-compat/lib/create-react-class",
// Not necessary unless you consume a module requiring `react-dom-factories`
"react-dom-factories": "preact-compat/lib/react-dom-factories",
},
},
output: {
filename: ({ chunk }) => {
@@ -115,6 +136,7 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
core: "./src/entrypoints/core.ts",
compatibility: "./src/entrypoints/compatibility.ts",
"custom-panel": "./src/entrypoints/custom-panel.ts",
"hass-icons": "./src/entrypoints/hass-icons.ts",
},
outputRoot: paths.root,
isProdBuild,
@@ -133,7 +155,7 @@ const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
`/static/translations/${englishFilename}`
] = `build-translations/output/${englishFilename}`;
translationMetadata.fragments.forEach((fragment) => {
Object.keys(translationMetadata.fragments).forEach((fragment) => {
workBoxTranslationsTemplatedURLs[
`/static/translations/${fragment}/${englishFilename}`
] = `build-translations/output/${fragment}/${englishFilename}`;
@@ -176,7 +198,7 @@ const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => {
},
outputRoot: paths.demo_root,
defineOverlay: {
__VERSION__: JSON.stringify(`DEMO-${env.version()}`),
__VERSION__: JSON.stringify(`DEMO-${version}`),
__DEMO__: true,
},
isProdBuild,
@@ -199,9 +221,6 @@ const createCastConfig = ({ isProdBuild, latestBuild }) => {
outputRoot: paths.cast_root,
isProdBuild,
latestBuild,
defineOverlay: {
__BACKWARDS_COMPAT__: true,
},
});
};
@@ -226,6 +245,9 @@ const createHassioConfig = ({ isProdBuild, latestBuild }) => {
};
const createGalleryConfig = ({ isProdBuild, latestBuild }) => {
if (!latestBuild) {
throw new Error("Gallery only supports latest build!");
}
const config = createWebpackConfig({
entry: {
entrypoint: path.resolve(paths.gallery_dir, "src/entrypoint.js"),

View File

@@ -1,3 +1,5 @@
import "../../../src/resources/ha-style";
import "../../../src/resources/roboto";
import "../../../src/components/ha-iconset-svg";
import "../../../src/resources/hass-icons";
import "./layout/hc-connect";

View File

@@ -1,53 +1,51 @@
import "@polymer/paper-item/paper-icon-item";
import "@polymer/paper-listbox/paper-listbox";
import { Auth, Connection } from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
html,
CSSResult,
css,
} from "lit-element";
import { CastManager } from "../../../../src/cast/cast_manager";
import {
castSendShowLovelaceView,
ensureConnectedCastSession,
} from "../../../../src/cast/receiver_messages";
import {
askWrite,
enableWrite,
saveTokens,
} from "../../../../src/common/auth/token_storage";
import { atLeastVersion } from "../../../../src/common/config/version";
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
import { Connection, Auth } from "home-assistant-js-websocket";
import "@polymer/iron-icon";
import "@polymer/paper-listbox/paper-listbox";
import "@polymer/paper-item/paper-icon-item";
import "../../../../src/components/ha-icon";
import {
getLegacyLovelaceCollection,
getLovelaceCollection,
LovelaceConfig,
} from "../../../../src/data/lovelace";
enableWrite,
askWrite,
saveTokens,
} from "../../../../src/common/auth/token_storage";
import {
ensureConnectedCastSession,
castSendShowLovelaceView,
} from "../../../../src/cast/receiver_messages";
import "../../../../src/layouts/loading-screen";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import { CastManager } from "../../../../src/cast/cast_manager";
import {
LovelaceConfig,
getLovelaceCollection,
getLegacyLovelaceCollection,
} from "../../../../src/data/lovelace";
import "./hc-layout";
import "@material/mwc-button/mwc-button";
import { generateDefaultViewConfig } from "../../../../src/panels/lovelace/common/generate-lovelace-config";
import { toggleAttribute } from "../../../../src/common/dom/toggle_attribute";
import { atLeastVersion } from "../../../../src/common/config/version";
@customElement("hc-cast")
class HcCast extends LitElement {
@property() public auth!: Auth;
@property() public connection!: Connection;
@property() public castManager!: CastManager;
@property() private askWrite = false;
@property() private lovelaceConfig?: LovelaceConfig | null;
protected render(): TemplateResult {
if (this.lovelaceConfig === undefined) {
return html` <loading-screen></loading-screen>> `;
return html`
<loading-screen></loading-screen>>
`;
}
const error =
@@ -77,12 +75,14 @@ class HcCast extends LitElement {
`
: ""}
${error
? html` <div class="card-content">${error}</div> `
? html`
<div class="card-content">${error}</div>
`
: !this.castManager.status
? html`
<p class="center-item">
<mwc-button raised @click=${this._handleLaunch}>
<ha-icon icon="hass:cast"></ha-icon>
<iron-icon icon="hass:cast"></iron-icon>
Start Casting
</mwc-button>
</p>
@@ -120,7 +120,7 @@ class HcCast extends LitElement {
${this.castManager.status
? html`
<mwc-button @click=${this._handleLaunch}>
<ha-icon icon="hass:cast-connected"></ha-icon>
<iron-icon icon="hass:cast-connected"></iron-icon>
Manage
</mwc-button>
`
@@ -242,7 +242,7 @@ class HcCast extends LitElement {
color: var(--secondary-text-color);
}
mwc-button ha-icon {
mwc-button iron-icon {
margin-right: 8px;
height: 18px;
}

View File

@@ -1,35 +1,35 @@
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import {
Auth,
Connection,
createConnection,
ERR_CANNOT_CONNECT,
ERR_HASS_HOST_REQUIRED,
ERR_INVALID_AUTH,
ERR_INVALID_HTTPS_TO_HTTP,
getAuth,
getAuthOptions,
} from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
customElement,
property,
TemplateResult,
html,
CSSResult,
css,
} from "lit-element";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
import {
getAuth,
createConnection,
Auth,
getAuthOptions,
ERR_HASS_HOST_REQUIRED,
ERR_INVALID_HTTPS_TO_HTTP,
Connection,
ERR_CANNOT_CONNECT,
ERR_INVALID_AUTH,
} from "home-assistant-js-websocket";
import "@polymer/iron-icon";
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import {
loadTokens,
saveTokens,
} from "../../../../src/common/auth/token_storage";
import "../../../../src/components/ha-icon";
import "../../../../src/layouts/loading-screen";
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
import { CastManager, getCastManager } from "../../../../src/cast/cast_manager";
import "./hc-layout";
import { castSendShowDemo } from "../../../../src/cast/receiver_messages";
import { registerServiceWorker } from "../../../../src/util/register-service-worker";
const seeFAQ = (qid) => html`
See <a href="./faq.html${qid ? `#${qid}` : ""}">the FAQ</a> for more
@@ -61,19 +61,13 @@ const INTRO = html`
@customElement("hc-connect")
export class HcConnect extends LitElement {
@property() private loading = false;
// If we had stored credentials but we cannot connect,
// show a screen asking retry or logout.
@property() private cannotConnect = false;
@property() private error?: string | TemplateResult;
@property() private auth?: Auth;
@property() private connection?: Connection;
@property() private castManager?: CastManager | null;
private openDemo = false;
protected render(): TemplateResult {
@@ -98,7 +92,9 @@ export class HcConnect extends LitElement {
}
if (this.castManager === undefined || this.loading) {
return html` <loading-screen></loading-screen> `;
return html`
<loading-screen></loading-screen>
`;
}
if (this.castManager === null) {
@@ -131,16 +127,20 @@ export class HcConnect extends LitElement {
@keydown=${this._handleInputKeyDown}
></paper-input>
</p>
${this.error ? html` <p class="error">${this.error}</p> ` : ""}
${this.error
? html`
<p class="error">${this.error}</p>
`
: ""}
</div>
<div class="card-actions">
<mwc-button @click=${this._handleDemo}>
Show Demo
<ha-icon
<iron-icon
.icon=${this.castManager.castState === "CONNECTED"
? "hass:cast-connected"
: "hass:cast"}
></ha-icon>
></iron-icon>
</mwc-button>
<div class="spacer"></div>
<mwc-button @click=${this._handleConnect}>Authorize</mwc-button>
@@ -211,8 +211,7 @@ export class HcConnect extends LitElement {
if (value === "") {
this.error = "Please enter a Home Assistant URL.";
return;
}
if (value.indexOf("://") === -1) {
} else if (value.indexOf("://") === -1) {
this.error =
"Please enter your full URL, including the protocol part (https://).";
return;
@@ -316,7 +315,7 @@ export class HcConnect extends LitElement {
color: darkred;
}
mwc-button ha-icon {
mwc-button iron-icon {
margin-left: 8px;
}

View File

@@ -1,28 +1,25 @@
import {
customElement,
LitElement,
TemplateResult,
html,
CSSResult,
css,
property,
} from "lit-element";
import {
Auth,
Connection,
getUser,
HassUser,
getUser,
} from "home-assistant-js-websocket";
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
} from "lit-element";
import "../../../../src/components/ha-card";
@customElement("hc-layout")
class HcLayout extends LitElement {
@property() public subtitle?: string | undefined;
@property() public auth?: Auth;
@property() public connection?: Connection;
@property() public user?: HassUser;
protected render(): TemplateResult {
@@ -40,7 +37,11 @@ class HcLayout extends LitElement {
this.auth.data.hassUrl.indexOf("//") + 2
)}</a
>
${this.user ? html` ${this.user.name} ` : ""}
${this.user
? html`
${this.user.name}
`
: ""}
</div>
`
: ""}

View File

@@ -1,2 +1 @@
/* eslint-disable no-undef */
export const castContext = cast.framework.CastReceiverContext.getInstance();

View File

@@ -1,4 +1,4 @@
import { convertEntities, Entity } from "../../../../src/fake_data/entity";
import { Entity, convertEntities } from "../../../../src/fake_data/entity";
export const castDemoEntities: () => Entity[] = () =>
convertEntities({

View File

@@ -1,6 +1,6 @@
import {
LovelaceCardConfig,
LovelaceConfig,
LovelaceCardConfig,
} from "../../../../src/data/lovelace";
import { castContext } from "../cast_context";

View File

@@ -1,10 +1,9 @@
/* eslint-disable no-undef */
import { CAST_NS } from "../../../src/cast/const";
import { HassMessage } from "../../../src/cast/receiver_messages";
import "../../../src/resources/custom-card-support";
import { castContext } from "./cast_context";
import { HcMain } from "./layout/hc-main";
import { ReceivedMessage } from "./types";
import { HassMessage } from "../../../src/cast/receiver_messages";
import { HcMain } from "./layout/hc-main";
import { CAST_NS } from "../../../src/cast/const";
const controller = new HcMain();
document.body.append(controller);

View File

@@ -1,20 +1,19 @@
import { customElement, html, property, TemplateResult } from "lit-element";
import { mockHistory } from "../../../../demo/src/stubs/history";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import { HassElement } from "../../../../src/state/hass-element";
import "./hc-lovelace";
import { customElement, TemplateResult, html, property } from "lit-element";
import {
MockHomeAssistant,
provideHass,
} from "../../../../src/fake_data/provide_hass";
import { HassElement } from "../../../../src/state/hass-element";
import { HomeAssistant } from "../../../../src/types";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import { castDemoEntities } from "../demo/cast-demo-entities";
import { castDemoLovelace } from "../demo/cast-demo-lovelace";
import "./hc-lovelace";
import { mockHistory } from "../../../../demo/src/stubs/history";
@customElement("hc-demo")
class HcDemo extends HassElement {
@property() public lovelacePath!: string;
@property() private _lovelaceConfig?: LovelaceConfig;
protected render(): TemplateResult {
@@ -29,7 +28,6 @@ class HcDemo extends HassElement {
></hc-lovelace>
`;
}
protected firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this._initialize();

View File

@@ -1,18 +1,17 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
html,
customElement,
CSSResult,
css,
property,
} from "lit-element";
import { HomeAssistant } from "../../../../src/types";
@customElement("hc-launch-screen")
class HcLaunchScreen extends LitElement {
@property() public hass?: HomeAssistant;
@property() public error?: string;
protected render(): TemplateResult {
@@ -23,7 +22,11 @@ class HcLaunchScreen extends LitElement {
/>
<div class="status">
${this.hass ? "Connected" : "Not Connected"}
${this.error ? html` <p>Error: ${this.error}</p> ` : ""}
${this.error
? html`
<p>Error: ${this.error}</p>
`
: ""}
</div>
</div>
`;

View File

@@ -1,17 +1,17 @@
import {
css,
CSSResult,
customElement,
html,
LitElement,
property,
TemplateResult,
html,
customElement,
CSSResult,
css,
property,
} from "lit-element";
import { LovelaceConfig } from "../../../../src/data/lovelace";
import { Lovelace } from "../../../../src/panels/lovelace/types";
import "../../../../src/panels/lovelace/views/hui-panel-view";
import "../../../../src/panels/lovelace/views/hui-view";
import "../../../../src/panels/lovelace/views/hui-panel-view";
import { HomeAssistant } from "../../../../src/types";
import { Lovelace } from "../../../../src/panels/lovelace/types";
import "./hc-launch-screen";
@customElement("hc-lovelace")
@@ -46,7 +46,6 @@ class HcLovelace extends LitElement {
? html`
<hui-panel-view
.hass=${this.hass}
.lovelace=${lovelace}
.config=${this.lovelaceConfig.views[index]}
></hui-panel-view>
`

View File

@@ -1,31 +1,31 @@
import {
createConnection,
getAuth,
createConnection,
UnsubscribeFunc,
} from "home-assistant-js-websocket";
import { customElement, html, property, TemplateResult } from "lit-element";
import { CAST_NS } from "../../../../src/cast/const";
import {
ConnectMessage,
GetStatusMessage,
HassMessage,
ShowDemoMessage,
ShowLovelaceViewMessage,
} from "../../../../src/cast/receiver_messages";
import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
import { atLeastVersion } from "../../../../src/common/config/version";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import {
fetchResources,
getLegacyLovelaceCollection,
getLovelaceCollection,
LegacyLovelaceConfig,
LovelaceConfig,
} from "../../../../src/data/lovelace";
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
import { customElement, TemplateResult, html, property } from "lit-element";
import { HassElement } from "../../../../src/state/hass-element";
import { castContext } from "../cast_context";
import {
HassMessage,
ConnectMessage,
ShowLovelaceViewMessage,
GetStatusMessage,
ShowDemoMessage,
} from "../../../../src/cast/receiver_messages";
import {
LovelaceConfig,
getLovelaceCollection,
fetchResources,
LegacyLovelaceConfig,
getLegacyLovelaceCollection,
} from "../../../../src/data/lovelace";
import "./hc-launch-screen";
import { castContext } from "../cast_context";
import { CAST_NS } from "../../../../src/cast/const";
import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
import { atLeastVersion } from "../../../../src/common/config/version";
let resourcesLoaded = false;
@@ -40,7 +40,6 @@ export class HcMain extends HassElement {
@property() private _error?: string;
private _unsubLovelace?: UnsubscribeFunc;
private _urlPath?: string | null;
public processIncomingMessage(msg: HassMessage) {
@@ -53,14 +52,16 @@ export class HcMain extends HassElement {
} else if (msg.type === "show_demo") {
this._handleShowDemo(msg);
} else {
// eslint-disable-next-line no-console
// tslint:disable-next-line: no-console
console.warn("unknown msg type", msg);
}
}
protected render(): TemplateResult {
if (this._showDemo) {
return html` <hc-demo .lovelacePath=${this._lovelacePath}></hc-demo> `;
return html`
<hc-demo .lovelacePath=${this._lovelacePath}></hc-demo>
`;
}
if (
@@ -90,17 +91,15 @@ export class HcMain extends HassElement {
super.firstUpdated(changedProps);
import("../second-load");
window.addEventListener("location-changed", () => {
const panelPath = `/${this._urlPath || "lovelace"}/`;
if (location.pathname.startsWith(panelPath)) {
this._lovelacePath = location.pathname.substr(panelPath.length);
if (location.pathname.startsWith("/lovelace/")) {
this._lovelacePath = location.pathname.substr(10);
this._sendStatus();
}
});
document.body.addEventListener("click", (ev) => {
const panelPath = `/${this._urlPath || "lovelace"}/`;
const href = isNavigationClick(ev);
if (href && href.startsWith(panelPath)) {
this._lovelacePath = href.substr(panelPath.length);
if (href && href.startsWith("/lovelace/")) {
this._lovelacePath = href.substr(10);
this._sendStatus();
}
});
@@ -172,9 +171,6 @@ export class HcMain extends HassElement {
this._error = "Cannot show Lovelace because we're not connected.";
return;
}
if (msg.urlPath === "lovelace") {
msg.urlPath = null;
}
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
this._urlPath = msg.urlPath;
if (this._unsubLovelace) {

View File

@@ -1,3 +1,5 @@
import "web-animations-js/web-animations-next-lite.min";
import "../../../src/resources/hass-icons";
import "../../../src/resources/roboto";
import "../../../src/components/ha-iconset-svg";
import "./layout/hc-lovelace";

View File

@@ -6,6 +6,6 @@ const { isProdBuild } = require("../build-scripts/env.js");
const latestBuild = true;
module.exports = createCastConfig({
isProdBuild: isProdBuild(),
isProdBuild,
latestBuild,
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

View File

@@ -291,7 +291,16 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "13:21",
attributes: {
attribution: "Data provided by Ring.com",
device_id: "e04f434dca02",
firmware: "Up to Date",
kind: "lpd_v2",
timezone: "America/New_York",
type: "doorbots",
wifi_name: "RingOfSecurity-hUrGKNlhR",
created_at: "2019-01-22T13:21:03-05:00",
answered: false,
recording_status: "ready",
category: "motion",
friendly_name: "Front Door Last Motion",
icon: "hademo:history",
},
@@ -304,7 +313,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
"cbd8dfac9efb441f19168e271cb8629b0372d0c1f721353394b23ed0202013b0",
motion_detection: true,
friendly_name: "Patio",
entity_picture: "/assets/arsaboo/images/camera.patio.jpg",
entity_picture:
"/api/camera_proxy/camera.patio?token=cbd8dfac9efb441f19168e271cb8629b0372d0c1f721353394b23ed0202013b0",
supported_features: 0,
},
},
@@ -316,7 +326,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
"479b332e0a7cad4c58e0fb98a1ecb7942e3b225190adb93a1341edfa7daf45b0",
motion_detection: true,
friendly_name: "Porch",
entity_picture: "/assets/arsaboo/images/camera.porch.jpg",
entity_picture:
"/api/camera_proxy/camera.porch?token=479b332e0a7cad4c58e0fb98a1ecb7942e3b225190adb93a1341edfa7daf45b0",
supported_features: 0,
},
},
@@ -328,7 +339,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
"9381b2e4edd1bb21e868e2193f5d132a5fae153ce4f458451d979a02712b4642",
motion_detection: true,
friendly_name: "Backyard",
entity_picture: "/assets/arsaboo/images/camera.backyard.jpg",
entity_picture:
"/api/camera_proxy/camera.backyard?token=9381b2e4edd1bb21e868e2193f5d132a5fae153ce4f458451d979a02712b4642",
supported_features: 0,
},
},
@@ -340,7 +352,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
"ac38bf88c2c5896eed66ae15739a3e726677f92d79e0d57f83f726ac28bda746",
motion_detection: true,
friendly_name: "Driveway",
entity_picture: "/assets/arsaboo/images/camera.driveway.jpg",
entity_picture:
"/api/camera_proxy/camera.driveway?token=ac38bf88c2c5896eed66ae15739a3e726677f92d79e0d57f83f726ac28bda746",
supported_features: 0,
},
},
@@ -464,7 +477,8 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
friendly_name: localize(
"ui.panel.page-demo.config.arsaboo.names.family_room"
),
entity_picture: "/assets/arsaboo/images/media_player_family_room.jpg",
entity_picture:
"/api/media_player_proxy/media_player.family_room_2?token=be41a86e2a360761d67c36a010b09654b730deec092016ee92aafef79b1978ff&cache=e03d22fb103202e7",
supported_features: 64063,
},
},
@@ -473,7 +487,16 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) =>
state: "06:44",
attributes: {
attribution: "Data provided by Ring.com",
device_id: "e04f434dca02",
firmware: "Up to Date",
kind: "lpd_v2",
timezone: "America/New_York",
type: "doorbots",
wifi_name: "RingOfSecurity-hUrGKNlhR",
created_at: "2019-01-22T06:44:31-05:00",
answered: false,
recording_status: "ready",
category: "ding",
friendly_name: "Front Door Last Ding",
icon: "hademo:history",
},

View File

@@ -1,6 +1,6 @@
import { DemoConfig } from "../types";
import { demoEntitiesArsaboo } from "./entities";
import { demoLovelaceArsaboo } from "./lovelace";
import { demoEntitiesArsaboo } from "./entities";
import { demoThemeArsaboo } from "./theme";
export const demoArsaboo: DemoConfig = {

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