diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 84% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md index 0aa8195d5a..2a6ed52bff 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,24 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: bug +assignees: "" +--- + +**Checklist:** + +- [ ] I updated to the latest version available +- [ ] I cleared the cache of my browser + **Home Assistant release with the issue:** + **Browser and Operating System:** + **Description of problem:** + - **Javascript errors shown in the web inspector (if applicable):** + ``` ``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000..51d1465c16 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,19 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: feature request +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.travis.yml b/.travis.yml index 4b357440b7..24433d2e24 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,19 +8,20 @@ install: yarn install script: - npm run build - hassio/script/build_hassio + # Because else eslint fails because hassio has cleaned that build + - ./node_modules/.bin/gulp gen-icons-app - npm run test # - xvfb-run wct --module-resolution=node --npm # - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi' services: - docker before_deploy: - - 'docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21' + - "docker pull lokalise/lokalise-cli@sha256:2198814ebddfda56ee041a4b427521757dd57f75415ea9693696a64c550cef21" deploy: provider: script script: script/travis_deploy - 'on': + "on": branch: master dist: trusty addons: sauce_connect: true - diff --git a/build-scripts/babel.js b/build-scripts/babel.js index 3951116cbd..67021b92c4 100644 --- a/build-scripts/babel.js +++ b/build-scripts/babel.js @@ -3,7 +3,7 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => { throw Error("latestBuild not defined for babel loader config"); } return { - test: /\.m?js$/, + test: /\.m?js$|\.tsx?$/, use: { loader: "babel-loader", options: { @@ -12,6 +12,12 @@ module.exports.babelLoaderConfig = ({ 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}) @@ -21,6 +27,12 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => { ], // Only support the syntax, Webpack will handle it. "@babel/syntax-dynamic-import", + [ + "@babel/transform-react-jsx", + { + pragma: "h", + }, + ], [ require("@babel/plugin-proposal-decorators").default, { decoratorsBeforeExport: true }, diff --git a/build-scripts/env.js b/build-scripts/env.js new file mode 100644 index 0000000000..101858a367 --- /dev/null +++ b/build-scripts/env.js @@ -0,0 +1,6 @@ +module.exports = { + isProdBuild: process.env.NODE_ENV === "production", + isStatsBuild: process.env.STATS === "1", + isTravis: process.env.TRAVIS === "true", + isNetlify: process.env.NETLIFY === "true", +}; diff --git a/build-scripts/gulp/app.js b/build-scripts/gulp/app.js index 1396af5528..985067c71f 100644 --- a/build-scripts/gulp/app.js +++ b/build-scripts/gulp/app.js @@ -1,10 +1,13 @@ // Run HA develop mode const gulp = require("gulp"); +const envVars = require("../env"); + require("./clean.js"); require("./translations.js"); require("./gen-icons.js"); require("./gather-static.js"); +require("./compress.js"); require("./webpack.js"); require("./service-worker.js"); require("./entry-html.js"); @@ -18,7 +21,7 @@ gulp.task( "clean", gulp.parallel( "gen-service-worker-dev", - "gen-icons", + gulp.parallel("gen-icons-app", "gen-icons-mdi"), "gen-pages-dev", "gen-index-app-dev", gulp.series("create-test-translation", "build-translations") @@ -35,13 +38,11 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean", - gulp.parallel("gen-icons", "build-translations"), + gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"), "copy-static", - gulp.parallel( - "webpack-prod-app", - // Do not compress static files in CI, it's SLOW. - ...(process.env.CI === "true" ? [] : ["compress-static"]) - ), + "webpack-prod-app", + ...// Don't compress running tests + (envVars.isTravis ? [] : ["compress-app"]), gulp.parallel( "gen-pages-prod", "gen-index-app-prod", diff --git a/build-scripts/gulp/cast.js b/build-scripts/gulp/cast.js index 974d2c29cf..34fcbb67a4 100644 --- a/build-scripts/gulp/cast.js +++ b/build-scripts/gulp/cast.js @@ -1,4 +1,3 @@ -// Run cast develop mode const gulp = require("gulp"); require("./clean.js"); @@ -16,7 +15,12 @@ gulp.task( process.env.NODE_ENV = "development"; }, "clean-cast", - gulp.parallel("gen-icons", "gen-index-cast-dev", "build-translations"), + gulp.parallel( + "gen-icons-app", + "gen-icons-mdi", + "gen-index-cast-dev", + "build-translations" + ), "copy-static-cast", "webpack-dev-server-cast" ) @@ -29,7 +33,7 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean-cast", - gulp.parallel("gen-icons", "build-translations"), + gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"), "copy-static-cast", "webpack-prod-cast", "gen-index-cast-prod" diff --git a/build-scripts/gulp/clean.js b/build-scripts/gulp/clean.js index f81728fcb3..41fec51e69 100644 --- a/build-scripts/gulp/clean.js +++ b/build-scripts/gulp/clean.js @@ -9,15 +9,31 @@ gulp.task( return del([config.root, config.build_dir]); }) ); + gulp.task( "clean-demo", gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { return del([config.demo_root, config.build_dir]); }) ); + gulp.task( "clean-cast", gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { return del([config.cast_root, config.build_dir]); }) ); + +gulp.task( + "clean-hassio", + gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { + return del([config.hassio_root, config.build_dir]); + }) +); + +gulp.task( + "clean-gallery", + gulp.parallel("clean-translations", function cleanOutputAndBuildDir() { + return del([config.gallery_root, config.build_dir]); + }) +); diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js new file mode 100644 index 0000000000..de604f7fa3 --- /dev/null +++ b/build-scripts/gulp/compress.js @@ -0,0 +1,38 @@ +// Tasks to compress + +const gulp = require("gulp"); +const zopfli = require("gulp-zopfli-green"); +const merge = require("merge-stream"); +const path = require("path"); +const paths = require("../paths"); + +gulp.task("compress-app", function compressApp() { + const jsLatest = gulp + .src(path.resolve(paths.output, "**/*.js")) + .pipe(zopfli()) + .pipe(gulp.dest(paths.output)); + + const jsEs5 = gulp + .src(path.resolve(paths.output_es5, "**/*.js")) + .pipe(zopfli()) + .pipe(gulp.dest(paths.output_es5)); + + const polyfills = gulp + .src(path.resolve(paths.static, "polyfills/*.js")) + .pipe(zopfli()) + .pipe(gulp.dest(path.resolve(paths.static, "polyfills"))); + + const translations = gulp + .src(path.resolve(paths.static, "translations/*.json")) + .pipe(zopfli()) + .pipe(gulp.dest(path.resolve(paths.static, "translations"))); + + return merge(jsLatest, jsEs5, polyfills, translations); +}); + +gulp.task("compress-hassio", function compressApp() { + return gulp + .src(path.resolve(paths.hassio_root, "**/*.js")) + .pipe(zopfli()) + .pipe(gulp.dest(paths.hassio_root)); +}); diff --git a/build-scripts/gulp/demo.js b/build-scripts/gulp/demo.js index 2c8070962d..de51760281 100644 --- a/build-scripts/gulp/demo.js +++ b/build-scripts/gulp/demo.js @@ -17,7 +17,8 @@ gulp.task( }, "clean-demo", gulp.parallel( - "gen-icons", + "gen-icons-app", + "gen-icons-mdi", "gen-icons-demo", "gen-index-demo-dev", "build-translations" @@ -34,7 +35,12 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean-demo", - gulp.parallel("gen-icons", "gen-icons-demo", "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" diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index 33abdff712..e8ad05e8b4 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -11,12 +11,6 @@ const config = require("../paths.js"); const templatePath = (tpl) => path.resolve(config.polymer_dir, "src/html/", `${tpl}.html.template`); -const demoTemplatePath = (tpl) => - path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`); - -const castTemplatePath = (tpl) => - path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`); - const readFile = (pth) => fs.readFileSync(pth).toString(); const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { @@ -25,10 +19,19 @@ const renderTemplate = (pth, data = {}, pathFunc = templatePath) => { }; const renderDemoTemplate = (pth, data = {}) => - renderTemplate(pth, data, demoTemplatePath); + renderTemplate(pth, data, (tpl) => + path.resolve(config.demo_dir, "src/html/", `${tpl}.html.template`) + ); const renderCastTemplate = (pth, data = {}) => - renderTemplate(pth, data, castTemplatePath); + renderTemplate(pth, data, (tpl) => + path.resolve(config.cast_dir, "src/html/", `${tpl}.html.template`) + ); + +const renderGalleryTemplate = (pth, data = {}) => + renderTemplate(pth, data, (tpl) => + path.resolve(config.gallery_dir, "src/html/", `${tpl}.html.template`) + ); const minifyHtml = (content) => minify(content, { @@ -209,8 +212,33 @@ gulp.task("gen-index-demo-prod", (done) => { es5Compatibility: es5Manifest["compatibility.js"], es5DemoJS: es5Manifest["main.js"], }); - const minified = minifyHtml(content).replace(/#THEMEC/g, "{{ theme_color }}"); + const minified = minifyHtml(content); fs.outputFileSync(path.resolve(config.demo_root, "index.html"), minified); 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", { + latestGalleryJS: "./entrypoint.js", + }); + + fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), content); + done(); +}); + +gulp.task("gen-index-gallery-prod", (done) => { + const latestManifest = require(path.resolve( + config.gallery_output, + "manifest.json" + )); + const content = renderGalleryTemplate("index", { + latestGalleryJS: latestManifest["entrypoint.js"], + }); + const minified = minifyHtml(content); + + fs.outputFileSync(path.resolve(config.gallery_root, "index.html"), minified); + done(); +}); diff --git a/build-scripts/gulp/gallery.js b/build-scripts/gulp/gallery.js new file mode 100644 index 0000000000..a46c6c1939 --- /dev/null +++ b/build-scripts/gulp/gallery.js @@ -0,0 +1,38 @@ +// Run demo develop mode +const gulp = require("gulp"); + +require("./clean.js"); +require("./translations.js"); +require("./gen-icons.js"); +require("./gather-static.js"); +require("./webpack.js"); +require("./service-worker.js"); +require("./entry-html.js"); + +gulp.task( + "develop-gallery", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "development"; + }, + "clean-gallery", + gulp.parallel("gen-icons-app", "gen-icons-app", "build-translations"), + "copy-static-gallery", + "gen-index-gallery-dev", + "webpack-dev-server-gallery" + ) +); + +gulp.task( + "build-gallery", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "production"; + }, + "clean-gallery", + gulp.parallel("gen-icons-app", "gen-icons-mdi", "build-translations"), + "copy-static-gallery", + "webpack-prod-gallery", + "gen-index-gallery-prod" + ) +); diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 3d80035e17..17a1a87ae8 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -4,8 +4,6 @@ const gulp = require("gulp"); const path = require("path"); const cpx = require("cpx"); const fs = require("fs-extra"); -const zopfli = require("gulp-zopfli-green"); -const merge = require("merge-stream"); const paths = require("../paths"); const npmPath = (...parts) => @@ -67,20 +65,6 @@ function copyMapPanel(staticDir) { ); } -function compressStatic(staticDir) { - const staticPath = genStaticPath(staticDir); - const polyfills = gulp - .src(staticPath("polyfills/*.js")) - .pipe(zopfli()) - .pipe(gulp.dest(staticPath("polyfills"))); - const translations = gulp - .src(staticPath("translations/*.json")) - .pipe(zopfli()) - .pipe(gulp.dest(staticPath("translations"))); - - return merge(polyfills, translations); -} - gulp.task("copy-static", (done) => { const staticDir = paths.static; const staticPath = genStaticPath(paths.static); @@ -100,8 +84,6 @@ gulp.task("copy-static", (done) => { done(); }); -gulp.task("compress-static", () => compressStatic(paths.static)); - gulp.task("copy-static-demo", (done) => { // Copy app static files fs.copySync( @@ -129,3 +111,15 @@ gulp.task("copy-static-cast", (done) => { copyTranslations(paths.cast_static); done(); }); + +gulp.task("copy-static-gallery", (done) => { + // Copy app static files + fs.copySync(polyPath("public/static"), paths.gallery_static); + // Copy gallery static files + fs.copySync(path.resolve(paths.gallery_dir, "public"), paths.gallery_root); + + copyMapPanel(paths.gallery_static); + copyFonts(paths.gallery_static); + copyTranslations(paths.gallery_static); + done(); +}); diff --git a/build-scripts/gulp/gen-icons.js b/build-scripts/gulp/gen-icons.js index 11548794c4..b116b8abea 100644 --- a/build-scripts/gulp/gen-icons.js +++ b/build-scripts/gulp/gen-icons.js @@ -57,18 +57,6 @@ function generateIconset(iconsetName, iconNames) { return `${iconDefs}`; } -// Generate the full MDI iconset -function genMDIIcons() { - 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)); -} - // Helper function to map recursively over files in a folder and it's subfolders function mapFiles(startPath, filter, mapFunc) { const files = fs.readdirSync(startPath); @@ -101,24 +89,27 @@ function findIcons(searchPath, iconsetName) { return icons; } -function genHassIcons() { +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)); -} - -gulp.task("gen-icons-mdi", (done) => { - genMDIIcons(); done(); }); -gulp.task("gen-icons-hass", (done) => { - genHassIcons(); - done(); -}); -gulp.task("gen-icons", gulp.series("gen-icons-hass", "gen-icons-mdi")); gulp.task("gen-icons-demo", (done) => { const iconNames = findIcons(path.resolve(paths.demo_dir, "./src"), "hademo"); @@ -129,8 +120,21 @@ gulp.task("gen-icons-demo", (done) => { done(); }); -module.exports = { - findIcons, - generateIconset, - genMDIIcons, -}; +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(); +}); diff --git a/build-scripts/gulp/hassio.js b/build-scripts/gulp/hassio.js new file mode 100644 index 0000000000..164a5b9deb --- /dev/null +++ b/build-scripts/gulp/hassio.js @@ -0,0 +1,34 @@ +const gulp = require("gulp"); + +const envVars = require("../env"); + +require("./clean.js"); +require("./gen-icons.js"); +require("./webpack.js"); +require("./compress.js"); + +gulp.task( + "develop-hassio", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "development"; + }, + "clean-hassio", + gulp.parallel("gen-icons-hassio", "gen-icons-mdi"), + "webpack-watch-hassio" + ) +); + +gulp.task( + "build-hassio", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "production"; + }, + "clean-hassio", + gulp.parallel("gen-icons-hassio", "gen-icons-mdi"), + "webpack-prod-hassio", + ...// Don't compress running tests + (envVars.isTravis ? [] : ["compress-hassio"]) + ) +); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index 6015009d2d..46a114540b 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -1,6 +1,5 @@ // Tasks to run webpack. const gulp = require("gulp"); -const path = require("path"); const webpack = require("webpack"); const WebpackDevServer = require("webpack-dev-server"); const log = require("fancy-log"); @@ -9,8 +8,33 @@ const { createAppConfig, createDemoConfig, createCastConfig, + createHassioConfig, + createGalleryConfig, } = require("../webpack"); +const bothBuilds = (createConfigFunc, params) => [ + createConfigFunc({ ...params, latestBuild: true }), + createConfigFunc({ ...params, latestBuild: false }), +]; + +const runDevServer = ({ + compiler, + contentBase, + port, + listenHost = "localhost", +}) => + new WebpackDevServer(compiler, { + open: true, + watchContentBase: true, + contentBase, + }).listen(port, listenHost, function(err) { + if (err) { + throw err; + } + // Server listening + log("[webpack-dev-server]", `http://localhost:${port}`); + }); + const handler = (done) => (err, stats) => { if (err) { console.log(err.stack || err); @@ -32,20 +56,11 @@ const handler = (done) => (err, stats) => { }; gulp.task("webpack-watch-app", () => { - const compiler = webpack([ - createAppConfig({ - isProdBuild: false, - latestBuild: true, - isStatsBuild: false, - }), - createAppConfig({ - isProdBuild: false, - latestBuild: false, - isStatsBuild: false, - }), - ]); - compiler.watch({}, handler()); // we are not calling done, so this command will run forever + webpack(bothBuilds(createAppConfig, { isProdBuild: false })).watch( + {}, + handler() + ); }); gulp.task( @@ -53,47 +68,17 @@ gulp.task( () => new Promise((resolve) => webpack( - [ - createAppConfig({ - isProdBuild: true, - latestBuild: true, - isStatsBuild: false, - }), - createAppConfig({ - isProdBuild: true, - latestBuild: false, - isStatsBuild: false, - }), - ], + bothBuilds(createAppConfig, { isProdBuild: true }), handler(resolve) ) ) ); gulp.task("webpack-dev-server-demo", () => { - const compiler = webpack([ - createDemoConfig({ - isProdBuild: false, - latestBuild: false, - isStatsBuild: false, - }), - createDemoConfig({ - isProdBuild: false, - latestBuild: true, - isStatsBuild: false, - }), - ]); - - new WebpackDevServer(compiler, { - open: true, - watchContentBase: true, - contentBase: path.resolve(paths.demo_dir, "dist"), - }).listen(8090, "localhost", function(err) { - if (err) { - throw err; - } - // Server listening - log("[webpack-dev-server]", "http://localhost:8090"); + runDevServer({ + compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })), + contentBase: paths.demo_root, + port: 8090, }); }); @@ -102,51 +87,22 @@ gulp.task( () => new Promise((resolve) => webpack( - [ - createDemoConfig({ - isProdBuild: true, - latestBuild: false, - isStatsBuild: false, - }), - createDemoConfig({ - isProdBuild: true, - latestBuild: true, - isStatsBuild: false, - }), - ], + bothBuilds(createDemoConfig, { + isProdBuild: true, + }), handler(resolve) ) ) ); gulp.task("webpack-dev-server-cast", () => { - const compiler = webpack([ - createCastConfig({ - isProdBuild: false, - latestBuild: false, - }), - createCastConfig({ - isProdBuild: false, - latestBuild: true, - }), - ]); - - new WebpackDevServer(compiler, { - open: true, - watchContentBase: true, - contentBase: path.resolve(paths.cast_dir, "dist"), - }).listen( - 8080, + runDevServer({ + compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })), + contentBase: paths.cast_root, + port: 8080, // Accessible from the network, because that's how Cast hits it. - "0.0.0.0", - function(err) { - if (err) { - throw err; - } - // Server listening - log("[webpack-dev-server]", "http://localhost:8080"); - } - ); + listenHost: "0.0.0.0", + }); }); gulp.task( @@ -154,16 +110,59 @@ gulp.task( () => new Promise((resolve) => webpack( - [ - createCastConfig({ - isProdBuild: true, - latestBuild: false, - }), - createCastConfig({ - isProdBuild: true, - latestBuild: true, - }), - ], + bothBuilds(createCastConfig, { + isProdBuild: true, + }), + + handler(resolve) + ) + ) +); + +gulp.task("webpack-watch-hassio", () => { + // we are not calling done, so this command will run forever + webpack( + createHassioConfig({ + isProdBuild: false, + latestBuild: false, + }) + ).watch({}, handler()); +}); + +gulp.task( + "webpack-prod-hassio", + () => + new Promise((resolve) => + webpack( + createHassioConfig({ + isProdBuild: true, + latestBuild: false, + }), + handler(resolve) + ) + ) +); + +gulp.task("webpack-dev-server-gallery", () => { + runDevServer({ + compiler: webpack( + createGalleryConfig({ latestBuild: true, isProdBuild: false }) + ), + contentBase: paths.gallery_root, + port: 8100, + }); +}); + +gulp.task( + "webpack-prod-gallery", + () => + new Promise((resolve) => + webpack( + createGalleryConfig({ + isProdBuild: true, + latestBuild: true, + }), + handler(resolve) ) ) diff --git a/build-scripts/paths.js b/build-scripts/paths.js index f59dc35399..4a26ab882c 100644 --- a/build-scripts/paths.js +++ b/build-scripts/paths.js @@ -20,4 +20,13 @@ module.exports = { cast_static: path.resolve(__dirname, "../cast/dist/static"), cast_output: path.resolve(__dirname, "../cast/dist/frontend_latest"), cast_output_es5: path.resolve(__dirname, "../cast/dist/frontend_es5"), + + gallery_dir: path.resolve(__dirname, "../gallery"), + gallery_root: path.resolve(__dirname, "../gallery/dist"), + gallery_output: path.resolve(__dirname, "../gallery/dist/frontend_latest"), + gallery_static: path.resolve(__dirname, "../gallery/dist/static"), + + hassio_dir: path.resolve(__dirname, "../hassio"), + hassio_root: path.resolve(__dirname, "../hassio/build"), + hassio_publicPath: "/api/hassio/app", }; diff --git a/build-scripts/webpack.js b/build-scripts/webpack.js index b7d8948f5d..ac04587333 100644 --- a/build-scripts/webpack.js +++ b/build-scripts/webpack.js @@ -3,8 +3,6 @@ const fs = require("fs"); const path = require("path"); const TerserPlugin = require("terser-webpack-plugin"); const WorkboxPlugin = require("workbox-webpack-plugin"); -const CompressionPlugin = require("compression-webpack-plugin"); -const zopfli = require("@gfx/zopfli"); const ManifestPlugin = require("webpack-manifest-plugin"); const paths = require("./paths.js"); const { babelLoaderConfig } = require("./babel.js"); @@ -17,288 +15,246 @@ if (!version) { } version = version[0]; -const genMode = (isProdBuild) => (isProdBuild ? "production" : "development"); -const genDevTool = (isProdBuild) => - isProdBuild ? "source-map" : "inline-cheap-module-source-map"; -const genFilename = (isProdBuild, dontHash = new Set()) => ({ chunk }) => { - if (!isProdBuild || dontHash.has(chunk.name)) { - return `${chunk.name}.js`; - } - return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; -}; -const genChunkFilename = (isProdBuild, isStatsBuild) => - isProdBuild && !isStatsBuild ? "chunk.[chunkhash].js" : "[name].chunk.js"; - -const resolve = { - extensions: [".ts", ".js", ".json", ".tsx"], - 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", - }, -}; - -const tsLoader = (latestBuild) => ({ - test: /\.ts|tsx$/, - exclude: [path.resolve(paths.polymer_dir, "node_modules")], - use: [ - { - loader: "ts-loader", - options: { - compilerOptions: latestBuild - ? { noEmit: false } - : { target: "es5", noEmit: false }, - }, - }, - ], -}); -const cssLoader = { - test: /\.css$/, - use: "raw-loader", -}; -const htmlLoader = { - test: /\.(html)$/, - use: { - loader: "html-loader", - options: { - exportAsEs6Default: true, - }, - }, -}; - -const plugins = [ - // Ignore moment.js locales - new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), - // Color.js is bloated, it contains all color definitions for all material color sets. - new webpack.NormalModuleReplacementPlugin( - /@polymer\/paper-styles\/color\.js$/, - path.resolve(paths.polymer_dir, "src/util/empty.js") - ), - // Ignore roboto pointing at CDN. We use local font-roboto-local. - new webpack.NormalModuleReplacementPlugin( - /@polymer\/font-roboto\/roboto\.js$/, - path.resolve(paths.polymer_dir, "src/util/empty.js") - ), - // Ignore mwc icons pointing at CDN. - new webpack.NormalModuleReplacementPlugin( - /@material\/mwc-icon\/mwc-icon-font\.js$/, - path.resolve(paths.polymer_dir, "src/util/empty.js") - ), -]; - -const optimization = (latestBuild) => ({ - minimizer: [ - new TerserPlugin({ - cache: true, - parallel: true, - extractComments: true, - sourceMap: true, - terserOptions: { - safari10: true, - ecma: latestBuild ? undefined : 5, - }, - }), - ], -}); - -const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { - const isCI = process.env.CI === "true"; - - // Create an object mapping browser urls to their paths during build - const translationMetadata = require("../build-translations/translationMetadata.json"); - const workBoxTranslationsTemplatedURLs = {}; - const englishFP = translationMetadata.translations.en.fingerprints; - Object.keys(englishFP).forEach((key) => { - workBoxTranslationsTemplatedURLs[ - `/static/translations/${englishFP[key]}` - ] = `build-translations/output/${key}.json`; - }); - - const entry = { - app: "./src/entrypoints/app.ts", - authorize: "./src/entrypoints/authorize.ts", - onboarding: "./src/entrypoints/onboarding.ts", - core: "./src/entrypoints/core.ts", - compatibility: "./src/entrypoints/compatibility.ts", - "custom-panel": "./src/entrypoints/custom-panel.ts", - "hass-icons": "./src/entrypoints/hass-icons.ts", - }; - - const rules = [tsLoader(latestBuild), cssLoader, htmlLoader]; - if (!latestBuild) { - rules.push(babelLoaderConfig({ latestBuild })); - } - +const createWebpackConfig = ({ + entry, + outputRoot, + defineOverlay, + isProdBuild, + latestBuild, + isStatsBuild, +}) => { return { - mode: genMode(isProdBuild), - devtool: genDevTool(isProdBuild), + mode: isProdBuild ? "production" : "development", + devtool: isProdBuild ? "source-map" : "inline-cheap-module-source-map", entry, module: { - rules, + rules: [ + babelLoaderConfig({ latestBuild }), + { + test: /\.css$/, + use: "raw-loader", + }, + { + test: /\.(html)$/, + use: { + loader: "html-loader", + options: { + exportAsEs6Default: true, + }, + }, + }, + ], + }, + optimization: { + minimizer: [ + new TerserPlugin({ + cache: true, + parallel: true, + extractComments: true, + sourceMap: true, + terserOptions: { + safari10: true, + ecma: latestBuild ? undefined : 5, + }, + }), + ], }, - optimization: optimization(latestBuild), plugins: [ new ManifestPlugin(), new webpack.DefinePlugin({ - __DEV__: JSON.stringify(!isProdBuild), - __DEMO__: false, + __DEV__: !isProdBuild, __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), __VERSION__: JSON.stringify(version), + __DEMO__: false, __STATIC_PATH__: "/static/", "process.env.NODE_ENV": JSON.stringify( isProdBuild ? "production" : "development" ), + ...defineOverlay, }), - ...plugins, - isProdBuild && - !isCI && - !isStatsBuild && - new CompressionPlugin({ - cache: true, - exclude: [/\.js\.map$/, /\.LICENSE$/, /\.py$/, /\.txt$/], - algorithm(input, compressionOptions, callback) { - return zopfli.gzip(input, compressionOptions, callback); - }, - }), - latestBuild && - new WorkboxPlugin.InjectManifest({ - swSrc: "./src/entrypoints/service-worker-hass.js", - swDest: "service_worker.js", - importWorkboxFrom: "local", - include: [/\.js$/], - templatedURLs: { - ...workBoxTranslationsTemplatedURLs, - "/static/icons/favicon-192x192.png": - "public/icons/favicon-192x192.png", - "/static/fonts/roboto/Roboto-Light.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2", - "/static/fonts/roboto/Roboto-Medium.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2", - "/static/fonts/roboto/Roboto-Regular.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2", - "/static/fonts/roboto/Roboto-Bold.woff2": - "node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2", - }, - }), + // Ignore moment.js locales + new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), + // Color.js is bloated, it contains all color definitions for all material color sets. + new webpack.NormalModuleReplacementPlugin( + /@polymer\/paper-styles\/color\.js$/, + path.resolve(paths.polymer_dir, "src/util/empty.js") + ), + // Ignore roboto pointing at CDN. We use local font-roboto-local. + new webpack.NormalModuleReplacementPlugin( + /@polymer\/font-roboto\/roboto\.js$/, + path.resolve(paths.polymer_dir, "src/util/empty.js") + ), + // Ignore mwc icons pointing at CDN. + new webpack.NormalModuleReplacementPlugin( + /@material\/mwc-icon\/mwc-icon-font\.js$/, + path.resolve(paths.polymer_dir, "src/util/empty.js") + ), ].filter(Boolean), + resolve: { + extensions: [".ts", ".js", ".json", ".tsx"], + 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: genFilename(isProdBuild), - chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), - path: latestBuild ? paths.output : paths.output_es5, + filename: ({ chunk }) => { + const dontHash = new Set(); + + if (!isProdBuild || dontHash.has(chunk.name)) { + return `${chunk.name}.js`; + } + return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; + }, + chunkFilename: + isProdBuild && !isStatsBuild + ? "chunk.[chunkhash].js" + : "[name].chunk.js", + path: path.resolve( + outputRoot, + latestBuild ? "frontend_latest" : "frontend_es5" + ), publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", // For workerize loader globalObject: "self", }, - resolve, }; }; +const createAppConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { + const config = createWebpackConfig({ + entry: { + app: "./src/entrypoints/app.ts", + authorize: "./src/entrypoints/authorize.ts", + onboarding: "./src/entrypoints/onboarding.ts", + core: "./src/entrypoints/core.ts", + compatibility: "./src/entrypoints/compatibility.ts", + "custom-panel": "./src/entrypoints/custom-panel.ts", + "hass-icons": "./src/entrypoints/hass-icons.ts", + }, + outputRoot: paths.root, + isProdBuild, + latestBuild, + isStatsBuild, + }); + + if (latestBuild) { + // Create an object mapping browser urls to their paths during build + const translationMetadata = require("../build-translations/translationMetadata.json"); + const workBoxTranslationsTemplatedURLs = {}; + const englishFP = translationMetadata.translations.en.fingerprints; + Object.keys(englishFP).forEach((key) => { + workBoxTranslationsTemplatedURLs[ + `/static/translations/${englishFP[key]}` + ] = `build-translations/output/${key}.json`; + }); + + config.plugins.push( + new WorkboxPlugin.InjectManifest({ + swSrc: "./src/entrypoints/service-worker-hass.js", + swDest: "service_worker.js", + importWorkboxFrom: "local", + include: [/\.js$/], + templatedURLs: { + ...workBoxTranslationsTemplatedURLs, + "/static/icons/favicon-192x192.png": + "public/icons/favicon-192x192.png", + "/static/fonts/roboto/Roboto-Light.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2", + "/static/fonts/roboto/Roboto-Medium.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2", + "/static/fonts/roboto/Roboto-Regular.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2", + "/static/fonts/roboto/Roboto-Bold.woff2": + "node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2", + }, + }) + ); + } + + return config; +}; + const createDemoConfig = ({ isProdBuild, latestBuild, isStatsBuild }) => { - const rules = [tsLoader(latestBuild), cssLoader, htmlLoader]; - if (!latestBuild) { - rules.push(babelLoaderConfig({ latestBuild })); - } - - return { - mode: genMode(isProdBuild), - devtool: genDevTool(isProdBuild), + return createWebpackConfig({ entry: { - main: "./demo/src/entrypoint.ts", - compatibility: "./src/entrypoints/compatibility.ts", - }, - module: { - rules, - }, - optimization: optimization(latestBuild), - plugins: [ - new ManifestPlugin(), - new webpack.DefinePlugin({ - __DEV__: !isProdBuild, - __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), - __VERSION__: JSON.stringify(`DEMO-${version}`), - __DEMO__: true, - __STATIC_PATH__: "/static/", - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development" - ), - }), - ...plugins, - ].filter(Boolean), - resolve, - output: { - filename: genFilename(isProdBuild), - chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), - path: path.resolve( - paths.demo_root, - latestBuild ? "frontend_latest" : "frontend_es5" + main: path.resolve(paths.demo_dir, "src/entrypoint.ts"), + compatibility: path.resolve( + paths.polymer_dir, + "src/entrypoints/compatibility.ts" ), - publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", - // For workerize loader - globalObject: "self", }, - }; + outputRoot: paths.demo_root, + defineOverlay: { + __VERSION__: JSON.stringify(`DEMO-${version}`), + __DEMO__: true, + }, + isProdBuild, + latestBuild, + isStatsBuild, + }); }; const createCastConfig = ({ isProdBuild, latestBuild }) => { - const isStatsBuild = false; const entry = { - launcher: "./cast/src/launcher/entrypoint.ts", + launcher: path.resolve(paths.cast_dir, "src/launcher/entrypoint.ts"), }; if (latestBuild) { - entry.receiver = "./cast/src/receiver/entrypoint.ts"; + entry.receiver = path.resolve(paths.cast_dir, "src/receiver/entrypoint.ts"); } - const rules = [tsLoader(latestBuild), cssLoader, htmlLoader]; - if (!latestBuild) { - rules.push(babelLoaderConfig({ latestBuild })); - } - - return { - mode: genMode(isProdBuild), - devtool: genDevTool(isProdBuild), + return createWebpackConfig({ entry, - module: { - rules, + outputRoot: paths.cast_root, + isProdBuild, + latestBuild, + }); +}; + +const createHassioConfig = ({ isProdBuild, latestBuild }) => { + if (latestBuild) { + throw new Error("Hass.io does not support latest build!"); + } + const config = createWebpackConfig({ + entry: { + entrypoint: path.resolve(paths.hassio_dir, "src/entrypoint.js"), }, - optimization: optimization(latestBuild), - plugins: [ - new ManifestPlugin(), - new webpack.DefinePlugin({ - __DEV__: !isProdBuild, - __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), - __VERSION__: JSON.stringify(version), - __DEMO__: false, - __STATIC_PATH__: "/static/", - "process.env.NODE_ENV": JSON.stringify( - isProdBuild ? "production" : "development" - ), - }), - ...plugins, - ].filter(Boolean), - resolve, - output: { - filename: genFilename(isProdBuild), - chunkFilename: genChunkFilename(isProdBuild, isStatsBuild), - path: path.resolve( - paths.cast_root, - latestBuild ? "frontend_latest" : "frontend_es5" - ), - publicPath: latestBuild ? "/frontend_latest/" : "/frontend_es5/", - // For workerize loader - globalObject: "self", + outputRoot: "", + isProdBuild, + latestBuild, + }); + + config.output.path = paths.hassio_root; + config.output.publicPath = paths.hassio_publicPath; + + return config; +}; + +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"), }, - }; + outputRoot: paths.gallery_root, + isProdBuild, + latestBuild, + }); + + return config; }; module.exports = { - resolve, - plugins, - optimization, createAppConfig, createDemoConfig, createCastConfig, + createHassioConfig, + createGalleryConfig, }; diff --git a/cast/webpack.config.js b/cast/webpack.config.js new file mode 100644 index 0000000000..bb9746cd11 --- /dev/null +++ b/cast/webpack.config.js @@ -0,0 +1,11 @@ +const { createCastConfig } = require("../build-scripts/webpack.js"); +const { isProdBuild } = require("../build-scripts/env.js"); + +// File just used for stats builds + +const latestBuild = true; + +module.exports = createCastConfig({ + isProdBuild, + latestBuild, +}); diff --git a/demo/src/configs/arsaboo/entities.ts b/demo/src/configs/arsaboo/entities.ts index 77a4d1c16a..fcae1bc846 100644 --- a/demo/src/configs/arsaboo/entities.ts +++ b/demo/src/configs/arsaboo/entities.ts @@ -217,6 +217,18 @@ export const demoEntitiesArsaboo: DemoConfig["entities"] = (localize) => icon: "hademo:currency-usd", }, }, + "sensor.study_temp": { + entity_id: "sensor.study_temp", + state: "20.9", + attributes: { + unit_of_measurement: "°C", + device_class: "temperature", + friendly_name: localize( + "ui.panel.page-demo.config.arsaboo.names.temperature_study" + ), + icon: "hademo:thermometer", + }, + }, "cover.garagedoor": { entity_id: "cover.garagedoor", state: "closed", diff --git a/demo/src/configs/arsaboo/lovelace.ts b/demo/src/configs/arsaboo/lovelace.ts index ddb95ad98b..79a275c578 100644 --- a/demo/src/configs/arsaboo/lovelace.ts +++ b/demo/src/configs/arsaboo/lovelace.ts @@ -446,6 +446,11 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({ "script.tv_off", ], }, + { + type: "sensor", + entity: "sensor.study_temp", + graph: "line", + }, { type: "entities", title: "Doorbell", diff --git a/demo/src/configs/jimpower/theme.ts b/demo/src/configs/jimpower/theme.ts index 8f9e28fe60..655c6da45d 100644 --- a/demo/src/configs/jimpower/theme.ts +++ b/demo/src/configs/jimpower/theme.ts @@ -23,27 +23,24 @@ export const demoThemeJimpower = () => ({ "paper-listbox-background-color": "#2E333A", "table-row-background-color": "#353840", "paper-grey-50": "var(--primary-text-color)", - "paper-toggle-button-checked-button-color": "var(--accent-color)", + "switch-checked-color": "var(--accent-color)", "paper-dialog-background-color": "#434954", "secondary-text-color": "#5294E2", "google-red-500": "#E45E65", "divider-color": "rgba(0, 0, 0, .12)", - "paper-toggle-button-unchecked-ink-color": "var(--disabled-text-color)", "google-green-500": "#39E949", - "paper-toggle-button-unchecked-button-color": "var(--disabled-text-color)", + "switch-unchecked-button-color": "var(--disabled-text-color)", "label-badge-border-color": "green", "paper-listbox-color": "var(--primary-color)", "paper-slider-disabled-secondary-color": "var(--disabled-text-color)", - "paper-toggle-button-checked-ink-color": "var(--accent-color)", "paper-card-background-color": "#434954", "label-badge-text-color": "var(--primary-text-color)", "paper-slider-knob-start-color": "var(--accent-color)", - "paper-toggle-button-unchecked-bar-color": "var(--disabled-text-color)", + "switch-unchecked-track-color": "var(--disabled-text-color)", "dark-primary-color": "var(--accent-color)", "paper-slider-secondary-color": "var(--secondary-background-color)", "paper-slider-pin-color": "var(--accent-color)", "paper-item-icon-active-color": "#F9C536", "accent-color": "#E45E65", - "paper-toggle-button-checked-bar-color": "var(--accent-color)", "table-row-alternative-background-color": "#3E424B", }); diff --git a/demo/src/configs/kernehed/theme.ts b/demo/src/configs/kernehed/theme.ts index a4efa29f40..078dc5ac93 100644 --- a/demo/src/configs/kernehed/theme.ts +++ b/demo/src/configs/kernehed/theme.ts @@ -24,27 +24,24 @@ export const demoThemeKernehed = () => ({ "paper-listbox-background-color": "#141414", "table-row-background-color": "#292929", "paper-grey-50": "var(--primary-text-color)", - "paper-toggle-button-checked-button-color": "var(--accent-color)", + "switch-checked-color": "var(--accent-color)", "paper-dialog-background-color": "#292929", "secondary-text-color": "#b58e31", "google-red-500": "#b58e31", "divider-color": "rgba(0, 0, 0, .12)", - "paper-toggle-button-unchecked-ink-color": "var(--disabled-text-color)", "google-green-500": "#2980b9", - "paper-toggle-button-unchecked-button-color": "var(--disabled-text-color)", + "switch-unchecked-button-color": "var(--disabled-text-color)", "label-badge-border-color": "green", "paper-listbox-color": "#777777", "paper-slider-disabled-secondary-color": "var(--disabled-text-color)", - "paper-toggle-button-checked-ink-color": "var(--accent-color)", "paper-card-background-color": "#292929", "label-badge-text-color": "var(--primary-text-color)", "paper-slider-knob-start-color": "var(--accent-color)", - "paper-toggle-button-unchecked-bar-color": "var(--disabled-text-color)", + "switch-unchecked-track-color": "var(--disabled-text-color)", "dark-primary-color": "var(--accent-color)", "paper-slider-secondary-color": "var(--secondary-background-color)", "paper-slider-pin-color": "var(--accent-color)", "paper-item-icon-active-color": "#b58e31", "accent-color": "#2980b9", - "paper-toggle-button-checked-bar-color": "var(--accent-color)", "table-row-alternative-background-color": "#292929", }); diff --git a/demo/src/configs/teachingbirds/theme.ts b/demo/src/configs/teachingbirds/theme.ts index 890100d90e..8861bd0663 100644 --- a/demo/src/configs/teachingbirds/theme.ts +++ b/demo/src/configs/teachingbirds/theme.ts @@ -12,8 +12,7 @@ export const demoThemeTeachingbirds = () => ({ "paper-slider-knob-color": "var(--primary-color)", "paper-listbox-color": "#FFFFFF", "paper-toggle-button-checked-bar-color": "var(--light-primary-color)", - "paper-toggle-button-checked-ink-color": "var(--dark-primary-color)", - "paper-toggle-button-unchecked-bar-color": "var(--primary-text-color)", + "switch-unchecked-track-color": "var(--primary-text-color)", "paper-card-background-color": "#4e4e4e", "label-badge-text-color": "var(--text-primary-color)", "primary-background-color": "#303030", @@ -22,7 +21,7 @@ export const demoThemeTeachingbirds = () => ({ "secondary-background-color": "#2b2b2b", "paper-slider-knob-start-color": "var(--primary-color)", "paper-item-icon-active-color": "#d8bf50", - "paper-toggle-button-checked-button-color": "var(--primary-color)", + "switch-checked-color": "var(--primary-color)", "secondary-text-color": "#389638", "disabled-text-color": "#545454", "paper-item-icon_-_color": "var(--primary-text-color)", diff --git a/demo/src/custom-cards/ha-demo-card.ts b/demo/src/custom-cards/ha-demo-card.ts index c9b4f1d060..45c887db9d 100644 --- a/demo/src/custom-cards/ha-demo-card.ts +++ b/demo/src/custom-cards/ha-demo-card.ts @@ -1,10 +1,4 @@ -import { - LitElement, - html, - CSSResult, - css, - PropertyDeclarations, -} from "lit-element"; +import { LitElement, html, CSSResult, css, property } from "lit-element"; import { until } from "lit-html/directives/until"; import "@material/mwc-button"; import "@polymer/paper-spinner/paper-spinner-lite"; @@ -20,19 +14,11 @@ import { } from "../configs/demo-configs"; export class HADemoCard extends LitElement implements LovelaceCard { - public lovelace?: Lovelace; - public hass!: MockHomeAssistant; - private _switching?: boolean; + @property() public lovelace?: Lovelace; + @property() public hass!: MockHomeAssistant; + @property() private _switching?: boolean; private _hidden = localStorage.hide_demo_card; - static get properties(): PropertyDeclarations { - return { - lovelace: {}, - hass: {}, - _switching: {}, - }; - } - public getCardSize() { return this._hidden ? 0 : 2; } diff --git a/demo/webpack.config.js b/demo/webpack.config.js index 266523cdbe..9ccb790f8b 100644 --- a/demo/webpack.config.js +++ b/demo/webpack.config.js @@ -1,10 +1,9 @@ const { createDemoConfig } = require("../build-scripts/webpack.js"); +const { isProdBuild, isStatsBuild } = require("../build-scripts/env.js"); -// This file exists because we haven't migrated the stats script yet +// File just used for stats builds -const isProdBuild = process.env.NODE_ENV === "production"; -const isStatsBuild = process.env.STATS === "1"; -const latestBuild = false; +const latestBuild = true; module.exports = createDemoConfig({ isProdBuild, diff --git a/gallery/public/index.html b/gallery/public/index.html deleted file mode 100644 index 7339634a60..0000000000 --- a/gallery/public/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - HAGallery - - - - - diff --git a/gallery/script/build_gallery b/gallery/script/build_gallery index cbf70c6d4e..3a7c77dde7 100755 --- a/gallery/script/build_gallery +++ b/gallery/script/build_gallery @@ -4,14 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -OUTPUT_DIR=dist - -rm -rf $OUTPUT_DIR - -cd .. -./node_modules/.bin/gulp build-translations gen-icons -cd gallery - -NODE_ENV=production ../node_modules/.bin/webpack -p --config webpack.config.js +./node_modules/.bin/gulp build-gallery diff --git a/gallery/script/develop_gallery b/gallery/script/develop_gallery index 56bb2c678b..b346ea60bc 100755 --- a/gallery/script/develop_gallery +++ b/gallery/script/develop_gallery @@ -4,10 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -cd .. -./node_modules/.bin/gulp build-translations gen-icons -cd gallery - -../node_modules/.bin/webpack-dev-server +./node_modules/.bin/gulp develop-gallery diff --git a/gallery/src/components/demo-card.js b/gallery/src/components/demo-card.js index adc7282ba1..b782aaf882 100644 --- a/gallery/src/components/demo-card.js +++ b/gallery/src/components/demo-card.js @@ -1,6 +1,6 @@ import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; -import JsYaml from "js-yaml"; +import { safeLoad } from "js-yaml"; import { createCardElement } from "../../../src/panels/lovelace/common/create-card-element"; @@ -62,7 +62,7 @@ class DemoCard extends PolymerElement { card.removeChild(card.lastChild); } - const el = createCardElement(JsYaml.safeLoad(config.config)[0]); + const el = createCardElement(safeLoad(config.config)[0]); el.hass = this.hass; card.appendChild(el); } diff --git a/gallery/src/components/demo-cards.js b/gallery/src/components/demo-cards.js index a73bc53a2f..db01c7fe13 100644 --- a/gallery/src/components/demo-cards.js +++ b/gallery/src/components/demo-cards.js @@ -26,7 +26,9 @@ class DemoCards extends PolymerElement {
- Show config + + Show config +
@@ -51,6 +53,10 @@ class DemoCards extends PolymerElement { }, }; } + + _showConfigToggled(ev) { + this._showConfig = ev.target.checked; + } } customElements.define("demo-cards", DemoCards); diff --git a/gallery/src/demos/demo-util-long-press.ts b/gallery/src/demos/demo-util-long-press.ts index 5f09e3598f..86d245fe50 100644 --- a/gallery/src/demos/demo-util-long-press.ts +++ b/gallery/src/demos/demo-util-long-press.ts @@ -12,7 +12,7 @@ export class DemoUtilLongPress extends LitElement { () => html` @@ -28,7 +28,7 @@ export class DemoUtilLongPress extends LitElement { `; } - private _handleTap(ev: Event) { + private _handleClick(ev: Event) { this._addValue(ev, "tap"); } diff --git a/gallery/src/html/index.html.template b/gallery/src/html/index.html.template new file mode 100644 index 0000000000..3240e59bb0 --- /dev/null +++ b/gallery/src/html/index.html.template @@ -0,0 +1,22 @@ + + + + + + + HAGallery + + + + + + diff --git a/gallery/webpack.config.js b/gallery/webpack.config.js index 0c3e364379..9bc82eedc3 100644 --- a/gallery/webpack.config.js +++ b/gallery/webpack.config.js @@ -1,6 +1,6 @@ const path = require("path"); const CopyWebpackPlugin = require("copy-webpack-plugin"); -const webpackBase = require("../build-scripts/webpack.js"); +const { createGalleryConfig } = require("../build-scripts/webpack.js"); const { babelLoaderConfig } = require("../build-scripts/babel.js"); const isProd = process.env.NODE_ENV === "production"; @@ -9,80 +9,64 @@ const buildPath = path.resolve(__dirname, "dist"); const publicPath = isProd ? "./" : "http://localhost:8080/"; const latestBuild = true; -const rules = [ - { - exclude: [path.resolve(__dirname, "../node_modules")], - test: /\.ts$/, - use: [ - { - loader: "ts-loader", - options: { - compilerOptions: latestBuild - ? { noEmit: false } - : { - target: "es5", - noEmit: false, - }, +module.exports = createGalleryConfig({ + latestBuild: true, +}); + +const bla = () => { + const oldExports = { + mode: isProd ? "production" : "development", + // Disabled in prod while we make Home Assistant able to serve the right files. + // Was source-map + devtool: isProd ? "none" : "inline-source-map", + entry: "./src/entrypoint.js", + module: { + rules: [ + babelLoaderConfig({ latestBuild }), + { + test: /\.css$/, + use: "raw-loader", }, - }, - ], - }, - { - test: /\.css$/, - use: "raw-loader", - }, - { - test: /\.(html)$/, - use: { - loader: "html-loader", - options: { - exportAsEs6Default: true, - }, + { + test: /\.(html)$/, + use: { + loader: "html-loader", + options: { + exportAsEs6Default: true, + }, + }, + }, + ], }, - }, -]; - -if (!latestBuild) { - rules.push(babelLoaderConfig({ latestBuild })); -} - -module.exports = { - mode: isProd ? "production" : "development", - // Disabled in prod while we make Home Assistant able to serve the right files. - // Was source-map - devtool: isProd ? "none" : "inline-source-map", - entry: "./src/entrypoint.js", - module: { - rules, - }, - optimization: webpackBase.optimization(latestBuild), - plugins: [ - new CopyWebpackPlugin([ - "public", - { from: "../public", to: "static" }, - { from: "../build-translations/output", to: "static/translations" }, - { - from: "../node_modules/leaflet/dist/leaflet.css", - to: "static/images/leaflet/", - }, - { - from: "../node_modules/roboto-fontface/fonts/roboto/*.woff2", - to: "static/fonts/roboto/", - }, - { - from: "../node_modules/leaflet/dist/images", - to: "static/images/leaflet/", - }, - ]), - ].filter(Boolean), - resolve: webpackBase.resolve, - output: { - filename: "[name].js", - chunkFilename: chunkFilename, - path: buildPath, - publicPath, - }, - devServer: { - contentBase: "./public", - }, + optimization: webpackBase.optimization(latestBuild), + plugins: [ + new CopyWebpackPlugin([ + "public", + { from: "../public", to: "static" }, + { from: "../build-translations/output", to: "static/translations" }, + { + from: "../node_modules/leaflet/dist/leaflet.css", + to: "static/images/leaflet/", + }, + { + from: "../node_modules/roboto-fontface/fonts/roboto/*.woff2", + to: "static/fonts/roboto/", + }, + { + from: "../node_modules/leaflet/dist/images", + to: "static/images/leaflet/", + }, + ]), + ].filter(Boolean), + resolve: webpackBase.resolve, + output: { + filename: "[name].js", + chunkFilename: chunkFilename, + path: buildPath, + publicPath, + }, + devServer: { + contentBase: "./public", + }, + }; }; diff --git a/hassio/script/build_hassio b/hassio/script/build_hassio index b8bd6d504e..193cbb0687 100755 --- a/hassio/script/build_hassio +++ b/hassio/script/build_hassio @@ -4,11 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -OUTPUT_DIR=build - -rm -rf $OUTPUT_DIR - -node script/gen-icons.js -NODE_ENV=production CI=false ../node_modules/.bin/webpack -p --config webpack.config.js +./node_modules/.bin/gulp build-hassio diff --git a/hassio/script/develop b/hassio/script/develop index c23fc2ea1f..0b62666b10 100755 --- a/hassio/script/develop +++ b/hassio/script/develop @@ -4,11 +4,6 @@ # Stop on errors set -e -cd "$(dirname "$0")/.." +cd "$(dirname "$0")/../.." -OUTPUT_DIR=build - -rm -rf $OUTPUT_DIR -mkdir $OUTPUT_DIR -node script/gen-icons.js -../node_modules/.bin/webpack --watch --progress +./node_modules/.bin/gulp develop-hassio diff --git a/hassio/script/gen-icons.js b/hassio/script/gen-icons.js deleted file mode 100755 index b355ef752b..0000000000 --- a/hassio/script/gen-icons.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node -const fs = require("fs"); -const { - findIcons, - generateIconset, - genMDIIcons, -} = require("../../build-scripts/gulp/gen-icons.js"); - -function genHassioIcons() { - const iconNames = findIcons("./src", "hassio"); - - for (const item of findIcons("../src", "hassio")) { - iconNames.add(item); - } - - fs.writeFileSync("./hassio-icons.html", generateIconset("hassio", iconNames)); -} - -genMDIIcons(); -genHassioIcons(); diff --git a/hassio/src/dashboard/hassio-update.ts b/hassio/src/dashboard/hassio-update.ts index 07b532cbae..3414c5fdf7 100644 --- a/hassio/src/dashboard/hassio-update.ts +++ b/hassio/src/dashboard/hassio-update.ts @@ -7,6 +7,7 @@ import { property, customElement, } from "lit-element"; +import "@polymer/iron-icon/iron-icon"; import { HomeAssistant } from "../../../src/types"; import { @@ -33,12 +34,15 @@ export class HassioUpdate extends LitElement { @property() public error?: string; protected render(): TemplateResult | void { - if ( - this.hassInfo.version === this.hassInfo.last_version && - this.supervisorInfo.version === this.supervisorInfo.last_version && - (!this.hassOsInfo || - this.hassOsInfo.version === this.hassOsInfo.version_latest) - ) { + const updatesAvailable: number = [ + this.hassInfo, + this.supervisorInfo, + this.hassOsInfo, + ].filter((value) => { + return !!value && value.version !== value.last_version; + }).length; + + if (!updatesAvailable) { return html``; } @@ -50,6 +54,11 @@ export class HassioUpdate extends LitElement { ` : ""}
+
+ ${updatesAvailable > 1 + ? "Updates Available 🎉" + : "Update Available 🎉"} +
${this._renderUpdateCard( "Home Assistant", this.hassInfo.version, @@ -57,7 +66,8 @@ export class HassioUpdate extends LitElement { "hassio/homeassistant/update", `https://${ this.hassInfo.last_version.includes("b") ? "rc" : "www" - }.home-assistant.io/latest-release-notes/` + }.home-assistant.io/latest-release-notes/`, + "hassio:home-assistant" )} ${this._renderUpdateCard( "Hass.io Supervisor", @@ -89,18 +99,31 @@ export class HassioUpdate extends LitElement { curVersion: string, lastVersion: string, apiPath: string, - releaseNotesUrl: string + releaseNotesUrl: string, + icon?: string ): TemplateResult { if (lastVersion === curVersion) { return html``; } return html` - +
- ${name} ${lastVersion} is available and you are currently running - ${name} ${curVersion}. + ${icon + ? html` +
+ +
+ ` + : ""} +
${name} ${lastVersion}
+
+ You are currently running version ${curVersion} +
`; @@ -140,6 +160,23 @@ export class HassioUpdate extends LitElement { display: inline-block; margin-bottom: 32px; } + .icon { + --iron-icon-height: 48px; + --iron-icon-width: 48px; + float: right; + margin: 0 0 2px 10px; + } + .update-heading { + font-size: var(--paper-font-subhead_-_font-size); + font-weight: 500; + margin-bottom: 0.5em; + } + .warning { + color: var(--secondary-text-color); + } + .card-actions { + text-align: right; + } .errors { color: var(--google-red-500); padding: 16px; diff --git a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts old mode 100644 new mode 100755 index 3d1cc54cdd..f0ae53eac4 --- a/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts +++ b/hassio/src/dialogs/snapshot/dialog-hassio-snapshot.ts @@ -3,6 +3,7 @@ import "@material/mwc-button"; import "@polymer/paper-checkbox/paper-checkbox"; import "@polymer/paper-dialog-scrollable/paper-dialog-scrollable"; import "@polymer/paper-icon-button/paper-icon-button"; +import "@polymer/iron-icon/iron-icon"; import "@polymer/paper-input/paper-input"; import { html } from "@polymer/polymer/lib/utils/html-tag"; import { PolymerElement } from "@polymer/polymer/polymer-element"; @@ -94,13 +95,23 @@ class HassioSnapshotDialog extends PolymerElement { .details { color: var(--secondary-text-color); } - .download { - color: var(--primary-color); - } .warning, .error { color: var(--google-red-500); } + .buttons { + display: flex; + flex-direction: column; + } + .buttons li { + list-style-type: none; + } + .buttons .icon { + margin-right: 16px; + } + .no-margin-top { + margin-top: 0; + }