Compare commits

..

1 Commits

Author SHA1 Message Date
Bram Kragten
a548d13931 Allow partial open of sidebar 2023-04-01 18:46:52 +02:00
562 changed files with 14246 additions and 25742 deletions

View File

@@ -1,33 +0,0 @@
[modern]
# Support for dynamic import is the main litmus test for serving modern builds.
# Although officially a ES2020 feature, browsers implemented it early, so this
# enables all of ES2017 and some features in ES2018.
supports es6-module-dynamic-import
# Exclude Safari 11-12 because of a bug in tagged template literals
# https://bugs.webkit.org/show_bug.cgi?id=190756
# Note: Dropping version 11 also enables several more ES2018 features
not Safari < 13
not iOS < 13
# Exclude unsupported browsers
not dead
[legacy]
# Legacy builds are served when modern requirements are not met and support browsers:
# - released in the last 7 years + current alpha/beta versionss
# - with global utilization above 0.05%
# The lattermost query ensures that support for popular old browsers is not dropped too early
# (e.g. IE 11, Android 4.4, or Samsung 4).
#
# In addition, legacy browsers must support some minimum features that cannot be polyfilled:
# - ES5 (strict mode)
# - web sockets to communicate with backend
# - inline SVG used widely in buttons, widgets, etc.
# - custom events used for most user interactions
# - CSS flexbox used in the majority of the layout
# Nearly all of these are redundant with the above rules.
# As of May 2023, only web sockets must be added to the query.
unreleased versions
last 7 years
> 0.05% and supports websockets

View File

@@ -5,3 +5,9 @@ ENV \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive \
DEVCONTAINER=true \ DEVCONTAINER=true \
PATH=$PATH:./node_modules/.bin PATH=$PATH:./node_modules/.bin
# Install nvm
COPY .nvmrc /tmp/.nvmrc
RUN \
su vscode -c \
"source /usr/local/share/nvm/nvm.sh && nvm install $(cat /tmp/.nvmrc) 2>&1"

View File

@@ -5,7 +5,7 @@
"context": ".." "context": ".."
}, },
"appPort": "8124:8123", "appPort": "8124:8123",
"postStartCommand": "script/bootstrap", "postCreateCommand": "script/bootstrap",
"containerEnv": { "containerEnv": {
"WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}"
}, },

View File

@@ -9,6 +9,7 @@ on:
- master - master
env: env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
@@ -21,14 +22,14 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
with: with:
ref: dev ref: dev
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
@@ -57,14 +58,14 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
with: with:
ref: master ref: master
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies

View File

@@ -11,6 +11,7 @@ on:
- master - master
env: env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -24,11 +25,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
run: yarn install --immutable run: yarn install --immutable
@@ -47,11 +48,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
run: yarn install --immutable run: yarn install --immutable
@@ -65,11 +66,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
run: yarn install --immutable run: yarn install --immutable
@@ -83,11 +84,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
run: yarn install --immutable run: yarn install --immutable

View File

@@ -23,7 +23,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
with: with:
# We must fetch at least the immediate parents so that if this is # We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head. # a pull request then we can checkout the head.

View File

@@ -10,26 +10,27 @@ on:
- master - master
env: env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
deploy_dev: deploy_dev:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Demo Development name: Demo Development
if: github.event_name != 'push' || github.ref_name != 'master' if: github.event_name != 'push' || github.ref != 'master'
environment: environment:
name: Demo Development name: Demo Development
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
with: with:
ref: dev ref: dev
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
@@ -52,20 +53,20 @@ jobs:
deploy_master: deploy_master:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Demo Production name: Demo Production
if: github.event_name == 'push' && github.ref_name == 'master' if: github.event_name == 'push' && github.ref == 'master'
environment: environment:
name: Demo Production name: Demo Production
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
with: with:
ref: master ref: master
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies

View File

@@ -6,6 +6,7 @@ on:
- cron: "0 0 * * *" - cron: "0 0 * * *"
env: env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
@@ -16,12 +17,12 @@ jobs:
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }} url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies

View File

@@ -11,6 +11,7 @@ on:
- dev - dev
env: env:
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
@@ -21,12 +22,12 @@ jobs:
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview') if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies

View File

@@ -7,6 +7,7 @@ on:
env: env:
PYTHON_VERSION: "3.10" PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
permissions: permissions:
@@ -20,17 +21,17 @@ jobs:
contents: write contents: write
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Set up Python ${{ env.PYTHON_VERSION }} - name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies

View File

@@ -7,6 +7,7 @@ on:
env: env:
PYTHON_VERSION: "3.10" PYTHON_VERSION: "3.10"
NODE_VERSION: 16
NODE_OPTIONS: --max_old_space_size=6144 NODE_OPTIONS: --max_old_space_size=6144
# Set default workflow permissions # Set default workflow permissions
@@ -23,7 +24,7 @@ jobs:
contents: write # Required to upload release assets contents: write # Required to upload release assets
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Verify version - name: Verify version
uses: home-assistant/actions/helpers/verify-version@master uses: home-assistant/actions/helpers/verify-version@master
@@ -33,10 +34,10 @@ jobs:
with: with:
python-version: ${{ env.PYTHON_VERSION }} python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Node - name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v3.6.0 uses: actions/setup-node@v3.6.0
with: with:
node-version-file: ".nvmrc" node-version: ${{ env.NODE_VERSION }}
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
@@ -74,7 +75,7 @@ jobs:
echo "home-assistant-frontend==$version" > ./requirements.txt echo "home-assistant-frontend==$version" > ./requirements.txt
- name: Build wheels - name: Build wheels
uses: home-assistant/wheels@2023.04.0 uses: home-assistant/wheels@2022.10.1
with: with:
abi: cp310 abi: cp310
tag: musllinux_1_2 tag: musllinux_1_2

View File

@@ -7,15 +7,19 @@ on:
paths: paths:
- src/translations/en.json - src/translations/en.json
env:
NODE_VERSION: 16
jobs: jobs:
upload: upload:
name: Upload name: Upload
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v3.5.2 uses: actions/checkout@v3.5.0
- name: Upload Translations - name: Upload Translations
run: | run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base ./script/translations_upload_base

2
.nvmrc
View File

@@ -1 +1 @@
18 16

View File

@@ -1,39 +0,0 @@
diff --git a/modular/sortable.complete.esm.js b/modular/sortable.complete.esm.js
index 02e9f2d6bebeb430fe6e7c1cc3f9c3c9df051f14..bb8268b0844a1faa4108cc92c0be2a3dbaf23f83 100644
--- a/modular/sortable.complete.esm.js
+++ b/modular/sortable.complete.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
diff --git a/modular/sortable.core.esm.js b/modular/sortable.core.esm.js
index b04c8b4634f7c6b4ef1aadbb48afe6564306dea9..39a107163c8c336ebd669b5ea8a936af87e1c1e7 100644
--- a/modular/sortable.core.esm.js
+++ b/modular/sortable.core.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();
diff --git a/modular/sortable.esm.js b/modular/sortable.esm.js
index 6ec7ed1bb557e21c2578200161e989c65d23150b..0a05475a22904472fac6c13f524c674da76584b0 100644
--- a/modular/sortable.esm.js
+++ b/modular/sortable.esm.js
@@ -1657,7 +1657,7 @@ Sortable.prototype =
target = parent; // store last element
}
/* jshint boss:true */
- while (parent = parent.parentNode);
+ while (parent = parent.parentNode || parent.getRootNode().host);
}
_unhideGhostForTarget();

File diff suppressed because one or more lines are too long

View File

@@ -8,4 +8,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools" spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.5.1.cjs yarnPath: .yarn/releases/yarn-3.5.0.cjs

View File

@@ -76,7 +76,7 @@ module.exports.htmlMinifierOptions = {
module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
safari10: !latestBuild, safari10: !latestBuild,
ecma: latestBuild ? 2015 : 5, ecma: latestBuild ? undefined : 5,
format: { comments: false }, format: { comments: false },
sourceMap: !isTestBuild, sourceMap: !isTestBuild,
}); });
@@ -84,25 +84,17 @@ module.exports.terserOptions = ({ latestBuild, isTestBuild }) => ({
module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
babelrc: false, babelrc: false,
compact: false, compact: false,
assumptions: {
privateFieldsAsProperties: true,
setPublicClassFields: true,
setSpreadProperties: true,
},
browserslistEnv: latestBuild ? "modern" : "legacy",
// Must be unambiguous because some dependencies are CommonJS only
sourceType: "unambiguous",
presets: [ presets: [
[ !latestBuild && [
"@babel/preset-env", "@babel/preset-env",
{ {
useBuiltIns: latestBuild ? false : "entry", useBuiltIns: "entry",
corejs: latestBuild ? false : { version: "3.30", proposals: true }, corejs: { version: "3.29", proposals: true },
bugfixes: true, bugfixes: true,
}, },
], ],
"@babel/preset-typescript", "@babel/preset-typescript",
], ].filter(Boolean),
plugins: [ plugins: [
[ [
path.resolve( path.resolve(
@@ -114,6 +106,22 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
ignoreModuleNotFound: true, ignoreModuleNotFound: true,
}, },
], ],
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [
"@babel/plugin-proposal-object-rest-spread",
{ loose: true, useBuiltIns: true },
],
// Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
// Support various proposals
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
// Minify template literals for production // Minify template literals for production
isProdBuild && [ isProdBuild && [
"template-html-minifier", "template-html-minifier",
@@ -131,13 +139,6 @@ module.exports.babelOptions = ({ latestBuild, isProdBuild, isTestBuild }) => ({
failOnError: true, // we can turn this off in case of false positives failOnError: true, // we can turn this off in case of false positives
}, },
], ],
// Import helpers and regenerator from runtime package
[
"@babel/plugin-transform-runtime",
{ version: require("../package.json").dependencies["@babel/runtime"] },
],
// Support some proposals still in TC39 process
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
].filter(Boolean), ].filter(Boolean),
exclude: [ exclude: [
// \\ for Windows, / for Mac OS and Linux // \\ for Windows, / for Mac OS and Linux
@@ -156,7 +157,7 @@ const publicPath = (latestBuild, root = "") =>
latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`; latestBuild ? `${root}/frontend_latest/` : `${root}/frontend_es5/`;
/* /*
BundleConfig { BundleConfig {
// Object with entrypoints that need to be bundled // Object with entrypoints that need to be bundled
entry: { [name: string]: pathToFile }, entry: { [name: string]: pathToFile },
// Folder where bundled files need to be written // Folder where bundled files need to be written
@@ -175,8 +176,8 @@ const publicPath = (latestBuild, root = "") =>
isTestBuild: boolean, isTestBuild: boolean,
// Names of entrypoints that should not be hashed // Names of entrypoints that should not be hashed
dontHash: Set<string> dontHash: Set<string>
} }
*/ */
module.exports.config = { module.exports.config = {
app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) { app({ isProdBuild, latestBuild, isStatsBuild, isTestBuild, isWDS }) {
@@ -259,7 +260,6 @@ module.exports.config = {
isHassioBuild: true, isHassioBuild: true,
defineOverlay: { defineOverlay: {
__SUPERVISOR__: true, __SUPERVISOR__: true,
__STATIC_PATH__: `"${paths.hassio_publicPath}/static/"`,
}, },
}; };
}, },

View File

@@ -1,16 +1,18 @@
import gulp from "gulp"; // Run HA develop mode
import env from "../env.cjs";
import "./clean.js"; const gulp = require("gulp");
import "./compress.js"; const env = require("../env.cjs");
import "./entry-html.js"; require("./clean.cjs");
import "./gather-static.js"; require("./translations.cjs");
import "./gen-icons-json.js"; require("./locale-data.cjs");
import "./locale-data.js"; require("./gen-icons-json.cjs");
import "./rollup.js"; require("./gather-static.cjs");
import "./service-worker.js"; require("./compress.cjs");
import "./translations.js"; require("./webpack.cjs");
import "./wds.js"; require("./service-worker.cjs");
import "./webpack.js"; require("./entry-html.cjs");
require("./rollup.cjs");
require("./wds.cjs");
gulp.task( gulp.task(
"develop-app", "develop-app",
@@ -22,7 +24,8 @@ gulp.task(
gulp.parallel( gulp.parallel(
"gen-service-worker-app-dev", "gen-service-worker-app-dev",
"gen-icons-json", "gen-icons-json",
"gen-pages-app-dev", "gen-pages-dev",
"gen-index-app-dev",
"build-translations", "build-translations",
"build-locale-data" "build-locale-data"
), ),
@@ -47,6 +50,10 @@ gulp.task(
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests // Don't compress running tests
...(env.isTestBuild() ? [] : ["compress-app"]), ...(env.isTestBuild() ? [] : ["compress-app"]),
gulp.parallel("gen-pages-app-prod", "gen-service-worker-app-prod") gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",
"gen-service-worker-app-prod"
)
) )
); );

View File

@@ -1,12 +1,13 @@
import gulp from "gulp"; const gulp = require("gulp");
import env from "../env.cjs"; const env = require("../env.cjs");
import "./clean.js";
import "./entry-html.js"; require("./clean.cjs");
import "./gather-static.js"; require("./translations.cjs");
import "./rollup.js"; require("./gather-static.cjs");
import "./service-worker.js"; require("./webpack.cjs");
import "./translations.js"; require("./service-worker.cjs");
import "./webpack.js"; require("./entry-html.cjs");
require("./rollup.cjs");
gulp.task( gulp.task(
"develop-cast", "develop-cast",
@@ -18,7 +19,7 @@ gulp.task(
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast", "copy-static-cast",
"gen-pages-cast-dev", "gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
) )
); );
@@ -34,6 +35,6 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-cast", "copy-static-cast",
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast", env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
"gen-pages-cast-prod" "gen-index-cast-prod"
) )
); );

View File

@@ -1,37 +1,37 @@
import { deleteSync } from "del"; const del = import("del");
import gulp from "gulp"; const gulp = require("gulp");
import paths from "../paths.cjs"; const paths = require("../paths.cjs");
import "./translations.js"; require("./translations.cjs");
gulp.task( gulp.task(
"clean", "clean",
gulp.parallel("clean-translations", async () => gulp.parallel("clean-translations", async () =>
deleteSync([paths.app_output_root, paths.build_dir]) (await del).deleteSync([paths.app_output_root, paths.build_dir])
) )
); );
gulp.task( gulp.task(
"clean-demo", "clean-demo",
gulp.parallel("clean-translations", async () => gulp.parallel("clean-translations", async () =>
deleteSync([paths.demo_output_root, paths.build_dir]) (await del).deleteSync([paths.demo_output_root, paths.build_dir])
) )
); );
gulp.task( gulp.task(
"clean-cast", "clean-cast",
gulp.parallel("clean-translations", async () => gulp.parallel("clean-translations", async () =>
deleteSync([paths.cast_output_root, paths.build_dir]) (await del).deleteSync([paths.cast_output_root, paths.build_dir])
) )
); );
gulp.task("clean-hassio", async () => gulp.task("clean-hassio", async () =>
deleteSync([paths.hassio_output_root, paths.build_dir]) (await del).deleteSync([paths.hassio_output_root, paths.build_dir])
); );
gulp.task( gulp.task(
"clean-gallery", "clean-gallery",
gulp.parallel("clean-translations", async () => gulp.parallel("clean-translations", async () =>
deleteSync([ (await del).deleteSync([
paths.gallery_output_root, paths.gallery_output_root,
paths.gallery_build, paths.gallery_build,
paths.build_dir, paths.build_dir,

View File

@@ -1,10 +1,10 @@
// Tasks to compress // Tasks to compress
import gulp from "gulp"; const gulp = require("gulp");
import zopfli from "gulp-zopfli-green"; const zopfli = require("gulp-zopfli-green");
import merge from "merge-stream"; const merge = require("merge-stream");
import path from "path"; const path = require("path");
import paths from "../paths.cjs"; const paths = require("../paths.cjs");
const zopfliOptions = { threshold: 150 }; const zopfliOptions = { threshold: 150 };

View File

@@ -1,13 +1,15 @@
import gulp from "gulp"; // Run demo develop mode
import env from "../env.cjs"; const gulp = require("gulp");
import "./clean.js"; const env = require("../env.cjs");
import "./entry-html.js";
import "./gather-static.js"; require("./clean.cjs");
import "./gen-icons-json.js"; require("./translations.cjs");
import "./rollup.js"; require("./gen-icons-json.cjs");
import "./service-worker.js"; require("./gather-static.cjs");
import "./translations.js"; require("./webpack.cjs");
import "./webpack.js"; require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
gulp.task( gulp.task(
"develop-demo", "develop-demo",
@@ -19,7 +21,7 @@ gulp.task(
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( gulp.parallel(
"gen-icons-json", "gen-icons-json",
"gen-pages-demo-dev", "gen-index-demo-dev",
"build-translations", "build-translations",
"build-locale-data" "build-locale-data"
), ),
@@ -40,6 +42,6 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"),
"copy-static-demo", "copy-static-demo",
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo", env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
"gen-pages-demo-prod" "gen-index-demo-prod"
) )
); );

View File

@@ -1,6 +1,6 @@
import fs from "fs/promises"; const gulp = require("gulp");
import gulp from "gulp"; const fs = require("fs/promises");
import mapStream from "map-stream"; const mapStream = require("map-stream");
const inDirFrontend = "translations/frontend"; const inDirFrontend = "translations/frontend";
const inDirBackend = "translations/backend"; const inDirBackend = "translations/backend";

View File

@@ -0,0 +1,351 @@
// Tasks to generate entry HTML
const gulp = require("gulp");
const fs = require("fs-extra");
const path = require("path");
const template = require("lodash.template");
const { minify } = require("html-minifier-terser");
const paths = require("../paths.cjs");
const env = require("../env.cjs");
const { htmlMinifierOptions, terserOptions } = require("../bundle.cjs");
const templatePath = (tpl) =>
path.resolve(paths.polymer_dir, "src/html/", `${tpl}.html.template`);
const readFile = (pth) => fs.readFileSync(pth).toString();
const renderTemplate = (pth, data = {}, pathFunc = templatePath) => {
const compiled = template(readFile(pathFunc(pth)));
return compiled({
...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
renderTemplate,
});
};
const renderDemoTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(paths.demo_dir, "src/html/", `${tpl}.html.template`)
);
const renderCastTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(paths.cast_dir, "src/html/", `${tpl}.html.template`)
);
const renderGalleryTemplate = (pth, data = {}) =>
renderTemplate(pth, data, (tpl) =>
path.resolve(paths.gallery_dir, "src/html/", `${tpl}.html.template`)
);
const minifyHtml = (content) =>
minify(content, {
...htmlMinifierOptions,
conservativeCollapse: false,
minifyJS: terserOptions({
latestBuild: false, // Shared scripts should be ES5
isTestBuild: true, // Don't need source maps
}),
});
const PAGES = ["onboarding", "authorize"];
gulp.task("gen-pages-dev", (done) => {
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: `/frontend_latest/${page}.js`,
es5PageJS: `/frontend_es5/${page}.js`,
});
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
content
);
}
done();
});
gulp.task("gen-pages-prod", async () => {
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
const minifiedHTML = [];
for (const page of PAGES) {
const content = renderTemplate(page, {
latestPageJS: latestManifest[`${page}.js`],
es5PageJS: es5Manifest[`${page}.js`],
});
minifiedHTML.push(
minifyHtml(content).then((minified) =>
fs.outputFileSync(
path.resolve(paths.app_output_root, `${page}.html`),
minified
)
)
);
}
await Promise.all(minifiedHTML);
});
gulp.task("gen-index-app-dev", (done) => {
let latestAppJS;
let latestCoreJS;
let latestCustomPanelJS;
if (env.useWDS()) {
latestAppJS = "http://localhost:8000/src/entrypoints/app.ts";
latestCoreJS = "http://localhost:8000/src/entrypoints/core.ts";
latestCustomPanelJS =
"http://localhost:8000/src/entrypoints/custom-panel.ts";
} else {
latestAppJS = "/frontend_latest/app.js";
latestCoreJS = "/frontend_latest/core.js";
latestCustomPanelJS = "/frontend_latest/custom-panel.js";
}
const content = renderTemplate("index", {
latestAppJS,
latestCoreJS,
latestCustomPanelJS,
es5AppJS: "/frontend_es5/app.js",
es5CoreJS: "/frontend_es5/core.js",
es5CustomPanelJS: "/frontend_es5/custom-panel.js",
}).replace(/#THEMEC/g, "{{ theme_color }}");
fs.outputFileSync(path.resolve(paths.app_output_root, "index.html"), content);
done();
});
gulp.task("gen-index-app-prod", async () => {
const latestManifest = require(path.resolve(
paths.app_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.app_output_es5,
"manifest.json"
));
const content = renderTemplate("index", {
latestAppJS: latestManifest["app.js"],
latestCoreJS: latestManifest["core.js"],
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5AppJS: es5Manifest["app.js"],
es5CoreJS: es5Manifest["core.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
});
const minified = (await minifyHtml(content)).replace(
/#THEMEC/g,
"{{ theme_color }}"
);
fs.outputFileSync(
path.resolve(paths.app_output_root, "index.html"),
minified
);
});
gulp.task("gen-index-cast-dev", (done) => {
const contentReceiver = renderCastTemplate("receiver", {
latestReceiverJS: "/frontend_latest/receiver.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "receiver.html"),
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: "/frontend_latest/media.js",
es5MediaJS: "/frontend_es5/media.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "faq.html"),
contentFAQ
);
const contentLauncher = renderCastTemplate("launcher", {
latestLauncherJS: "/frontend_latest/launcher.js",
es5LauncherJS: "/frontend_es5/launcher.js",
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "index.html"),
contentLauncher
);
done();
});
gulp.task("gen-index-cast-prod", (done) => {
const latestManifest = require(path.resolve(
paths.cast_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.cast_output_es5,
"manifest.json"
));
const contentReceiver = renderCastTemplate("receiver", {
latestReceiverJS: latestManifest["receiver.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "receiver.html"),
contentReceiver
);
const contentMedia = renderCastTemplate("media", {
latestMediaJS: latestManifest["media.js"],
es5MediaJS: es5Manifest["media.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "media.html"),
contentMedia
);
const contentFAQ = renderCastTemplate("launcher-faq", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "faq.html"),
contentFAQ
);
const contentLauncher = renderCastTemplate("launcher", {
latestLauncherJS: latestManifest["launcher.js"],
es5LauncherJS: es5Manifest["launcher.js"],
});
fs.outputFileSync(
path.resolve(paths.cast_output_root, "index.html"),
contentLauncher
);
done();
});
gulp.task("gen-index-demo-dev", (done) => {
const content = renderDemoTemplate("index", {
latestDemoJS: "/frontend_latest/main.js",
es5DemoJS: "/frontend_es5/main.js",
});
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
content
);
done();
});
gulp.task("gen-index-demo-prod", async () => {
const latestManifest = require(path.resolve(
paths.demo_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.demo_output_es5,
"manifest.json"
));
const content = renderDemoTemplate("index", {
latestDemoJS: latestManifest["main.js"],
es5DemoJS: es5Manifest["main.js"],
});
const minified = await minifyHtml(content);
fs.outputFileSync(
path.resolve(paths.demo_output_root, "index.html"),
minified
);
});
gulp.task("gen-index-gallery-dev", (done) => {
const content = renderGalleryTemplate("index", {
latestGalleryJS: "./frontend_latest/entrypoint.js",
});
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
content
);
done();
});
gulp.task("gen-index-gallery-prod", async () => {
const latestManifest = require(path.resolve(
paths.gallery_output_latest,
"manifest.json"
));
const content = renderGalleryTemplate("index", {
latestGalleryJS: latestManifest["entrypoint.js"],
});
const minified = await minifyHtml(content);
fs.outputFileSync(
path.resolve(paths.gallery_output_root, "index.html"),
minified
);
});
gulp.task("gen-index-hassio-dev", async () => {
writeHassioEntrypoint(
`${paths.hassio_publicPath}/frontend_latest/entrypoint.js`,
`${paths.hassio_publicPath}/frontend_es5/entrypoint.js`
);
});
gulp.task("gen-index-hassio-prod", async () => {
const latestManifest = require(path.resolve(
paths.hassio_output_latest,
"manifest.json"
));
const es5Manifest = require(path.resolve(
paths.hassio_output_es5,
"manifest.json"
));
writeHassioEntrypoint(
latestManifest["entrypoint.js"],
es5Manifest["entrypoint.js"]
);
});
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
function loadES5() {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
}
`,
{ encoding: "utf-8" }
);
}

View File

@@ -1,233 +0,0 @@
// Tasks to generate entry HTML
import fs from "fs-extra";
import gulp from "gulp";
import { minify } from "html-minifier-terser";
import template from "lodash.template";
import path from "path";
import { htmlMinifierOptions, terserOptions } from "../bundle.cjs";
import env from "../env.cjs";
import paths from "../paths.cjs";
const renderTemplate = (templateFile, data = {}) => {
const compiled = template(
fs.readFileSync(templateFile, { encoding: "utf-8" })
);
return compiled({
...data,
useRollup: env.useRollup(),
useWDS: env.useWDS(),
// Resolve any child/nested templates relative to the parent and pass the same data
renderTemplate: (childTemplate) =>
renderTemplate(
path.resolve(path.dirname(templateFile), childTemplate),
data
),
});
};
const WRAP_TAGS = { ".js": "script", ".css": "style" };
const minifyHtml = (content, ext) => {
const wrapTag = WRAP_TAGS[ext] || "";
const begTag = wrapTag && `<${wrapTag}>`;
const endTag = wrapTag && `</${wrapTag}>`;
return minify(begTag + content + endTag, {
...htmlMinifierOptions,
conservativeCollapse: false,
minifyJS: terserOptions({
latestBuild: false, // Shared scripts should be ES5
isTestBuild: true, // Don't need source maps
}),
}).then((wrapped) =>
wrapTag ? wrapped.slice(begTag.length, -endTag.length) : wrapped
);
};
// Function to generate a dev task for each project's configuration
// Note Currently WDS paths are hard-coded to only work for app
const genPagesDevTask =
(
pageEntries,
inputRoot,
outputRoot,
useWDS = false,
inputSub = "src/html",
publicRoot = ""
) =>
async () => {
for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`),
{
latestEntryJS: entries.map((entry) =>
useWDS
? `http://localhost:8000/src/entrypoints/${entry}.ts`
: `${publicRoot}/frontend_latest/${entry}.js`
),
es5EntryJS: entries.map(
(entry) => `${publicRoot}/frontend_es5/${entry}.js`
),
latestCustomPanelJS: useWDS
? "http://localhost:8000/src/entrypoints/custom-panel.ts"
: `${publicRoot}/frontend_latest/custom-panel.js`,
es5CustomPanelJS: `${publicRoot}/frontend_es5/custom-panel.js`,
}
);
fs.outputFileSync(path.resolve(outputRoot, page), content);
}
};
// Same as previous but for production builds
// (includes minification and hashed file names from manifest)
const genPagesProdTask =
(
pageEntries,
inputRoot,
outputRoot,
outputLatest,
outputES5,
inputSub = "src/html"
) =>
async () => {
const latestManifest = fs.readJsonSync(
path.resolve(outputLatest, "manifest.json")
);
const es5Manifest = outputES5
? fs.readJsonSync(path.resolve(outputES5, "manifest.json"))
: {};
const minifiedHTML = [];
for (const [page, entries] of Object.entries(pageEntries)) {
const content = renderTemplate(
path.resolve(inputRoot, inputSub, `${page}.template`),
{
latestEntryJS: entries.map((entry) => latestManifest[`${entry}.js`]),
es5EntryJS: entries.map((entry) => es5Manifest[`${entry}.js`]),
latestCustomPanelJS: latestManifest["custom-panel.js"],
es5CustomPanelJS: es5Manifest["custom-panel.js"],
}
);
minifiedHTML.push(
minifyHtml(content, path.extname(page)).then((minified) =>
fs.outputFileSync(path.resolve(outputRoot, page), minified)
)
);
}
await Promise.all(minifiedHTML);
};
// Map HTML pages to their required entrypoints
const APP_PAGE_ENTRIES = {
"authorize.html": ["authorize"],
"onboarding.html": ["onboarding"],
"index.html": ["core", "app"],
};
gulp.task(
"gen-pages-app-dev",
genPagesDevTask(
APP_PAGE_ENTRIES,
paths.polymer_dir,
paths.app_output_root,
env.useWDS()
)
);
gulp.task(
"gen-pages-app-prod",
genPagesProdTask(
APP_PAGE_ENTRIES,
paths.polymer_dir,
paths.app_output_root,
paths.app_output_latest,
paths.app_output_es5
)
);
const CAST_PAGE_ENTRIES = {
"faq.html": ["launcher"],
"index.html": ["launcher"],
"media.html": ["media"],
"receiver.html": ["receiver"],
};
gulp.task(
"gen-pages-cast-dev",
genPagesDevTask(CAST_PAGE_ENTRIES, paths.cast_dir, paths.cast_output_root)
);
gulp.task(
"gen-pages-cast-prod",
genPagesProdTask(
CAST_PAGE_ENTRIES,
paths.cast_dir,
paths.cast_output_root,
paths.cast_output_latest,
paths.cast_output_es5
)
);
const DEMO_PAGE_ENTRIES = { "index.html": ["main"] };
gulp.task(
"gen-pages-demo-dev",
genPagesDevTask(DEMO_PAGE_ENTRIES, paths.demo_dir, paths.demo_output_root)
);
gulp.task(
"gen-pages-demo-prod",
genPagesProdTask(
DEMO_PAGE_ENTRIES,
paths.demo_dir,
paths.demo_output_root,
paths.demo_output_latest,
paths.demo_output_es5
)
);
const GALLERY_PAGE_ENTRIES = { "index.html": ["entrypoint"] };
gulp.task(
"gen-pages-gallery-dev",
genPagesDevTask(
GALLERY_PAGE_ENTRIES,
paths.gallery_dir,
paths.gallery_output_root
)
);
gulp.task(
"gen-pages-gallery-prod",
genPagesProdTask(
GALLERY_PAGE_ENTRIES,
paths.gallery_dir,
paths.gallery_output_root,
paths.gallery_output_latest
)
);
const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] };
gulp.task(
"gen-pages-hassio-dev",
genPagesDevTask(
HASSIO_PAGE_ENTRIES,
paths.hassio_dir,
paths.hassio_output_root,
undefined,
"src",
paths.hassio_publicPath
)
);
gulp.task(
"gen-pages-hassio-prod",
genPagesProdTask(
HASSIO_PAGE_ENTRIES,
paths.hassio_dir,
paths.hassio_output_root,
paths.hassio_output_latest,
paths.hassio_output_es5,
"src"
)
);

View File

@@ -1,15 +1,14 @@
// Task to download the latest Lokalise translations from the nightly workflow artifacts // Task to download the latest Lokalise translations from the nightly workflow artifacts
import { createOAuthDeviceAuth } from "@octokit/auth-oauth-device"; const del = import("del");
import { retry } from "@octokit/plugin-retry"; const fs = require("fs/promises");
import { Octokit } from "@octokit/rest"; const path = require("path");
import { deleteAsync } from "del"; const process = require("process");
import { mkdir, readFile, writeFile } from "fs/promises"; const gulp = require("gulp");
import gulp from "gulp"; const jszip = require("jszip");
import jszip from "jszip"; const tar = require("tar");
import path from "path"; const { Octokit } = require("@octokit/rest");
import process from "process"; const { createOAuthDeviceAuth } = require("@octokit/auth-oauth-device");
import tar from "tar";
const MAX_AGE = 24; // hours const MAX_AGE = 24; // hours
const OWNER = "home-assistant"; const OWNER = "home-assistant";
@@ -38,7 +37,7 @@ gulp.task("fetch-nightly-translations", async function () {
// and stop if they are not old enough // and stop if they are not old enough
let currentArtifact; let currentArtifact;
try { try {
currentArtifact = JSON.parse(await readFile(ARTIFACT_FILE, "utf-8")); currentArtifact = JSON.parse(await fs.readFile(ARTIFACT_FILE, "utf-8"));
const currentAge = const currentAge =
(Date.now() - Date.parse(currentArtifact.created_at)) / 3600000; (Date.now() - Date.parse(currentArtifact.created_at)) / 3600000;
if (currentAge < MAX_AGE) { if (currentAge < MAX_AGE) {
@@ -53,7 +52,7 @@ gulp.task("fetch-nightly-translations", async function () {
} }
// To store file writing promises // To store file writing promises
const createExtractDir = mkdir(EXTRACT_DIR, { recursive: true }); const createExtractDir = fs.mkdir(EXTRACT_DIR, { recursive: true });
const writings = []; const writings = [];
// Authenticate to GitHub using GitHub action token if it exists, // Authenticate to GitHub using GitHub action token if it exists,
@@ -63,7 +62,7 @@ gulp.task("fetch-nightly-translations", async function () {
tokenAuth = { token: process.env.GITHUB_TOKEN }; tokenAuth = { token: process.env.GITHUB_TOKEN };
} else { } else {
try { try {
tokenAuth = JSON.parse(await readFile(TOKEN_FILE, "utf-8")); tokenAuth = JSON.parse(await fs.readFile(TOKEN_FILE, "utf-8"));
} catch { } catch {
if (!allowTokenSetup) { if (!allowTokenSetup) {
console.log("No token found so build wil continue with English only"); console.log("No token found so build wil continue with English only");
@@ -88,7 +87,7 @@ gulp.task("fetch-nightly-translations", async function () {
tokenAuth = await auth({ type: "oauth" }); tokenAuth = await auth({ type: "oauth" });
writings.push( writings.push(
createExtractDir.then( createExtractDir.then(
writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2)) fs.writeFile(TOKEN_FILE, JSON.stringify(tokenAuth, null, 2))
) )
); );
} }
@@ -96,7 +95,7 @@ gulp.task("fetch-nightly-translations", async function () {
// Authenticate with token and request workflow runs from GitHub // Authenticate with token and request workflow runs from GitHub
console.log("Fetching new translations..."); console.log("Fetching new translations...");
const octokit = new (Octokit.plugin(retry))({ const octokit = new Octokit({
userAgent: "Fetch Nightly Translations", userAgent: "Fetch Nightly Translations",
auth: tokenAuth.token, auth: tokenAuth.token,
}); });
@@ -132,13 +131,17 @@ gulp.task("fetch-nightly-translations", async function () {
} }
writings.push( writings.push(
createExtractDir.then( createExtractDir.then(
writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2)) fs.writeFile(ARTIFACT_FILE, JSON.stringify(latestArtifact, null, 2))
) )
); );
// Remove the current translations // Remove the current translations
const deleteCurrent = Promise.all(writings).then( const deleteCurrent = Promise.all(writings).then(
deleteAsync([`${EXTRACT_DIR}/*`, `!${ARTIFACT_FILE}`, `!${TOKEN_FILE}`]) (await del).deleteAsync([
`${EXTRACT_DIR}/*`,
`!${ARTIFACT_FILE}`,
`!${TOKEN_FILE}`,
])
); );
// Get the download URL and follow the redirect to download (stored as ArrayBuffer) // Get the download URL and follow the redirect to download (stored as ArrayBuffer)

View File

@@ -1,19 +1,22 @@
import fs from "fs"; // Run demo develop mode
import { glob } from "glob"; const gulp = require("gulp");
import gulp from "gulp"; const fs = require("fs");
import yaml from "js-yaml"; const path = require("path");
import { marked } from "marked"; const { marked } = require("marked");
import path from "path"; const glob = require("glob");
import env from "../env.cjs"; const yaml = require("js-yaml");
import paths from "../paths.cjs";
import "./clean.js"; const env = require("../env.cjs");
import "./entry-html.js"; const paths = require("../paths.cjs");
import "./gather-static.js";
import "./gen-icons-json.js"; require("./clean.cjs");
import "./rollup.js"; require("./translations.cjs");
import "./service-worker.js"; require("./gen-icons-json.cjs");
import "./translations.js"; require("./gather-static.cjs");
import "./webpack.js"; require("./webpack.cjs");
require("./service-worker.cjs");
require("./entry-html.cjs");
require("./rollup.cjs");
gulp.task("gather-gallery-pages", async function gatherPages() { gulp.task("gather-gallery-pages", async function gatherPages() {
const pageDir = path.resolve(paths.gallery_dir, "src/pages"); const pageDir = path.resolve(paths.gallery_dir, "src/pages");
@@ -156,7 +159,7 @@ gulp.task(
"gather-gallery-pages" "gather-gallery-pages"
), ),
"copy-static-gallery", "copy-static-gallery",
"gen-pages-gallery-dev", "gen-index-gallery-dev",
gulp.parallel( gulp.parallel(
env.useRollup() env.useRollup()
? "rollup-dev-server-gallery" ? "rollup-dev-server-gallery"
@@ -190,6 +193,6 @@ gulp.task(
), ),
"copy-static-gallery", "copy-static-gallery",
env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery", env.useRollup() ? "rollup-prod-gallery" : "webpack-prod-gallery",
"gen-pages-gallery-prod" "gen-index-gallery-prod"
) )
); );

View File

@@ -1,9 +1,9 @@
// Gulp task to gather all static files. // Gulp task to gather all static files.
import fs from "fs-extra"; const gulp = require("gulp");
import gulp from "gulp"; const path = require("path");
import path from "path"; const fs = require("fs-extra");
import paths from "../paths.cjs"; const paths = require("../paths.cjs");
const npmPath = (...parts) => const npmPath = (...parts) =>
path.resolve(paths.polymer_dir, "node_modules", ...parts); path.resolve(paths.polymer_dir, "node_modules", ...parts);
@@ -111,10 +111,9 @@ gulp.task("copy-translations-supervisor", async () => {
copyTranslations(staticDir); copyTranslations(staticDir);
}); });
gulp.task("copy-static-supervisor", async () => { gulp.task("copy-locale-data-supervisor", async () => {
const staticDir = paths.hassio_output_static; const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir); copyLocaleData(staticDir);
copyFonts(staticDir);
}); });
gulp.task("copy-static-app", async () => { gulp.task("copy-static-app", async () => {

View File

@@ -1,15 +1,17 @@
import fs from "fs"; const gulp = require("gulp");
import gulp from "gulp"; const path = require("path");
import hash from "object-hash"; const fs = require("fs");
import path from "path"; const hash = require("object-hash");
import paths from "../paths.cjs";
const ICON_PACKAGE_PATH = path.resolve("node_modules/@mdi/svg/"); const ICON_PACKAGE_PATH = path.resolve(
__dirname,
"../../node_modules/@mdi/svg/"
);
const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json"); const META_PATH = path.resolve(ICON_PACKAGE_PATH, "meta.json");
const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json"); const PACKAGE_PATH = path.resolve(ICON_PACKAGE_PATH, "package.json");
const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg"); const ICON_PATH = path.resolve(ICON_PACKAGE_PATH, "svg");
const OUTPUT_DIR = path.resolve(paths.build_dir, "mdi"); const OUTPUT_DIR = path.resolve(__dirname, "../../build/mdi");
const REMOVED_ICONS_PATH = new URL("../removedIcons.json", import.meta.url); const REMOVED_ICONS_PATH = path.resolve(__dirname, "../removedIcons.json");
const encoding = "utf8"; const encoding = "utf8";

View File

@@ -1,13 +1,13 @@
import gulp from "gulp"; const gulp = require("gulp");
import env from "../env.cjs"; const env = require("../env.cjs");
import "./clean.js"; require("./clean.cjs");
import "./compress.js"; require("./compress.cjs");
import "./entry-html.js"; require("./entry-html.cjs");
import "./gather-static.js"; require("./gather-static.cjs");
import "./gen-icons-json.js"; require("./gen-icons-json.cjs");
import "./rollup.js"; require("./rollup.cjs");
import "./translations.js"; require("./translations.cjs");
import "./webpack.js"; require("./webpack.cjs");
gulp.task( gulp.task(
"develop-hassio", "develop-hassio",
@@ -17,11 +17,11 @@ gulp.task(
}, },
"clean-hassio", "clean-hassio",
"gen-dummy-icons-json", "gen-dummy-icons-json",
"gen-pages-hassio-dev", "gen-index-hassio-dev",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data", "build-locale-data",
"copy-static-supervisor", "copy-locale-data-supervisor",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
) )
); );
@@ -37,9 +37,9 @@ gulp.task(
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data", "build-locale-data",
"copy-static-supervisor", "copy-locale-data-supervisor",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-pages-hassio-prod", "gen-index-hassio-prod",
...// Don't compress running tests ...// Don't compress running tests
(env.isTestBuild() ? [] : ["compress-hassio"]) (env.isTestBuild() ? [] : ["compress-hassio"])
) )

View File

@@ -1,22 +1,24 @@
import { deleteSync } from "del"; const del = import("del");
import fs from "fs"; const path = require("path");
import gulp from "gulp"; const gulp = require("gulp");
import path from "path"; const fs = require("fs");
import paths from "../paths.cjs"; const paths = require("../paths.cjs");
const outDir = "build/locale-data"; const outDir = "build/locale-data";
gulp.task("clean-locale-data", async () => deleteSync([outDir])); gulp.task("clean-locale-data", async () => (await del).deleteSync([outDir]));
gulp.task("ensure-locale-data-build-dir", async () => { gulp.task("ensure-locale-data-build-dir", (done) => {
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true }); fs.mkdirSync(outDir, { recursive: true });
}
done();
}); });
const modules = { const modules = {
"intl-relativetimeformat": "RelativeTimeFormat", "intl-relativetimeformat": "RelativeTimeFormat",
"intl-datetimeformat": "DateTimeFormat", "intl-datetimeformat": "DateTimeFormat",
"intl-numberformat": "NumberFormat", "intl-numberformat": "NumberFormat",
"intl-displaynames": "DisplayNames",
}; };
gulp.task("create-locale-data", (done) => { gulp.task("create-locale-data", (done) => {
@@ -28,13 +30,10 @@ gulp.task("create-locale-data", (done) => {
Object.entries(modules).forEach(([module, className]) => { Object.entries(modules).forEach(([module, className]) => {
Object.keys(translationMeta).forEach((lang) => { Object.keys(translationMeta).forEach((lang) => {
try { try {
const localeData = fs const localeData = String(
.readFileSync( fs.readFileSync(
path.resolve( require.resolve(`@formatjs/${module}/locale-data/${lang}.js`)
paths.polymer_dir, )
`node_modules/@formatjs/${module}/locale-data/${lang}.js`
),
"utf-8"
) )
.replace( .replace(
new RegExp( new RegExp(
@@ -46,13 +45,15 @@ gulp.task("create-locale-data", (done) => {
.replace(/\)\s*}/im, ""); .replace(/\)\s*}/im, "");
// make sure we have valid JSON // make sure we have valid JSON
JSON.parse(localeData); JSON.parse(localeData);
if (!fs.existsSync(path.join(outDir, module))) {
fs.mkdirSync(path.join(outDir, module), { recursive: true }); fs.mkdirSync(path.join(outDir, module), { recursive: true });
}
fs.writeFileSync( fs.writeFileSync(
path.join(outDir, `${module}/${lang}.json`), path.join(outDir, `${module}/${lang}.json`),
localeData localeData
); );
} catch (e) { } catch (e) {
if (e.code !== "ENOENT") { if (e.code !== "MODULE_NOT_FOUND") {
throw e; throw e;
} }
} }

View File

@@ -1,14 +1,13 @@
// Tasks to run Rollup // Tasks to run Rollup
const path = require("path");
import log from "fancy-log"; const gulp = require("gulp");
import gulp from "gulp"; const rollup = require("rollup");
import http from "http"; const handler = require("serve-handler");
import open from "open"; const http = require("http");
import path from "path"; const log = require("fancy-log");
import { rollup } from "rollup"; const open = require("open");
import handler from "serve-handler"; const rollupConfig = require("../rollup.cjs");
import paths from "../paths.cjs"; const paths = require("../paths.cjs");
import rollupConfig from "../rollup.cjs";
const bothBuilds = (createConfigFunc, params) => const bothBuilds = (createConfigFunc, params) =>
gulp.series( gulp.series(

View File

@@ -1,12 +1,11 @@
// Generate service worker. // Generate service worker.
// Based on manifest, create a file with the content as service_worker.js // Based on manifest, create a file with the content as service_worker.js
const gulp = require("gulp");
import fs from "fs-extra"; const path = require("path");
import gulp from "gulp"; const fs = require("fs-extra");
import path from "path"; const workboxBuild = require("workbox-build");
import sourceMapUrl from "source-map-url"; const sourceMapUrl = require("source-map-url");
import workboxBuild from "workbox-build"; const paths = require("../paths.cjs");
import paths from "../paths.cjs";
const swDest = path.resolve(paths.app_output_root, "service_worker.js"); const swDest = path.resolve(paths.app_output_root, "service_worker.js");
@@ -29,9 +28,10 @@ self.addEventListener('install', (event) => {
gulp.task("gen-service-worker-app-prod", async () => { gulp.task("gen-service-worker-app-prod", async () => {
// Read bundled source file // Read bundled source file
const bundleManifestLatest = fs.readJsonSync( const bundleManifestLatest = require(path.resolve(
path.resolve(paths.app_output_latest, "manifest.json") paths.app_output_latest,
); "manifest.json"
));
let serviceWorkerContent = fs.readFileSync( let serviceWorkerContent = fs.readFileSync(
paths.app_output_root + bundleManifestLatest["service_worker.js"], paths.app_output_root + bundleManifestLatest["service_worker.js"],
"utf-8" "utf-8"
@@ -46,9 +46,10 @@ gulp.task("gen-service-worker-app-prod", async () => {
); );
// Remove ES5 // Remove ES5
const bundleManifestES5 = fs.readJsonSync( const bundleManifestES5 = require(path.resolve(
path.resolve(paths.app_output_es5, "manifest.json") paths.app_output_es5,
); "manifest.json"
));
fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]); fs.removeSync(paths.app_output_root + bundleManifestES5["service_worker.js"]);
fs.removeSync( fs.removeSync(
paths.app_output_root + bundleManifestES5["service_worker.js.map"] paths.app_output_root + bundleManifestES5["service_worker.js.map"]

View File

@@ -1,24 +1,19 @@
import { createHash } from "crypto"; const del = import("del");
import { deleteSync } from "del"; const crypto = require("crypto");
import { const path = require("path");
mkdirSync, const source = require("vinyl-source-stream");
readdirSync, const vinylBuffer = require("vinyl-buffer");
readFileSync, const gulp = require("gulp");
renameSync, const fs = require("fs");
writeFile, const flatmap = require("gulp-flatmap");
} from "fs"; const merge = require("gulp-merge-json");
import gulp from "gulp"; const rename = require("gulp-rename");
import flatmap from "gulp-flatmap"; const transform = require("gulp-json-transform");
import transform from "gulp-json-transform"; const { mapFiles } = require("../util.cjs");
import merge from "gulp-merge-json"; const env = require("../env.cjs");
import rename from "gulp-rename"; const paths = require("../paths.cjs");
import path from "path";
import vinylBuffer from "vinyl-buffer"; require("./fetch-nightly-translations.cjs");
import source from "vinyl-source-stream";
import env from "../env.cjs";
import paths from "../paths.cjs";
import { mapFiles } from "../util.cjs";
import "./fetch-nightly-translations.js";
const inFrontendDir = "translations/frontend"; const inFrontendDir = "translations/frontend";
const inBackendDir = "translations/backend"; const inBackendDir = "translations/backend";
@@ -38,12 +33,7 @@ gulp.task(
// Panel translations which should be split from the core translations. // Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys( const TRANSLATION_FRAGMENTS = Object.keys(
JSON.parse( require("../../src/translations/en.json").ui.panel
readFileSync(
path.resolve(paths.polymer_dir, "src/translations/en.json"),
"utf-8"
)
).ui.panel
); );
function recursiveFlatten(prefix, data) { function recursiveFlatten(prefix, data) {
@@ -130,14 +120,17 @@ function lokaliseTransform(data, original, file) {
return output; return output;
} }
gulp.task("clean-translations", async () => deleteSync([workDir])); gulp.task("clean-translations", async () => (await del).deleteSync([workDir]));
gulp.task("ensure-translations-build-dir", async () => { gulp.task("ensure-translations-build-dir", (done) => {
mkdirSync(workDir, { recursive: true }); if (!fs.existsSync(workDir)) {
fs.mkdirSync(workDir, { recursive: true });
}
done();
}); });
gulp.task("create-test-metadata", (cb) => { gulp.task("create-test-metadata", (cb) => {
writeFile( fs.writeFile(
workDir + "/testMetadata.json", workDir + "/testMetadata.json",
JSON.stringify({ JSON.stringify({
test: { test: {
@@ -310,14 +303,15 @@ const fingerprints = {};
gulp.task("build-translation-fingerprints", () => { gulp.task("build-translation-fingerprints", () => {
// Fingerprint full file of each language // Fingerprint full file of each language
const files = readdirSync(fullDir); const files = fs.readdirSync(fullDir);
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = { fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes // In dev we create fake hashes
hash: env.isProdBuild() hash: env.isProdBuild()
? createHash("md5") ? crypto
.update(readFileSync(path.join(fullDir, files[i]), "utf-8")) .createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex") .digest("hex")
: "dev", : "dev",
}; };
@@ -333,7 +327,7 @@ gulp.task("build-translation-fingerprints", () => {
throw new Error(`Unable to find hash for ${filename}`); throw new Error(`Unable to find hash for ${filename}`);
} }
renameSync( fs.renameSync(
filename, filename,
`${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${ `${parsed.dir}/${parsed.name}-${fingerprints[parsed.name].hash}${
parsed.ext parsed.ext

View File

@@ -0,0 +1,11 @@
// Tasks to run Rollup
const gulp = require("gulp");
const { startDevServer } = require("@web/dev-server");
gulp.task("wds-watch-app", () => {
startDevServer({
config: {
watch: true,
},
});
});

View File

@@ -1,10 +0,0 @@
import gulp from "gulp";
import { startDevServer } from "@web/dev-server";
gulp.task("wds-watch-app", async () => {
startDevServer({
config: {
watch: true,
},
});
});

View File

@@ -1,20 +1,19 @@
// Tasks to run webpack. // Tasks to run webpack.
const fs = require("fs");
import log from "fancy-log"; const gulp = require("gulp");
import fs from "fs"; const webpack = require("webpack");
import gulp from "gulp"; const WebpackDevServer = require("webpack-dev-server");
import path from "path"; const log = require("fancy-log");
import webpack from "webpack"; const path = require("path");
import WebpackDevServer from "webpack-dev-server"; const env = require("../env.cjs");
import env from "../env.cjs"; const paths = require("../paths.cjs");
import paths from "../paths.cjs"; const {
import {
createAppConfig, createAppConfig,
createCastConfig,
createDemoConfig, createDemoConfig,
createGalleryConfig, createCastConfig,
createHassioConfig, createHassioConfig,
} from "../webpack.cjs"; createGalleryConfig,
} = require("../webpack.cjs");
const bothBuilds = (createConfigFunc, params) => [ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: true }), createConfigFunc({ ...params, latestBuild: true }),

View File

@@ -1,59 +0,0 @@
#!/usr/bin/env node
// Script to print Babel plugins and Core JS polyfills that will be used by browserslist environments
import { version as babelVersion } from "@babel/core";
import presetEnv from "@babel/preset-env";
import compilationTargets from "@babel/helper-compilation-targets";
import coreJSCompat from "core-js-compat";
import { logPlugin } from "@babel/preset-env/lib/debug.js";
import { babelOptions } from "./bundle.cjs";
const detailsOpen = (heading) =>
`<details>\n<summary><h4>${heading}</h4></summary>\n`;
const detailsClose = "</details>\n";
const dummyAPI = {
version: babelVersion,
assertVersion: () => {},
caller: (callback) =>
callback({
name: "Dummy Bundler",
supportsStaticESM: true,
supportsDynamicImport: true,
supportsTopLevelAwait: true,
supportsExportNamespaceFrom: true,
}),
targets: () => ({}),
};
for (const buildType of ["Modern", "Legacy"]) {
const browserslistEnv = buildType.toLowerCase();
const babelOpts = babelOptions({ latestBuild: browserslistEnv === "modern" });
const presetEnvOpts = babelOpts.presets[0][1];
// Invoking preset-env in debug mode will log the included plugins
console.log(detailsOpen(`${buildType} Build Babel Plugins`));
presetEnv.default(dummyAPI, {
...presetEnvOpts,
browserslistEnv,
debug: true,
});
console.log(detailsClose);
// Manually log the Core-JS polyfills using the same technique
if (presetEnvOpts.useBuiltIns) {
console.log(detailsOpen(`${buildType} Build Core-JS Polyfills`));
const targets = compilationTargets.default(babelOpts?.targets, {
browserslistEnv,
});
const polyfillList = coreJSCompat({ targets }).list;
console.log(
"The following %i polyfills may be injected by Babel:\n",
polyfillList.length
);
for (const polyfill of polyfillList) {
logPlugin(polyfill, targets, coreJSCompat.data);
}
console.log(detailsClose);
}
}

View File

@@ -132,17 +132,6 @@ const createWebpackConfig = ({
), ),
path.resolve(paths.polymer_dir, "src/util/empty.js") path.resolve(paths.polymer_dir, "src/util/empty.js")
), ),
// See `src/resources/intl-polyfill-legacy.ts` for explanation
!latestBuild &&
new webpack.NormalModuleReplacementPlugin(
new RegExp(
path.resolve(paths.polymer_dir, "src/resources/intl-polyfill.ts")
),
path.resolve(
paths.polymer_dir,
"src/resources/intl-polyfill-legacy.ts"
)
),
!isProdBuild && new LogStartCompilePlugin(), !isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {

View File

@@ -1,24 +0,0 @@
<meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Cast" />
<meta property="og:site_name" content="Home Assistant Cast" />
<meta property="og:url" content="https://cast.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."
/>
<meta
property="og:image"
content="https://cast.home-assistant.io/images/google-nest-hub.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant Cast" />
<meta
name="twitter:description"
content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen."
/>
<meta
name="twitter:image"
content="https://cast.home-assistant.io/images/google-nest-hub.png"
/>

View File

@@ -1,35 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Home Assistant Cast</title>
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
<style>
body {
background-color: #e5e5e5;
}
</style>
<%= renderTemplate("_social_meta.html.template") %>
</head>
<body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<hc-connect></hc-connect>
<script>
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
window.latestJS = true;
</script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-57927901-9', 'auto');
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
</script>
</body>
</html>

View File

@@ -3,7 +3,7 @@
<head> <head>
<title>Home Assistant Cast - FAQ</title> <title>Home Assistant Cast - FAQ</title>
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" /> <link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
<%= renderTemplate("../../../src/html/_style_base.html.template") %> <%= renderTemplate('_style_base') %>
<style> <style>
body { body {
background-color: #e5e5e5; background-color: #e5e5e5;
@@ -35,14 +35,25 @@
/> />
</head> </head>
<body> <body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %> <%= renderTemplate('_js_base') %>
<script> <script>
<% for (const entry of latestEntryJS) { %> import("<%= latestLauncherJS %>");
import("<%= entry %>");
<% } %>
window.latestJS = true; window.latestJS = true;
</script> </script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");
};
<% } else { %>
_ls("<%= es5LauncherJS %>");
<% } %>
}
</script>
<hc-layout subtitle="FAQ"> <hc-layout subtitle="FAQ">
<style> <style>
a { a {

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<title>Home Assistant Cast</title>
<link rel="manifest" href="/manifest.json" />
<link rel="icon" href="/images/ha-cast-icon.png" type="image/png" />
<%= renderTemplate('_style_base') %>
<style>
body {
background-color: #e5e5e5;
}
</style>
<meta property="fb:app_id" content="338291289691179">
<meta property="og:title" content="Home Assistant Cast">
<meta property="og:site_name" content="Home Assistant Cast">
<meta property="og:url" content="https://cast.home-assistant.io/">
<meta property="og:type" content="website">
<meta property="og:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
<meta property="og:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@home_assistant">
<meta name="twitter:title" content="Home Assistant Cast">
<meta name="twitter:description" content="Show Home Assistant on your Chromecast or Google Assistant devices with a screen.">
<meta name="twitter:image" content="https://cast.home-assistant.io/images/google-nest-hub.png">
</head>
<body>
<%= renderTemplate('_js_base') %>
<hc-connect></hc-connect>
<script>
import("<%= latestLauncherJS %>");
window.latestJS = true;
</script>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5LauncherJS %>");
};
<% } else { %>
_ls("<%= es5LauncherJS %>");
<% } %>
}
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-57927901-9', 'auto');
ga('send', 'pageview', location.pathname.includes("auth_callback") === -1 ? location.pathname : "/");
</script>
</body>
</html>

View File

@@ -22,14 +22,25 @@
</script> </script>
</head> </head>
<body> <body>
<%= renderTemplate("../../../src/html/_js_base.html.template") %> <%= renderTemplate('_js_base') %>
<cast-media-player></cast-media-player> <cast-media-player></cast-media-player>
<script> <script>
<% for (const entry of latestEntryJS) { %> import("<%= latestMediaJS %>");
import("<%= entry %>");
<% } %>
window.latestJS = true; window.latestJS = true;
</script> </script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5MediaJS %>");
};
<% } else { %>
_ls("<%= es5MediaJS %>");
<% } %>
}
</script>
</body> </body>
</html> </html>

View File

@@ -1,10 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script> <script src="//www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js"></script>
<% for (const entry of latestEntryJS) { %> <script type="module" src="<%= latestReceiverJS %>"></script>
<script type="module" src="<%= entry %>"></script> <%= renderTemplate('_style_base') %>
<% } %>
<%= renderTemplate("../../../src/html/_style_base.html.template") %>
<style> <style>
body { body {
background-color: white; background-color: white;

View File

@@ -190,7 +190,7 @@ export class HcConnect extends LitElement {
private _handleInputKeyDown(ev: KeyboardEvent) { private _handleInputKeyDown(ev: KeyboardEvent) {
// Handle pressing enter. // Handle pressing enter.
if (ev.key === "Enter") { if (ev.keyCode === 13) {
this._handleConnect(); this._handleConnect();
} }
} }

View File

@@ -1,5 +1,3 @@
import { cast } from "chromecast-caf-receiver";
const castContext = cast.framework.CastReceiverContext.getInstance(); const castContext = cast.framework.CastReceiverContext.getInstance();
const playerManager = castContext.getPlayerManager(); const playerManager = castContext.getPlayerManager();

View File

@@ -1,3 +1,2 @@
import { cast } from "chromecast-caf-receiver"; /* eslint-disable no-undef */
export const castContext = cast.framework.CastReceiverContext.getInstance(); export const castContext = cast.framework.CastReceiverContext.getInstance();

View File

@@ -1,4 +1,4 @@
import { cast } from "chromecast-caf-receiver"; /* eslint-disable no-undef */
import { CAST_NS } from "../../../src/cast/const"; import { CAST_NS } from "../../../src/cast/const";
import { HassMessage } from "../../../src/cast/receiver_messages"; import { HassMessage } from "../../../src/cast/receiver_messages";
import "../../../src/resources/custom-card-support"; import "../../../src/resources/custom-card-support";

View File

@@ -1,26 +0,0 @@
<meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Demo" />
<meta property="og:site_name" content="Home Assistant" />
<meta property="og:url" content="https://demo.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
property="og:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant" />
<meta
name="twitter:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
name="twitter:image"
content="https://www.home-assistant.io/images/default-social.png"
/>

View File

@@ -1,8 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Home Assistant Demo</title> <meta charset="utf-8" />
<%= renderTemplate("../../../src/html/_header.html.template") %> <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="icon" href="/static/icons/favicon.ico" />
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" /> <link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#03a9f4" />
<link <link
rel="apple-touch-icon" rel="apple-touch-icon"
@@ -34,7 +35,33 @@
content="width=device-width, initial-scale=1, shrink-to-fit=no" content="width=device-width, initial-scale=1, shrink-to-fit=no"
/> />
<meta name="theme-color" content="#03a9f4" /> <meta name="theme-color" content="#03a9f4" />
<%= renderTemplate("_social_meta.html.template") %> <meta property="fb:app_id" content="338291289691179" />
<meta property="og:title" content="Home Assistant Demo" />
<meta property="og:site_name" content="Home Assistant" />
<meta property="og:url" content="https://demo.home-assistant.io/" />
<meta property="og:type" content="website" />
<meta
property="og:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
property="og:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@home_assistant" />
<meta name="twitter:title" content="Home Assistant" />
<meta
name="twitter:description"
content="Open source home automation that puts local control and privacy first."
/>
<meta
name="twitter:image"
content="https://www.home-assistant.io/images/default-social.png"
/>
<title>Home Assistant Demo</title>
<style> <style>
html { html {
background-color: var(--primary-background-color, #fafafa); background-color: var(--primary-background-color, #fafafa);
@@ -80,19 +107,29 @@
</svg> </svg>
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div> <div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
</div> </div>
<ha-demo></ha-demo> <ha-demo></ha-demo>
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> <%= renderTemplate('_js_base') %>
<%= renderTemplate('_preload_roboto') %>
<script> <script>
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5 import("<%= latestDemoJS %>");
if (!isS11_12) {
<% for (const entry of latestEntryJS) { %>
import("<%= entry %>");
<% } %>
window.latestJS = true; window.latestJS = true;
</script>
<script>
if (!window.latestJS) {
<% if (useRollup) { %>
_ls("/static/js/s.min.js").onload = function() {
System.import("<%= es5DemoJS %>");
};
<% } else { %>
_ls("<%= es5DemoJS %>");
<% } %>
} }
</script> </script>
<%= renderTemplate("../../../src/html/_script_load_es5.html.template") %>
<script> <script>
var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]]; var _gaq = [["_setAccount", "UA-57927901-5"], ["_trackPageview"]];
(function (d, t) { (function (d, t) {

View File

@@ -1,7 +1,7 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfigEntries = (hass: MockHomeAssistant) => { export const mockConfigEntries = (hass: MockHomeAssistant) => {
hass.mockWS("config_entries/get_matching", () => [ hass.mockWS("config_entries/get", () => [
{ {
entry_id: "co2signal", entry_id: "co2signal",
domain: "co2signal", domain: "co2signal",

View File

@@ -45,10 +45,6 @@ export default [
header: "Users", header: "Users",
pages: ["user-types", "configuration-menu"], pages: ["user-types", "configuration-menu"],
}, },
{
category: "date-time",
header: "Date and Time",
},
{ {
category: "design.home-assistant.io", category: "design.home-assistant.io",
header: "About", header: "About",

View File

@@ -1,3 +1,5 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style"; import "../../src/resources/ha-style";
import "../../src/resources/roboto"; import "../../src/resources/roboto";
import "./ha-gallery"; import "./ha-gallery";

View File

@@ -8,9 +8,8 @@
/> />
<meta name="theme-color" content="#2157BC" /> <meta name="theme-color" content="#2157BC" />
<title>Home Assistant Design</title> <title>Home Assistant Design</title>
<% for (const entry of latestEntryJS) { %>
<script type="module" src="<%= entry %>"></script> <script type="module" src="<%= latestGalleryJS %>"></script>
<% } %>
<style> <style>
body { body {
font-family: Roboto, Noto, sans-serif; font-family: Roboto, Noto, sans-serif;

View File

@@ -63,7 +63,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
<div class="condition"> <div class="condition">
<span> <span>
${this._condition ${this._condition
? describeCondition(this._condition, this.hass, []) ? describeCondition(this._condition, this.hass)
: "<invalid YAML>"} : "<invalid YAML>"}
</span> </span>
<ha-yaml-editor <ha-yaml-editor
@@ -76,7 +76,7 @@ export class DemoAutomationDescribeCondition extends LitElement {
${conditions.map( ${conditions.map(
(conf) => html` (conf) => html`
<div class="condition"> <div class="condition">
<span>${describeCondition(conf as any, this.hass, [])}</span> <span>${describeCondition(conf as any, this.hass)}</span>
<pre>${dump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `

View File

@@ -74,7 +74,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
<div class="trigger"> <div class="trigger">
<span> <span>
${this._trigger ${this._trigger
? describeTrigger(this._trigger, this.hass, []) ? describeTrigger(this._trigger, this.hass)
: "<invalid YAML>"} : "<invalid YAML>"}
</span> </span>
<ha-yaml-editor <ha-yaml-editor
@@ -86,7 +86,7 @@ export class DemoAutomationDescribeTrigger extends LitElement {
${triggers.map( ${triggers.map(
(conf) => html` (conf) => html`
<div class="trigger"> <div class="trigger">
<span>${describeTrigger(conf as any, this.hass, [])}</span> <span>${describeTrigger(conf as any, this.hass)}</span>
<pre>${dump(conf)}</pre> <pre>${dump(conf)}</pre>
</div> </div>
` `

View File

@@ -336,7 +336,7 @@ const SCHEMAS: {
["and", "another_one"], ["and", "another_one"],
["option", "1000"], ["option", "1000"],
], ],
name: "select many options", name: "select many otions",
default: "default", default: "default",
}, },
], ],
@@ -364,7 +364,7 @@ const SCHEMAS: {
and: "another_one", and: "another_one",
option: "1000", option: "1000",
}, },
name: "multi many options", name: "multi many otions",
default: ["default"], default: ["default"],
}, },
], ],

View File

@@ -1,3 +0,0 @@
---
title: HS Color Picker
---

View File

@@ -1,120 +0,0 @@
import "../../../../src/components/ha-hs-color-picker";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-slider";
import { hsv2rgb } from "../../../../src/common/color/convert-color";
@customElement("demo-components-ha-hs-color-picker")
export class DemoHaHsColorPicker extends LitElement {
@state()
brightness = 255;
@state()
value: [number, number] = [0, 0];
@state()
liveValue?: [number, number];
private _brightnessChanged(ev) {
this.brightness = Number(ev.target.value);
}
private _hsColorCursor(ev) {
this.liveValue = ev.detail.value;
}
private _hsColorChanged(ev) {
this.value = ev.detail.value;
}
private _hueChanged(ev) {
this.value = [ev.target.value, this.value[1]];
}
private _saturationChanged(ev) {
this.value = [this.value[0], ev.target.value];
}
protected render(): TemplateResult {
const h = (this.liveValue ?? this.value)[0];
const s = (this.liveValue ?? this.value)[1];
const rgb = hsv2rgb([h, s, this.brightness]);
return html`
<ha-card>
<div class="card-content">
<p class="value">${h}° - ${Math.round(s * 100)}%</p>
<p class="value">${rgb.map((v) => Math.round(v)).join(", ")}</p>
<ha-hs-color-picker
colorBrightness=${this.brightness}
.value=${this.value}
@value-changed=${this._hsColorChanged}
@cursor-moved=${this._hsColorCursor}
></ha-hs-color-picker>
<p>Hue : ${this.value[0]}</p>
<ha-slider
step="1"
pin
min="0"
max="360"
.value=${this.value[0]}
@change=${this._hueChanged}
>
</ha-slider>
<p>Saturation : ${this.value[1]}</p>
<ha-slider
step="0.01"
pin
min="0"
max="1"
.value=${this.value[1]}
@change=${this._saturationChanged}
>
</ha-slider>
<p>Color Brighness : ${this.brightness}</p>
<ha-slider
step="1"
pin
min="0"
max="255"
.value=${this.brightness}
@change=${this._brightnessChanged}
>
</ha-slider>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.card-content {
display: flex;
align-items: center;
flex-direction: column;
}
ha-hs-color-picker {
width: 400px;
}
.value {
font-size: 22px;
font-weight: bold;
margin: 0 0 12px 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-hs-color-picker": DemoHaHsColorPicker;
}
}

View File

@@ -1,3 +0,0 @@
---
title: Temp Color Picker
---

View File

@@ -1,117 +0,0 @@
import "../../../../src/components/ha-temp-color-picker";
import { css, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-slider";
@customElement("demo-components-ha-temp-color-picker")
export class DemoHaTempColorPicker extends LitElement {
@state()
min = 3000;
@state()
max = 7000;
@state()
value = 4000;
@state()
liveValue?: number;
private _minChanged(ev) {
this.min = Number(ev.target.value);
}
private _maxChanged(ev) {
this.max = Number(ev.target.value);
}
private _valueChanged(ev) {
this.value = Number(ev.target.value);
}
private _tempColorCursor(ev) {
this.liveValue = ev.detail.value;
}
private _tempColorChanged(ev) {
this.value = ev.detail.value;
}
protected render(): TemplateResult {
return html`
<ha-card>
<div class="card-content">
<p class="value">${this.liveValue ?? this.value} K</p>
<ha-temp-color-picker
.min=${this.min}
.max=${this.max}
.value=${this.value}
@value-changed=${this._tempColorChanged}
@cursor-moved=${this._tempColorCursor}
></ha-temp-color-picker>
<p>Min temp : ${this.min} K</p>
<ha-slider
step="1"
pin
min="2000"
max="10000"
.value=${this.min}
@change=${this._minChanged}
>
</ha-slider>
<p>Max temp : ${this.max} K</p>
<ha-slider
step="1"
pin
min="2000"
max="10000"
.value=${this.max}
@change=${this._maxChanged}
>
</ha-slider>
<p>Value : ${this.value} K</p>
<ha-slider
step="1"
pin
min=${this.min}
max=${this.max}
.value=${this.value}
@change=${this._valueChanged}
>
</ha-slider>
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.card-content {
display: flex;
align-items: center;
flex-direction: column;
}
ha-temp-color-picker {
width: 400px;
}
.value {
font-size: 22px;
font-weight: bold;
margin: 0 0 12px 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-components-ha-temp-color-picker": DemoHaTempColorPicker;
}
}

View File

@@ -1,7 +0,0 @@
---
title: (Numeric) Date Formatting
---
This pages lists all supported languages with their available (numeric) date formats.
Formatting function: `const formatDateNumeric: (dateObj: Date, locale: FrontendLocaleData) => string`

View File

@@ -1,106 +0,0 @@
import { html, css, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../../src/components/ha-card";
import { HomeAssistant } from "../../../../src/types";
import { translationMetadata } from "../../../../src/resources/translations-metadata";
import { formatDateNumeric } from "../../../../src/common/datetime/format_date";
import {
FrontendLocaleData,
NumberFormat,
TimeFormat,
DateFormat,
FirstWeekday,
} from "../../../../src/data/translation";
@customElement("demo-date-time-date")
export class DemoDateTimeDate extends LitElement {
@property({ attribute: false }) hass!: HomeAssistant;
protected render() {
const defaultLocale: FrontendLocaleData = {
language: "en",
number_format: NumberFormat.language,
time_format: TimeFormat.language,
date_format: DateFormat.language,
first_weekday: FirstWeekday.language,
};
const date = new Date();
return html`
<mwc-list>
<div class="container header">
<div>Language</div>
<div class="center">Default (lang)</div>
<div class="center">Day-Month-Year</div>
<div class="center">Month-Day-Year</div>
<div class="center">Year-Month-Day</div>
</div>
${Object.entries(translationMetadata.translations)
.filter(([key, _]) => key !== "test")
.map(
([key, value]) => html`
<div class="container">
<div>${value.nativeName}</div>
<div class="center">
${formatDateNumeric(date, {
...defaultLocale,
language: key,
date_format: DateFormat.language,
})}
</div>
<div class="center">
${formatDateNumeric(date, {
...defaultLocale,
language: key,
date_format: DateFormat.DMY,
})}
</div>
<div class="center">
${formatDateNumeric(date, {
...defaultLocale,
language: key,
date_format: DateFormat.MDY,
})}
</div>
<div class="center">
${formatDateNumeric(date, {
...defaultLocale,
language: key,
date_format: DateFormat.YMD,
})}
</div>
</div>
`
)}
</mwc-list>
`;
}
static get styles() {
return css`
.header {
font-weight: bold;
}
.center {
text-align: center;
}
.container {
max-width: 600px;
margin: 12px auto;
display: flex;
align-items: center;
justify-content: space-evenly;
}
.container > div {
flex-grow: 1;
width: 20%;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-date-time-date": DemoDateTimeDate;
}
}

View File

@@ -50,7 +50,6 @@ const SENSOR_DEVICE_CLASSES = [
"temperature", "temperature",
"timestamp", "timestamp",
"volatile_organic_compounds", "volatile_organic_compounds",
"volatile_organic_compounds_parts",
"voltage", "voltage",
"volume", "volume",
"water", "water",

View File

@@ -130,7 +130,6 @@ export class HassioAddonRepositoryEl extends LitElement {
css` css`
ha-card { ha-card {
cursor: pointer; cursor: pointer;
overflow: hidden;
} }
.not_available { .not_available {
opacity: 0.6; opacity: 0.6;

View File

@@ -70,7 +70,7 @@ export class HassioAddonStore extends LitElement {
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
} finally { } finally {
this._loadData(); await this._loadData();
} }
} }
@@ -92,17 +92,21 @@ export class HassioAddonStore extends LitElement {
.route=${this.route} .route=${this.route}
.header=${this.supervisor.localize("panel.store")} .header=${this.supervisor.localize("panel.store")}
> >
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}> <ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-icon-button <ha-icon-button
.label=${this.supervisor.localize("common.menu")} .label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}
slot="trigger" slot="trigger"
></ha-icon-button> ></ha-icon-button>
<mwc-list-item> <mwc-list-item>
${this.supervisor.localize("store.check_updates")} ${this.supervisor.localize("store.repositories")}
</mwc-list-item> </mwc-list-item>
<mwc-list-item> <mwc-list-item>
${this.supervisor.localize("store.repositories")} ${this.supervisor.localize("store.check_updates")}
</mwc-list-item> </mwc-list-item>
${this.hass.userData?.showAdvanced && ${this.hass.userData?.showAdvanced &&
atLeastVersion(this.hass.config.version, 0, 117) atLeastVersion(this.hass.config.version, 0, 117)
@@ -177,10 +181,10 @@ export class HassioAddonStore extends LitElement {
private _handleAction(ev: CustomEvent<ActionDetail>) { private _handleAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) { switch (ev.detail.index) {
case 0: case 0:
this.refreshData(); this._manageRepositoriesClicked();
break; break;
case 1: case 1:
this._manageRepositoriesClicked(); this.refreshData();
break; break;
case 2: case 2:
this._manageRegistries(); this._manageRegistries();
@@ -198,25 +202,25 @@ export class HassioAddonStore extends LitElement {
this._manageRepositories(); this._manageRepositories();
} }
private _manageRepositories(url?: string) { private async _manageRepositories(url?: string) {
showRepositoriesDialog(this, { showRepositoriesDialog(this, {
supervisor: this.supervisor, supervisor: this.supervisor,
url, url,
}); });
} }
private _manageRegistries() { private async _manageRegistries() {
showRegistriesDialog(this, { supervisor: this.supervisor }); showRegistriesDialog(this, { supervisor: this.supervisor });
} }
private _loadData() { private async _loadData() {
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor", collection: "supervisor",
}); });
} }
private _filterChanged(e) { private async _filterChanged(e) {
this._filter = e.detail.value; this._filter = e.detail.value;
} }

View File

@@ -140,12 +140,7 @@ class HassioAddonConfig extends LitElement {
? { ? {
name: entry.name, name: entry.name,
required: entry.required, required: entry.required,
selector: { selector: { number: { mode: "box" } },
number: {
mode: "box",
step: entry.type === "float" ? "any" : undefined,
},
},
} }
: entry : entry
) )
@@ -173,7 +168,7 @@ class HassioAddonConfig extends LitElement {
${this.supervisor.localize("addon.configuration.options.header")} ${this.supervisor.localize("addon.configuration.options.header")}
</h2> </h2>
<div class="card-menu"> <div class="card-menu">
<ha-button-menu @action=${this._handleAction}> <ha-button-menu corner="BOTTOM_START" @action=${this._handleAction}>
<ha-icon-button <ha-icon-button
.label=${this.supervisor.localize("common.menu")} .label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}

View File

@@ -29,6 +29,7 @@ import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../../src/common/config/version"; import { atLeastVersion } from "../../../../src/common/config/version";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate"; import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
@@ -46,7 +47,6 @@ import {
HassioAddonSetOptionParams, HassioAddonSetOptionParams,
HassioAddonSetSecurityParams, HassioAddonSetSecurityParams,
installHassioAddon, installHassioAddon,
rebuildLocalAddon,
restartHassioAddon, restartHassioAddon,
setHassioAddonOption, setHassioAddonOption,
setHassioAddonSecurity, setHassioAddonSecurity,
@@ -640,12 +640,13 @@ class HassioAddonInfo extends LitElement {
</ha-progress-button> </ha-progress-button>
${this.addon.build ${this.addon.build
? html` ? html`
<ha-progress-button <ha-call-api-button
class="warning" class="warning"
@click=${this._rebuildClicked} .hass=${this.hass}
.path="hassio/addons/${this.addon.slug}/rebuild"
> >
${this.supervisor.localize("addon.dashboard.rebuild")} ${this.supervisor.localize("addon.dashboard.rebuild")}
</ha-progress-button> </ha-call-api-button>
` `
: ""}` : ""}`
: ""} : ""}
@@ -965,21 +966,6 @@ class HassioAddonInfo extends LitElement {
button.progress = false; button.progress = false;
} }
private async _rebuildClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
button.progress = true;
try {
await rebuildLocalAddon(this.hass, this.addon.slug);
} catch (err: any) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.rebuild"),
text: extractApiErrorMessage(err),
});
}
button.progress = false;
}
private async _startClicked(ev: CustomEvent): Promise<void> { private async _startClicked(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any; const button = ev.currentTarget as any;
button.progress = true; button.progress = true;
@@ -1138,6 +1124,10 @@ class HassioAddonInfo extends LitElement {
ha-svg-icon.stopped { ha-svg-icon.stopped {
color: var(--error-color); color: var(--error-color);
} }
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
protection-enable mwc-button { protection-enable mwc-button {
--mdc-theme-primary: white; --mdc-theme-primary: white;
} }

View File

@@ -3,11 +3,11 @@ import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js"; import { mdiBackupRestore, mdiDelete, mdiDotsVertical, mdiPlus } from "@mdi/js";
import { import {
css,
CSSResultGroup, CSSResultGroup,
html,
LitElement, LitElement,
PropertyValues, PropertyValues,
css,
html,
nothing, nothing,
} from "lit"; } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
@@ -26,9 +26,9 @@ import "../../../src/components/ha-fab";
import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-icon-button";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { import {
HassioBackup,
fetchHassioBackups, fetchHassioBackups,
friendlyFolderName, friendlyFolderName,
HassioBackup,
reloadHassioBackups, reloadHassioBackups,
removeBackup, removeBackup,
} from "../../../src/data/hassio/backup"; } from "../../../src/data/hassio/backup";
@@ -43,7 +43,6 @@ import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subp
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload"; import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { showHassioBackupLocationDialog } from "../dialogs/backup/show-dialog-hassio-backu-location";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup"; import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup"; import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
@@ -196,7 +195,11 @@ export class HassioBackups extends LitElement {
: "/config"} : "/config"}
supervisor supervisor
> >
<ha-button-menu slot="toolbar-icon" @action=${this._handleAction}> <ha-button-menu
corner="BOTTOM_START"
slot="toolbar-icon"
@action=${this._handleAction}
>
<ha-icon-button <ha-icon-button
.label=${this.supervisor?.localize("common.menu")} .label=${this.supervisor?.localize("common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}
@@ -205,9 +208,6 @@ export class HassioBackups extends LitElement {
<mwc-list-item> <mwc-list-item>
${this.supervisor.localize("common.reload")} ${this.supervisor.localize("common.reload")}
</mwc-list-item> </mwc-list-item>
<mwc-list-item>
${this.supervisor.localize("dialog.backup_location.title")}
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116) ${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item> ? html`<mwc-list-item>
${this.supervisor.localize("backup.upload_backup")} ${this.supervisor.localize("backup.upload_backup")}
@@ -274,9 +274,6 @@ export class HassioBackups extends LitElement {
this.refreshData(); this.refreshData();
break; break;
case 1: case 1:
showHassioBackupLocationDialog(this, { supervisor: this.supervisor });
break;
case 2:
this._showUploadBackupDialog(); this._showUploadBackupDialog();
break; break;
} }

View File

@@ -1,13 +1,12 @@
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js"; import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
import "@polymer/paper-input/paper-input"; import { PaperInputElement } from "@polymer/paper-input/paper-input";
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
import { import {
css, css,
CSSResultGroup, CSSResultGroup,
html, html,
LitElement, LitElement,
nothing,
TemplateResult, TemplateResult,
nothing,
} from "lit"; } from "lit";
import { customElement, property, query } from "lit/decorators"; import { customElement, property, query } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
@@ -24,11 +23,8 @@ import {
HassioPartialBackupCreateParams, HassioPartialBackupCreateParams,
} from "../../../src/data/hassio/backup"; } from "../../../src/data/hassio/backup";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { import { PolymerChangedEvent } from "../../../src/polymer-types";
HomeAssistant, import { HomeAssistant, TranslationDict } from "../../../src/types";
TranslationDict,
ValueChangedEvent,
} from "../../../src/types";
import "./supervisor-formfield-label"; import "./supervisor-formfield-label";
type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] & type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] &
@@ -420,7 +416,7 @@ export class SupervisorBackupContent extends LitElement {
this[input.name] = input.value; this[input.name] = input.value;
} }
private _handleTextValueChanged(ev: ValueChangedEvent<string>) { private _handleTextValueChanged(ev: PolymerChangedEvent<string>) {
const input = ev.currentTarget as PaperInputElement; const input = ev.currentTarget as PaperInputElement;
this[input.name!] = ev.detail.value; this[input.name!] = ev.detail.value;
} }

View File

@@ -1,13 +1,10 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js"; import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/search-input";
import { HassioAddonInfo } from "../../../src/data/hassio/addon";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -20,25 +17,11 @@ class HassioAddons extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean;
@state() private _filter?: string;
protected render(): TemplateResult { protected render(): TemplateResult {
return html` return html`
<div class="search">
<search-input
.hass=${this.hass}
suffix
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this.supervisor.localize("dashboard.search_addons")}
>
</search-input>
</div>
<div class="content"> <div class="content">
${!atLeastVersion(this.hass.config.version, 2021, 12) ${!atLeastVersion(this.hass.config.version, 2021, 12)
? html`<h1>${this.supervisor.localize("dashboard.addons")}</h1>` ? html` <h1>${this.supervisor.localize("dashboard.addons")}</h1> `
: ""} : ""}
<div class="card-group"> <div class="card-group">
${!this.supervisor.addon.addons.length ${!this.supervisor.addon.addons.length
@@ -51,9 +34,21 @@ class HassioAddons extends LitElement {
</div> </div>
</ha-card> </ha-card>
` `
: this._getAddons(this.supervisor.addon.addons, this._filter).map( : this.supervisor.addon.addons
.sort((a, b) =>
caseInsensitiveStringCompare(
a.name,
b.name,
this.hass.locale.language
)
)
.map(
(addon) => html` (addon) => html`
<ha-card outlined .addon=${addon} @click=${this._addonTapped}> <ha-card
outlined
.addon=${addon}
@click=${this._addonTapped}
>
<div class="card-content"> <div class="card-content">
<hassio-card-content <hassio-card-content
.hass=${this.hass} .hass=${this.hass}
@@ -66,12 +61,16 @@ class HassioAddons extends LitElement {
? mdiArrowUpBoldCircle ? mdiArrowUpBoldCircle
: mdiPuzzle} : mdiPuzzle}
.iconTitle=${addon.state !== "started" .iconTitle=${addon.state !== "started"
? this.supervisor.localize("dashboard.addon_stopped") ? this.supervisor.localize(
"dashboard.addon_stopped"
)
: addon.update_available! : addon.update_available!
? this.supervisor.localize( ? this.supervisor.localize(
"dashboard.addon_new_version" "dashboard.addon_new_version"
) )
: this.supervisor.localize("dashboard.addon_running")} : this.supervisor.localize(
"dashboard.addon_running"
)}
.iconClass=${addon.update_available .iconClass=${addon.update_available
? addon.state === "started" ? addon.state === "started"
? "update" ? "update"
@@ -96,28 +95,6 @@ class HassioAddons extends LitElement {
`; `;
} }
private _getAddons = memoizeOne(
(addons: HassioAddonInfo[], filter?: string) => {
if (filter) {
addons = addons.filter((addon) => {
const lowerCaseFilter = filter.toLowerCase();
return (
addon.name.toLowerCase().includes(lowerCaseFilter) ||
addon.description.toLowerCase().includes(lowerCaseFilter) ||
addon.slug.toLowerCase().includes(lowerCaseFilter)
);
});
}
return addons.sort((a, b) =>
caseInsensitiveStringCompare(a.name, b.name, this.hass.locale.language)
);
}
);
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,
@@ -125,17 +102,6 @@ class HassioAddons extends LitElement {
css` css`
ha-card { ha-card {
cursor: pointer; cursor: pointer;
overflow: hidden;
}
.search {
position: sticky;
top: 0;
z-index: 2;
}
search-input {
display: block;
--mdc-text-field-fill-color: var(--sidebar-background-color);
--mdc-text-field-idle-line-color: var(--divider-color);
} }
`, `,
]; ];

View File

@@ -1,4 +1,4 @@
import { mdiStorePlus, mdiUpdate } from "@mdi/js"; import { mdiStorePlus } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
@@ -9,11 +9,8 @@ import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs"; import { supervisorTabs } from "../hassio-tabs";
import "./hassio-addons"; import "./hassio-addons";
import "./hassio-update";
import "../../../src/layouts/hass-subpage"; import "../../../src/layouts/hass-subpage";
import { reloadHassioAddons } from "../../../src/data/hassio/addon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { fireEvent } from "../../../src/common/dom/fire_event";
@customElement("hassio-dashboard") @customElement("hassio-dashboard")
class HassioDashboard extends LitElement { class HassioDashboard extends LitElement {
@@ -25,12 +22,6 @@ class HassioDashboard extends LitElement {
@property({ attribute: false }) public route!: Route; @property({ attribute: false }) public route!: Route;
firstUpdated() {
if (!atLeastVersion(this.hass.config.version, 2022, 5)) {
import("./hassio-update");
}
}
protected render(): TemplateResult { protected render(): TemplateResult {
if (atLeastVersion(this.hass.config.version, 2022, 5)) { if (atLeastVersion(this.hass.config.version, 2022, 5)) {
return html`<hass-subpage return html`<hass-subpage
@@ -40,16 +31,9 @@ class HassioDashboard extends LitElement {
back-path="/config" back-path="/config"
.header=${this.supervisor.localize("panel.addons")} .header=${this.supervisor.localize("panel.addons")}
> >
<ha-icon-button
slot="toolbar-icon"
@click=${this._handleCheckUpdates}
.path=${mdiUpdate}
.label=${this.supervisor.localize("store.check_updates")}
></ha-icon-button>
<hassio-addons <hassio-addons
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this.supervisor} .supervisor=${this.supervisor}
.narrow=${this.narrow}
></hassio-addons> ></hassio-addons>
<a href="/hassio/store"> <a href="/hassio/store">
<ha-fab <ha-fab
@@ -60,7 +44,7 @@ class HassioDashboard extends LitElement {
<ha-svg-icon <ha-svg-icon
slot="icon" slot="icon"
.path=${mdiStorePlus} .path=${mdiStorePlus}
></ha-svg-icon></ha-fab ></ha-svg-icon> </ha-fab
></a> ></a>
</hass-subpage>`; </hass-subpage>`;
} }
@@ -110,18 +94,6 @@ class HassioDashboard extends LitElement {
`; `;
} }
private async _handleCheckUpdates() {
try {
await reloadHassioAddons(this.hass);
} catch (err) {
showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
} finally {
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
}
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return [ return [
haStyle, haStyle,

View File

@@ -1,154 +0,0 @@
import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-form/ha-form";
import type { SchemaUnion } from "../../../../src/components/ha-form/types";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { changeMountOptions } from "../../../../src/data/supervisor/mounts";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioBackupLocationDialogParams } from "./show-dialog-hassio-backu-location";
const SCHEMA = memoizeOne(
() =>
[
{
name: "default_backup_mount",
required: true,
selector: { backup_location: {} },
},
] as const
);
@customElement("dialog-hassio-backup-location")
class HassioBackupLocationDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false })
public _dialogParams?: HassioBackupLocationDialogParams;
@state() private _data?: { default_backup_mount: string | null };
@state() private _waiting?: boolean;
@state() private _error?: string;
public async showDialog(
dialogParams: HassioBackupLocationDialogParams
): Promise<void> {
this._dialogParams = dialogParams;
}
public closeDialog(): void {
this._data = undefined;
this._error = undefined;
this._waiting = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render() {
if (!this._dialogParams) {
return nothing;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
.heading=${this._dialogParams.supervisor.localize(
"dialog.backup_location.title"
)}
@closed=${this.closeDialog}
>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: nothing}
<ha-form
.hass=${this.hass}
.data=${this._data}
.schema=${SCHEMA()}
.computeLabel=${this._computeLabelCallback}
.computeHelper=${this._computeHelperCallback}
@value-changed=${this._valueChanged}
dialogInitialFocus
></ha-form>
<mwc-button
slot="secondaryAction"
@click=${this.closeDialog}
dialogInitialFocus
>
${this._dialogParams.supervisor.localize("common.cancel")}
</mwc-button>
<mwc-button
.disabled=${this._waiting || !this._data}
slot="primaryAction"
@click=${this._changeMount}
>
${this._dialogParams.supervisor.localize("common.save")}
</mwc-button>
</ha-dialog>
`;
}
private _computeLabelCallback = (
// @ts-ignore
schema: SchemaUnion<ReturnType<typeof SCHEMA>>
): string =>
this._dialogParams!.supervisor.localize(
`dialog.backup_location.options.${schema.name}.name`
) || schema.name;
private _computeHelperCallback = (
// @ts-ignore
schema: SchemaUnion<ReturnType<typeof SCHEMA>>
): string =>
this._dialogParams!.supervisor.localize(
`dialog.backup_location.options.${schema.name}.description`
);
private _valueChanged(ev: CustomEvent) {
const newLocation = ev.detail.value.default_backup_mount;
this._data = {
default_backup_mount: newLocation === "/backup" ? null : newLocation,
};
}
private async _changeMount() {
if (!this._data) {
return;
}
this._error = undefined;
this._waiting = true;
try {
await changeMountOptions(this.hass, this._data);
} catch (err: any) {
this._error = extractApiErrorMessage(err);
this._waiting = false;
return;
}
this.closeDialog();
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
.delete-btn {
--mdc-theme-primary: var(--error-color);
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-backup-location": HassioBackupLocationDialog;
}
}

View File

@@ -7,7 +7,6 @@ import { atLeastVersion } from "../../../../src/common/config/version";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation"; import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
import { slugify } from "../../../../src/common/string/slugify"; import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";

View File

@@ -1,17 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioBackupLocationDialogParams {
supervisor: Supervisor;
}
export const showHassioBackupLocationDialog = (
element: HTMLElement,
dialogParams: HassioBackupLocationDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup-location",
dialogImport: () => import("./dialog-hassio-backup-location"),
dialogParams,
});
};

View File

@@ -316,7 +316,7 @@ export class DialogHassioNetwork
> >
<div class="radio-row"> <div class="radio-row">
<ha-formfield <ha-formfield
.label=${this.supervisor.localize("dialog.network.auto")} .label=${this.supervisor.localize("dialog.network.dhcp")}
> >
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}

View File

@@ -218,7 +218,7 @@ class HassioRepositoriesDialog extends LitElement {
private _handleKeyAdd(ev: KeyboardEvent) { private _handleKeyAdd(ev: KeyboardEvent) {
ev.stopPropagation(); ev.stopPropagation();
if (ev.key !== "Enter") { if (ev.keyCode !== 13) {
return; return;
} }
this._addRepository(); this._addRepository();

View File

@@ -1,22 +0,0 @@
(function () {
function loadES5(src) {
var el = document.createElement("script");
el.src = src;
document.body.appendChild(el);
}
if (/.*Version\/(?:11|12)(?:\.\d+)*.*Safari\//.test(navigator.userAgent)) {
<% for (const entry of es5EntryJS) { %>
loadES5("<%= entry %>");
<% } %>
} else {
try {
<% for (const entry of latestEntryJS) { %>
new Function("import('<%= entry %>')")();
<% } %>
} catch (err) {
<% for (const entry of es5EntryJS) { %>
loadES5("<%= entry %>");
<% } %>
}
}
})();

View File

@@ -2,7 +2,6 @@
import "../../src/resources/compatibility"; import "../../src/resources/compatibility";
import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings"; import { setCancelSyntheticClickEvents } from "@polymer/polymer/lib/utils/settings";
import "../../src/resources/roboto"; import "../../src/resources/roboto";
import "../../src/resources/ha-style";
import "../../src/resources/safari-14-attachshadow-patch"; import "../../src/resources/safari-14-attachshadow-patch";
import "./hassio-main"; import "./hassio-main";

View File

@@ -9,6 +9,7 @@ import { navigate } from "../../src/common/navigate";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager";
import "../../src/layouts/hass-loading-screen";
import { HomeAssistant } from "../../src/types"; import { HomeAssistant } from "../../src/types";
import "./hassio-router"; import "./hassio-router";
import { SupervisorBaseElement } from "./supervisor-base-element"; import { SupervisorBaseElement } from "./supervisor-base-element";

View File

@@ -5,8 +5,12 @@ import {
RouterOptions, RouterOptions,
} from "../../src/layouts/hass-router-page"; } from "../../src/layouts/hass-router-page";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast. // Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard"; import "./dashboard/hassio-dashboard";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./backups/hassio-backups";
import "./system/hassio-system";
@customElement("hassio-panel-router") @customElement("hassio-panel-router")
class HassioPanelRouter extends HassRouterPage { class HassioPanelRouter extends HassRouterPage {
@@ -27,15 +31,12 @@ class HassioPanelRouter extends HassRouterPage {
}, },
store: { store: {
tag: "hassio-addon-store", tag: "hassio-addon-store",
load: () => import("./addon-store/hassio-addon-store"),
}, },
backups: { backups: {
tag: "hassio-backups", tag: "hassio-backups",
load: () => import("./backups/hassio-backups"),
}, },
system: { system: {
tag: "hassio-system", tag: "hassio-system",
load: () => import("./system/hassio-system"),
}, },
}, },
}; };

View File

@@ -4,7 +4,6 @@ import {
Supervisor, Supervisor,
supervisorCollection, supervisorCollection,
} from "../../src/data/supervisor/supervisor"; } from "../../src/data/supervisor/supervisor";
import "../../src/layouts/hass-loading-screen";
import { HomeAssistant, Route } from "../../src/types"; import { HomeAssistant, Route } from "../../src/types";
import "./hassio-panel-router"; import "./hassio-panel-router";

View File

@@ -1,11 +1,11 @@
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
import { HassioPanelInfo } from "../../src/data/hassio/supervisor"; import { HassioPanelInfo } from "../../src/data/hassio/supervisor";
import { Supervisor } from "../../src/data/supervisor/supervisor"; import { Supervisor } from "../../src/data/supervisor/supervisor";
import { import {
HassRouterPage, HassRouterPage,
RouterOptions, RouterOptions,
} from "../../src/layouts/hass-router-page"; } from "../../src/layouts/hass-router-page";
import "../../src/resources/ha-style";
import { HomeAssistant } from "../../src/types"; import { HomeAssistant } from "../../src/types";
// Don't codesplit it, that way the dashboard always loads fast. // Don't codesplit it, that way the dashboard always loads fast.
import "./hassio-panel"; import "./hassio-panel";
@@ -23,15 +23,9 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = { protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it. // Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard", defaultPage: "dashboard",
beforeRender: (page: string) => { beforeRender: (page: string) =>
if (page === "snapshots") { page === "snapshots" ? "backups" : undefined,
return "backups"; initialLoad: () => this._redirectIngress(),
}
if (page === "dashboard" && this.panel.config?.ingress) {
return "ingress";
}
return undefined;
},
showLoading: true, showLoading: true,
routes: { routes: {
dashboard: { dashboard: {
@@ -62,28 +56,32 @@ class HassioRouter extends HassRouterPage {
protected updatePageEl(el) { protected updatePageEl(el) {
// the tabs page does its own routing so needs full route. // the tabs page does its own routing so needs full route.
const hassioPanel = el.localName === "hassio-panel"; const hassioPanel = el.nodeName === "HASSIO-PANEL";
const ingressPanel = el.localName === "hassio-ingress-view"; const route = hassioPanel ? this.route : this.routeTail;
const route = hassioPanel
? this.route if (hassioPanel && this.panel.config?.ingress) {
: ingressPanel && this.panel.config?.ingress this._redirectIngress();
? this._ingressRoute(this.panel.config?.ingress) return;
: this.routeTail; }
el.hass = this.hass; el.hass = this.hass;
el.narrow = this.narrow; el.narrow = this.narrow;
el.route = route; el.route = route;
el.supervisor = this.supervisor; el.supervisor = this.supervisor;
if (ingressPanel) { if (el.localName === "hassio-ingress-view") {
el.ingressPanel = Boolean(this.panel.config?.ingress); el.ingressPanel = this.panel.config && this.panel.config.ingress;
} }
} }
private _ingressRoute = memoizeOne((ingress: string) => ({ private async _redirectIngress() {
prefix: "/hassio/ingress", if (this.panel.config && this.panel.config.ingress) {
path: `/${ingress}`, this.route = {
})); prefix: "/hassio",
path: `/ingress/${this.panel.config.ingress}`,
};
}
}
} }
declare global { declare global {

View File

@@ -44,6 +44,10 @@ export const hassioStyle = css`
grid-template-columns: repeat(auto-fit, minmax(300px, 0.25fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 0.25fr));
} }
} }
ha-call-api-button {
font-weight: 500;
color: var(--primary-color);
}
.error { .error {
color: var(--error-color); color: var(--error-color);
margin-top: 16px; margin-top: 16px;

View File

@@ -53,54 +53,78 @@ export class SupervisorBaseElement extends urlSyncMixin(
public connectedCallback(): void { public connectedCallback(): void {
super.connectedCallback(); super.connectedCallback();
if (!this.hasUpdated) { this._initializeLocalize();
return;
}
if (this.route?.prefix === "/hassio") {
this._initSupervisor();
}
} }
public disconnectedCallback() { public disconnectedCallback() {
super.disconnectedCallback(); super.disconnectedCallback();
Object.keys(this._unsubs).forEach((unsub) => { Object.keys(this._unsubs).forEach((unsub) => {
this._unsubs[unsub](); this._unsubs[unsub]();
delete this._unsubs[unsub];
}); });
this.removeEventListener(
"supervisor-collection-refresh",
this._handleSupervisorStoreRefreshEvent
);
} }
protected willUpdate(changedProperties: PropertyValues) { protected updated(changedProperties: PropertyValues) {
if (!this.hasUpdated) { super.updated(changedProperties);
if (this.route?.prefix === "/hassio") {
this._initSupervisor();
}
}
if (changedProperties.has("hass")) { if (changedProperties.has("hass")) {
const oldHass = changedProperties.get("hass") as const oldHass = changedProperties.get("hass") as
| HomeAssistant | HomeAssistant
| undefined; | undefined;
if (oldHass?.language !== this.hass.language) { if (
oldHass !== undefined &&
oldHass.language !== undefined &&
oldHass.language !== this.hass.language
) {
this._language = this.hass.language; this._language = this.hass.language;
} }
} }
if (changedProperties.has("_language") || !this.hasUpdated) { if (changedProperties.has("_language")) {
if (changedProperties.get("_language") !== this._language) {
this._initializeLocalize(); this._initializeLocalize();
} }
} }
protected _updateSupervisor(update: Partial<Supervisor>): void { if (changedProperties.has("_collections")) {
this.supervisor = { ...this.supervisor, ...update }; if (this._collections) {
const unsubs = Object.keys(this._unsubs);
for (const collection of Object.keys(this._collections)) {
if (!unsubs.includes(collection)) {
this._unsubs[collection] = this._collections[collection].subscribe(
(data) => this._updateSupervisor({ [collection]: data })
);
}
}
}
}
}
protected _updateSupervisor(obj: Partial<Supervisor>): void {
this.supervisor = { ...this.supervisor, ...obj };
}
protected firstUpdated(changedProps: PropertyValues): void {
super.firstUpdated(changedProps);
if (
this._language !== this.hass.language &&
this.hass.language !== undefined
) {
this._language = this.hass.language;
}
this._initializeLocalize();
if (this.route?.prefix === "/hassio") {
this._initSupervisor();
}
} }
private async _initializeLocalize() { private async _initializeLocalize() {
const { language, data } = await getTranslation(null, this._language); const { language, data } = await getTranslation(
null,
this._language,
"/api/hassio/app/static/translations"
);
this._updateSupervisor({ this.supervisor = {
...this.supervisor,
localize: await computeLocalize<SupervisorKeys>( localize: await computeLocalize<SupervisorKeys>(
this.constructor.prototype, this.constructor.prototype,
language, language,
@@ -108,7 +132,7 @@ export class SupervisorBaseElement extends urlSyncMixin(
[language]: data, [language]: data,
} }
), ),
}); };
} }
private async _handleSupervisorStoreRefreshEvent(ev) { private async _handleSupervisorStoreRefreshEvent(ev) {
@@ -125,17 +149,6 @@ export class SupervisorBaseElement extends urlSyncMixin(
this._updateSupervisor({ [collection]: response.data }); this._updateSupervisor({ [collection]: response.data });
} }
private _subscribeCollection(collection: string) {
if (this._unsubs[collection]) {
this._unsubs[collection]();
}
this._unsubs[collection] = this._collections[collection].subscribe((data) =>
this._updateSupervisor({
[collection]: data,
})
);
}
private async _initSupervisor(): Promise<void> { private async _initSupervisor(): Promise<void> {
this.addEventListener( this.addEventListener(
"supervisor-collection-refresh", "supervisor-collection-refresh",
@@ -145,7 +158,6 @@ export class SupervisorBaseElement extends urlSyncMixin(
if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) { if (atLeastVersion(this.hass.config.version, 2021, 2, 4)) {
Object.keys(supervisorCollection).forEach((collection) => { Object.keys(supervisorCollection).forEach((collection) => {
if (collection in this._collections) { if (collection in this._collections) {
this._subscribeCollection(collection);
this._collections[collection].refresh(); this._collections[collection].refresh();
} else { } else {
this._collections[collection] = getSupervisorEventCollection( this._collections[collection] = getSupervisorEventCollection(
@@ -153,14 +165,18 @@ export class SupervisorBaseElement extends urlSyncMixin(
collection, collection,
supervisorCollection[collection] supervisorCollection[collection]
); );
if (this._collections[collection].state) { }
// happens when the grace period of the collection unsubscribe has not passed yet });
Object.keys(this._collections).forEach((collection) => {
if (
this.supervisor === undefined ||
this.supervisor[collection] === undefined
) {
this._updateSupervisor({ this._updateSupervisor({
[collection]: this._collections[collection].state, [collection]: this._collections[collection].state,
}); });
} }
this._subscribeCollection(collection);
}
}); });
} else { } else {
const [ const [

View File

@@ -184,7 +184,7 @@ class HassioHostInfo extends LitElement {
` `
: ""} : ""}
<ha-button-menu> <ha-button-menu corner="BOTTOM_START">
<ha-icon-button <ha-icon-button
.label=${this.supervisor.localize("common.menu")} .label=${this.supervisor.localize("common.menu")}
.path=${mdiDotsVertical} .path=${mdiDotsVertical}

View File

@@ -42,6 +42,9 @@ import { updateCore } from "../../../src/data/supervisor/core";
import { StoreAddon } from "../../../src/data/supervisor/store"; import { StoreAddon } from "../../../src/data/supervisor/store";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import "../../../src/layouts/hass-loading-screen";
import "../../../src/layouts/hass-subpage";
import "../../../src/layouts/hass-tabs-subpage";
import { HomeAssistant, Route } from "../../../src/types"; import { HomeAssistant, Route } from "../../../src/types";
import { addonArchIsSupported, extractChangelog } from "../util/addon"; import { addonArchIsSupported, extractChangelog } from "../util/addon";

View File

@@ -25,33 +25,31 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@babel/runtime": "7.22.3",
"@braintree/sanitize-url": "6.0.2", "@braintree/sanitize-url": "6.0.2",
"@codemirror/autocomplete": "6.7.1", "@codemirror/autocomplete": "6.4.2",
"@codemirror/commands": "6.2.4", "@codemirror/commands": "6.2.2",
"@codemirror/language": "6.7.0", "@codemirror/language": "6.6.0",
"@codemirror/legacy-modes": "6.3.2", "@codemirror/legacy-modes": "6.3.2",
"@codemirror/search": "6.4.0", "@codemirror/search": "6.3.0",
"@codemirror/state": "6.2.1", "@codemirror/state": "6.2.0",
"@codemirror/view": "6.12.0", "@codemirror/view": "6.9.3",
"@egjs/hammerjs": "2.0.17", "@egjs/hammerjs": "2.0.17",
"@formatjs/intl-datetimeformat": "6.8.0", "@formatjs/intl-datetimeformat": "6.5.1",
"@formatjs/intl-displaynames": "6.3.2", "@formatjs/intl-getcanonicallocales": "2.1.0",
"@formatjs/intl-getcanonicallocales": "2.2.0", "@formatjs/intl-locale": "3.1.1",
"@formatjs/intl-locale": "3.3.0", "@formatjs/intl-numberformat": "8.3.5",
"@formatjs/intl-numberformat": "8.5.0", "@formatjs/intl-pluralrules": "5.1.10",
"@formatjs/intl-pluralrules": "5.2.2", "@formatjs/intl-relativetimeformat": "11.1.10",
"@formatjs/intl-relativetimeformat": "11.2.2", "@fullcalendar/core": "6.1.5",
"@fullcalendar/core": "6.1.8", "@fullcalendar/daygrid": "6.1.5",
"@fullcalendar/daygrid": "6.1.8", "@fullcalendar/interaction": "6.1.5",
"@fullcalendar/interaction": "6.1.8", "@fullcalendar/list": "6.1.5",
"@fullcalendar/list": "6.1.8", "@fullcalendar/timegrid": "6.1.5",
"@fullcalendar/timegrid": "6.1.8", "@lezer/highlight": "1.1.4",
"@lezer/highlight": "1.1.6", "@lit-labs/context": "0.3.0",
"@lit-labs/context": "0.3.1",
"@lit-labs/motion": "1.0.3", "@lit-labs/motion": "1.0.3",
"@lit-labs/virtualizer": "2.0.2", "@lit-labs/virtualizer": "1.0.1",
"@lrnwebcomponents/simple-tooltip": "7.0.0", "@lrnwebcomponents/simple-tooltip": "4.1.0",
"@material/chips": "=14.0.0-canary.53b3cad2f.0", "@material/chips": "=14.0.0-canary.53b3cad2f.0",
"@material/data-table": "=14.0.0-canary.53b3cad2f.0", "@material/data-table": "=14.0.0-canary.53b3cad2f.0",
"@material/mwc-button": "0.27.0", "@material/mwc-button": "0.27.0",
@@ -77,7 +75,7 @@
"@material/mwc-top-app-bar": "0.27.0", "@material/mwc-top-app-bar": "0.27.0",
"@material/mwc-top-app-bar-fixed": "0.27.0", "@material/mwc-top-app-bar-fixed": "0.27.0",
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0", "@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
"@material/web": "=1.0.0-pre.9", "@material/web": "=1.0.0-pre.4",
"@mdi/js": "7.2.96", "@mdi/js": "7.2.96",
"@mdi/svg": "7.2.96", "@mdi/svg": "7.2.96",
"@polymer/app-layout": "3.1.0", "@polymer/app-layout": "3.1.0",
@@ -92,40 +90,41 @@
"@polymer/paper-toast": "3.0.1", "@polymer/paper-toast": "3.0.1",
"@polymer/polymer": "3.5.1", "@polymer/polymer": "3.5.1",
"@thomasloven/round-slider": "0.6.0", "@thomasloven/round-slider": "0.6.0",
"@vaadin/combo-box": "24.0.7", "@vaadin/combo-box": "23.3.9",
"@vaadin/vaadin-themable-mixin": "24.0.7", "@vaadin/vaadin-themable-mixin": "23.3.9",
"@vibrant/color": "3.2.1-alpha.1", "@vibrant/color": "3.2.1-alpha.1",
"@vibrant/core": "3.2.1-alpha.1", "@vibrant/core": "3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1", "@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
"@vue/web-component-wrapper": "1.3.0", "@vue/web-component-wrapper": "1.3.0",
"@webcomponents/scoped-custom-element-registry": "0.0.9", "@webcomponents/scoped-custom-element-registry": "0.0.8",
"@webcomponents/webcomponentsjs": "2.8.0", "@webcomponents/webcomponentsjs": "2.7.0",
"app-datepicker": "5.1.1", "app-datepicker": "5.1.1",
"chart.js": "3.3.2", "chart.js": "3.3.2",
"comlink": "4.4.1", "comlink": "4.4.1",
"core-js": "3.30.2", "core-js": "3.29.1",
"cropperjs": "1.5.13", "cropperjs": "1.5.13",
"date-fns": "2.30.0", "date-fns": "2.29.3",
"date-fns-tz": "2.0.0", "date-fns-tz": "2.0.0",
"deep-clone-simple": "1.1.1", "deep-clone-simple": "1.1.1",
"deep-freeze": "0.0.1", "deep-freeze": "0.0.1",
"fuse.js": "6.6.2", "fuse.js": "6.6.2",
"google-timezones-json": "1.1.0", "google-timezones-json": "1.0.2",
"hls.js": "1.4.4", "hls.js": "1.3.5",
"home-assistant-js-websocket": "8.0.1", "home-assistant-js-websocket": "8.0.1",
"idb-keyval": "6.2.1", "idb-keyval": "6.2.0",
"intl-messageformat": "10.3.5", "intl-messageformat": "10.3.3",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"leaflet": "1.9.4", "leaflet": "1.9.3",
"leaflet-draw": "1.0.4", "leaflet-draw": "1.0.4",
"lit": "2.7.4", "lit": "2.7.0",
"marked": "4.3.0", "marked": "4.3.0",
"memoize-one": "6.0.0", "memoize-one": "6.0.0",
"node-vibrant": "3.2.1-alpha.1", "node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "0.3.2", "proxy-polyfill": "0.3.2",
"punycode": "2.3.0", "punycode": "2.3.0",
"qr-scanner": "1.4.2", "qr-scanner": "1.4.2",
"qrcode": "1.5.3", "qrcode": "1.5.1",
"regenerator-runtime": "0.13.11",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"roboto-fontface": "0.10.0", "roboto-fontface": "0.10.0",
"rrule": "2.7.2", "rrule": "2.7.2",
@@ -140,32 +139,37 @@
"vue": "2.7.14", "vue": "2.7.14",
"vue2-daterange-picker": "0.6.8", "vue2-daterange-picker": "0.6.8",
"weekstart": "2.0.0", "weekstart": "2.0.0",
"workbox-cacheable-response": "6.6.0", "workbox-cacheable-response": "6.5.4",
"workbox-core": "6.6.0", "workbox-core": "6.5.4",
"workbox-expiration": "6.6.0", "workbox-expiration": "6.5.4",
"workbox-precaching": "6.6.0", "workbox-precaching": "6.5.4",
"workbox-routing": "6.6.0", "workbox-routing": "6.5.4",
"workbox-strategies": "6.6.0", "workbox-strategies": "6.5.4",
"xss": "1.0.14" "xss": "1.0.14"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.22.1", "@babel/core": "7.21.3",
"@babel/plugin-proposal-decorators": "7.22.3", "@babel/plugin-external-helpers": "7.18.6",
"@babel/plugin-transform-runtime": "7.22.4", "@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/preset-env": "7.22.4", "@babel/plugin-proposal-decorators": "7.21.0",
"@babel/preset-typescript": "7.21.5", "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-proposal-optional-chaining": "7.21.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-syntax-import-meta": "7.10.4",
"@babel/plugin-syntax-top-level-await": "7.14.5",
"@babel/preset-env": "7.20.2",
"@babel/preset-typescript": "7.21.0",
"@koa/cors": "4.0.0", "@koa/cors": "4.0.0",
"@octokit/auth-oauth-device": "4.0.4", "@octokit/auth-oauth-device": "4.0.4",
"@octokit/plugin-retry": "4.1.3", "@octokit/rest": "19.0.7",
"@octokit/rest": "19.0.11",
"@open-wc/dev-server-hmr": "0.1.4", "@open-wc/dev-server-hmr": "0.1.4",
"@rollup/plugin-babel": "6.0.3", "@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.0", "@rollup/plugin-commonjs": "24.0.1",
"@rollup/plugin-json": "6.0.0", "@rollup/plugin-json": "6.0.0",
"@rollup/plugin-node-resolve": "15.0.2", "@rollup/plugin-node-resolve": "15.0.1",
"@rollup/plugin-replace": "5.0.2", "@rollup/plugin-replace": "5.0.2",
"@types/babel__plugin-transform-runtime": "7.9.2", "@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-receiver": "6.0.9",
"@types/chromecast-caf-sender": "1.0.5", "@types/chromecast-caf-sender": "1.0.5",
"@types/esprima": "4.0.3", "@types/esprima": "4.0.3",
"@types/glob": "8.1.0", "@types/glob": "8.1.0",
@@ -173,47 +177,47 @@
"@types/js-yaml": "4.0.5", "@types/js-yaml": "4.0.5",
"@types/leaflet": "1.9.3", "@types/leaflet": "1.9.3",
"@types/leaflet-draw": "1.0.6", "@types/leaflet-draw": "1.0.6",
"@types/marked": "4.3.1", "@types/marked": "4.0.8",
"@types/mocha": "10.0.1", "@types/mocha": "10.0.1",
"@types/qrcode": "1.5.0", "@types/qrcode": "1.5.0",
"@types/serve-handler": "6.1.1", "@types/serve-handler": "6.1.1",
"@types/sortablejs": "1.15.1", "@types/sortablejs": "1.15.1",
"@types/tar": "6.1.5", "@types/tar": "6.1.4",
"@types/webspeechapi": "0.0.29", "@types/webspeechapi": "0.0.29",
"@typescript-eslint/eslint-plugin": "5.59.7", "@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.59.7", "@typescript-eslint/parser": "5.57.0",
"@web/dev-server": "0.1.38", "@web/dev-server": "0.1.37",
"@web/dev-server-rollup": "0.4.1", "@web/dev-server-rollup": "0.4.0",
"babel-loader": "9.1.2", "babel-loader": "9.1.2",
"babel-plugin-template-html-minifier": "4.1.0", "babel-plugin-template-html-minifier": "4.1.0",
"chai": "4.3.7", "chai": "4.3.7",
"del": "7.0.0", "del": "7.0.0",
"eslint": "8.41.0", "eslint": "8.37.0",
"eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-base": "15.0.0",
"eslint-config-airbnb-typescript": "17.0.0", "eslint-config-airbnb-typescript": "17.0.0",
"eslint-config-prettier": "8.8.0", "eslint-config-prettier": "8.8.0",
"eslint-import-resolver-webpack": "0.13.2", "eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-disable": "2.0.3", "eslint-plugin-disable": "2.0.3",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"eslint-plugin-lit": "1.8.3", "eslint-plugin-lit": "1.8.2",
"eslint-plugin-lit-a11y": "2.4.1", "eslint-plugin-lit-a11y": "2.4.0",
"eslint-plugin-unused-imports": "2.0.0", "eslint-plugin-unused-imports": "2.0.0",
"eslint-plugin-wc": "1.5.0", "eslint-plugin-wc": "1.4.0",
"esprima": "4.0.1", "esprima": "4.0.1",
"fancy-log": "2.0.0", "fancy-log": "2.0.0",
"fs-extra": "11.1.1", "fs-extra": "11.1.1",
"glob": "10.2.6", "glob": "9.3.2",
"gulp": "4.0.2", "gulp": "4.0.2",
"gulp-flatmap": "1.0.2", "gulp-flatmap": "1.0.2",
"gulp-json-transform": "0.4.8", "gulp-json-transform": "0.4.8",
"gulp-merge-json": "2.1.2", "gulp-merge-json": "2.1.2",
"gulp-rename": "2.0.0", "gulp-rename": "2.0.0",
"gulp-zopfli-green": "6.0.1", "gulp-zopfli-green": "6.0.1",
"html-minifier-terser": "7.2.0", "html-minifier-terser": "7.1.0",
"husky": "8.0.3", "husky": "8.0.3",
"instant-mocha": "1.5.1", "instant-mocha": "1.5.0",
"jszip": "3.10.1", "jszip": "3.10.1",
"lint-staged": "13.2.2", "lint-staged": "13.2.0",
"lit-analyzer": "1.2.1", "lit-analyzer": "1.2.1",
"lodash.template": "4.5.0", "lodash.template": "4.5.0",
"magic-string": "0.30.0", "magic-string": "0.30.0",
@@ -221,39 +225,38 @@
"merge-stream": "2.0.0", "merge-stream": "2.0.0",
"mocha": "10.2.0", "mocha": "10.2.0",
"object-hash": "3.0.0", "object-hash": "3.0.0",
"open": "9.1.0", "open": "8.4.2",
"pinst": "3.0.0", "pinst": "3.0.0",
"prettier": "2.8.8", "prettier": "2.8.7",
"rollup": "2.79.1", "rollup": "2.79.1",
"rollup-plugin-string": "3.0.0", "rollup-plugin-string": "3.0.0",
"rollup-plugin-terser": "7.0.2", "rollup-plugin-terser": "7.0.2",
"rollup-plugin-visualizer": "5.9.0", "rollup-plugin-visualizer": "5.9.0",
"serve-handler": "6.1.5", "serve-handler": "6.1.5",
"sinon": "15.1.0", "sinon": "15.0.3",
"source-map-url": "0.4.1", "source-map-url": "0.4.1",
"systemjs": "6.14.1", "systemjs": "6.14.1",
"tar": "6.1.15", "tar": "6.1.13",
"terser-webpack-plugin": "5.3.9", "terser-webpack-plugin": "5.3.7",
"ts-lit-plugin": "1.2.1", "ts-lit-plugin": "1.2.1",
"typescript": "4.9.5", "typescript": "4.9.5",
"vinyl-buffer": "1.0.1", "vinyl-buffer": "1.0.1",
"vinyl-source-stream": "2.0.0", "vinyl-source-stream": "2.0.0",
"webpack": "5.84.1", "webpack": "=5.72.1",
"webpack-cli": "5.1.1", "webpack-cli": "5.0.1",
"webpack-dev-server": "4.15.0", "webpack-dev-server": "4.13.1",
"webpack-manifest-plugin": "5.0.0", "webpack-manifest-plugin": "5.0.0",
"webpackbar": "5.0.2", "webpackbar": "5.0.2",
"workbox-build": "6.6.0" "workbox-build": "6.5.4"
}, },
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch", "_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"resolutions": { "resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch", "@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@material/mwc-button@^0.25.3": "^0.27.0", "@material/mwc-button@^0.25.3": "^0.27.0"
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch"
}, },
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "always" "arrowParens": "always"
}, },
"packageManager": "yarn@3.5.1" "packageManager": "yarn@3.5.0"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "home-assistant-frontend" name = "home-assistant-frontend"
version = "20230601.1" version = "20230401.0"
license = {text = "Apache-2.0"} license = {text = "Apache-2.0"}
description = "The Home Assistant frontend" description = "The Home Assistant frontend"
readme = "README.md" readme = "README.md"

View File

@@ -1,4 +1,4 @@
#!/bin/bash -i #!/bin/sh
# Resolve all frontend dependencies that the application requires to develop. # Resolve all frontend dependencies that the application requires to develop.
# Stop on errors # Stop on errors
@@ -6,17 +6,5 @@ set -e
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
# Install/upgrade node when inside devcontainer
if [[ -n "$DEVCONTAINER" ]]; then
nodeCurrent=$(nvm version default || echo "")
nodeLatest=$(nvm version-remote "$(cat .nvmrc)")
if [[ -z "$nodeCurrent" ]]; then
nvm install
elif [[ "$nodeCurrent" != "$nodeLatest" ]]; then
nvm install --reinstall-packages-from="$nodeCurrent" --default
nvm uninstall "$nodeCurrent"
fi
fi
# Install node modules # Install node modules
yarn install yarn install

View File

@@ -9,7 +9,6 @@ import {
PropertyValues, PropertyValues,
} from "lit"; } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-alert"; import "../components/ha-alert";
import "../components/ha-checkbox"; import "../components/ha-checkbox";
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data"; import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
@@ -21,12 +20,13 @@ import {
DataEntryFlowStep, DataEntryFlowStep,
DataEntryFlowStepForm, DataEntryFlowStepForm,
} from "../data/data_entry_flow"; } from "../data/data_entry_flow";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
import "./ha-password-manager-polyfill"; import "./ha-password-manager-polyfill";
type State = "loading" | "error" | "step"; type State = "loading" | "error" | "step";
@customElement("ha-auth-flow") @customElement("ha-auth-flow")
export class HaAuthFlow extends LitElement { export class HaAuthFlow extends litLocalizeLiteMixin(LitElement) {
@property({ attribute: false }) public authProvider?: AuthProvider; @property({ attribute: false }) public authProvider?: AuthProvider;
@property() public clientId?: string; @property() public clientId?: string;
@@ -35,8 +35,6 @@ export class HaAuthFlow extends LitElement {
@property() public oauth2State?: string; @property() public oauth2State?: string;
@property() public localize!: LocalizeFunc;
@state() private _state: State = "loading"; @state() private _state: State = "loading";
@state() private _stepData?: Record<string, any>; @state() private _stepData?: Record<string, any>;
@@ -105,7 +103,7 @@ export class HaAuthFlow extends LitElement {
} }
this.addEventListener("keypress", (ev) => { this.addEventListener("keypress", (ev) => {
if (ev.key === "Enter") { if (ev.keyCode === 13) {
this._handleSubmit(ev); this._handleSubmit(ev);
} }
}); });

View File

@@ -82,13 +82,12 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
.redirectUri=${this.redirectUri} .redirectUri=${this.redirectUri}
.oauth2State=${this.oauth2State} .oauth2State=${this.oauth2State}
.authProvider=${this._authProvider} .authProvider=${this._authProvider}
.localize=${this.localize}
></ha-auth-flow> ></ha-auth-flow>
${inactiveProviders.length > 0 ${inactiveProviders.length > 0
? html` ? html`
<ha-pick-auth-provider <ha-pick-auth-provider
.localize=${this.localize} .resources=${this.resources}
.clientId=${this.clientId} .clientId=${this.clientId}
.authProviders=${inactiveProviders} .authProviders=${inactiveProviders}
@pick-auth-provider=${this._handleAuthProviderPick} @pick-auth-provider=${this._handleAuthProviderPick}

View File

@@ -2,10 +2,10 @@ import "@material/mwc-list";
import { css, html, LitElement } from "lit"; import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { fireEvent } from "../common/dom/fire_event"; import { fireEvent } from "../common/dom/fire_event";
import { LocalizeFunc } from "../common/translations/localize";
import "../components/ha-icon-next"; import "../components/ha-icon-next";
import "../components/ha-list-item"; import "../components/ha-list-item";
import { AuthProvider } from "../data/auth"; import { AuthProvider } from "../data/auth";
import { litLocalizeLiteMixin } from "../mixins/lit-localize-lite-mixin";
declare global { declare global {
interface HASSDomEvents { interface HASSDomEvents {
@@ -14,11 +14,9 @@ declare global {
} }
@customElement("ha-pick-auth-provider") @customElement("ha-pick-auth-provider")
export class HaPickAuthProvider extends LitElement { export class HaPickAuthProvider extends litLocalizeLiteMixin(LitElement) {
@property() public authProviders: AuthProvider[] = []; @property() public authProviders: AuthProvider[] = [];
@property() public localize!: LocalizeFunc;
protected render() { protected render() {
return html` return html`
<p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p> <p>${this.localize("ui.panel.page-authorize.pick_auth_provider")}:</p>

View File

@@ -1,5 +1,8 @@
/* eslint-disable no-console */ /* eslint-disable no-undef, no-console */
import {
CastStateEventData,
SessionStateEventData,
} from "chromecast-caf-receiver/cast.framework";
import { Auth } from "home-assistant-js-websocket"; import { Auth } from "home-assistant-js-websocket";
import { castApiAvailable } from "./cast_framework"; import { castApiAvailable } from "./cast_framework";
import { CAST_APP_ID, CAST_DEV, CAST_NS } from "./const"; import { CAST_APP_ID, CAST_DEV, CAST_NS } from "./const";
@@ -45,11 +48,11 @@ export class CastManager {
}); });
context.addEventListener( context.addEventListener(
cast.framework.CastContextEventType.SESSION_STATE_CHANGED, cast.framework.CastContextEventType.SESSION_STATE_CHANGED,
this._sessionStateChanged (ev) => this._sessionStateChanged(ev)
); );
context.addEventListener( context.addEventListener(
cast.framework.CastContextEventType.CAST_STATE_CHANGED, cast.framework.CastContextEventType.CAST_STATE_CHANGED,
this._castStateChanged (ev) => this._castStateChanged(ev)
); );
} }
@@ -116,7 +119,7 @@ export class CastManager {
} }
} }
private _sessionStateChanged = (ev: cast.framework.SessionStateEventData) => { private _sessionStateChanged(ev: SessionStateEventData) {
if (__DEV__) { if (__DEV__) {
console.log("Cast session state changed", ev.sessionState); console.log("Cast session state changed", ev.sessionState);
} }
@@ -137,14 +140,14 @@ export class CastManager {
this.status = undefined; this.status = undefined;
this._fireEvent("connection-changed"); this._fireEvent("connection-changed");
} }
}; }
private _castStateChanged = (ev: cast.framework.CastStateEventData) => { private _castStateChanged(ev: CastStateEventData) {
if (__DEV__) { if (__DEV__) {
console.log("Cast state changed", ev.castState); console.log("Cast state changed", ev.castState);
} }
this._fireEvent("state-changed"); this._fireEvent("state-changed");
}; }
private _attachMessageListener() { private _attachMessageListener() {
const session = this.castSession; const session = this.castSession;

View File

@@ -1,106 +0,0 @@
import { clamp } from "../number/clamp";
const DEFAULT_MIN_KELVIN = 2700;
const DEFAULT_MAX_KELVIN = 6500;
export const temperature2rgb = (
temperature: number
): [number, number, number] => {
const value = temperature / 100;
return [
temperatureRed(value),
temperatureGreen(value),
temperatureBlue(value),
];
};
const temperatureRed = (temperature: number): number => {
if (temperature <= 66) {
return 255;
}
const red = 329.698727446 * (temperature - 60) ** -0.1332047592;
return clamp(red, 0, 255);
};
const temperatureGreen = (temperature: number): number => {
let green: number;
if (temperature <= 66) {
green = 99.4708025861 * Math.log(temperature) - 161.1195681661;
} else {
green = 288.1221695283 * (temperature - 60) ** -0.0755148492;
}
return clamp(green, 0, 255);
};
const temperatureBlue = (temperature: number): number => {
if (temperature >= 66) {
return 255;
}
if (temperature <= 19) {
return 0;
}
const blue = 138.5177312231 * Math.log(temperature - 10) - 305.0447927307;
return clamp(blue, 0, 255);
};
const matchMaxScale = (
inputColors: number[],
outputColors: number[]
): number[] => {
const maxIn: number = Math.max(...inputColors);
const maxOut: number = Math.max(...outputColors);
let factor: number;
if (maxOut === 0) {
factor = 0.0;
} else {
factor = maxIn / maxOut;
}
return outputColors.map((value) => Math.round(value * factor));
};
const mired2kelvin = (miredTemperature: number) =>
Math.floor(1000000 / miredTemperature);
const kelvin2mired = (kelvintTemperature: number) =>
Math.floor(1000000 / kelvintTemperature);
export const rgbww2rgb = (
rgbww: [number, number, number, number, number],
minKelvin?: number,
maxKelvin?: number
): [number, number, number] => {
const [r, g, b, cw, ww] = rgbww;
// Calculate color temperature of the white channels
const maxMireds: number = kelvin2mired(minKelvin ?? DEFAULT_MIN_KELVIN);
const minMireds: number = kelvin2mired(maxKelvin ?? DEFAULT_MAX_KELVIN);
const miredRange: number = maxMireds - minMireds;
let ctRatio: number;
try {
ctRatio = ww / (cw + ww);
} catch (_error) {
ctRatio = 0.5;
}
const colorTempMired = minMireds + ctRatio * miredRange;
const colorTempKelvin = colorTempMired ? mired2kelvin(colorTempMired) : 0;
const [wR, wG, wB] = temperature2rgb(colorTempKelvin);
const whiteLevel = Math.max(cw, ww) / 255;
// Add the white channels to the rgb channels.
const rgb = [
r + wR * whiteLevel,
g + wG * whiteLevel,
b + wB * whiteLevel,
] as [number, number, number];
// Match the output maximum value to the input. This ensures the
// output doesn't overflow.
return matchMaxScale([r, g, b, cw, ww], rgb) as [number, number, number];
};
export const rgbw2rgb = (
rgbw: [number, number, number, number]
): [number, number, number] => {
const [r, g, b, w] = rgbw;
const rgb = [r + w, g + w, b + w] as [number, number, number];
return matchMaxScale([r, g, b, w], rgb) as [number, number, number];
};

View File

@@ -50,7 +50,6 @@ import {
mdiRobotVacuum, mdiRobotVacuum,
mdiScriptText, mdiScriptText,
mdiSineWave, mdiSineWave,
mdiSpeakerMessage,
mdiSpeedometer, mdiSpeedometer,
mdiSunWireless, mdiSunWireless,
mdiThermometer, mdiThermometer,
@@ -76,15 +75,13 @@ export const DEFAULT_DOMAIN_ICON = mdiBookmark;
/** Icons for each domain */ /** Icons for each domain */
export const FIXED_DOMAIN_ICONS = { export const FIXED_DOMAIN_ICONS = {
air_quality: mdiAirFilter,
alert: mdiAlert, alert: mdiAlert,
air_quality: mdiAirFilter,
calendar: mdiCalendar, calendar: mdiCalendar,
climate: mdiThermostat, climate: mdiThermostat,
configurator: mdiCog, configurator: mdiCog,
conversation: mdiMicrophoneMessage, conversation: mdiMicrophoneMessage,
counter: mdiCounter, counter: mdiCounter,
datetime: mdiCalendarClock,
date: mdiCalendar,
demo: mdiHomeAssistant, demo: mdiHomeAssistant,
google_assistant: mdiGoogleAssistant, google_assistant: mdiGoogleAssistant,
group: mdiGoogleCirclesCommunities, group: mdiGoogleCirclesCommunities,
@@ -109,13 +106,10 @@ export const FIXED_DOMAIN_ICONS = {
script: mdiScriptText, script: mdiScriptText,
select: mdiFormatListBulleted, select: mdiFormatListBulleted,
sensor: mdiEye, sensor: mdiEye,
simple_alarm: mdiBell,
siren: mdiBullhorn, siren: mdiBullhorn,
stt: mdiMicrophoneMessage, simple_alarm: mdiBell,
text: mdiFormTextbox, text: mdiFormTextbox,
time: mdiClock,
timer: mdiTimerOutline, timer: mdiTimerOutline,
tts: mdiSpeakerMessage,
updater: mdiCloudUpload, updater: mdiCloudUpload,
vacuum: mdiRobotVacuum, vacuum: mdiRobotVacuum,
zone: mdiMapMarkerRadius, zone: mdiMapMarkerRadius,
@@ -162,7 +156,6 @@ export const FIXED_DEVICE_CLASS_ICONS = {
temperature: mdiThermometer, temperature: mdiThermometer,
timestamp: mdiClock, timestamp: mdiClock,
volatile_organic_compounds: mdiMolecule, volatile_organic_compounds: mdiMolecule,
volatile_organic_compounds_parts: mdiMolecule,
voltage: mdiSineWave, voltage: mdiSineWave,
volume: mdiCarCoolantLevel, volume: mdiCarCoolantLevel,
water: mdiWater, water: mdiWater,
@@ -212,8 +205,6 @@ export const DOMAINS_INPUT_ROW = [
"automation", "automation",
"button", "button",
"cover", "cover",
"date",
"datetime",
"fan", "fan",
"group", "group",
"humidifier", "humidifier",
@@ -232,7 +223,6 @@ export const DOMAINS_INPUT_ROW = [
"select", "select",
"switch", "switch",
"text", "text",
"time",
"vacuum", "vacuum",
]; ];

View File

@@ -1,23 +0,0 @@
import { isSameDay, isSameYear } from "date-fns";
import { FrontendLocaleData } from "../../data/translation";
import {
formatShortDateTime,
formatShortDateTimeWithYear,
} from "./format_date_time";
import { formatTime } from "./format_time";
export const absoluteTime = (
from: Date,
locale: FrontendLocaleData,
to?: Date
): string => {
const _to = to ?? new Date();
if (isSameDay(from, _to)) {
return formatTime(from, locale);
}
if (isSameYear(from, _to)) {
return formatShortDateTime(from, locale);
}
return formatShortDateTimeWithYear(from, locale);
};

View File

@@ -1,7 +1,11 @@
import { getWeekStartByLocale } from "weekstart"; import { getWeekStartByLocale } from "weekstart";
import { FrontendLocaleData, FirstWeekday } from "../../data/translation"; import { FrontendLocaleData, FirstWeekday } from "../../data/translation";
import "../../resources/intl-polyfill"; import { polyfillsLoaded } from "../translations/localize";
if (__BUILD__ === "latest" && polyfillsLoaded) {
await polyfillsLoaded;
}
export const weekdays = [ export const weekdays = [
"sunday", "sunday",

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